https://adventar.org/calendars/3353 の4日目エントリーです。
クロスプラットフォームMIDI API
前回はオーディオAPIに標準といえるものが無くプラットフォーム固有のAPIになる、という話を書きましたが、MIDIについても同様にプラットフォーム固有のAPIになります。一方でアプリケーションで使用したいMIDIの機能は「デバイスに接続して、MIDIポートを開いて、MIDIメッセージを送受信する」程度の単純なものであることが多いです。
これを実現できる程度に単純化されたクロスプラットフォームのMIDIライブラリがいくつか開発されています。有名なのがportmidiとrtmidiでしょう。どちらもCのAPIを公開しているので、他言語からのバインディングが比較的容易に行なえます。rtmidiのAPIはもともとC++のみだったのですが、しばらく前にわたしがCのAPIを追加して、その後何人かのコントリビューターが改良を加えているようです。
portmidiとporttime
一般的には、MIDIのデータ転送はオーディオのデータ転送ほど大規模ではなく、処理中のデータの加工も比較的手軽に行えます。一方で、リアルタイム性については(中で行われる処理が軽量であるとしても)オーディオに準ずる正確性が期待されます。また、オーディオのチャンネルは大概はステレオ構成でデータも単純なPCMデータですが、MIDIのトラックは多数になることがあり、それらを1回のMIDIメッセージ処理時間の間に全て処理することが求められます。現代的にはいわゆるMIDIデバイスとのやり取りで問題になることは少ないと思いますが、たとえばVSTなどを活用したソフトウェア・シンセサイザーのチャンネルであるとしたら、処理が重くなるかもしれません。
portmidiは、リアルタイム処理を意識したタイマーライブラリporttimeがそのサブセットとして含まれています。rtmidiについては、リアルタイム呼び出しはアプリケーション側の責務ということになります。
メッセージを構造化するか否か
わたしはどちらかというとportmidiよりはrtmidiのほうが好みなのですが、その最大の理由はMIDIメッセージを無理に構造化しないことにあります。MIDIデータはタイムスタンプとMIDIイベントのペアを時系列順にやり取りするものであり、MIDIイベントはチャンネル、ステータスコード、1バイトまたは2バイトの引数またはシステムエクスクルーシブメッセージやメタデータについては可変長の引数、といった構造になっています。
これをMIDI仕様に合わせて構造化することもできるのですが、SMFからロードしてそのままMIDIプレイヤーやMIDIデバイスに転送したい場合、特にパフォーマンスを考慮して無駄なエンコード/デコード処理を施したくない場合には、メッセージの構造化は無駄な処理でしかありません。残念ながらportmidiはメッセージの送受信にバイト列ではなく構造化されたメッセージ(正確には、32bit intに変形した短いメッセージか、構造化したF[n]hメッセージ)を受け取るようになっているので、無駄な処理が避けられません。
MIDI APIを各種言語で実装する場合のアプローチ
わたしはMono/.NET環境向けのクロスプラットフォームMIDIライブラリとしてmanaged-midiを公開しているのですが、MIDIデバイスアクセス部分には当初はportmidiとrtmidiのバインディングを中心とした構成となっていました。しかしportmidiは前述の問題で抵抗感があり、またrtmidiにもプラットフォームによって生じる問題があり、さらにいずれの場合もプラットフォームごとにビルド済バイナリを一緒に提供しなければならないのは面倒なので、結局自前でWinMM/CoreMIDI/ALSAのバインディングを作ってしまいました。
このアプローチだと、余計なネイティブライブラリ配布の手間が省けて楽ですが、プラットフォームごとのMIDIアクセスのAPIを学ばなければならなくなるので、深入りするのでなければ、あまりお勧めはできません。一方でクロスプラットフォームAPIが提供していない機能にも、比較的容易に対応できるでしょう。
同様のアプローチを採っているものとしては、Rust用のAPIであるmidirがあります: https://github.com/Boddlnagg/midir