2012年5月21日月曜日

自作音ゲー(もどき)の技術解説(後編)

またもや少しご無沙汰してしまいました。今回はいよいよ核心部分となるWebAudioAPIの使い方について説明します。
まず、WebAudioAPIで音を鳴らすには2つの方法があります。

  • HTML5のAudioタグのように別途用意した外部の音声ファイルを再生する方法
  • 必要に応じて逐次波形を生成する方法
です。これらのうち、前者についてW3Cの規格では、あくまで「長さの短い効果音などの音声ファイルに対して適用すべき手法であり、普通の曲などについては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タグを使う場合に比べてかなりすることが多くなっていますね。まあ、そこら辺は仕方ないところでしょう。
一方、リアルタイムに波形を生成する場合はこうなります。
//簡単なコード例を思案中
ここでは、個々の処理の意味は解説しません。いずれ、このゲームのシステムを元にしたシーケンサーを公開した時にでもより詳細な記事をあげようと思います。

0 件のコメント:

コメントを投稿

なにか意見や感想、質問などがあれば、ご自由にお書きください。