rsp

長い MIDI System Exclusive に迫る罠 (JUCE on macOS での1バイト欠落問題の原因)

sigboost ソフトウェアエンジニア @skb_apos です。 midiglue を作っています。

記事の概要

  • MIDI Exclusive を濫用応用してファイルを送信するプロトコルを作って実装したら
  • macOS でのみ「周期的に 1バイト欠ける 」問題が起きた
  • ユーザーコード → JUCE フレームワーク → CoreMidi ライブラリ → USB MIDI ドライバ
    と手渡されていく中での原因を突き止めた

MIDI System Exclusive とは

基礎編~システムエクスクルーシヴ - DTMゼミ

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 バイト。

f:id:nor_isio:20181205010009p:plain

上の画像でハイライトされている箇所は、よく見ると 連番の規則性を崩している 。何が起きたかというと、 一定間隔で MIDI メッセージの1バイト*1が欠落する ということのよう。

原因は何か

手元で作った System Exclusive メッセージが通過する箇所をそれぞれ見ていこう。

ユーザーが1000バイトの System Exclusive メッセージを作る。 f:id:nor_isio:20181205015803p:plain

サウンドを扱うフレームワーク(今回はJUCE) は、 macOS の CoreMIDI の最大メッセージ長 256 バイト に合わせるため、メッセージを 256 バイトごとに分割する f:id:nor_isio:20181205015823p:plain

USB MIDI ドライバは、4バイト固定長 USB MIDI イベントパケット (1バイトヘッダ + 3バイト MIDI ペイロード) を作るため、最大 256 バイトのメッセージをさらに 3 バイトごとに分割する f:id:nor_isio:20181205015839p:plain

ここで図の「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

先達はあらまほしきことなり

*1:前述のようにMIDIメッセージ内では7ビットしか自由にできないので、任意ファイルの1バイトを2バイトに分割する処理を事前にしている。画像では4ビット欠落しているが、これをMIDIメッセージに換算すると1バイトの欠落。

*2:限られた環境でしか起きてないのだから、まぁ、いいのか…。