MIDI-CI Property Exchangeに関する覚書

この覚書の端緒

2023/12/2にjuce-midi-ciに関する記事を書いたやつの延長戦で(?)、atsushieno/ktmidiMIDI-CI実装をアップデートして機能追加している。この記事にも書いたが、JUCEのCapabilityInquiryDemoは割と機能豊富なので、自分のMIDI-CI responder実装のdogfoodingで重宝している。

記事を書いた時点では、そもそもMIDI接続に使用しているrtmidiバインディングのビルドが安定せず(各種OSでのArm64サポートが期待できないrtmidi-jnaから新しく作っているrtmidi-javacppに移行しつつあって、Kotlin/Gradleでの実例が乏しく難易度が高いし、仮想MIDIポートのサポートも調整が必要だった)、運良く接続できた時だけ稼働させて調べて書いていた。先週ようやくMIDI-CIサポートが「開発可能」な状態まで来たので、MIDI-CIの関連しようとにらめっこしながら実装を追加している。

記事でも書いたが、MIDI-CIはバージョン1.2で大幅な仕様変更が加えられた。Protocol Negotiationは削除されてUMP仕様の一部となり、代わりにProcess Inquiryが追加された。Function Blockのコンセプトが追加されてプロファイルの適用対象のコンテキストの新たな要素となった(MIDI-CIのメッセージバイト列に破壊的変更はない)。

Common Rules

MIDI-CIには、無視できない関連仕様がいくつか存在する。MIDI-CI仕様を読み込んで実装できるのは、MIDI-CIのメッセージフォーマットのバイナリシリアライゼーションくらいで、それらのメッセージがアプリケーション間でどのような手順に基づいて実装されるかは、実は別の仕様に記述されている。具体的には、まず次の2つが重要だ:

  • M2-102: Common Rules for MIDI-CI Profile
  • M2-103: Common Rules for MIDI-CI Property Exchange

これらの仕様がいかに重要であるかを説明するために(といっても本稿はProperty Exchange(以下PE)に関する覚書なのでPEについてのみ追求する)、まずMIDI-CI仕様本体の規定内容を把握しておこう。MIDI-CI仕様本体では、PEについては次のメッセージが規定されている:

  • Property Exchange Capabilities InquiryおよびReply to it
  • Get Property DataおよびReply to it
  • Set Property DataおよびReply to it
  • SubscriptionおよびReply to it
  • Notify

そして、これらがそれぞれヘッダーと本体をもつ、ということくらいしか規定されていない。プロパティを取得したり設定したりするためには、そのプロパティのメタ情報が必要になるが、MIDI-CI仕様そのものには、これが含まれていない。MIDI-CIの機能を使うなら、「プロパティ情報のリスト」もまたPEによって対象デバイスに問い合わせる必要がある(他にもたとえばProfile Detailsに基づいてできなくもないだろう)。個別のシステムに特化してプロパティ情報が既知である(つまりMIDI-CIでやり取りする必要がない)プログラムでもない限り、MIDI-CI仕様のPEに関する規定だけでは何も出来ないというわけだ。

Common Rules for PEの仕様では、このヘッダーにどのような項目が含まれているべきかが具体的に規定されている。たとえば、ヘッダーは必ずJSONシリアライズされ、本体は通常はJSONシリアライズされる(他のmime typeも利用できる)、Get Property Dataのヘッダーには取得したいプロパティ名を{"resource": "xyz"}のように指定する、Reply to Get Property Dataの本体にはプロパティリストが含まれる(そのJSONフォーマットのスキーマもある)、といったことが規定されている。MIDI-CI仕様本体には「JSONで送受信する」ということすら規定されていない。

Common Rules for PEの仕様にはバージョンがあって、これは仕様書の箇所によってはProperty Exchangeのバージョンとも呼ばれている。PEのバージョンはReply to PE Capababilitiesメッセージにも含まれていて、MIDI-CI仕様では特定のPEのバージョンのみを想定した内容にはなっていない。つまり、MIDI-CI仕様のバージョンが変わらなくても、Common Rules for PEのバージョンが上がると、同じPEの仕組みの上でも異なるアプリケーション プロトコルに基づくやり取りが発生する可能性が(将来的には)ある。逆に、MIDI-CIの仕様はバージョン1.2で大きく変化したが、Common Rules for PEの仕様は根本的には変わっていない。2024年初現在でも2020年12月発行のバージョン1.1が最新だ。

MIDI-CI実装上の考慮事項としては、PEの実装とプロパティメタデータの扱いは境界を分けておいて、Common Rules for PEに準拠する部分はプロパティメタデータ操作インターフェースの実装のひとつとして作っておくと良さそうだ。

プロパティ操作機能の有無と互換性の確認

MIDI-CI Initiatorにとって、接続したResponderでPEがサポートされているかどうかを確認するには、2つのアプローチが考えられる:

  • 自分が発行したDiscovery Messageに対するReply to Discovery MessageのフィールドCapability Inquiry Category Supportedフィールドで、PEのフラグが立っていなければ、それはそのResponderがPEをサポートしていないことを意味する。ただしこれはDiscoveryが先行していることが前提なので、最初からMUIDまで把握している(あるいはMUIDを特に解釈しない)システムでは、このアプローチは成り立たない。
  • PE Capabilities Inquiryを送信して、Reply to PE Capabilitiesで応答が返ってくることを確認する。応答がないことも考えられるし、応答はNAKかもしれない(PEをサポートしていないことになる)。

Property Exchange Foundational Resource

Common Rules for PE仕様(バージョン1.0)のセクション7(Resources and Resource Data in a Device)では、プロパティのリストを取得するためのResourceListをサポートすることが必要とされている。そのリソース定義の詳細はセクション12にまとめられている。

また、M2-105-UM: Foundational Resources: DeviceInfo, ChannelList, JSONSchemaという仕様では以下のプロパティも規定されていて、これらをサポートすることが強く推奨されている("strongly recommended"):

  • DeviceInfo : そのデバイス/エンドポイントの情報。SysExでやり取りされるmanufacturer ID、family ID、model ID、version ID(software revision)などのフィールドのほか、文字列で定義される名前もフィールドとして規定されている
  • ChannelList : デバイスが用意・想定しているチャンネルの構成。チャンネルの「クラスター」やプログラム名などが取得できることになっている
  • JSONSchema : そのデバイス/エンドポイントのリソースのJSONスキーマ定義

プロパティのメタ情報を使わないシナリオ

Common Rules for PEが無ければプロパティのリストすら取得できないことになるので、一般的なMIDI-CIの機構では、Common Rules for PEの実装は必須であるようにも見える。しかし、すでにResponderのPEのメタ情報が既知で(どんなプロパティが存在するのかがわかっていて、どのチャンネルを利用できるかもわかっている場合)、値の取得/設定/変更通知のみが必要である、と事前にわかっているのであれば、Common Rulesに基づくやり取りは、必須ではないと考えられる。

メッセージチャンクと巨大なプロパティ情報の並列処理

MIDI-CI仕様(本体)では、PEのSysExパケットを分割してプロパティの本体(body)を送受信するメッセージチャンクの仕組みが規定されている。MIDI 2.0 UMPのMDS (mixed data set)と同様、複数のGet Property Data / Set Property Dataが並列して処理できるようになっている。これはSysExパケットそのものを分割して(F7で終わらないF0メッセージとして)送受信するかどうかとは別の話だ。

PEの並列処理を可能にするために、PEのbodyを含むメッセージではリクエストIDが渡される。これが7ビットしかないため、並列処理最大数は127になる。この数値はProperty Exchange CapabilitiesでInitiator側の最大値を渡せて、そのReply to ...のメッセージでResponder側の最大値が返されることになる

リアルタイム処理とプロパティヘッダの解析

Common Rules for PEでは、headerもbodyもJSONが使われることがほぼ前提になっていて、特にheaderはJSON以外のフォーマットを使うことが想定されていない。PEのメタデータの取得そのものはPEにおいて必須ではないが、Get Property Data やReply to Get Property Dataといったメッセージでも、このJSON前提のPEヘッダーが必要になることに変わりはない。JSONの解析は明らかにリアルタイム処理向きではない。

もっとも、Common Rules for PEのヘッダーで利用できるJSONは、JSON仕様よりもはるかに厳しい字句上の制約が加えられている ("5.1.1 JSON Header Data Property Additional Rules"):

  • プロパティ値はnumber, boolean, stringのみ
  • stringプロパティには最大長が必ず指定される
  • プロパティのkey名はcamelCaseで最大20文字のASCII文字列
  • リクエスト(Get Property DataやSet Property Data)のヘッダーの最初のプロパティはresourceでなければならない
  • レスポンス(Reply to Get Property DataやReply to Set Property Data)のヘッダーの最初のプロパティはstatusでなければならない
  • ヘッダーに空白文字を含めることはできない
    • 値にもnon-ASCIIな改行文字は含まれないため、必ず1行で完結しなければならない

Common Rules for PEヘッダーの解析はサイズ上の制約から事前にアロケーションを済ませたメモリ領域だけに展開でき、空白文字等の利用も許さないので、リアルタイム解析を可能にするべく最大限配慮した設計になっているともいえるが、realtime readyな言語で実装した上で詳細を検討すべきところだろう。

ちなみに本体のほうはmedia typeを問わないため、単なるapplication/octet-streamでもよいが、application/jsonやzlib+Mcoded7のようにリアルタイム性能を損なうようなデータ処理を要するmedia typeが指定される可能性もある。