Edited at

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

More than 1 year has passed since last update.

※下記イベントでの発表資料です。

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