JUCE Advent Calendar 2023、2日目の記事です。
2023年にはほとんどJUCEらしいコードをいじる機会が無かったatsushienoです。今年はJUCE 7.0.9にやや唐突に追加されたMIDI-CI (Capability Inquiry) サポートについて解説します。これならちょっと詳しい程度で書けるっ
そもそもMIDI-CIとは
MIDI-CIは、「MIDIデバイス」同士でリクエスト/レスポンス方式の通信を行えるユニバーサル・システムエクスクルーシブ (SysEx) メッセージです(SysExでやり取りできればいいので、デバイスがMIDI 2.0である必要はありません)。MIDI 1.0の時代は単方向だったので、MIDI-CIが想定する双方向通信を実現するには、相互にinとoutが繋がっている必要があります。
「MIDIデバイス」と括弧書きにしてあるのは、実際には物理デバイスではなくソフトウェアMIDI等もこの中に含まれるので、抽象的な概念だと理解したほうが適切です。プラットフォームMIDIアクセスAPIにおける「デバイス」の概念があれば、それに近いと考えてよいでしょう(「MIDIポート」のほうが近い環境もあるでしょう)。
実際には、メッセージの送受信さえ出来ていれば、その間のトランスポートが何であるかは問わないので、たとえばMIDI 1.0のinとoutが繋がっている「必要」があるわけではありません。とはいえ、現実的にはプラットフォームMIDIアクセスAPIでMIDIデバイスと認識されているものに接続するというのが汎用的でしょう。他にもBonjour (mDNS) プロトコルなどを用いてデバイスを探索するという方法も考えられます。
MIDI-CIの具体的な機能の内容を説明する前に言及しておくべきことがあります。実はMIDI 2.0の仕様は2023年6月に大幅な改訂を加えていて、この時にMIDI-CIも「バージョン1.2」に更新されています。以前のMIDI-CIについて解説されているものでは不十分だと思ったほうがよいです。MIDI-CI関連記事の新旧を見極める基準のひとつとしては、MIDI 2.0を構成する「3つのP (Three Ps)」としてProfile Configuration, Protocol Negotiation, Property Exchangeを挙げているものは、バージョン1.2で「廃止された」Protocol Negotiationが含まれているので、古いと思ってよいでしょう(根本的に仕様が変わったので、それについて言及していて然るべきではあります)。バージョン1.2ではProcess Inquiryが新たに追加されたので(「3つのP」は首をすげ替えられて生きているといえるでしょう)、それについて言及していれば新しいです。またMIDI-CI仕様の詳細に踏み込むのであれば無視できない概念として機能ブロック(Function Block)というコンセプトも追加されています。
MIDI-CIバージョン1.2では、以下のカテゴリーに分類できるメッセージが規定されています。
- Profile Configuration: デバイスに問い合わせを送って、そのデバイスがどのような「プロファイル」に準拠したものであるかを知ることができます。「ピアノだ」「フルートだ」「ギターペダルだ」「GM総合音源だ」「ソフトウェアシンセだ」といった返答が来ることになります。具体的な楽器プロファイルの仕様はまだありません。
- Property Exchange: デバイスの「プロパティ」の情報を取得/設定するためのものです。メッセージの内容はデフォルトではJSONでやり取りされます(独自のMIME typeで送受信することも論理的には可能です)。最近KORGのKeyStage(未発売)が世界で初めてPEをサポートするMIDIキーボードとして話題になりました。
- Process Inquiry: デバイスの現在のMIDIメッセージの保持された状態があれば、それを一括して取得する等の機能を含むメッセージ群です。MIDI dumpの類を規定したと考えられます。
MIDI-CI APIがカバーすべき機能
「MIDI-CIの機能をサポートするライブラリ」が実装すべきものは、MIDI-CIが規定する各種メッセージのSysExバイナリのシリアライゼーション/デシリアライゼーションと、その送受信に基づいてデバイスの情報を保持する機構ということになります。それぞれのメッセージはUniversal SysExのフォーマットに準拠するかたちで詳細データフォーマットが定められており、それらを抽象化するオブジェクトが有用です。たとえばDiscoveryMessage
、DiscoveryResponse
、EndpointInquiry
、EndpointInquiryResponse
…といった構造体が規定されるでしょう(これらの擬似的な名前はjuce_midi_ciをもとにしています)。
MIDI-CI 1.2仕様では、問い合わせを送信するMIDIデバイスをInitiator、応答を返すMIDIデバイスをResponderと呼んでおり、これらを表現するクラスにイベントハンドラーとして「デバイス検出の問い合わせが来た」「デバイス検出の応答が返ってきた」等をもち、またメッセージの処理フロー(たとえば「デバイス検出リクエストがResponderに届いたらInitiatorに返信を送る」等)を実装できていれば、基本的な送受信の仕組みはできていることになります。MIDI-CIにはタイムアウト等の規定も含まれているので、場合によっては実装でカバーする必要があるかもしれません。
実際の送受信はプラットフォームMIDIアクセスAPI等に丸投げできるので、自前で実装する必要はないでしょう。Bonjour等で独自に実装できるように、メッセージの送受信部分は外部から設定できることが望ましいです。
SysExバイナリデータの内容の取得・生成
MIDI-CIが前提とするSysExデータは、MIDI 1.0バイトストリームとMIDI 2.0 UMPの各トランスポートでバイナリデータフォーマットが異なります。
- MIDI 1.0 SysEx:
F0h
...F7h
、7-bit encoded - MIDI 2.0 SysEx7 UMP:
3Gh
xxh
xxh
...、7-bit encoded - MIDI 2.0 SysEx8 UMP:
5Gh
xxh
xxh
...、8-bit (not encoded)
(G
はgroup)
MIDI-CIの主要な用途がMIDI 2.0システムであることを考えると、MIDI-CIライブラリはMIDI 2.0を想定してこれらを全てサポートしていることが望ましく、MIDI-CIのSysExバイナリデータを解析したり生成したりするときには、これらをデコードした8-bitバイナリデータで処理できることが望ましいでしょう。
余談: プラットフォームMIDIアクセスAPIのサポートは?
2023年はLinuxのALSA APIにMIDI 2.0サポートが追加された年であり(2023年6月の更新版に基づいています)、すでに対応APIが追加されていたmacOS/iOS群のCoreMIDIと合わせて、基本的なMIDI-CIサポートをクロスプラットフォームで利用できる状態だったといえます。Windowsでは2024年まで出ない予定です(開発者がほぼ1人しかいないし、現実的に対応できるのかどうかは不透明です)。Androidでの対応は未公表です(やる気はあるみたいですが)。
とはいえ、プラットフォームMIDIアクセスAPIにおけるMIDI 2.0対応は、MIDI-CIの機能を実現する上で必須というわけではありません。前述の通り、MIDI-CIはMIDI 1.0のトランスポートのI/Oのペアの上でも構築できます。実のところ、JUCE 7.0.9のMIDI-CIサポートでは、プラットフォームMIDIアクセスAPIを必要としていません。MIDI 2.0対応が遅れているWindowsやAndroidでも、仕様上は安心してMIDI-CIを使用できます。
MIDI-CIをどこまで高レベルでサポートするかは、API次第です。たとえばMIDI-CIをサポートしているとされるCoreMIDIでも、Property ExchangeについてはほとんどAPIがなく、「なまのSysExをやり取りする程度なら可能」というレベルでしかありません。これはProperty Exchangeで必要とされるパケットの分割に対応するのが面倒くさいという問題と、リアルタイムシステムを前提としたMIDIの世界においてProperty Exchangeの実装で必要となるJSONをどのようにサポートするかという厄介な問題を含んでいるためかもしれません。筆者もMIDI-CIをKotlinで途中まで実装してあるのですが(2023年6月アップデート未対応)、Property Exchangeは面倒なので未着手です。
JUCE 7.0.9におけるMIDI-CIのサポート
juce_midi_ciモジュール
JUCE 7.0.9でMIDI-CIのサポートが追加されました。JUCEのlead developerの手によるもので、相応に本気度の高いコードになっています。これは現時点ではjuce_midi_ci
という新しいモジュールに含まれています。juce_audio_devices
やjuce_audio_basics
ではありません。具体的にはISCライセンスではなくGPLv3ライセンスという大きな違いが出てきます。
juce_midi_ciは、プラットフォームのMIDIアクセスAPIからは独立して実装されています。バイナリストリームの処理とMIDI-CIの処理フローを体現したInitiatorとResponderの構成が中心です。
MIDI-CIの対応バージョンも1.2なのでしっかり最新版に追従しています。さらに(これはMIDI-CI仕様で要求されていて面倒くさいのですが)バージョン1.1以前のメッセージも正しく解析できるようになっています(生成のサポートは不要です)。一部のMessage
クラスフィールドは、MIDI-CIバージョン1.2のメッセージでのみ有効です。
juce_midi_ciモジュールの主な役割は次のカテゴリーに分けられます。全てjuce::midi_ci
namespaceに含まれます。
Message
(namespace) : MIDI CIのデータ生成と解析Device
: MIDI-CI Initiatorの実装Responder
: MIDI-CI Responderの実装ProfileHost
,PropertyHost
などが個別のホスト機能を分散して担当している
Encodings
: 7-bit encodedのストリームからの8-bitストリームへの変換実装や、Mcoded7の実装などResponderOutput
: MIDI-CIバイトストリームの出力を抽象化したもの
MIDI-CIメッセージは以下の表のように定義されています。
機能 | Initiator | Responder |
---|---|---|
デバイスの検出 | Discovery | DiscoveryResponse |
エンドポイントの探索 | EndpointInquiry | EndpointInquiryResponse |
MUIDの無効化 | InvalidateMUID | - |
通知類 | ACK, NAK | |
プロファイル問い合わせ | ProfileInquiry | ProfileInquiryResponse |
プロファイル通知 | ProfileAdded, ProfileRemoved | |
プロファイル詳細取得 | ProfileDetails | ProfileDetailsResponse |
プロファイル有効化 | ProfileOn | ProfileEnabledReport |
プロファイル無効化 | ProfileOff | ProfileDisabledReport |
プロファイル固有詳細 | ProfileSpecificData | |
プロパティリスト取得 | PropertyExchangeCapabilities | PropertyExchangeCapabilitiesResponse |
プロパティ取得 | PropertyGetData | PropertyGetDataResponse |
プロパティ設定 | PropertySetData | PropertySetDataResponse |
プロパティ通知登録 | PropertySubscribe | PropertySubscribeResponse |
プロセス問い合わせ | ProcessInquiry | ProcessInquiryResponse |
プロセスMIDIレポート | ProcessMidiMessageReport | ProcessMidiMessageReportResponse, ProcessEndMidiMessageReport |
ちなみに、筆者がコードリーディングした限りでは、2023年6月に新しく仕様に追加されたProcess Inquiry関連のメッセージ処理を実装しているコードはありませんでした。
CapabilityInquiryDemo
juce_midi_ciモジュールでどんなことができるようになるかは、JUCE/examples/Audio/CapabilityInquiryDemo.h
というデモアプリで試せます。5000行近くあるそれなりに大規模なサンプルです。JUCEのPIPになっているので、PIPとしてビルドすれば足りそうですが、JUCEトップディレクトリからcmakeでビルドするのが簡単でしょう:
cmake . -B cmake-build -DJUCE_BUILD_EXAMPLES=ON -DJUCE_BUILD_EXTRAS=ON cmake --build cmake-build --target CapabilityInquiryDemo
-DJUCE_BUILD_EXAMPLES=ON -DJUCE_BUILD_EXTRAS=ON
をcmakeの引数として追加すればCLion NovaみたいなIDEからデバッグも可能です。./cmake-build/examples/Audio/CapabilityInquiryDemo_artefacts/CapabilityInquiryDemo
などの実行ファイルがビルドされるはずなので、これを起動して、MIDI-CIデモの動作を確認してみましょう。
このアプリではプラットフォームのMIDIデバイスが列挙されますが、これはjuce_midi_ciの機能ではなく、デモアプリのコードとして実装されています。
CapabilityInquiryDemoアプリの中では、MIDI-CIのメッセージは、この"MIDI I/O"のタブで選択されたMidiInput
とMidiOutput
の間で送受信されることが前提になります。最初にDevice Discoveryメッセージのやり取りが成功すると、"Discovery"タブの内容が更新されます。
Profilesのリストは空白ですが、これは今回テストで稼働していたMIDI-CIクライアントがProfile Inquiryに空白で応答していたためです。あと、このスクショでは見えませんが、下の方にはさらにProperty Exchangeのためのリストも含まれています(これもProperty Exchangeをサポートする実装をもっていないので今回は何も出ていません)。リスト項目が出ていればSet Profile On/OffとかProperty Get/Setみたいな操作も可能になるでしょう。
"Logging"タブを開くと、このアプリが送受信したMIDI-CIメッセージのログを参照できます。
CapabilityInquiryDemoアプリでは、DiscoveryResponse
が適宜処理されると、続いてProfileInquiry
を投げて、その結果はこのタブの"Profiles"の項目に反映されるようですが、筆者はProfile Configurationを処理できるMIDI-CI実装をもっていないので、とりあえず実際に動かしてみたものはここまでとします。Device
(とResponder
)のAPIの使い方次第なので、これ以上はCapabilityInquiryDemoでこれらのクラスがどのように使われているかを追及してみてください。
誰がこのAPIを使う必要があるのか
MIDI-CI APIは、MIDI 2.0の機能を活用できるMIDIデバイスやMIDIシステムの開発者が使用するものであって、現状では利用する意味のある開発者はかなり限定的です。たとえばまだMIDI 1.0しかサポートされていないJUCEプラグインAPIを使っている開発者にとってはほぼ無意味です。AudioUnitのようにMIDI 2.0プロトコルを利用できるとされている場合は、MIDI-CIに対する問い合わせに応答することにはそれなりの意味があるでしょう。
スタンドアローンアプリケーションでMIDI入力を送受信する場合は、このAPIが便利である可能性は高いです。たとえば楽器メーカーがMIDI 2.0対応デバイスを開発するとき、PC側ホストでMIDI 2.0サポートを実装する必要があるなら、MIDI-CIを使用する必要性が高いでしょう。そういうときはこのjuce_midi_ciが役に立つかもしれません。
筆者は、この記事を書くために、CapabilityInquiryDemoと相互運用できるか試してみる目的で、atsushieno/ktmidiに含まれるMIDI-CI実装を2023年6月仕様に部分的に追従させてみましたが、CapabilityInquiryDemoはMIDI-CI開発のヘルプツールとしてなかなか有用でした。JUCEを利用できない場合(ktmidiのようにC++でない場合など)でも、利用する場面はあるかもしれません。
筆者が1年くらい前にktmidiで作っていたMidiCIInitiator
とMidiCIResponder
というクラスが、それぞれJUCEのDevice
とResponder
に近い構成なので(プラットフォームAPIから独立している部分まで含めて類似)、誰が作ってもおよそ似たような構成になるのだろうと思います。CoreMIDIはもう少しエンドユーザー向けのAPIになっていたと記憶していますが、2023年6月のアップデートを受けて何かしら変更が加えられたかもしれません(未確認)。
(本稿執筆時点で筆者の環境でALSAサポートが期待通りに動いていないので稼働サンプルが出せませんが、上記MidiCIResponder
を使ったシンプルなコードです。)