2月の開発記録 (2024)

Linuxデスクトップ環境がずっと死んでいて消耗していますが、今月はかろうじて生き延びました…(!?)

2月はAndroid 15というでかいリリース(developer previewですが)があった関係でだいぶ揺り動かされました。先にそっちから書きます。

Android MidiUmpDeviceService対応

数日前にAndroid 15のMIDI 2.0対応についてここに書きました。

atsushieno.hatenablog.com

AAPはイベントメッセージのフォーマットとしてUMPを採用しているし、任意のインストゥルメントプラグインMIDI 1.0のMidiDeviceServiceとして公開できる仕組みを構築しているわけで、MidiDeviceServiceのUMP対応が出てきたら、当然やらないわけにはいきません。

resident-midi-keyboardを開発したときのコンセプトのひとつは「AAPと連携してUMPをやり取りできる仕組み」であり、従前から作り込んでいたので、特にAPIとしてほぼMidiDeviceServiceと同じであれば、24時間以内に全部対応できるでしょ…!と意気込んで着手したわけですが、なぜかうまく動かない…原因は↑で書いたドキュメンテーションのバグだったわけですが、泣く泣く24時間以内リリースはあきらめました(!?)

「新API公開から24時間以内にリリース」は自分がXamarin.Androidの開発で昔いつもやっていたことだったので(もちろんそれが許されるくらいのプロダクトの規模感だったときの話ですが)、ちょっと懐かしいノリですね。

いずれにせよ、↑で書いた通り原因は判明したので、resident-midi-keyboardにはUMP対応がクライアント/サービスともに追加され、AAPはMidiUmpDeviceService化できるようになりました。Androidオーディオチームのリーダー曰く、MidiUmpDeviceServiceをサポートした世界初の事例だそうです。そりゃそうだ…!

ktmidi-ci-toolにも仮想MIDIポートとして使えるMidiDeviceServiceがあるので(Android OSとしては動的に作成できる仮想ポートの類は無いので、あくまで固定のソフトウェアMIDIポートです)、こちらもMidiUmpDeviceServiceを追加したのですが、MIDI 1.0の時と同じように「自分自身のvirtual MIDIポートと繋いだらinitiatorとreceiverのテストができるのでは…?」と考えて接続しようとしても、「同一ポート」への接続を試みていることになってしまうので(実際そうだし)、UMP接続ではメッセージがdropされてしまうようです。自分ではEchoUmpDeviceServiceみたいなやつを作って、in portに来たものをそのままout portsに送りつけています(EchoというかForwardというべきかもしれない)。

ktmidi-ci-toolのほうは、「UMP対応!」と言うためにはgroupの扱いがきちんとできている必要があって、ここはgroupの概念が存在しないMIDI 1.0しかサポートしてこなかったので、その対応を進めているところです…といいたいところですが、まず先送りしていたMIDI-CIサポートAPIの整備から着手しているところです。まあUMPサポートがあるだけでも大きいので、現状のまま出してもいい気がしてきましたが…(!?)

ktmidi 0.8.0-pre work

MidiAccessのUMPポート対応

ktmidiは「MIDI 2.0をサポートするライブラリ」ですが、これにはプラットフォームMIDI APIのサポートが含まれていません。UMPはApple OSとAndroid 13以降とLinux kernel 6.5以降 & alsa-lib 1.5.10以降でしかサポートされていなかったので、ずっと先送りにしていました。Android 13もUSB MIDI 2.0デバイスのみの対応だったので、自分には関係ない話でした。

Androdi 15でこの状況が変わったので、MidiAccessAPIでは、MidiPortDetailsに暫定的にUMP用ポートか否かを判別するプロパティが追加されています。Android APIでいえばMidiDeviceInfo.typeALSAでいえばsnd_seq_client_infoに追加されたtypeに相当する情報です。resident-midi-keyboardのはktmidiのAPIMIDIバイスを列挙して接続するので、

ALSAなら普段使いのデスクトップですぐできるだろ?と思われそうですが、AlsaMidiAccessが利用しているalsaktはプラットフォーム上に存在するlibasound.soをロードして使うjavacppのライブラリだったので、まずこれをalsa-libをstatic linkしてlibjnialsa.so(JavaCPPが自動生成するネイティブライブラリ)を構築するように変更する必要があります。

これが以外と筋が悪く、libasoundはシステム上に存在するconfigをロードして使わなければならないところ、alsa-libがstatic linkされている場合にこのconfigファイルはどこに存在するのか/バンドルしていいものなのか…といった問題があり、現状ではconfigのロードで問題が出るくらいなら従来のlibasound.soを動的にロードする方式に戻すか…となっています。そうなると、システム上に存在するlibasound.soUbuntu 22.04ならlibasound2-devが提供しているもの)がalsa-lib 1.5.10以降でない環境では、たとえkernelが6.5以降が動いていても、まだUMPが使えないことになります。

そんなわけで悩みどころなのですが、今Linuxデスクトップ環境が絶賛死亡中なので、いつ着手できるかわからない状態です。マウスコンピューターを捨ててMSI機を復活させるしか無いか…

rtmidiバインディングとの格闘(現在進行形)

2月はJavaCPPの使い方もいろいろ調べ直していました。JavaCPPは元来Java用のバインディング生成機構で、ビルドもMavenが前提になっているところが割とあり、gradle-javacppが公式といえど情報が少ないやつです。特にGradleプラグインは「いつ」必要になるのか不鮮明で、今のところ「バインディングライブラリ用のGradleプラグインと、そのライブラリを参照するモジュール用のGradleプラグインが別々にあり、参照する側のモジュールでは直接参照しないアプリケーションでもこのプラグインが必要になる」という理解で動かしています。.NETでいえばPCLのプロファイルの概念が曖昧なまま、要件としては(JavaCPP固有のレベルで)存在している感じです(と書くとどれだけ面倒なやつか伝わるかも)。

このプラグインをちゃんと使わないと、パッケージの参照解決で「存在し得ない」プラットフォームネイティブコードを含むパッケージが要求されるようになったり(Linux上でWindows用jarを要求されたりする)、逆に実行時に必要なjnirtmidi.soを含むjarがビルド時に参照として解決されずにUnsatisfiedLinkErrorが発生したりすることになります。

あと、これだけいろいろ調べて解決してきたのに、「rtmidi-javacppをMaven Centralから引っ張ってきたやつはjarにクラスが含まれていないかのように失敗し、MavenLocalから引っ張ってきたやつはうまくビルドできる」みたいな問題がまだ発生していて、こんなに問題を引き起こすならrtmidi-jnaを復活させるほうがまだ筋が良いか…?みたいになっています。が、librtmidi.*をバンドルする等の問題が発生するはずなので、それもそれで手を出しにくい…

rtmidi cinteropバインディングとの格闘(現在進行形)

rtmidi-javacppはKotlin/JVMの問題なのですが、rtmidiはKotlin-Nativeでも問題になります(!) Kotlin-Nativeのライブラリも、参照関係をstatic linkしないと実行時に動的に必要になる…というのでstatic linkするように作り変えました。これで特にApple OS上ではうまく行っているはずなのですが(iOSはそもそもrtmidiがサポートするようになったのが最近なので未確認)、Linux上でこのバインディングを使うアプリケーションでALSAのリンクを動的に解決できず失敗する…みたいな問題が起きています。多分どこかに簡単に対応できるやり方があるんだけど多方面に問題が出すぎていて未解決です。とにかく人手が足りない…(!?)

misc. AAP work

Gradle/AGPとの格闘

2月はまず、ずっとリリースできていなかったAAP v0.8.0をリリースするための作業から始まりました。一般向けにはこれで終わりです。この節の残りは実際に何をやっていたかを記録しておきます。(開発記録なので!)

0.8.0のコードは11月上旬からあまり変わってなかったのですが、リリース作業が面倒でやっていなかったわけではなくて、AAPのaarをプロジェクト内でimplementation(project(...))で参照しているとビルドできるのにMavenパッケージとしてimplementation("org.androidaudioplugin...")で参照しているとオーディオファイルが正常にロードできないという謎の問題がリリースを阻んでいました。

AAP 0.7.8のリリース時には、AAPの全派生プロジェクトでaap-coreをgit submoduleにしてaarをプロジェクト内参照する構造にするという前提で、控えめのアップデートを行っていたのですが(0.7.8に追従させたパッケージは少ないです)、このアプローチではaap-juceの特にProjucerを前提としたプロジェクトが移行できませんでした。仕方ない、根本的にビルドシステム(GradleとAGP)に踏み込んで問題を解決するか…という意気で、GradleやAGP (AOSP) のソースを、主にPrefabやCMakeのサポートを中心に読んで過ごしました。

問題の一端は、project(...)で参照解決しているとデバッグビルドが流用され、Maven(MavenLocalを含む)経由で参照解決するとリリースビルドが使われる、というGradleの挙動にありました。そしてリリースビルドでのみ想定通りに動作しなかったのは、Cコードの未定義動作がLLVMの最適化フラグによって挙動が変わった結果であって(LLVMのvectorizationが問題のトリガーになっているところまで突き止めました)、問題が再現するかどうかはデバッグビルドかリリースビルドかに依存する、という「よくある問題」のいち類型でした。project(...)で参照していたらデバッグビルドになる、とは一般的には理解されていないと思いますが、そんなハマり方があるんですね…

なお0.8.0のWhat's newはすでに11月に書いていたとおりです(これ以上書くことがない…)

NdkBinderのcallbackとの格闘

AAP拡張機能の新バージョンで目玉だったはずのホスト拡張機能のサポートですが、ホストとプラグインが別々のアプリケーションだとBinderがcallbackオブジェクトを渡してくれないという問題が残っていて、これが情報が多くなくて「BnCInterfaceをJava APIでいうところのStub代わりに使えばよい」というところまで辿り着くのにしばらくかかりました。NdkBinderもだいぶ自分が草分け的に使っているので、情報がほぼ無いんですよね…

3月の予定

3月は割と日本にいないので進捗は悪いだろうなと思います。あとM3用(あとたぶん技術書典用)の新刊も書かないとですし。あと、ひさびさにオーディオ勉強会をやることになると思って画策しています(やるなら4月)。

Android 15のMIDI 2.0サポートについて

2/16(PST)にAndroid 15 DP1が公開され、"Virtual MIDI 2.0 Devices" のサポートが追加されました。これについてMIDI 2.0の最新の状況(自分が知りうる公知のもの)を踏まえて解説します。この機能を要望したのは自分なので*1、たぶん自分が説明するのが一番正しいはず…

承前: Android 13のMIDI 2.0サポートからどうなった?

AndroidMIDI 2.0の機能が初めて追加されたのは2年前のAndroid 13です。正式版がリリースされたとき、このブログでその詳細を解説しました。

atsushieno.hatenablog.com

内容を箇条書きでまとめるとこんな感じです:

  • USB-MIDI 2.0プロトコルの規格が固まったので、Android 13でプラットフォームのレベルで実装された
  • MIDI 2.0のサポートは既存のMIDIポートの接続でもできる(UMPストリームをMIDI 1.0メッセージの代わりに流せばいいだけ)
  • OSが認識できる((MidiManager.getDevicesForTransport()で取得できる))ソフトウェアMIDI 2.0デバイスはまだ作成できない(のでissuetrackerで要望を出した)
    • 後方互換性の問題で単なる機能追加はできない
  • GoogleAndroidオーディオチームがこの辺の新機能を仕切り直してMIDI-CIサポートも追加してくると思う

ここから2年経って現在に至るわけですが、ソフトウェアMIDI 2.0デバイスのサポートが実際に追加された以外の部分は、半分くらいは「ハズレ」になった感じです。

え、そんなに外れるような予想を出したの??と思われそうですが、もちろん理由があります。2023年6月に、MIDI 2.0仕様は大きく変容したのです。これについては6月に詳しくまとめました。

atsushieno.hatenablog.com

ここで重要なのは「Protocol Negotiationがなくなった」という部分です。↑の話はProtocol Negotiationを使うことで論理的に1つのMIDIポートをMIDI 1.0 bytestreamとMIDI 2.0 UMPの両方で切り替えて使い回せる前提だったのが*2、そうではなくなってしまったわけです。代わりに導入されたUMP Stream ConfigurationメッセージはUMPとして規定されているので、MIDI 1.0でも2.0でもUniversal SysExであればよかったProtocol Negotiationとは異なり、そのポートが最初からUMPトランスポートであることが前提となりました。

2022年の時点では、MIDI 2.0を包括的にサポートしているプラットフォームAPIAppleのCoreMIDIとAndroidのUSB限定MIDI 2.0ポートのサポートしかなく、各プラットフォームでどうMIDI 2.0接続をデザインするかは不透明な状態でした。おそらく各社がMMAMIDI協会)で相談・総合接続の検証を行っていく過程で、「ポートはMIDI 1.0トランスポートかUMPポートか『あらかじめ』規定しておく」という合意が形成されたように(外からは)見えます。

ちなみにもうひとつ「外した」と思われるMIDI-CIサポートも追加してくると思う」ですが、ktmidiでMIDI-CI実装をゼロから作ったので、将来的には代わりにコレを使ってもらえればと思います。まだAPIが未整備…

MidiUmpDeviceService: 唯一のAPI変更点

上記のまとめでは「新機能を仕切り直してMIDI-CIサポートも追加してくると思う」と書いたわけですが、MIDI-CIの面倒なProtocol Negotiationがそもそもなくなったので、プラットフォームのAPIMIDI-CIは不要になりました。結果的に、Android 15で追加されたのは、新しいMidiUmpDeviceServiceという、MidiDeviceServiceの兄弟分のようなクラス1つのみです。使い方は基本的にMidiDeviceServiceとほぼ同じです。MidiUmpDeviceServiceから派生した実装クラスを作成し、AndroidManifest.xml<service>android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"<meta-data>付きで登録し、対応するump_device_info.xmlのようなファイル(リソース名前は任意)をres/xmlに置くだけです。

特に、MidiDeviceServiceの中で使われるMidiReceiverMidiSenderMidiInputPortMidiOutputPortなどのクラスは全て共通です。なぜなら、(わたしが「外れた」予想の説明で書いた通り)MIDI 1.0のトランスポートの上にUMPを流しても、単なるByteArrayを送受信することにしかならないので、APIを変える必要が無いからです。

ただし、AndroidManifest.xml<service>MIDIバイスメタデータXMLの内容は微妙に異なっているので気をつける必要があります。具体的には、(1) MidiDeviceServiceではmeta-data属性で指定していましたが、MidiUmpDeviceServiceではproperty属性で指定し(この属性の存在を知らなかったのですが、API Level 31で追加されていました)、また(2) MidiDeviceServiceのときは<input-port><output-port>をそれぞれ記述していたのに対し、MIDI 2.0のUMPポートは双方向なので、<port>という要素を使うことになります。Android 15では、ここからinputとoutputのペアが自動的に作成されることになります。

特にproperty属性を使う点に注意してください。2/21現時点ではMidiUmpDeviceServiceのAPIリファレンスにある記述が正しく、android.media.midiパッケージのOverviewにある記述は間違っています(自分はここでハマりました)。

あとonGetInputPortReceivers()のreturnが地味にArray<MidiReceiver>ではなくList<MidiReceiver>になっています。わたしはこれがArrayだったのは良くないと思っているので、地味に良い変更だと思います。Arrayだとポート数がService生成時に固定になってしまうので、将来的に動的ポート生成をサポートしようとしても整合しなくなる可能性があります。(とはいえまだdeveloper previewなので、単なる実装での見落としとして「元に戻される」かもしれません。)

仮想MIDIバイスの可能性

MidiUmpDeviceServiceを使うと、これまでUSBに限られていたMIDI 2.0デバイスが、理論上はAndroid APIでリーチできる任意のデバイスまで広がったことになります。まあこれは半分は言い方の問題で、これまででも任意のAndroid Serviceを提供することで、android.hardware.usbAPIを使ってオレオレUSB MIDI 2.0サポートを実装することもできたでしょうが、公式にAPIを与えられたServiceならおよそ誰が作ったものでも相互接続できるようになるでしょう。

「仮想MIDIバイスを作れる」と言われても、ソフトウェアMIDIシンセサイザーを作る人以外は関係ないのでは…と思われるかもしれませんが、実際にはもう少し適用領域があります。

  • 筆者の事例のひとつとして、ktmidi-ci-toolでは仮想MIDIポートを公開しており、これ自体は他の「MIDI-CIデバイス」に接続して操作するためのアプリケーションですが、MIDI-CIのリファレンス実装として、他のMIDI-CIクライアントアプリケーションがこのアプリケーションに「接続」して使うことも可能になっています。(ktmidi-ci-toolはKotlin Multiplatformアプリであり、LinuxMacOSでも同様に動作します。)
  • MIDIキーボードアプリでたまに見られるのは、仮想MIDI入力ポートも作成しておいて、そこに届いたメッセージをそのまま自らがクライアントとして接続しているMIDI出力に送信するというものです(たとえばvmpk, kmmk
  • UMP用の仮想ポートを作成して、MIDI 1.0デバイスにUMPから変換して送信するような機能も実装可能でしょう。MidiDeviceServiceもMidiUmpDeviceServiceも動的にポートを追加することは出来ませんが、MIDI1のデバイスであればUMPのグループを別々に扱うことで1つのUMPポートから16デバイスまではアプリケーション側で対応可能でしょう(デバイス名等をクライアント側に認識させるにはUMP StreamメッセージのEndpoint Discoveryを送ってEndpoint Name Notificationを受け取る必要がありそうです)

MIDI 2.0 UMPのクライアントアプリケーションの作り方は、Android 13の頃と変わりません。MidiManager.getDevicesForTransport()でUMP対応デバイスを取得し、あとはMIDI 1.0の頃と同じAPIで、ただしMIDIメッセージのByteArrayにはMIDI 1.0バイトストリームではなくUMPを流す、というだけです。

MIDI 2.0アプリケーションを開発するのにMIDI-CIの機能は必須ではありませんが、MIDI-CIが必要であれば、C++アプリならJUCE 7.0.9から存在しているjuce_midi_ciモジュール(解説)、Kotlinなら上述のとおり筆者のktmidiでそのための機能を提供しています…と言いたいところですが、ktmidi-ci-toolの開発の過程でAPIはひっちゃかめっちゃかなので、API整備を待ったほうが良いでしょう。

*1:こういうのをアレオレ(「アレはオレがやった」)って言う界隈があるそうですね

*2:そういう目的で存在しているとは仕様上は書かれていないですが

1月の開発記録 (2024)

2024年最初の月次報告書です(かしこまった言い方)

ktmidi 0.7.0, and ktmidi-ci-tool first preview release

先月ずっと開発していたktmidi-ci-toolというかMIDI-CI基盤ですが、いつまでも遊んではいられない(!?)ので、今月Mavenパッケージのレベルでktmidiからktmidi-ciを新たに切り離して、それがProfile Configuration, Property Exchange, Process Inquiryの3つの柱(PIがthree Psに該当するのかは怪しいところですが)を実装できたところでfeature completeとしてリリースに踏み切りました。

ktmidi-ciのAPIはktmidi-ci-toolの開発過程でだいぶひっちゃかめっちゃかになって、今でもend user developers向けのAPIとしては全然整備されていないのですが、これは「そういうもの」と明示して、とりあえずktmidi-ci-toolを公開することを最優先課題として動いています。この場合の「公開」は、ざっくり書くと

というボリューム感で、1週間くらいはこのリリース作業そのものに追われました。特にUnderstanding MIDI-CI Toolsは「そろそろMIDI-CIをちゃんと理解したい人なら誰でも参照する資料」みたいなやつがほしいと思って書いたやつなので(同じノリで書いたやつとしてはAndroid+JUCE+CMakeのやつとか、日本語だとlanguage server protocolの解説記事とか)、今後もMIDI-CIの説明が必要になったときに適宜指していこうと思います。

日本語版をまだ書いていないので(JUCE Advent Calendarの記事で満足しちゃったかも)、英語を読めないと置いてきぼりですが、日本語で読みたい人はDeepLなりChatGPTなりにでもかけてください。

この過程でMMAが(関係者が開発しているため)midi.orgでしょっちゅう言及しているMIDI 2.0 Workbenchなんかも試してみたんですが、JUCE CapabilityInquiryDemoの実装のほうが圧倒的に実用性が高いので、そっちを使ったほうが理解が早いと思います(というかworkbenchのほうは「実装検証用ツール」以外の側面ではほぼ無ですね)。ktmidi-ci-toolは仮想的なMIDI-CIデバイスを作れるCapabilityInquiryDemoと「同等」にするのを目指して作ったので、だいたいの機能は同様に使えます。

post-release momentum

ktmidi 0.7.0というリリースを出したのは「とりあえずひと段落つける」ためでしたが、ktmidi-ciのAPIはひっちゃかめっちゃかな状態で他人に使ってもらえる状態ではないので、一旦整理する必要があると思っています。本当はMIDI 2.0サポートをMidiAccessに追加してktmidi-ciでもUMPをサポートしたかったのですが、ALSA以外で使えるのはCoreMIDIくらいしかなく、CoreMIDIをサポートするにはJVMもNativeも自分で実装しなければならず、ktmidiにばかりかまけているとAAPが進まなくなる…という感じで先送りにしたので(圧倒的に開発の人手(?)が足りない…)、この辺も今後のTODOです。まあMidiAccessなしでUMPをサポートする方法も無いわけではないですが(実利に乏しい)。

ktmidi-ci-toolはアプリとしてだいぶプリミティブなCompose Multiplatformプロジェクトになっていて、まともなアーキテクチャも適用しておらず(最初Essentyを使おうとしたのですが、状態管理実装がAPIレベルでしかなさそうだったので、ドキュメントなり資料なりが増えるまで見送り)、androidx側でLiveDataとかをKMPに移植しそうなふいんきもあるので少し待とうと思っています。あとKotlin/Wasmがもう少し整備されるかもしれないKotlin 2.0待ちなところもあります。KotlinConf 2024が5月下旬なので、そこまで出ないかも。

あとProcess InquiryのMIDI Message Reportの機能をもう少し拡充して仮想MIDIレジスタステートマシンにしてしまえば、「汎用MIDI 2.0コントローラー」のようなものも作れると思います。Roland/YAMAHA/KORGあたりが自前で作っていそうな気もしますが(!) 特にKORGはKeyStageでPEの実装まで持ってるはずだし。PEの実装は割と大変で、AppleのCoreMIDIにも含まれていないやつです。これは「CoreMIDIの中でJSONやりたくない(JSON処理系を参照したくない・取り込みたくない)」が理由かもしれませんが(!?)

Android audioチームも同様のライブラリを作っている可能性が割とありますが、たぶんAndroid専用になるでしょうし、Kotlinかどうかはわかりません。

what is new in this month

ktmidi-ciとktmidi-ci-tool、この1ヶ月で何が変わったのかを集めてみましたが、だいぶ多いので箇条書きで終わらせます…

  • Responder UIの内容が空っぽだった(要するに何の機能も無かった)のを作った
  • MidiCIResponderのProfile Configurationサポートが実装されてなかったので作った
  • PEのメッセージに含まれる高度な機能を全部作った: columns, subscriptions, body chunking, pagination, mutualEncoding, partial updates(これはクライアント側だけあった)
  • Profile Details Inquiry/Replyサポートが無かったので実装した
  • Process Inquiryを実装した
  • 単なるdata classだったMessageクラスでのコード共通化(common parts, serializerなど)
  • zlib+Mcoded7のためにzlib実装をDesktop/Androidだけ何とか使えるようにした
  • 設定のロード/セーブを実装した(セーブは手動)
  • ktor-ioの参照を外して(ByteOrder等の自前実装)Wasmビルドを追加した
  • MidiCIInitiatorとMidiCIResponderをMidiCIDeviceに融合した(一部古いものが残っている)
  • InitiatorScreenで複数のMIDI-CIデバイスを表示するようにした(もしあれば)
  • Android用の仮想ポートとしてMidiDeviceServiceを追加した
  • PEでDeviceInfoをやり取りしてInitiator側で表示できるようにした

AAP 2023 Year in Review and 2024 Roadmap

そんなわけで1月もほぼktmidi-ciの開発に費やしてしまったのですが、2月はAAPに少しずつ戻ってこようと思っています。まずは例年なら年末に公開していたYear in Reviewの記事を書きました。去年はそれまでと比べてだいぶAAPの作業を多めにやっていたので(勉強会も開催していなかったし)、進捗が大きい…

https://atsushieno.github.io/2024/01/28/aap-2023-year-in-review.html

(先月はAndroidでのオーディオプラグインAPIのリアルタイム拡張機能についても記事を書いていて、今月は上記ktmidiの記事2本も上げたので、英語ブログの更新が多い…)

それから、これも例年なら年初に公開というか更新していた2024 Roadmapを出しました。

https://github.com/atsushieno/aap-core/issues/191

2023年初には20項目くらいあったのですが、去年はGUIを消化したりMIDI2 UMPで拡張機能メセージングを整理した関係で割と多めに課題を解消できていて、今年は14件でスタートです。

あと「ほぼktmidi-ciの開発に費やしていた」とかしれっと書きましたがちょっとウソで、Tracktion Engine on Androidの開発相談を受けてちょいちょい調べ物をしたりしていました。tracktion_engineのDemoRunnerをAndroidでビルドできるようにしたので、これに手を加えてこのRoadmapにあるaap-juceのTE対応タスクも完了となる可能性が高いです(楽観的)。TE、Androidでは非常に重いやつがあるのですが、OboeからOpenSLESにしたら軽くなったとかいう謎報告も受けていて、まだ調べ物をする必要があるやつかもしれません(仕事ではないので今はやってない)。

2月の予定

そんなわけで、2月はたぶんAAPメインに戻ってくると思います。ただM3 2024春にサークル参加することになったので、もしかしたらそっちの作業を何かやっているかもしれません。MIDI-CI本くらいは出そうと思っています(2月でなくてもいい)。

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が指定される可能性もある。

12月の開発記録 (2023)

12月、あっという間に過ぎていきました。開発記録はほぼ一つのことしかやっていないので短めです。シンプルにいきます。

ktmidi midi-ci-tool

今月初頭にJUCE 7.0.9で追加されたMIDI-CIサポートについてというエントリを書きましたが、当該エントリにもある通り、これを書いた時点ではktmidiのMIDI接続まわりで期待通りの動作ができていなくて、じっくり機能を調べることができていませんでした。その後ほどなく、実際には仮想MIDIバイス接続さえ出来てしまえば何とかなることがわかって、JUCEのCapabilityInquiryDemoとの間で相互接続できるようになって、開発が一気に進捗するようになりました。

上記エントリでも言及しましたが、ktmidiのMIDI-CIサポートには、MIDI-CIのInitiatorとResponder(ざっくりクライアントとサーバーのようなものだと思って大丈夫です)のagentのようなものが実装されていましたが、あくまでproof of conceptとしてしか存在していなかったので、APIとしての利用価値はほぼ無いものでした。それでは存在している意味がないので、ここはひとつJUCEに倣ってCapabilityInquiryDemoのようなツールをktmidiだけでも作ってみよう、と思って、新しいサンプル ユーティリティをCompose Multiplatformで開発しています(未完成):

ktmidi midi-ci-tool with juce CapabilityInquiryDemo Initiatorの機能はむしろResponderより後に作り始めたものだったのですが、UIを含むアプリケーションとしての機能はむしろResponderより先にざっくり出来上がってしまいました。Responder側の機能は昨日ようやく開発が始動した感じです。年始の暇な時間に作り上げようと思っています。

Compose Multiplatformで作っているので、デスクトップとAndroid、あわよくばCompose for WasmでWebも…という感じなのですが、現状ktmidiが依存しているKtorがまだWasmに対応していないので、Web版はまだすぐには出来なそうです。Android版は…Googleが新しいAPIを作っていれば && MIDI-CIがそこに入っていれば必要ないという話があるのですが(!)、現状まだ無いので、ここが最大の使い所かもしれません。atsushieno/aap-coreに統合すれば、AAPの拡張機能の問い合わせをMIDI-CIでできるようになる可能性がまあまああります。

(AAPの開発の進捗は今月はほぼゼロです。)

MIDI-CIの実装に際しては、MIDI-CIと関連仕様についていろいろ覚書を貯めているので、これはいずれ公開しようと思っています(今月出すつもりだったけどプライベートで忙しくて無理でした)。

Kotlin MultiplatformでMIDI 1.0/2.0ライブラリを開発している話

ktmidiの開発が進んでいた頃、20日にKotlin/Swift愛好会(たまに参加している)があって、直近まで登壇枠が開いていたので、そんなら…と思ってktmidiの話をしてきました。

speakerdeck.com

内容はほぼプラットフォーム別バックエンドをどう作るとかMaven Centralでの発行どうする?みたいな部分に集中してしまいましたが、参加者のみなさんの質問の大部分(いろいろ来てありがたかったところです)は「Kotlin/Swiftはリアルタイム性ないぜ〜」って話したところに集中していました。上記MIDI-CIやMIDI 2.0の話はほぼゼロです(!)

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を使ったシンプルなコードです。)

11月の開発記録 (2023)

11月、いつも31日まであると思ってしまうのですが、これは一生治らないのかもしれん…

aap-core 0.8.0, almost there

2ヶ月以上前から取り掛かっていたAAP拡張機能の再編成ですが、今月初頭にようやくひと段落して、ABIに影響するAPIの変更はほぼ発生しなくなってきたので、バージョンを新たに0.8.0とする準備ができました。これに合わせて、AAP拡張機能のバージョンも全て v3 に統一されています。

(まだ最終リリースを出していないのは、Android Gradle PluginがAAPのAARをimplementation project("...")ではなくMavenLocalからパッケージとして解決したとき「だけ」各種の不具合が発生するという問題を解決してから、各種プラグイン移植のコードに影響を及ぼさずにリリースしたいという思いがあるためなのですが、これはAGPの深淵を掘り下げないと直せない気がするので、たぶん早々に諦めて現状でリリースすると思います。すでにaap-lv2プラグインはほぼ全てこのバグに対処済です。aap-juceプラグインは…特にProjucerベースのやつを直したくない…)

先月リストアップしたAAP拡張機能(とAAPXS = AAP拡張機能サービス機構)の課題はこんな感じでした:

  • 拡張機能APIが同期メソッド前提なので、非同期前提にする
  • リアルタイム処理時に文字列比較しなくてすむようにURIDを導入する
  • プラグインコードもホストコードも直接認識していないような拡張機能操作のメッセージをやり取りする仕組み(AAPXS untyped runtime)
  • Kotlinからの拡張機能のuntypedな呼び出し
  • 動的にロード可能なAAPXS
  • ホスト拡張機能
  • 独立性の高いAAPXS実装用ヘルパーAPI

このうちまだ実現していないのはKotlinからの呼び出しのみです。まあ現状「呼び出せる可能性」さえあれば十分かなと思っています。

まだ拡張機能APIの非同期化がホストアプリケーション(androidaudioplugin-manager)のレベルまで浸透しているわけではないのですが(JNI越しに非同期呼び出しを実現するのはやや面倒)、リアルタイムAAPXSで拡張機能の呼び出し時にロック機構を導入せざるを得なくなってからaap-juce-byodでプリセット一覧の取得に何秒もかかっていたのが、v0.8.0ではミリ秒単位で完了するようになっています。

気が向いたらARA2サポートとか実装してみたいところですが、さすがに時間がかかりそうな気がします(APIそのものをあまり把握していない)。ARACLAP.hと同程度のことができればいいのであれば、もしかしたらすぐできるのかも…?

alsakt 0.3.3

先月ALSA 1.2.10対応として0.2.0を出したばかりのalsaktですが、JavaCPP力でライブラリをバンドルする方法を調べて、JavaCPPランタイムが自動的にjarから展開してロードできるようになりました。ローカルにインストールされているlibasound.soが1.2.10でなくても、Linux kernelが6.5以降であればUMPエンドポイントを構築できるようです。

ktmidi最新版(未リリース)のktmidi-jvm-desktopモジュールにはAlsaMidiAccessというクラスがありますが、このvirtual portを作るときにPortCreatorContext.midiProtocol1にするとMIDI 1.0 UMPで、2にするとMIDI 2.0 UMPで、ALSA sequencerのポートが作成されます。

作成されたUMPポートではMIDI 1.0バイトストリームではなくUMPストリームを受け取れるようになっていると思うのですが、まだatsushieno/kmmkに手を加えたものしか手持ちのUMPアプリがなく、十分に試せていない状態です。

ktmidiのMIDI-CIアップデート

Twitterを見ないので最近まですっかり忘れていたのですが、12月はAdvent Calendarの季節…ということで、JUCE Advent Calendar 2023に参加します。実のところ今年はほとんどJUCEでコードを書いていないのでネタが無い…と思っていたのですが、最近そういえばMIDI-CIサポートが追加されたとJUCE開発者から聞いて眺めていたので、つい先日7.0.9で正式リリースもされたし、これがちょうどいいだろうと思って書くことにしました。

それで、その実験をやるためにはMIDI-CIの実装が必要だな?となって、ちょうどalsaktでUMP対応もしたしMIDI 2.0まわりの実験には向いているのではないかと思って、ktmidiに1年前に仕込んでいたMIDI-CIサポートを活用してみることにしました。当然ながら2023年6月アップデートには未対応だったので、そこから着手しています。一応繋がってパケットのやり取りは一部できていたようですが、まだ安定的に相互運用できる状態になっていない状態です。まあ詳しくはAdvent Calendar当日の記事を見てもらえればと思います(記事は作成済)。

ADC23(オフライン参加)

4年ぶりにaudio developers conferenceに現地参加してきました。10月の5回目ワクチンが効いたのか、混雑しているカンファレンスからも無傷で帰還しました(ロンドンの感染状況も悪くないですし)。

ADCオンラインでのコネクションは、正直3年間で全く増えなかったので、初めてリアルで会った知人はほとんどがTAP Discord等での繋がりだったと思います。旧知の知人も割と戻ってきているようでしたが、まだまだ回復しきっていない様子でした。まあ4年も開いたらその間に業界が変わった人もいるだろうし(?)

今年参加して気づいたのは、以前なら日本の楽器メーカー(Roland/YAMAHA/KORGあたり)から人が来ていたのですが、流石にまだ戻ってきていない感じでした。今年はDreamtonicsがSynthesizer Vの展示を出していたので、日本人参加者の大半はその方面だったと思います(SynthV関連のセッションもありました)。

自分の関連技術方面では、CLAPの開発者が来ていたので前日からいろいろ話を聞いたり(自分もプラグインフォーマットを作ってるんだ、みたいな話もしたり)、ask the JUCE teamでいろいろ相談したりはしましたが、主に他の分野の話を聞いてきた感じにはなりました。またADCの動画が公開されてきたらいろいろ話せるかもしれません。

12月の予定

AAP 0.8.0リリースがひとつ完了予定のタスクですね。多分その後にGradle/AGPまわりを調査することになって、コードの追加は控えめになると予想されます。

ktmidiのMIDI-CIサポートは、もう少し時間をかけて使い物になるようにするか、他の作業を進めるか(↑のGradle/AGPまわりの調査もかなり気合が必要)、迷っているところです。MIDI-CIそのものは、特にMIDI-CIからProtocol Negotiationが消えた今、自分のコードではほぼ不要になったので、優先度は低いです。とはいえ一般的なMIDI 2.0アプリケーションを書くには必須の技術とはいえそうです(だからこそJUCEでも内部的なMIDI 2.0サポートに先駆けて実装しているわけで)。まあ12月の方向性はそんな感じです。