前回のMIDIライブラリの説明が長くなりすぎたので、本来書くつもりだった話がまるっと一日ずれてしまったのだけど、今回はMIDIプレイヤーのAPIについて。今日は時間が無いので短い内容で。
話の続きを書く前に、コードのリポジトリを明記しておこうと思う: https://github.com/atsushieno/managed-midi/
MIDIプレイヤーの基本設計は単純だ。前回書いたSMFライブラリで得られたMIDIメッセージ(デルタタイムとMIDIイベント)のマージされた集合を入力として、デルタタイムが経過するのを待機してタイミング良くMIDIイベントを「送信」するだけだ。
上記の動作を反映したものが、MidiPlayerというクラスとして実装されている。特筆すべきこととして、このクラスはMIDIイベントを送信する部分については「何もしない」。正確に言えば、「もしイベントハンドラーが登録されていたら、それを呼び出す」。
今回のAdvent Calendarの第1回で、MLDSPというMoonlightベースのプレイヤーを作ったという話を書いた。このプレイヤーは実は(当然というべきか)Silverlight(あるいはブラウザ上のMoonlight)でも動作する。Silverlightはブラウザプラグインだから、MIDIアクセスのAPIは存在しない。少なくともSilverlight3までは、デスクトップのネイティブAPIにアクセスする手段は全く無かった(MLDSPはSilverlight 2.0の頃に作られている)。ブラウザ上で動作するMLDSPでは、MIDI音源へのアクセスは一切無い。何をしていたかというと、「何もしなかった」。つまり、楽曲のMIDIメッセージ送信タイミングに合わせて、画面上でキーボードやキーオンメーターなどが踊っていただけだった。もちろんMIDIプレイヤーとしてはほぼ意味がないのだけど、このMIDIプレイヤーAPIが限りなくプラットフォーム中立なかたちで利用できることが分かると思う。
筆者が見てきた限りでは、世にある.NETのMIDIライブラリは、Windows(winmm)を前提としたものばかりで、他のプラットフォームに対応しているものは無く、汎用性に欠ける。managed-midiライブラリは、platform abstraction layerを明確に切り離すことで、プラットフォームの流行に巻き添えを食わない形で生き残れるようにしたいと思っている。
実際にデスクトップ上で動作するMIDIプレイヤーでは、portmidiというCベースのクロスプラットフォームMIDIライブラリのラッパーAPIをC#で作成し、それを呼び出すPortMidiPlayerというクラスを使っている(MLDSPでも使っていた)。
クロスプラットフォームなネイティブMIDIアクセスライブラリは、portmidi以外にもあって、たとえば筆者が最近乗り換えを検討しているのはrtmidiというライブラリだ。こちらはiOSなどにも対応している。これに対応したRtMidiPlayerを作るのも簡単だった。(ちなみに乗り換えを検討しているのは、portmidiがJava SDKをセットアップしていないとビルドできないという問題があって、開発者の多くがビルドできないことが予想されたためだ。portmidiに依存するせいで利便性が巻き添えになっては困る。)
rtmidiのバインディング及びプレイヤーは既に作成してみたのだけど、rtmidi本体はC++で書かれているので別途Cで作り直したAPIが必要になったことと、まだ一度も実際に使っていないこともあって、github上には https://github.com/atsushieno/rtmidi-sharp として置いてあるものの、いずれモジュールを分割した上で、今あるものは廃棄される予定だ。
このMidiPlayerは、演奏時に遅延が生じると、単純に遅延を引きずってしまう。適時タイマーの計測値とデルタタイムの総計を比較するだけなので、補正処理を入れようと思えば出来なくはないけど、とりあえずその必要が生じたことがないので、やっていない。他の言語におけるMIDIプレイヤーのAPIとの大きな違いは、そのくらいだろうかと思う。