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

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

---

##自己紹介
(大枠は、イベント冒頭でしてしまったので割愛。WebAudioはド素人(4,5人日くらい)。)
![自己紹介.png](https://qiita-image-store.s3.amazonaws.com/0/99740/1beea712-5ea7-5496-e91f-2fb8dcc4d00b.png)


---

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

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

![WebVR空間の巨大スクリーンでteratailを見れるか試してみた話 - Google スライド.png](https://qiita-image-store.s3.amazonaws.com/0/99740/d14eb526-0917-5aa4-c45d-d51bdc607f18.png)


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


---
#結果…！！

---

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

---

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

---

#では順を追って。

---

##3D空間を用意

---
■3Dの基礎

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

---
図で言うとこんな感じ

![20160512WebGL入門ハンズオンLT資料 - Google スライド.png](https://qiita-image-store.s3.amazonaws.com/0/99740/fdf17f7a-c29d-c9b7-b6ce-175f7a38e63a.png)

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

![初心者でも絶対わかる、WebGLプログラミング＜three.js最初の一歩＞   HTML5Experts.jp (1).png](https://qiita-image-store.s3.amazonaws.com/0/99740/a70d7623-41a5-44ed-90dd-9f03010ad20a.png)

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

※今回はCSS3Dオブジェクトをもレンダリングするので、`WebGLRenderer`と`CSS3DRenderer`を用意します！

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

```js

/*
 * 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 );
```

---

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

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

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

```js
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);
```

---

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

```js
/*
 * 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オブジェクト)を用意

```js

/*
 *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以外でもいろんなサイトで同じことが可能：)

```js

/*
 *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でさっき回答来てた](https://teratail.com/questions/63037))

↑

Demoまでつけてくれてたので、のちほど：）

---

コードはこんな感じ。

```js
/*
 * 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オブジェクトを生成する

---

```js
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);
```
---

##レンダリング

---

```js
/*
 *レンダリング
 */
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://qiita-image-store.s3.amazonaws.com/0/99740/68c6c04e-99d2-bdf9-649d-b33f57311c6b.png)


---

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

---

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