コンテンツへスキップ
ものがたり
戻る

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アクセス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では、以下のカテゴリーに分類できるメッセージが規定されています。

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の各トランスポートでバイナリデータフォーマットが異なります。

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_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に含まれます。

MIDI-CIメッセージは以下の表のように定義されています。

機能InitiatorResponder
デバイスの検出DiscoveryDiscoveryResponse
エンドポイントの探索EndpointInquiryEndpointInquiryResponse
MUIDの無効化InvalidateMUID-
通知類ACK, NAK
プロファイル問い合わせProfileInquiryProfileInquiryResponse
プロファイル通知ProfileAdded, ProfileRemoved
プロファイル詳細取得ProfileDetailsProfileDetailsResponse
プロファイル有効化ProfileOnProfileEnabledReport
プロファイル無効化ProfileOffProfileDisabledReport
プロファイル固有詳細ProfileSpecificData
プロパティリスト取得PropertyExchangeCapabilitiesPropertyExchangeCapabilitiesResponse
プロパティ取得PropertyGetDataPropertyGetDataResponse
プロパティ設定PropertySetDataPropertySetDataResponse
プロパティ通知登録PropertySubscribePropertySubscribeResponse
プロセス問い合わせProcessInquiryProcessInquiryResponse
プロセスMIDIレポートProcessMidiMessageReportProcessMidiMessageReportResponse, 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を使ったシンプルなコードです。)


この記事を共有:

前の記事
12月の開発記録 (2023)
次の記事
11月の開発記録 (2023)