まだパフォーマンスの観点で少なからず不満点はあるものの、Xwtまわりの作業がそれなりに落ち着いたところで、rtmidiのサポートに真面目に取り組むことにした。何しろ全く動かしていなかったので、まともに使えるかどうか不透明だったのである。そして実際まともに使えなかった。
rtmidiはportmidiとは実装のアプローチがだいぶ異なる。rtmidiにはtimestampのサポートが無い。タイミングが重要な場合は、常に自分で調整しなければならない。portmidiでは一応porttimeというタイマー実装がある…のだけど、実はこれはかなりstaticおじさん設計になっていて、具体的には2つのタイマーを同時に使うことが出来ない(!)。複数デバイス・複数ポートをサポートしようと思ったら、実質的に使い物にならない。そんなわけで、この意味ではportmidiにアドバンテージは無い。メッセージに使われないtimestampが入っている分、かえって邪魔なだけだ。
rtmidiはバイトストリームを受け取るだけなので、portmidiのようにsysexメッセージまわりでややこしい処理を加える必要がない。portmidiはメッセージにtimestampが付いてデータをデバイスに直接送信できないため、sysexを送信する命令は別途定義されていて、この扱いが煩雑になる(rtmidiなら愚直に送るだけ)。
rtmidiにはもうひとつメリットがある。rtmidiはデバイスリストの取得をきちんと動的に行う。呼び出された時点でのデバイスリストがきちんと返ってくる。portmidiはそうなっていない。最初に呼び出された時点でのデバイスリストがキャッシュされ、毎回それが返ってくる。ということは、portmidiを使うアプリだと、起動してデバイスリストを見て「あ、しまった、MIDI楽器を接続し忘れた」と思って、接続して、アプリケーションに戻ってきても、アプリケーションを終了しない限り、過去のリストが残り続けて結局使えないということになるわけである。これは好ましくない。
rtmidiが劣る部分もある。rtmidiはデバイスとポートを別々に扱うようになっていて、一見妥当そうに見えるのだけど、実際に「デバイス」の名前に対応しているのはMIDIドライバーの種類であって、たとえばLinuxならalsaまたはjackだ。それは明らかにデバイスではない。そしてそれぞれのデバイスにポートがある。portmidiでは同一のデバイスがinputとoutputを兼ねるものがあれば、同一のデバイスIDでポートを開くことが出来た。rtmidiでは、inputとoutputは、仮に実際には同一であったとしても区別され、別々のポートIDが割り当てられる(前述の通りrtmidiの「デバイス」はMIDIドライバー種別なので、異なるデバイスは実際にはポートのみで区別される。何とも誤解を招くAPIだ)。
いずれにしろ、筆者としてはさっさとrtmidiに移行したかったわけである(デバイスIDの混乱には目をつぶって)。もっとも、いくつか問題を解決する必要があった。
まず、rtmidiはC++のライブラリであるため、そのままではC#から使えない。P/Invokeするには、Cで書かれたAPIのようにアクセスできる必要がある。C++のライブラリをP/Invokeするためには、名前空間をサポートするために導入された、各種C++コンパイラで使われている命名規則に従ってシンボル名を加工したもの(この加工作業をmanglingという)を指定しなければならず、実質的には無理だ(manglingのルールはコンパイラに依存するから。msvc方式とかgcc方式とかいろいろある)。そんなわけで、単純にrtmidiのAPIを呼び出してcdeclでの呼び出しが可能になるようなrtmidi-cというライブラリを作った。実のところまだOSXビルドもWindowsビルドも用意していないので、正直まだクロスプラットフォームとは言いがたい。そのうちMakefileをいじってビルドできるようにしようとは思っている。(rtmidiのconfigureオプションを変えてリンクするライブラリのフラグを変える程度だ。)
実はC++でビルドされたライブラリのAPIを.NETから使えるようにするためのプロジェクトがあって、過去にはCxxiと呼ばれていて、今はCppSharpと呼ばれているのだけど(名前が変わった理由は日本人にとってはどうでもいいのだがcxxiがsexyと同じ発音になるのが気に入らなかったらしい。あほらしい)、なぜか現状Windowsでしか使えないというイミフな状態になったので、今回は最初から考慮の対象外だった。
rtmidiにはportmidiのようにデバイス管理情報のAPIが整備されていなかったので、portmidi-sharpのAPIに近いものを、rtmidi-sharpの中でも追加することにした。この時、例のデバイスIDとポート番号まわりで少なからず混乱が生じて、デバイスにアクセスするなり落ちたりして、多少対応に時間を取られたものの、その辺りを片付けたら、何とかノートメッセージを送れるようになった。
ただ、ノートメッセージは処理されるようになったものの、なぜか音色変更などが正しく行われない。よく見ると、rtmidiから何かしらエラーがstdoutあるいはstderrに出力されていた(うーん、それは困るかも…)。一体どのメッセージが処理できていないのか調べてみると、どうやらプログラムチェンジ (PC; 0xB0) で失敗しているようだった。ふと思い出して調べてみると、案の定というべきか、PCのメッセージサイズは2バイトだった(!) portmidiの時は、メッセージを受け取ったportmidiの実装側がよろしくやってくれていたわけである。PCの他にチャンネルプレッシャー (CAf; 0xC0) も2バイトだった。
というわけで、StatusByteの値が0xB0, 0xC0だったら2バイト、他は3バイトで送ることで、この問題は解決…するはずだったが、何やら解決したのは一部の音だけだった。…よく考えてみたら、StatusByteにはチャネルも含まれるので、0xB0, 0xC0 ではなく 0xBx, 0xCx でなければならないわけだ。ありがちな失敗だった。
ともあれ、それらの問題を全て修正した結果、rtmidi-sharpは何とか動くようになった。
とりあえず、rtmidi-sharpは過去のリポジトリではrtmidi-cと一緒くたになっていたのだけど、rtmidi-cだけを独立したリポジトリに含めるようにして、rtmidi-sharpはportmidi-sharpと並べてmanaged-midiに含めることにした。そして最新版のxmdspではrtmidi-sharpを使うようにした。