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タグを使う場合に比べてかなりすることが多くなっていますね。まあ、そこら辺は仕方ないところでしょう。
一方、リアルタイムに波形を生成する場合はこうなります。
//簡単なコード例を思案中
ここでは、個々の処理の意味は解説しません。いずれ、このゲームのシステムを元にしたシーケンサーを公開した時にでもより詳細な記事をあげようと思います。

2012年5月10日木曜日

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

二重の意味で間が開いてしまいましたが、先日jsdo.itで公開した音ゲー(もどき)の技術解説をしようと思います。まだ遊んだことがないという方は、ここに貼り付けておくのでどうぞご自分で一度遊んでみてください


まず、今回のゲームで核となる機能は次のとおりです。

  • 音楽データとパネル表示パターン生成に使うMMLパーサー
  • 音ゲーゆえに音がならなければ話しにならないので、ブラウザー上で音を鳴らす(何らかの)手段
  • ゲームとして成り立たせるためのフレームワーク
上記の内、今回特に苦労したのが前者2つでした。MMLパーサーの"パーサー"の部分については、以前別のゲームを制作した際に汎用性の高い部分をライブラリのような形で定義していたので、それにMML固有の文法や各種パラメータ抽出のセマンティックアクションを定義するだけでよかったのですが、いかんせん、MML自体昔の規格だからか読みづらい!(まあ、MIDIよりはアルファベットでかける分マシなんでしょうけど・・・ちょっと余談ですが、世の中は実に広く不思議なもので、この世には画像ファイルをバイナリで"解析"するバイナリアンという人たちがいます。なんでも、彼らはあの数字の羅列から"画像"をイメージすることが可能なんだとか。もしMIDIファイルのバイナリアンがいたら、数字の羅列から曲をイメージできたりするんですかね〜。そうか、MIDIのバイナリでアスキーアートを表現しつつ、MIDIシーケンサーにかけたらちゃんとした曲になるっていうファイルを作るのも面白そうかもな〜。まあ、きっと二番煎じだろうけど)
え〜、随分脱線してしまいましたが、本題に戻しましょう。MML自体が読みづらいのは構わないんですよ。人間が読みづらいものとプログラムにとってパースしにくいものは等価ではないので。(今でこそプログラミングを当たり前のようにしているので、MMLのソースを見ても特になんとも思わなくなりましたけど、耐性のない人が読んだら確実に暗号ですよね、あれって。試しに次のソースを読んでみてください。なんの曲だかわかりますか?

   @1 t110 l4 cdef edcr efga gfer
   crcr crcr l8 ccddeeff l4 edcr
   l2 {ccd}{dee} {ffr}e dc r4
   (t2) @0 l4 "ceg""ceg""ceg""ceg" "cfa""cfa""cfa""cfa"
   "dgb""dgb""dgb""dgb" "gb<d>""gb<d>""gb<d>""gb<d>"
   "ceg"r"ceg"r "gb<d>"r"gb<d>"r "ceg""dfa" "egb""fa<c>"
   "dfa<c>""g<cd>""eg<c>"r
   l2 "ceg""dfa" "egb""cdfa" "dfgb""ceg" l4 "<ceg<c"


正解は「カエルの歌」です。今回の主題のゲームのサンプル曲から引用したものなんですが、これが歌だとひと目でわかるでしょうか?わからないですよね・・・)

いかんいかん、また脱線してしまった。MMLパーサーを作るにあたってなやんだことは、文法の方言の多さなんですね。昔、(それこそBASICの時代)にMMLは隆盛を極めたようで色々亜種が存在するようです。このゲームを作り始めるまでMMLについて存在自体は知っていたものの、具体的にどのような規格なのかまでは知らなかったのでまず、どのような文法があるか調べその中からどの文法を採用するかに随分時間がかかってしまいました。
なんだか技術的な話ではなくなってきてしまったので次行きましょう。

次は音ゲーの命とも言える音なのですが、このゲームを作成するにあたってFlashは一切使わずに完成させたかったので選択肢は2つに絞られてしまいます。一つ目はMozillaが独自に実装しているAudio Data APIを使う方法、そしてもうひとつがW3Cの勧告にもあるWeb Audio APIを使う方法です。
今回の場合、なるべく早く完成させたかったのとドキュメントの充実性という観点から後者を選択することにしました。(まあ、ドキュメントが充実していると言っても実際はW3Cの勧告ドキュメントだったんですが。それでも、ちょっと検索しただけで実働デモ以上のプログラムが発見できたのは大きなプラスでしたね〜)
なんだか今回は余談だらけで長くなってしまったので、一旦ここで切りたいと思います。次回はWeb Audio APIの解説をします。それでは!(^O^)/