JUCE 7.0.9で追加されたMIDI-CIサポートについて

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アクセスAPIMIDIバイスと認識されているものに接続するというのが汎用的でしょう。他にも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で送受信することも論理的には可能です)。最近KORGKeyStage(未発売)が世界で初めてPEをサポートするMIDIキーボードとして話題になりました。
  • Process Inquiry: デバイスの現在のMIDIメッセージの保持された状態があれば、それを一括して取得する等の機能を含むメッセージ群です。MIDI dumpの類を規定したと考えられます。

MIDI-CI APIがカバーすべき機能

MIDI-CIの機能をサポートするライブラリ」が実装すべきものは、MIDI-CIが規定する各種メッセージのSysExバイナリのシリアライゼーション/デシリアライゼーションと、その送受信に基づいてデバイスの情報を保持する機構ということになります。それぞれのメッセージはUniversal SysExのフォーマットに準拠するかたちで詳細データフォーマットが定められており、それらを抽象化するオブジェクトが有用です。たとえばDiscoveryMessageDiscoveryResponseEndpointInquiryEndpointInquiryResponse…といった構造体が規定されるでしょう(これらの擬似的な名前は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年はLinuxALSA APIMIDI 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対応が遅れているWindowsAndroidでも、仕様上は安心して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_devicesjuce_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デモの動作を確認してみましょう。

CapabilityInquiryDemo

このアプリではプラットフォームのMIDIバイスが列挙されますが、これはjuce_midi_ciの機能ではなく、デモアプリのコードとして実装されています。

CapabilityInquiryDemoアプリの中では、MIDI-CIのメッセージは、この"MIDI I/O"のタブで選択されたMidiInputMidiOutputの間で送受信されることが前提になります。最初にDevice Discoveryメッセージのやり取りが成功すると、"Discovery"タブの内容が更新されます。

Discoveryの結果が反映されている様子

Profilesのリストは空白ですが、これは今回テストで稼働していたMIDI-CIクライアントがProfile Inquiryに空白で応答していたためです。あと、このスクショでは見えませんが、下の方にはさらにProperty Exchangeのためのリストも含まれています(これもProperty Exchangeをサポートする実装をもっていないので今回は何も出ていません)。リスト項目が出ていればSet Profile On/OffとかProperty Get/Setみたいな操作も可能になるでしょう。

"Logging"タブを開くと、このアプリが送受信したMIDI-CIメッセージのログを参照できます。

Loggingタブの内容

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で作っていたMidiCIInitiatorMidiCIResponderというクラスが、それぞれJUCEのDeviceResponderに近い構成なので(プラットフォームAPIから独立している部分まで含めて類似)、誰が作ってもおよそ似たような構成になるのだろうと思います。CoreMIDIはもう少しエンドユーザー向けのAPIになっていたと記憶していますが、2023年6月のアップデートを受けて何かしら変更が加えられたかもしれません(未確認)。

(本稿執筆時点で筆者の環境でALSAサポートが期待通りに動いていないので稼働サンプルが出せませんが、上記MidiCIResponderを使ったシンプルなコードです。)