LoginSignup
3
3

More than 5 years have passed since last update.

Cross Platform MIDI Tools Advent Calendar #2: MIDIライブラリについて

Posted at

これは過去に書いたコードの使い回しになるが、MIDIプレイヤーを作るためには、MIDIまわりを操作するライブラリが必要になる(なまのバイトデータを直接送受信するのでもない限り)。MIDIプレイヤーというのは具体的には(少なくとも現状では)標準MIDIファイル (SMF; Standard MIDI File) のプレイヤーだから、SMFを解析してMIDIメッセージにしてデバイスに送信する仕組みが必要になる。

CライブラリとしてSMFを解析できるライブラリは既にいくつもあるので、P/Invokeしてしまえば必ずしも自前で作る必要はないが、明らかに無駄なパフォーマンスコストが発生するし、SMFを読むのは難しくないので、C#ネイティブで実装した。

SMF解析について難しいことはあまりないが、注意点はいくつかある。まず、SMFに含まれるMIDIメッセージには、(1)デルタタイムと(2)MIDIイベントが含まれている。(1)デルタタイムは、「可変長数値表現」を用いた、「8ビット目を用いない数値」の連続で表現する。7ビットの表現範囲を超える値については、デルタタイムは2バイト以上になる。14ビットの表現範囲を超えると(全休符が何小節も続くとこれが発生しうる)、3バイト目が必要になる。

また、通常の(2)MIDIイベントには、(2a)ステータスコードと呼ばれる「命令」と、(2b)MSB、(2c)LSBという各1バイト(ただしデータとしては各7ビット)の「データ」が含まれるが、通常の(2a)ステータスコードは必ず7ビット目が1になっている(16進数であれば0x80〜0xFFとなる)ところ、SMFでは、直前のMIDIイベントと同一のイベントが送信される場合、この部分は完全に省略することができる。これは特にエクスプレッションやモジュレーションなどを連続的に増大させる場合などに圧縮効果が大きい(とは言ってもほんの数バイトなので現代的な問題ではないが)。MIDIイベントの省略を検出するには、データの先頭ビットを見る。1(16進数なら0x80〜0xFF)であれば、それは省略されていないMIDIイベントだ。もしデータの先頭ビットが0なら、それはランニングステータスの省略を意味している。

解析されたSMFは、ヘッダ(デルタタイムの扱いを除いては意味のある情報はほとんど無い)とトラック群から成り、トラックにはMIDIメッセージのみが含まれる、単純な構成だ。

SMFの解析が終わったら、次はこれを時系列に沿って演奏することになるが、その前にひとつ処理しておくべきことがある。SMFフォーマット1のSMFは、複数のトラックで構成されているが、実際に演奏する前に、それらを1つのイベントの列にしておくことが望ましい。というのは、演奏命令はタイマーを使用して逐次送信するため、単一のタイマーを使用してイベントを送信するほうが効率的だし、何より同期のミスマッチにかかる不安がない(小さい)。

そのため、MIDIメッセージを時系列に沿ってマージすることが必要になるが、これは単純に「デルタタイムを変換して得られた絶対時間」をキーにして全トラックのメッセージを合わせてソートすれば良いというものではない。ひとつのトラックにおいて「前のノートをオフ、同じタイミングで同じノートをオン」といったイベントの流れがある場合、この順序は保持されなければならない(時間をおいて2つのノートオンが続いた後、1つのノートオフが送信された場合、2番目のノートは即座に消えてしまい、何も発音されないことになる)。単純なソートではこの「意味のある順序」が保持されない。というわけで、トラックのマージは「同じタイミングで送信されるイベント群」を並べ替える必要がある。

本当はこの後プレイヤーのAPIについても書くつもりだったのだけど、どうも長くなってきたみたいなので、今日はこの辺にして明日続きを書くことにする。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3