まず、WebAudioAPIで音を鳴らすには2つの方法があります。
- HTML5のAudioタグのように別途用意した外部の音声ファイルを再生する方法
- 必要に応じて逐次波形を生成する方法
さて、WebAudioAPIで逐次波形を生成するに当たって気になる点がいくつかあります。
まず、パフォーマンスの問題です。WebAudioAPIを制御するのは当然JavaScriptです。ブラウザ上で動作する(多くの場合)インタプリタ言語に過ぎないJavaScriptで聞くに耐えるほどのリアルタイム波形生成が可能なのか?同様に速度面という意味では、GCによる遅延の発生も懸念点の一つになりますね。
さらに、JavaScriptでどの程度まで波形を操ることができるのかということも、本格的にWebAudioAPIを活用することを考えた場合に問題になるでしょう。
上から順に一つ一つ疑問点を解決して行きましょう。まず、パフォーマンスの問題ですが、現状まともにWebAudioAPIを実装しているユーザーエージェントがGoogle Chromeしかないため、はっきり言ってなんとも言えない状態です。今回実際に使用してみてChromeによる実装では特にパフォーマンスが問題になることはないだろうと感じたのですが、Webkit系のブラウザでChromeと双璧をなすSafariでは動作することが確認できていないのでパフォーマンスについて言及する意味がないと判断しています。もっとも、実際の音を鳴らす部分については(Chrome)の実装だとネイティブ側でバックグラウンドで行なっているようなので、バッファリングさえきちんと行われればパフォーマンスが問題になることはほぼないでしょう。
GCによる遅延の発生もChromeで使用する分には特に大きなグリッチも発生せず許容できる範囲でしょう。
さて、この中で一番問題になりそうな、どの程度インターフェースが充実しているのかについてですが、これについてはWebAudioAPIの仕組みを解説しながら考えるのが一番いいでしょう。
まず、WebAudioAPIでは一つ一つの処理をノードとして表します。ゲインや各種フィルターはこのノードして表され、これをつなぎ合わせることで必要な処理手順を示します。そして、これらを入力や出力をあらわすノードに接続することで音が鳴るわけです。
つまり、WebAudioAPIでできる事の種類は、このノードの種類とその接続方法の数で決まるという事です。現状、W3Cの規格には16種類のノードが規定されています。そして、これらの接続順序は特に規定されていません。ユーザーが完全に自由に組み合わせることができます。また、JavascriptNodeというものを使用すれば、ユーザー独自の処理をJavascriptで記述することができます。ゆえに、処理速度を度外視すればどんな処理も実行可能なわけです。
さて、そろそろ規格の話は終わりにして実際にWebAudioAPIを使う方法を見ていく事にしましょう。WebAudioAPIで音を鳴らすには前述のとおり、現状ふたとおりのアプローチがあります。前者については再生する音楽ファイルさえ準備できていれば例えばこんなコードで音を鳴らすことができます。
var context = new webkitAudioContext();//2016/9/1 追記: (new AudioContext();とも) var source = context.createBufferSource(); var gain_node = context.createGain(); //2016/9/1 追記: API変更 var gain_node = context.createGainNode(); //音量変えるノード gain_node.gain.value = 0.5; source.connect(gain_node); gain_node.connect(context.destination); //destinationが最終的な出力 var request = new XMLHttpRequest(); var url = "path/to/music/file"; request.open("GET", url, true); request.responseType = "arraybuffer"; request.onload = function() { //2016/9/1 追記: API変更により以下に変更 source.buffer = context.createBuffer(request.response, false); //ArrayBufferからバッファを作成 第2引数をtrueにするとモノラルに //source.noteOn(context.currentTime); //指定した時間に再生する もし指定した時間がcontext.currentTimeより小さい場合はすぐ再生される context.decodeAudioData(request.response, function(buffer){ source.buffer = buffer; source.start(context.currentTime); //指定した時間に再生する もし指定した時間がcontext.currentTimeより小さい場合はすぐ再生される }, function(){ }) }; request.send();audioタグを使う場合に比べてかなりすることが多くなっていますね。まあ、そこら辺は仕方ないところでしょう。
一方、リアルタイムに波形を生成する場合はこうなります。
//簡単なコード例を思案中ここでは、個々の処理の意味は解説しません。いずれ、このゲームのシステムを元にしたシーケンサーを公開した時にでもより詳細な記事をあげようと思います。