長い MIDI System Exclusive に迫る罠 (JUCE on macOS での1バイト欠落問題の原因)
sigboost ソフトウェアエンジニア @skb_apos です。 midiglue を作っています。
記事の概要
- MIDI Exclusive を
濫用応用してファイルを送信するプロトコルを作って実装したら - macOS でのみ「周期的に 1バイト欠ける 」問題が起きた
- ユーザーコード → JUCE フレームワーク → CoreMidi ライブラリ → USB MIDI ドライバ
と手渡されていく中での原因を突き止めた
MIDI System Exclusive とは
MIDI の共通規格を超えた制御信号(など)を送りたい場合に、MIDI 機器のベンダが独自に定めることのできる MIDI メッセージの種類。
DIN を使う従来の MIDI でも、 USB MIDI でも共通に使える。
SC-88Pro という音源の場合の例がこちら。 「リバーブレベル」のメッセージ内容を以下に引用する。
Rev Level: F0 41 10 42 12 40 01 33 (00-7F) sum F7
0xF0
で始まり、 0xF7
で終わる可変長のメッセージ。上記の例はリバーブレベルを 0x00
から 0x7F
の 128 段階で変更できるよう。
1バイトのうち、MSB側の1ビット (ビットマスク 0b1000'0000
) のビットは、特別な意味を持つのでメッセージ内 (開始・終了バイト以外) のバイトは使用できない。
長い System Exclusive を送ったときに起きたこと
問題の検証のために、uint8_t
で連番を記録したファイルを転送した。このときの System Exclusive メッセージの長さは 1038 バイト。
上の画像でハイライトされている箇所は、よく見ると 連番の規則性を崩している 。何が起きたかというと、 一定間隔で MIDI メッセージの1バイト*1が欠落する ということのよう。
原因は何か
手元で作った System Exclusive メッセージが通過する箇所をそれぞれ見ていこう。
ユーザーが1000バイトの System Exclusive メッセージを作る。
サウンドを扱うフレームワーク(今回はJUCE) は、 macOS の CoreMIDI の最大メッセージ長 256 バイト に合わせるため、メッセージを 256 バイトごとに分割する
USB MIDI ドライバは、4バイト固定長 USB MIDI イベントパケット (1バイトヘッダ + 3バイト MIDI ペイロード) を作るため、最大 256 バイトのメッセージをさらに 3 バイトごとに分割する
ここで図の「255」のバイトは、1 バイトの断片となってしまった。
ところが、USB MIDI イベントパケットのヘッダには、System Exclusive 関連のものは以下のものしかない。
CIN | MIDI_x サイズ | 説明 |
---|---|---|
0x04 | 3 | システム・エクスクルーシブ始端 / 中間 |
0x05 | 1 | 1 バイト・システムコモン・メッセージ/システム・エクスクルーシブ終端 (1 バイト) |
0x06 | 2 | システム・エクスクルーシブ終端 (2 バイト) |
0x07 | 3 | システム・エクスクルーシブ終端 (3 バイト) |
(シンセ・アンプラグド USB-MIDI (10) より抜粋引用)
つまり、 「System Exclusive の中間」 0x04
のヘッダが使われるのはいつでも残りメッセージ長が 3 を超える ときであり、メッセージが1バイトに断片化することは (MIDIの) 規格上想定されていない。
ここから先は推測でしかないが、ドライバは、3 バイトすべて書き込まれたときか、1 バイト・2 バイトのそれぞれで System Exclusive が終端されるときのみメッセージを送信する。断片化した 1 バイトは送信されず欠落してしまう。
要するに
「256 バイトごと (CoreMIDI) 、 3 バイトごと (MIDI/USB MIDI) というそれぞれの都合によるメッセージの断片化が、規格の想定を超えた断片を作ってしまい、うまく送れなかったよ!!」
誰のバグであるとも結論づけにくい。
System Exclusive メッセージは 256 バイトを超えないようにしようね、っていうのが今回の結論であったが、 "macOS + JUCE" という限られた環境での解決策でしかない *2。