※下記イベントでの発表資料です。
https://webaudiotokyo.connpass.com/event/48120/
##自己紹介
(大枠は、イベント冒頭でしてしまったので割愛。WebAudioはド素人(4,5人日くらい)。)
##3D空間でYoutube見たくないですか??
##やりたいこと
3D空間で、
映画館のような大画面で音楽PVを見れて、
その音楽に合わせて演出がついたらおもしろそう。
(後々はこれをVR空間でこれやりたい。。。)
#方法
- 3D空間を用意
- 3D空間に平面と、iframeの3Dオブジェクトを用意して連動させる
- iframeからYouTubeを再生、音を拾ってAnalyzerへ
- 変動する波形データの値を利用して、3Dオブジェクトを生成する
- レンダリングする
#結果…!!
##ハウリングの対処とYoutubeの音声取得がうまくいかず...orz
##それっぽいやつですが、一応...Demo
#では順を追って。
##3D空間を用意
■3Dの基礎
- カメラ(視点)用意する
- シーンを用意する
- ライト(光源)つくる→シーンに追加
- 物体(Mesh)つくる→シーンに追加
- レンダリングする
図で言うとこんな感じ
詳しくはこちらの記事で:)
https://html5experts.jp/yomotsu/5225/
ということで、three.jsを使って、
まずは、レンダラー、シーン、カメラ、ライトを用意します。
※今回はCSS3Dオブジェクトをもレンダリングするので、WebGLRenderer
とCSS3DRenderer
を用意します!
コードはこんな感じ(レンダラー)
/*
* 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。てか著作権的に、そもそもできないかもしれない。)
と思ったら、実はできる…!?
↑
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()
らしいです。(まだゆっくり読んでません)
#ご静聴ありがとうございました。
##参考資料
これをベースに改造していきました。
https://webmusicdevelopers.appspot.com/codelabs/webaudio/index.html?ja-jp#8