Three.jsをWebMIDI、WebAudioから動かす超簡単なVjサンプル

**[Web Music ハッカソン#5](http://googledevjp.blogspot.jp/2016/06/web-music-5.html) ＠Google Japan (2016/7/30)が開催されます！ テーマは「DJ/VJ」ですので、宜しければ、こちらのサンプルも参考にしてみてください。**
# まずは動作サンプルをご覧ください。
- [サンプル]
 (https://sascacci.com/vj_smpl/)
    - ↑Chromeでアクセスしてください。
    - マイクで拾った音に反応して平面が揺らぎます。
    - 外部MIDI機器を接続して、Control Change(CC)を送ると視点（カメラ・アングル）が変わります。
<img width="1152" alt="スクリーンショット 2016-07-21 21.36.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/131750/cec5ffea-c7c0-64e2-1d4f-16373dc757cb.png">


#概要
- getUserMedia でマイクからの音声を拾って、WebAudioAPIのanalyzerNode getFloatTimeDomainData()で波形情報を取得。
- そのWebAudioからゲットした情報を使って Three.js で作った Plane を揺らしています。
- WebMIDIは、CCを受けると Three.jsのカメラの角度が変わるように実装しています。

#準備
- three.min.jsは別途[http://threejs.org/](http://threejs.org/)から入手してください。zipファイルをダウンロードすると、buildフォルダ内にあります。あとは、以下のソースコードをコピペすれば動くはずですが、getUserMediaを使っているので、httpsでないと動作しません。ご注意ください。

#ソース解説
```html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title> Vj Smpl Three.js </title>
<style>
	body {
		cursor:crosshair;
		margin: 0px;
		overflow: hidden;
	}
</style>
</head>

<body onload="setup()">
<script src="three.min.js"></script>

<script>

// Web Audio　の初期化
window.AudioContext = window.AudioContext;
var context = new AudioContext();
var analyser = context.createAnalyser();

// three.jsで使う変数
var scene=null;
var camera=null;
var renderer=null;

var radius = 500;
var angle = 0;
var plane = null;

// マイクの音声を取得。　WebAudioのanalyserにわたしている。
navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia);
navigator.getUserMedia(
	{ video:false, audio:true },
	function(stream) {
		var microphone = context.createMediaStreamSource(stream);
		microphone.connect(analyser);
	},
	function(error) { return; }
);

// 画面のリサイズ
function resize() {
	camera.aspect = window.innerWidth / window.innerHeight;
	camera.updateProjectionMatrix();
	renderer.setSize( window.innerWidth, window.innerHeight );
}

// three.js のカメラとかオブジェクトとかモロモロ初期化
function three_init() 
{
	// まずシーンをつくります。
	scene = new THREE.Scene();
	// 次にカメラをつくります。
	camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 2000);
	// カメラをシーンに追加。
	scene.add(camera);
	// カメラ位置を設定
	camera.position.z = radius;
	// レンダラーをDOM上に設置する
	renderer = new THREE.WebGLRenderer({antialias: true});
	renderer.setSize(window.innerWidth, window.innerHeight);	
	document.body.appendChild(renderer.domElement);
	// 背景の色
	renderer.setClearColor(0x000000);

	// 平面を生成
	three_plane_init();
}

// 平面オブジェクト生成してsceneに　add
function three_plane_init()
{
	// 平面のマテリアルを設定
	var mat = new THREE.MeshBasicMaterial( { color: 0xFFFFFF, wireframe: true } );
	// サイズとグリッドを設定して形状を決める
	var geo = new THREE.PlaneGeometry (500, 500, 20, 20);
	// マテリアルと形状をもとに物体を生成します
	plane = new THREE.Mesh(geo, mat);

	// 水平面になるように90度回転
	plane.rotation.set(Math.PI/2,0,0);

	// 平面をシーンに追加
	scene.add( plane );
}

// Web MIDI　API　の初期化
function web_midi_init()
{
	navigator.requestMIDIAccess({sysex:false}).then( success, failure );
}

// Web MIDIの初期化が失敗したときに呼ばれる関数
function failure( error ) { }

// Web MIDIの初期化が成功したときに呼ばれる関数
function success( m ) {
	var inputs = null;
	var midi=m;

	if (typeof midi.inputs === "function") {
		inputs = midi.inputs();
	} else {
		var inputIterator = midi.inputs.values();
		inputs = [];
		for (var o = inputIterator.next(); !o.done; o = inputIterator.next()) {
			inputs.push(o.value)
		}
	}
	// 全てのMIDI機器の入力を受ける
	for (var i = 0; i < inputs.length; i++) {
		inputs[i].onmidimessage = handleMIDIMessage;
	}
}

// Web MIDI 入力のコールバック関数
// CCを受けたらカメラのangleが変化するように実装。
function handleMIDIMessage( ev ) {
	switch(ev.data[0] & 0xF0) {
	case 0x90: // Note On
		break;
	case 0x80: // Note Off
		break;
	case 0xB0: // CC
		// MIDI 0-127の値を0-2PIに変換
		angle = 2 * Math.PI * ev.data[2] / 127;
		break;
	default:
		break;
	}
}

// カメラのアングル設定
function camera_update()
{
	// カメラ位置を設定
	camera.position.x = radius*Math.cos(angle);
	camera.position.y = 200;
	camera.position.z = radius*Math.sin(angle);

	// 常に原点を向くように
	camera.lookAt(new THREE.Vector3( 0, 0, 0 ));
}

// 平面に波形の揺らぎを付加
function plane_update(waveData)
{
	// updateのフラグを立てる
	plane.geometry.verticesNeedUpdate = true;

	// WebAudio AnalyzerNodeから取得したデータで平面のz座標を動かす
	for (var i = 0; i < plane.geometry.vertices.length; i++) {
		var vertex = plane.geometry.vertices[i];
		vertex.z = waveData[i%waveData.length]*100;
	}

}

// 初期化
function setup(e)
{
	three_init();
	web_midi_init();
	window.addEventListener("resize", resize, false);
	draw();
}

// 毎フレーム呼ばれる関数
function draw() {
	// 波形情報を取得
	var waveData = new Float32Array(analyser.frequencyBinCount);
	analyser.getFloatTimeDomainData(waveData);

	// カメラ位置をアップデート
	camera_update();
	// 平面をアップデート
	plane_update(waveData);

	// レンダリングされる
	renderer.render(scene, camera);

	window.requestAnimationFrame(draw);
}

</script>
</body></html>
```

#参考
- [Canvasを使ったVjサンプル](http://sascacci-blog.tumblr.com/post/95651677058/web-browser-vj-tutorial)