BLE MIDIのデバイスやライブラリを自前で実装してみようかな、という方にとって参考になるかもしれません。
BLE MIDIのタイムスタンプの仕様
BLE MIDIのデータの形式はこんな感じです(仕様書より)。従来のMIDI 1.0なメッセージにタイムスタンプ情報が付いた形です。BLEの1パケットに収まらない場合は分割することもできます。
上図はSysExというデータで、システムエクスクルーシブメッセージといい、機器の設定情報などをバイナリで送受信するためのものです。
- SysEx開始は 0xF0 (最初の緑色背景のデータ)
- SysEx終端は 0xF7 (最後の緑色背景のデータ)
- SysEx終端が来るまでが、データです。データの内容・長さは自由です。
- SysEx終端もMIDIメッセージなので、「直前1バイト」に、タイムスタンプ下位7bitと0x80を論理和(OR)したデータが入ります (水色背景のデータ)
- タイムスタンプは、下位7ビットと、ヘッダ内にある上位の6ビット分と組み合わせて13ビット(0〜8191、ミリ秒)の値になります。この値で通信のレイテンシを吸収します。
ハマりポイント1: タイムスタンプの精度
タイムスタンプは送信側デバイスのクロック精度に依存するので、送信側のタイムスタンプの値が1000増えたからといって、受信側の時計でも正確に1000ミリ秒が経過しているとは限りません。
単体のキーボードや音源など、マイコンで処理しているデバイスでは精度がわりと怪しくなります。あくまで前回のイベントからの間隔として扱うのが良さそうです。8192ミリ秒までの間隔しか測れないので気をつけます。
ハマりポイント2: タイムスタンプの上限値跨ぎ処理
1パケットで複数のMIDIメッセージを受信することがあり、それらのタイムスタンプ下位が0xFFを跨いだ場合(図の例だと、SysEx開始時が0xFFで、SysEx終端が0x00のとき)や、タイムスタンプ全体の値が8191を跨いだ場合も、デバッグしていて微妙にハマります。8秒遅れてイベントが発火したり…。
こちらは、前回のイベントのタイムスタンプとの差がマイナスなら8192を足す、みたいな対処で回避できるでしょう。
ハマりポイント3: SysExデータの末尾に現れるタイムスタンプ情報
SysEx終端の直前にタイムスタンプ下位が入るので、その1バイトだけを除いたデータをSysExデータとする必要があります。先頭の0xF0と末尾の0xF7を含めたデータを返却することを前提としたAPIであれば、ちょっとだけ面倒な実装になりそうです。ハマりポイント4: たまにSysExデータが欠ける
これが一番デカい罠だと思います。
SysExとタイムスタンプの仕様を見て、素直に「SysEx終端:0xF7が来るまでデータを読んだら、その直前の1バイトをタイムスタンプとして扱う」という解析処理を書いていると、仕様の罠にハマります。
そういう風に実装すると、イベントの発火タイミングが最大で127ミリ秒ズレて、その際にデータが欠損します。これはSysEx送受信の128回に1度の確率で発生します。
何故こんなことになるかというと…
タイムスタンプ下位7bitが0x77だった場合、送信されるバイト値は0xF7になります。0xF7を受信したからといって、この時点ではSysExをイベントキュー等に入れてはいけません。
正しくは「0xF7を受信したら次のデータ1バイトをチェックし、次も0xF7なら次のデータまでがSysEx、それ以外ならこのデータまでがSysEx」という風に実装する必要があります。
BLE MIDIは様々なデバイス間で使われる通信プロトコルのため、送受信の実装が拙いデバイスについても、異常動作しないように対応する必要があります。
具体的にはこの2つです。
- SysEx受信のときは、0xF7の2回受信を考慮して解析する。
- SysEx送信のときは、タイムスタンプの値として0xF7を送信しない。
以上、BLE MIDIを実装する際のハマりポイントでした。