今週は外出が多かったのでコードをいじる時間があまり無かったので、今日ようやく少しだけ更新。
PortMidiPlayerを少しいじった。まずSysExメッセージのサポート。portmidiのPm_WriteSysEx()の引数の意味が不明確で、ソースを読まないと冒頭でF0/F7を指定してやらなければならないということが分からなかった。わざわざPm_WriteSysEx()なんて名前にしているんだから、それくらい自動的に補完してやれば良いのに。
あと非同期演奏モードを追加した。と言ってもまだThread.Sleep()でタイミングをとっている同期プレイヤを、別スレッドで走らせて、一時停止でスレッドを止めたりしているだけだ。そしてMoonlight上で動かしたらどうもきちんと動いてくれないので、やはりUIスレッドの問題を解決しなければならないだろうというところに落ち着いた。いずれTimerベースで書き換えるかもしれないけど、とりあえずDispatcher.BeginInvokeを使うことにして、イベントをループから拾って描画できるようにはなった。
ただ、一時停止機能を実装していて、ひとつ大きな問題があることに気がついた。MIDI楽器における一時停止というのは、単なるメッセージ送信の一時停止で済むものではなく、ノートオンに対応するノートオフを送信するべきだということだ。そうしないと、発音しているものがそのまま発音しっぱなしで残ってしまうのである。これでは必要な一時停止の機能が実現できていない。
これを解決するために何が必要になるか。通常のMIDIプレイヤでは、一時停止すると、ノートオンしているキーの全てについて、対応するノートオフを送信していると思われる。ということは、現在どのキーがオンになっているか、把握しているということになるだろう。16チャネルの128キーの全てについて、これを把握しているということになる(まあGM Level 1では、同時発音数は16 + 8 = 24音までなので、本当はそこまで必要ないはずだけど、実際の音源モジュールではもっと発音できるのが普通だ)。記憶するだけなら、16 x 128のビットフラグ = 256バイトのメモリがあれば足りる。
ただ、これは面倒なので、いっそVolume = 0 を送信してしまえばいいのではないかとも考えている。ただ、確かVelocity = 0でも音が出る音源があったはずなので、Volume=0が安全かどうかは確実なことは言えない。Volumeと異なりVelocityにはリリースレートやキーオフディレイなどの要素があり得るので消音ではない、というだけのことかもしれないけど。
いずれにせよ、ある程度音源に送信しているMIDIステータスを、プレイヤ側で把握するレジスタマシンのような、エミュレータのような存在が、必要になるかもしれないと思った。そういうものを作るのも楽しそうだ。