2014/12/23 (2014年12月 のアーカイブ)
WebGLデモに音を付けるあれこれ
この記事は「WebGL Advent Calendar 2014」23日目の記事です。
WebGL Advent Calendar 2014WebGLのアドベントカレンダーですが、音関係のお話です。
と言うかですね、音を扱うためのWeb Audio APIという強力なツールがブラウザ上でサポートされつつありますので、WebGLでイケてるデモを作ったら是非音も付けてみて欲しいな、と思うわけです。昔から音と映像は切っても切れない関係で、両方が揃うとインパクト倍増ですからね。
という事で、この記事では映像は出来たけど音をどうやって付けるか? というあたりのあれこれについての紹介です。
ブラウザのサポート状況
ブラウザ上で音を自由自在に扱えるという「Web Audio API」ですが、ブラウザサポート状況はまだまだ道半ばで、フルにガシガシ使うにはブラウザを限定する必要があります。今現在の各ブラウザのサポート状況は下の表のような感じです。Chrome | 最もサポートが進んでおり、リファレンスとなっています |
Safari | YosemiteでかなりChromeに近くなりましたが、一部挙動が異なります。それ以前のものはAPIが古い仕様になっていたりします |
Firefox | 一通りAPIがサポートされた所ですが、まだ挙動が安定しない事が割とよくあります |
IE | 開発に着手した事がアナウンスされましたが、まだ出てきていません |
いやもうmp3やらoggやらで裏で鳴らしたい音も出来上がってます、という場合、別にややこしい事はしないで<audio>タグで埋め込むだけでいいですね。と言っては身も蓋もないですが、今の所単純に準備済みのBGMをどの環境でも同じように鳴らしたいという話なら<audio>タグを使うのが有力です。これなら大体どのブラウザでも最新のものを使えば対応しています。ただ、これだと映像と音はそれぞれ勝手に走っているだけなので相互作用的なものを作るのは困難です。各ブラウザ共開発は進みつつありますので今後に期待しましょう。
なお、<audio>タグを使った埋め込みでもWeb Audio APIと連携させる事で相互作用を作る事も可能です。Web Audio APIの全貌をちゃんと知りたいという方はこちらをどうぞ。仕様の日本語訳とか解説を置いてあります。
Web Audio API 日本語訳
Web Audio API 解説
AutoBeats
とりあえずお手軽に音を鳴らす方法としてAutoBeatsというものをご紹介します。https://github.com/g200kg/AutoBeats
<audio>タグで楽曲を埋め込むにしても、権利関係の問題は結構厄介ですね。WebGLベースでちょっとしたデモを作ろうとしても流石に曲を作る所まで手が回らないという人も多いかと思います。AutoBeatsはいわゆる自動作曲でタイトルを設定するだけで勝手に曲のようなものを演奏しますので手軽に音を付けたいという場合には向いているかと思います。使い方は単純で、まず、autobeats.jsを読み込みます。polymer版もありますが、単にJavaScriptで使うだけならファイルとしてはautobeats.jsだけあれば良いです。
<script src="autobeats.js"></script>
beats = new AutoBeats("song title");
beats.start();
サンプルはここにあります。このページで「title」を色々書き換えてみてください。
AutoBeatsサンプル
音と映像の相互作用
さて、これで音を付ける事ができましたが映像と音は相互に作用してこそ威力を発揮します。ここでは音に合わせて映像が変化するビジュアライザー的なものの作り方を紹介します。Web Audio APIにはビジュアライザーなどに使えるAnalyserノードというものがあるのでこれを利用します。AutoBeatsは単体で勝手に音を出しますが音を出力先を指定できるようになっていますので、ここにAnalyserを接続します。audioctx=new AudioContext();
analyser=audioctx.createAnalyser();
analyser.connect(audioctx.destination);
beats=new AutoBeats("song title",0,audioctx,analyser);
beats.start();
[AutoBeats] => [Analyser] => [AudioContext.destination]
こういう繋ぎになります。
音のデータの取り出し方はこのanalyserのgetByteTimeDomainData()を呼び出せば波形そのままのデータ、getFrequencyData()を呼び出せばスペクトラムデータになります。引数には適当な長さのUint8Arrayを渡せば配列を埋めてくれます。後はこれを表示に反映するだけですね。
シェーダーで使うのでしたら、一度canvasのimgdatに落としてTexture2D経由で受け渡しするのが一般的かと思います。
x=window.innerWidth;
y=window.innerHeight;
wavdat=new Uint8Array(256);
wavimgdat=document.createElement("canvas").getContext("2d").createImageData(256,1);
z=Date.now();
(function(){
analyser.getByteTimeDomainData(wavdat); //波形データの取得
//analyser.getByteFrequencyData(wavdat); //こっちを使うとスペクトラムデータ
for(var i=0;i<256;++i) {
var j=i<<2;
wavimgdat.data[j]=wavdat[i];
wavimgdat.data[j+3]=255;
}
gl.clear(gl.COLOR_BUFFER_BIT);
gl.uniform1f(u.t,(Date.now()-z)*0.001);//<==経過時間(msec)
gl.uniform2fv(u.r,[x,y]);//<==画面のレゾリューション
gl.uniform1i(u.wav,0);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, wavimgdat);//<==音データ
gl.drawArrays(gl.TRIANGLE_STRIP,0,4);
gl.flush();
requestAnimationFrame(arguments.callee);
})();
こんな感じで、テクスチャー(ここでは256 x 1)にしてGLSLに渡して、後は渡されたテクスチャーデータを使ってシェーダーで好きなように描画します。ちょっと前に作った奴ですが下にサンプルがありますので参考にどうぞ。
ここでは渡された256 x 1のテクスチャーwavを
と言う風に描画の色を決めて波形データを表示しています。
みなさんご存じのGLSL sandboxなんかでぐりぐり動くデモでもインプットとなる情報は時間とマウス座標くらいですけど、ここに音データが加わるとまた表現の幅が広くなりますね。
Planets
ここでは渡された256 x 1のテクスチャーwavを
vec2 p=(gl_FragCoord.xy*2.-reso)/reso;
vec4 v=texture2D(wav,vec2((p.x+1.)*.5,0.));
で読み出し、
vec3 wave(vec2 p,float v){ float l=p.y-v;
float r=pow(max(0.,1.-distance(p+vec2(0.,+.5),vec2(p.x,v))),10.0);
return vec3(r,r*r,0.);
}
みなさんご存じのGLSL sandboxなんかでぐりぐり動くデモでもインプットとなる情報は時間とマウス座標くらいですけど、ここに音データが加わるとまた表現の幅が広くなりますね。
Planets
Posted by g200kg : 2014/12/23 09:15:00