はじめに

Google Chrome では Web Audio API という API を使って音を鳴らすことができます。
今回、これをつかって Sound Font を使った標準 MIDI ファイル(以下 SMF と表記)のプレイヤーを作ってみました。
なお、仕様の具体的な話しなどはほとんどしません。
音楽的な知識などもほとんどないため、何かおかしなことをしていたらご指摘いただけるとありがたいです。

また、今回の実装はあくまでも実験・検証用のものなので実用にはまだ手を加えなくてはいけないところが多いため、もし利用としようと思う方がいたらそこは注意してください。
動作環境は PC 版の Google Chrome のみです。
現在開催中の Google I/O で Chrome for Android でのサポートも明言されたそうですので、そのうち Android でも利用可能になるかも知れません。

もう少し完成度たかめてから公開しようかとも思っていたのですが、旬の話題みたいなのでビッグウェーブに乗ろうと思います。

デモ

以下のページで公開しています。
サウンドフォント(約10MB)のダウンロードがあるのでロードが終わるまでは気長に待ってください。

構成

まず、大きく分けて SMF を扱う部分と Sound Font を扱う部分に分かれます。
そして、この二つの部分を WebMidiLink というものでつないています。

  • SMF
    • SMF の Parser
      • MFi(着メロで使われたファイルフォーマット。拡張子は mld ) から SMF への変換モジュールもあります
    • MIDI メッセージのスケジューリングを行い、WebMidiLink へメッセージを流すプレイヤー
  • Sound Font
    • Sound Font version 2 の Parser
    • WebMidiLink のメッセージを受け取り実際に音を鳴らすシンセサイザー

WebMidiLink とは g200kg さんの提唱している window の message イベントを利用した MIDI メッセージをやり取りする仕様です。
http://www.g200kg.com/en/docs/webmidilink/

Sound Font

Sound Font では音色毎にいくつかの音域に分けてサンプルが格納されています。
このサンプルにはキーが設定されているため、その音域はその指定されているキーをもとにして平均律で算出し、再生速度を調整することで鳴らす事が出来ます。
また、各サンプルには Loop Start, Loop End が設定されていて、Note On されている時はこのループをぐるぐる再生し続けます。
(この再生方法のため、Safari では loop の実装が整っていないためおかしく聞こえます)

Sound Font の再生では他に以下の機能に対応しています。(しているつもりです…。)

  • Attack, Decay, Sustain, Release
  • Pitch Bend 変更
  • ピッチの補正
    • coarseTune
    • fineTune
    • Pitch Correction
    • modEnvToPitch
  • Web Audio API で扱えないサンプリングレートの補正(22,050 未満を引き延ばす)

また、今回つくったものでは A320U という GPLv2 ライセンスで公開されている Sound Font を標準で利用するようにしています。

ループ

一部のゲームなどでは SMF 内で独自にループ再生の目印を付けていることがあります。
Ys2 Eternal で使われているメタイベントの Marker を用いたループや、PC 版 RPG ツクールシリーズなどで使われている CC#111 のループが有名です。
今回作成したプレイヤーでは上記の2つのループ方式にも対応しています。

音を鳴らす

SMF では 16 のチャンネルがあり、それぞれに Volume, Panpot, Pitch Bend などが設定できます。
今回作成したものでは、各ノートを以下のように接続して鳴らしています。

[BufferSource] ---> [Panner] ---> [Gain(Volume, ADSR)] ---+
[BufferSource] ---> [Panner] ---> [Gain(Volume, ADSR)] ---+---> [Gain(Master)] ---> [Destination]
[BufferSource] ---> [Panner] ---> [Gain(Volume, ADSR)] ---+

BufferSource は各ノートで、PannerGain(Volume, ADSR) でチャンネル毎の設定を適用しています。
そして最後はマスター・ボリュームである Gain(Master) に繋いでそれを出力するという感じになっています。

現在は割と大雑把に上記のような構成の AudioNode を NoteOn ごとに作って使い捨てているのですが、今後はこのあたりをもっと効率的に行う予定です。

謝辞

g200kg さんの Web Audio API の解説 にはかなりお世話になりました。
また、音がずれてるなどの指摘をくださった @miyazaqui さん、音楽関係の基礎知識などについて丁寧に説明してくださった @yoya さんのおかげでなんとか聞こえるレベルまでこぎ着ける事が出来ました。ありがとうございます。

その他

今回は minify したファイルのみの公開とします。
もったいぶってるわけではなくて、近いうちに大規模な書き直しを行おうと思っているからです。
minify されててもいいってひとは適当にデモからソースを読んでください。

また A320U を使用しているとき Cello の音がズレているという指摘をもらっています。
これはサウンドフォントの modEnvToPitch という設定を無視するようにすれば正しい音程になるようですが、これを無効にすると他の音でおかしくなるので現在調査中です。

音程がずれる問題の原因 (2013/06/03 16:15 追記)

@g200kg さんからご指摘をいただきまして、Modulation Envelope の適用を行ったら改善したようです。 modEnvToPitch では Attack でピッチ・ローパスフィルタの設定が最も変わるというだけで、この記事を公開した段階ではその実装のみを行っていたのですが、Envelope なので Attack と Sustain の差によってピッチの変化がずれ、その結果音程がずれていたものだと思います。

追記:2013/06/03 16:15

以下の場所でソースコードの公開を行いました。