LoginSignup
3

More than 5 years have passed since last update.

3D空間でYouTubeの音楽を聴きながら、音に合わせて3Dオブジェクトを動かしたい!

Last updated at Posted at 2017-01-23
1 / 37

※下記イベントでの発表資料です。
https://webaudiotokyo.connpass.com/event/48120/


自己紹介

(大枠は、イベント冒頭でしてしまったので割愛。WebAudioはド素人(4,5人日くらい)。)
自己紹介.png


3D空間でYoutube見たくないですか??


やりたいこと

3D空間で、
映画館のような大画面で音楽PVを見れて、
その音楽に合わせて演出がついたらおもしろそう。
(後々はこれをVR空間でこれやりたい。。。)

WebVR空間の巨大スクリーンでteratailを見れるか試してみた話 - Google スライド.png


方法

  • 3D空間を用意
  • 3D空間に平面と、iframeの3Dオブジェクトを用意して連動させる
  • iframeからYouTubeを再生、音を拾ってAnalyzerへ
  • 変動する波形データの値を利用して、3Dオブジェクトを生成する
  • レンダリングする

結果…!!


ハウリングの対処とYoutubeの音声取得がうまくいかず...orz


それっぽいやつですが、一応...Demo


では順を追って。


3D空間を用意


■3Dの基礎

  • カメラ(視点)用意する
  • シーンを用意する
  • ライト(光源)つくる→シーンに追加
  • 物体(Mesh)つくる→シーンに追加
  • レンダリングする

図で言うとこんな感じ

20160512WebGL入門ハンズオンLT資料 - Google スライド.png


詳しくはこちらの記事で:)
https://html5experts.jp/yomotsu/5225/

初心者でも絶対わかる、WebGLプログラミング<three.js最初の一歩>   HTML5Experts.jp (1).png


ということで、three.jsを使って、
まずは、レンダラー、シーン、カメラ、ライトを用意します。

※今回はCSS3Dオブジェクトをもレンダリングするので、WebGLRendererCSS3DRendererを用意します!


コードはこんな感じ(レンダラー)


/*
 * WebGLRendererの生成
 */
var renderer = new THREE.WebGLRenderer({antialias: true});
    //↑antialias:trueで物体の輪郭がギザギザになることを抑える
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
    //↑画面のピクセル比を設定
renderer.setClearColor(0x000000, 1.0);
    //↑レンダラーの背景色を黒色(不透過)に設定
//CanvasをDOMツリーに追加
document.body.appendChild(renderer.domElement);


/*
 * CSS3DRendererの生成
 */
var cssRenderer = new THREE.CSS3DRenderer();
cssRenderer.setSize(window.innerWidth, window.innerHeight);
cssRenderer.domElement.style.position = 'absolute';
cssRenderer.domElement.style.top = 0;
cssRenderer.domElement.style.margin = 0;
cssRenderer.domElement.style.padding = 0;
document.body.appendChild( cssRenderer.domElement );

コードはこんな感じ(シーン)

var scene = new THREE.Scene();
var cssScene    = new THREE.Scene();

コードはこんな感じ(カメラ)

var fov = 75;
var aspect = window.innerWidth / window.innerHeight;
var near = 0.1;
var far = 10000;
var camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
var camera_position_x0 = 0;
var camera_position_y0 = 0;
var camera_position_z0 = 800;
camera.position.set(camera_position_x0,camera_position_y0,camera_position_z0);

コードはこんな感じ(ライト)

/*
 * Lightをシーンに追加
 */
var light = new THREE.DirectionalLight(0xffffff);
light.position.set(0,30,-50);
scene.add(light);
cssScene.add(light);


/*
 *環境光も追加
 */
var ambient = new THREE.AmbientLight(0x333333);
scene.add(ambient);
cssScene.add(ambient);

3D空間に平面と、iframeの3Dオブジェクトを用意して連動させる


平面(Planeオブジェクト)を用意


/*
 *WebGLSceneにPlaneオブジェクトを追加
  */
var pcWidth = 1024;
var pcHeight = 512;
var floorGeo = new THREE.PlaneGeometry(pcWidth,pcHeight);
var floorMesh = new THREE.MeshBasicMaterial({color:0xc0c0c0});
// var floorMesh = new THREE.MeshBasicMaterial({wireframe:true});
var floor = new THREE.Mesh( floorGeo, floorMesh );
var objePositionX = 0;
var objePositionY = 0;
var objePositionZ = 0;
floor.position.set(objePositionX, objePositionY, objePositionZ);
// floor.rotation.x = Math.PI/2;
scene.add( floor );


CSS3Dオブジェクトを追加。

ここでiframe要素を3D空間出現させ、Youtubeを呼んでいる。
もちろん、srcを変えればYoutube以外でもいろんなサイトで同じことが可能:)


/*
 *cssSceneにCSS3Dオブジェクトを追加
 */
var element = document.createElement('iframe');
element.src = 'https://www.youtube.com/embed/tRdY4s_2D88';
element.style.width = pcWidth + 'px';
element.style.height = pcHeight + 'px';
// create the object3d for tjhis element
var cssObject = new THREE.CSS3DObject( element );
// we reference the same position and rotation
cssObject.position.set(objePositionX, objePositionY, objePositionZ);
cssObject.rotation = floor.rotation;
// add it to the css scene
cssScene.add(cssObject);

ポイントは

  • element.style.width = pcWidth + 'px';element.style.height = pcHeight + 'px';でオブジェクトの大きさを揃える
  • cssObject.position.set(objePositionX, objePositionY, objePositionZ);の部分で、先ほどのPlaneオブジェクトと位置座標を一致させる

iframeからYouTubeを再生、音を拾ってAnalyzerへ


WebRTCのnavigator.getUserMediaを使って、マイクから音声を拾って、analyzerにつなぐ

(本当は外部入力の音を消したかったけど、方法が分からず…orz。てか著作権的に、そもそもできないかもしれない。)


と思ったら、実はできる…!?

(teratailでさっき回答来てた)

Demoまでつけてくれてたので、のちほど:)


コードはこんな感じ。

/*
 * PCのマイクから音をひろってくる
 */

var getUserMedia = navigator.getUserMedia ? 'getUserMedia' :
    navigator.webkitGetUserMedia ? 'webkitGetUserMedia' :
    navigator.mozGetUserMedia ? 'mozGetUserMedia' :
    navigator.msGetUserMedia ? 'msGetUserMedia' :
    undefined;
var astream, micsrc;
var conditions={audio:true, video:false};
const Mic = () => {
    navigator[getUserMedia](
        conditions,
        (stream) => {
            astream=stream;
            micsrc=audioctx.createMediaStreamSource(stream);
            micsrc.connect(audioctx.destination);
            micsrc.connect(analyser);
        },
        (e) => { console.error(e); }
    );
    /*
     * ネクストアクション 入ってくる音声をPCないのものだけにする!!
     */
}

//Mic集音開始
Mic();


//Analyserを用意
var audioctx = new AudioContext();

var timerId;
var analyser = audioctx.createAnalyser();
analyser.fftSize = 128;
analyser.minDecibels = -100;
analyser.maxDecibels = -30;


変動する波形データの値を利用して、3Dオブジェクトを生成する


var obj;
const make3dObj = () => {

    var data = new Uint8Array(512);

    scene.remove(obj);

    // analyser.getByteTimeDomainData(data); //Waveform Data
    analyser.getByteFrequencyData(data); //Spectrum Dataもとれるよ

    var geo = new THREE.TorusKnotGeometry( //小数をかけてるのは値を小さくして、3Dオブジェクトのサイズを小さくするため
        Math.round(data[0] * 2.0), //全体的な大きさ
        Math.round(data[1] * 0.3), //チューブの太さ
        Math.round(40), //クネクネの進む方向に対してなん分割するか
        Math.round(8), //チューブ方向に何分割するか
        Math.round(data[2] * 0.3), //なんかクネクネ具合が変わる数値その1
        Math.round(2) //なんかクネクネ具合が変わる数値その2
    );

    var mat = new THREE.MeshBasicMaterial({color: 0xffffff, wireframe:true });
    obj = new THREE.Mesh( geo, mat);
    scene.add(obj);

    requestAnimationFrame(make3dObj);
}
timerId=requestAnimationFrame(make3dObj);

レンダリング


/*
 *レンダリング
 */
function renderRender() {
  renderer.render(scene, camera);
  cssRenderer.render(cssScene, camera);

  requestAnimationFrame(animate);
}

/*
 *アニメーション
 */
function animate(){
  renderRender()
}

animate();


WebAudioやってみた感想

・そもそも音楽についての前提知識のハードルが高い!!
・データの取得方法さえわかれば3Dとの繋ぎこみは簡単だった。
 (Web3Dに関する知識ある前提)
・パッと見すごめな動きをできるけど、音が持つ完成と3Dの表現を一致させるのがしんどそうだな。(激しい曲には激しい3D表現、静かな曲には静かな3D表現など。。。)
 →音楽面、3D面、どちらも勉強しなきゃな。。。


おまけ

teratail回答でもらったDemo
https://turbographics2000.github.io/get_youtube_audiodata/


できてるっ!!!!!


Chrome拡張機能 + MediaElement.captureStream()
らしいです。(まだゆっくり読んでません)

JavaScript - PC内で流れる音源の音声データをJSで取得する方法を教えてください! 63037 |teratail.png


ご静聴ありがとうございました。


参考資料

これをベースに改造していきました。
https://webmusicdevelopers.appspot.com/codelabs/webaudio/index.html?ja-jp#8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3