12月の開発記録 (2025)

年始にはこの月報スタイルでまとめていくスタイルもどうかと思う、変えるかも、などと書いておきながらまるっと1年経ってしまいましたが、何もかも忘れたふりをして書いていきます。

今月はちょいちょい勉強会に参加して4回 くらい しゃべったり して いたのですが、どれも5分LTくらいでスライドはナシでいったので、あんまし特筆すべきことがありません。compose-audio-control、uapmd、ADC25、uapmd(だいたい1回目と同じ)の話をしただけでした。

あとは9割9分uapmdの開発しかしていなかったので、uapmdの進展について書きます。

UAPMD v0.1 released with installable packages

時系列としては月末の話からになってしまいますが、ついにv0.1をリリースしました。

https://github.com/atsushieno/uapmd/releases/tag/v0.1

といってもこれからリリースノート的なブログを書いて公開することになります(昨日書ききれなかった)。リリースに合わせて、ソースコードから手作業でビルドする必要が無いように、Linux用パッケージとmacOS用のhomebrewパッケージをセットアップしたので、誰でも試せるようになりました。homebrewではソースからビルドすることになるのでまあまあ時間がかかります。

UAPMDの開発を始めてから15ヶ月くらい経っているらしいのですが、今年は裏稼業で時間を取られてしまい(まあそれでギリギリの社会性を維持しているという話もあります)、code frequencyを見るとまだ実質6人月くらいの工数でここまできているようです。いろいろ動くようになったので、v0.1に合わせてスクショを取って使いまわしています。

パッケージングはCPackやhomebrew前提でやっていて自分が頑張った部分はあまり無いのですが、公開APIに含まれるヘッダファイルからchocやconcurrentqueueのような外部ライブラリの参照を引きはがず作業が発生し、これはそれなりに設計を調整することになりました。公開APIがstableになったわけではありませんが、それでも余計な依存ファイルが追加されると、余計なファイルがインストールされることになるので、きちんと回避しておくべきものです。これは日頃から「公開APIのクラスや関数のレベルでは、なるべく外部ライブラリのAPIを含まない」という設計方針にしてあったので、大して無理無く実現できたことです(自画自賛)。

midicci / cmidi2の改善

UAPMD v0.1としてリリースするに至った最大の理由は、Stateの保存と復元がついにMIDI-CI経由で機能するようになったためです。先月remidyのレベルではStateの保存と復元が全フォーマットで実現できていたので、今月はuapmdの段階のみを意識して対応できました。大部分はcmidi2のsysex処理からmidicciのMIDI-CI処理に至るまでのさまざまなバグフィックス作業でした。

Stateまわりは、当初は「StateListプロパティを取得して、その中に含まれているStateのresIdを全部拾って、それらを全部取得して、最後に結合する…全部集める並列リクエストの統合が必要…」という面倒な作業を想定していましたが、MIDI-CI Get and Set State Property仕様を眺めていて、「fullStateというresIdが全てのStateを含むバイナリとして指定できる」と書かれていることに気づき、現在はこのfullStateStateのリクエスト1発で済ませています。

あと、もう1つ仕様の見落としがあった話になるのですが、当初の計画ではState Propertyの実装で得られたバイナリデータはSMF2Clipで保存できるつもりでした。しかしSMF2Clip仕様書では、Property Exchangeの内容はClip FileではなくContainer Fileに含めるべきものとして明言されている、ということに気付いたため、「じゃあSysExに変換して保存しなくてもいいか…」となって、むしろ想定していたよりもシンプルな、通信経路での変換のみを気にするという作業になりました。

そんな感じで、Stateサポートの作業が思っていたよりだいぶ軽くなったので(それでも主なメッセージングトラブルはここで発生するのですが)、リリースを早められたことになります。

Stateサポートは、Property Exchange実装の中でも特にresIdを使用する初めてのユースケースだったので、MIDI-CI実装をktmidi、midicciともに改善する機会になりました。また、StateプロパティはmutualEncodingMcoded7を使用してmediaTypeapplication/octet-streamを使用する実用例でもあって、MIDI-CI基盤をいろいろ修正することにもなりました。また、Mcoded7は単純な変換で済むのですが、zlib+Mcoded7にはzlib圧縮が必要になるので、zlibを使って解決…とシンプルに済ませるはずがWindowsビルドで躓き、zlib-ngなどで回避策をとることにもなりました。

さらに、MIDI-CIプロパティの値はこれまで静的なものばかりだったので(AllCtrlListなどはパラメーターメタデータのみで、実際のパラメーターの変更はAssignable Controllerで送ることになります)、プロパティの値も事前に溜め込まれていたものを返す方式だったのですが、Stateの値はuapmd側でGUI等で設定したStateが随時反映されることになるので、プロパティの値を動的に返せる仕組みをMIDI-CI基盤に導入することになりました。実際のところ、Property Exchangeはこの方式だけで実装するほうが良いでしょう。

remidy GUI container fixes

先月書いていたのですが、今月の当初の課題はKontakt 8 AUのUIが出せなかったり、GUIのリサイズができなかったり、GUIを表示した後でインスタンスを削除したらクラッシュしたり、といったGUIにまつわるさまざまな問題があり、今月はこれを全て解決していました。uapmdのホストは目下のところImGuiであり、バックエンドとしては主にSDL3(またはSDL2)なので、OpenGL contextの扱いが問題になることが多かったのですが(概ね先月解決していたはずです)、

uapmd-appへの一本化

uapmdの開発作業で面倒だったのが、remidy-plugin-hostではホスト上でパラメーターやプリセットをGUI以外からも簡単に変更できるのにuapmd-serviceではできないとか、逆にuapmd-serviceでだけオーディオグラフを構築するUIが実装されていたりといった、2つの概ね同じ機能を実現しているホスト間での細かい機能の違いでした。Stateのバイナリデータの比較など、1つのプロセスのデバッグ中であれば簡単にできるのに、別々にデバッグするとなると一旦ファイルに出力して…といった面倒が生じます。

そういうわけで、今月ついにこの2つを統合してuapmd-appという1つのGUIアプリケーションだけ提供することにしました。uapmd-serviceはもともとCUIで動作する前提だったので、その頃から比べると方針の大転換といえますが、GUIなしにプラグインの設定を調整するのは無理があるわけで、これは現時点であきらめています。

2つのアプリケーションが統合されたことで、このアプリ自体の機能改善が気軽に加えられるようになって(後述)、開発速度がだいぶ上がったといえます。

あとmidicciでも現在同じような作業を行っています。midicciには、ktmidi-ci-toolの移植であるmidicci-guiと、UMP+MIDI-CIキーボードであるmidicci-keyboardの2つのQtアプリケーションが含まれているのですが、これらをImGuiに書き換えた上で統合する作業を行っているところです(進行中)。これが完了したら、midicciもuapmdのようにLinuxパッケージやhomebrewパッケージを作って公開することになるでしょう。

オーディオファイル再生機能等の実装

uapmd-appは(あるいはremidy-plugin-hostとしてもuapmd-serviceとしても)、エフェクトプラグインMIDI 2.0デバイスとして外部から操作できることを想定しています。ただ、この場合、エフェクトを適用した結果というのは何らかのオーディオ入力が無ければわからず、これまではインストゥルメントプラグインの出力に繋げるしかありませんでした。アプリ上はオーディオファイルを再生できるかのように見えていたのですが、Claude Codeが勝手に生成したスタブ(!)でしかなかったわけです。SMF2 Container Fileにはオーディオクリップも含まれることになるので、オーディオファイルを指定して再生しながらエフェクトを適用できる機能は、いずれにせよあるべきです。

そういうわけで、今月は単一のオーディオファイルながら、外部ファイルを指定して再生できる機能が追加されました。また、再生中のオーディオ波形等を視覚的に確認できるようなミニマムなスペアナも追加されたので、エフェクトプラグインが想定通りにオーディオ波形を生成できているか、簡単に確認できるようになりました。v0.1のスクショにも盛り込まれています。

この機能は、将来的にはシーケンサー機能として、複数のオーディオクリップやSMF2Clipをタイムスタンプ付きで指定できるようにすると良いのですが、それってつまりDAWなのでは…?と思わなくもないです(意図的)。これを大過無く実現するには、複数のトラックを効率的に再生できる仕組みが必要になり、レイテンシーレポートを受け取って調整するオーディオグラフの機能が必要になってきます(現在は単なる直列グラフを複数本直列処理しているだけ)。

オーディオファイルの再生に加えて、オーディオデバイス設定の機能も実装しました。これも長らくスタブだったのですが、正しいデバイスリストを取得して適用できるようになっています。

UapmdUmpMapperとIMidiMapping2サポート

UAPMDの今月の最大の課題が、UMP経由でパラメーター変更を問題なくやり取りできるようにすることでした。これまではオーディオ処理のprocess()関数で渡されたUMPシーケンスをプラグインフォーマットごとに処理する実装になっていました。AUの場合、UMP入力をそのままMIDIEventListとして渡せるので、内容を見ずにそのままコピーできますが、VST3はMIDIイベントを全てプラグインのイベントにマッピングするので、1つずつ内容を見て対応する機能を呼び出す必要があります。といっても、対応しない機能はスルーするしかありません。

UAPMDの場合、プラグインのパラメーター変更を全てAssignable Controllerにマッピングしますが、これらはVST3イベントの処理でパラメーター変更に復元されていました。この復元処理はAUでも必要になるはずなのに、AUの実装ではUMPがそのままMIDIEventListに渡されてしまい、復元されていませんでした。そもそもプラグインパラメーターとのマッピングはフォーマットを問わずに行われるべきものであり、受信したUMPのAssignable Controllerを復元する処理と、パラメーター変更イベントをUMPとして送信するために変換する処理は、機能的に一対になっているべきものです。

これと関連して、MIDI mapping機能をremidyの機能の一部として取り込むべきか否かを検討していました。というのは、VST3の他にCLAPもMIDI mapping拡張機能のドラフト版を追加していたからです。しかし最新版の仕様ではこれは「MIDI 2.0を使えば十分」として削除されていたため、この機能はもしremidyでサポートするとしてもVST3 plugin instanceのExtensibilityに含める、という方針にしました。

VST3のUMPからの変換では、プラグインが提供するIMidiMapping対応が実装されていましたが、VST3.8.0ではMIDI 2.0のUMPに対応するIMidiMapping2インターフェースが新たに追加されたので、これを実装したプラグインがもしあれば(現時点でまず無いでしょうIMidiMappingより先にマッピングを取得するようにしました。これで期待通りなのかはわからないところですが、IMidiMapping2をサポートするOSSのホストは他に無いので、とりあえずは十分でしょう(何)。

ノート別コントローラーの実装

UAPMDの存在意義は仮想MIDI 2.0デバイスを実現することであり、MIDI 2.0デバイス「らしい」機能として、ノート別コントローラーの機能は実現しておきたいところでした。単なるパラメーターとは異なり、ノート別コントローラーではパラメーターを操作する対象を選択できなければなりません。ノート別コントローラーといいつつ、実際にはチャンネルを指定することもあります。uapmd-app GUI上でもノート別コントローラーをサポートする必要があるため、パラメーターの対象セレクターが追加され、「単なるパラメーター」はグローバルパラメーターとしてサポートされることになりました。

ノート別コントローラーをサポートするプラグインはあまり多くありませんが、VST3のSerum 2(AUのSerum 2は未対応)、CLAP版Surge XTやZebralette(VST3版は未対応)などが対応しています。midicci-keyboardではPer-Note Assignable Controllerをまだ実装していないので、これから対応することになります。

その他

今月はこれら以外に、JavaScript/TypeScriptバインディングAPIを実験的に作って追加したりしています。これはremidyとuapmdをスクリプトで操作できるようにしたいという目論見があってのことなのですが、その後興味本位でremidy-plugin-hostをWeb UIで再実装しようとして、GUIループの問題に全く上手く立ち向かえずに爆死したので(GUIコンテナの実装を別途構築する必要がありそう)、これは今やることではないと思って放置しています。JSバインディング自体は、QuickJSなどを組み込めば便利に使えるようになるでしょう。

総括

今月は先月以上にUAPMDの開発が随分がっつり進められて、だいぶ進捗を出せました。v0.1では現在サポートしているプラグインフォーマットについては、プラグインホストの機能面でUAPMDがやるべきことの大部分をカバーしたように思います。次のマイルストーンでは、簡単なシーケンサー機能を実装して、レイテンシーレポートやオフラインレンダリングの挙動を確認・調整することになるでしょう。それから、このプラグインホストをAAPで使えるようにするのも目標の1つです。

juce_audio_processors_headlessについて

TL;DR 今までJUCE non-GUIアプリケーションしかheadlessでビルド/実行できなかったけど、JUCEプラグインも新たにheadlessでビルド/実行できるようになったらしいよ(プラグインホストはまだだよ)/ただ実際にはどうかな


今回は軽めの話題として、JUCEの次期masterブランチに入ってくるであろうdevelopブランチの様子をお伝えします。JUCE 9.0ではUMPをサポートする新しいAudioProcessorやCLAPサポートが入ってくる予定で、次のバージョンがJUCE 9.0になる様子はありませんが、本稿執筆時点でのJUCEのdevelopブランチでは、JUCEモジュールの再編成が行われているようです。

developブランチでは新しくjuce_audio_processor_headlessというモジュールが追加されています。これまでjuce_audio_processorモジュールで実装されていた多くのクラスが、このモジュールに移動したようです。このheadlessモジュールは、プラグインを実装するAudioProcessorをheadlessにできるということでしょうか? そもそもheadlessとは何を意味しているのでしょうか?

JUCE6のheadlessサポート

JUCEはバージョン6.0でheadlessサポートを追加しています("Added support for running headlessly on Linux")。正確にはLinuxでの実行のサポートの話ですが、WindowsmacOSでheadlessサポートはあまり意味が無いでしょう。JUCE6ではCMakeサポートが追加されており、ProjucerをCI環境でビルドして --resave することなくプロジェクト本体をビルドできるようになりました。

ただ、これは「CI環境でProjucerを実行する必要がなくなった」という以上のものにはなっていません。2020年にJUCEチームがRaw Material Software社として買収された当時はheadless modeについてもう少し期待していたところがありますが、これはオーディオプラグインやホストがheadlessで動くようになるという性質のものではありませんでした(ただこのエントリを書いた当時はそもそもjuce_eventsに依存していると全てX11依存になると思っていたようで、期待の内容のほうも妥当とは言い難いところがあります)。とはいえ、juce_audio_basicsjuce_audio_devicesくらいしか使用しないアプリケーションでは、Xは不要になっているはずです。

今回headlessモジュールが追加されて構成が変わったjuce_audio_processorは、ホスト側ではなくプラグイン側のモジュールであり、ホスト側のAPIであるjuce_audio_plugin_clientは本稿執筆時点ではheadlessになっていません。CI環境でプラグインホストアプリケーションを実行してプラグインを適用するやり方は2021年に書いていますが、これが変わることにはならないといえます。

https://atsushieno.hatenablog.com/entry/2021/12/07/214945

juce_audio_processors_headlessとjuce_audio_processorsの棲み分け

juce_audio_processors_headlessは、juce_audio_processorsから何をheadlessとして切り離したのでしょうか? これはむしろjuce_audio_processorsには何が残ったか、という確認の仕方がより妥当そうです(残ったほうが少ないので)。以下のコードは引き続きjuce_audio_processorsにあります:

  • format_types: AudioPluginFormat::createPluginInstance()の実装 (overrides)
  • processors : AudioProcessorEditorGenericAudioProcessorEditor
  • scanning : PluginListComponentとその依存関係(KnownPluginListなど。これらについては上記2021年のエントリーに詳しく書いてあります)
  • utilities : APVTS (AudioProcessorValueTreeState), ParameterAttachment, PluginHostTypeなど(このクラスについては去年のAdvent Calendarで解説しています)

juce_audio_processors_headlessの意義

プラグインAudioProcessor単体であれば、headlessでも十分にテストできる環境が整理されたといえそうです。 JUCEでGUIサポートを必要とするモジュールはjuce_gui_basicsであり、これはjuce_audio_processors_headlessでは依存モジュールとなっていません。

一方、juce_audio_processorsは引き続きjuce_gui_extrasに依存しており、これはjuce_gui_basicsに依存しているので、こちらはGUI環境が無いとビルドできません。またjuce_audio_plugin_clientjuce_audio_processorsに依存しているので、こちらもGUI環境が必須です。

プラグインのビルドにはX11 dev packagesが(まだ)必要

これはプラグインに限らずJUCE 6.0以来のアプリケーション全般についても当てはまることですが、現状では(?)、headlessモードのサポートとは、アプリケーションの実行時にX11が不要であることを意味する程度です。ビルド時にX11が存在しない環境でもビルドが通るようになったというわけではありません。CMakeで普通にビルドするときに、依存パッケージが不在であればそのモジュールのビルドをスキップする、といった調整は行われません。CMakeLists.txtjuce_generate_juce_header()を呼び出しても条件付きで取り込まれるようになるわけではありません(この関数を使うのは推奨されませんし)。

自分で#include <juce_audio_processors/juce_audio_processors.hを指定するなら、このヘッダー自体は依存パッケージの有無で内容をスキップするようには(まだ?)作られていないので、自分でheadlessビルドかどうかを判別しなければならないことになりますが、そのための仕組みが現状では用意されていません。少なくともJUCEのCMake APIドキュメントにはありません。

そういうわけで、headless実行がサポートされているとしても、まだまだX11 devパッケージのインストールは必要になるでしょう。

プラグインをホストするには結局xvfb-runが必要では…?

ビルドには引き続きX11 devパッケージが必要だとしても、プラグインを実行する環境では不要になっていることでしょう。果たして実際にはどうでしょうか。…ここでわれわれは別の問題にぶち当たります。standalone exeとは異なり、プラグインのテストにはホストが必要になります。headlessで動作するプラグインをロードできる、headlessで動作するホストは存在するのでしょうか? juce_audio_plugin_clientのheadless版は(何度か言及している通り)まだ(?)存在しません。

ターミナルレンダラーとしてはVST2の頃にはMrsWatsonというツールがありましたが、もう開発が終了しています。Reaperにもターミナルだけで完結するモードがあるようですが、ReaperはOSSではないのでパブリックなCIサーバー上にセットアップできません(プライベートなセルフホストで良いのであれば、必ずしもheadlessな環境で動かさなくても良いように思います)。筆者も独自のプラグインホストのライブラリを開発しているのですが、headlessは優先度が低く、この記事のためだけに手を出すのは無理でした。

…というわけで、筆者の執筆時点での所見としては、「今後headlessなホストが出現すれば役に立つかもしれないけど、今はまだその時ではない」というものですが、もしheadlessに使えるホストがある、という情報をお持ちの方がいたら教えてもらえればと思います。

もちろん、プラグインとして実行できないとしても、standalone exeとしてheadlessにビルドできるようになれば、プラグインのプロジェクトとしてテスト実行できるわけで、その意味では価値があるといえるでしょう。もっとも、この意味では、DSPはもともとテストできるように(できればJUCEに依存しないかたちで)構成しておくほうがよいともいえます(そうすればJUCEがサポートしていないフォーマットに持ち込む場合にも有用です)。それはおそらくすでに単独でテスト可能なライブラリの体裁をとっていることでしょう。

まとめ

juce_audio_processorsからGUI依存部分を取り除いたjuce_audio_processors_headlessというモジュールが開発されつつあって、これが正式にmasterブランチに入ってくると、今後はCI環境等でxvfb-runを使わなくてもプラグインプロジェクトをビルド…はできないにしても、テストできるようになる…かもしれません。今後に期待しましょう。

11月の開発記録 (2025)

ADC Bristolと技術書典19

2年ぶりにADCにオフライン参加してきました。今年のセッションはあんまり強い傾向の見えないラインアップだったように思いますが、強いて言えばAccessibilityまわりは充実していました。MIDI 2.0、空間オーディオ、AI技術あたりの話題は見ていません(AIはさすがに皆無ではない)。個人的に良かったのは最終日のキーノートでした。Strudelコミュニティの隆盛を紹介した話だったのですが、いろんな意味でフリーダムすぎるんだけど(「誰でも好きに変更できる。何なら全部消すこともできる」)、でもそれがうまく回っている、みたいな話で、めちゃくちゃ面白かったです。これは幅広く見られてほしいです。技術的なトピックとしては、 std::memory_orderの話をしているな??という感じでした。あと初日がワークショップと1トラックのセッションの並列進行だったので、ワークショップに興味のない自分はセッションをつまみ食いできてよかったです。

セッションの時間以外は今回は何かと政治的に(?)振る舞っていました:

  • the VST3 developerが来ていたので、AAPを見せて「これをVST3に手を加えてできるようにすべき」という話をしてきました( Androidの元リーダーも来ているから相談してみてよ、って言ったら実際相談するようになって、「DSPはいけそうだ、って話をした」と言っていたので(GUIも技術的にはいけるんだけど)、もしかしたらこの方面で進展が見られるかもしれません。
  • Androidオーディオチームの元リーダー(最近辞めてしまった)には2019年にもうAAPを見せていたので、今回はむしろuapmdで仮想MIDI 2.0デバイスMIDI-CIでプラグインのパラメーターリストを取り出したりするのを見せたりしていました(MIDI 2.0の導入も関与していたので)
  • AudioUnitの開発者も来ていたので、AAPを見せて「iOSではどうやってXNU Turnstilesを導入してもらったんだ…?」と訊いたりしていました(ほとんどカーネルチーム主導でオーディオチームからの働きかけは無かったらしいとか)。ADC 2023ではthe CLAP developerにもAAPを見せていたので(今年はいなかった)、あとはLV2開発者に見せればフルコンです(違)
  • Cmajorの開発者に「Androidで使いたいんだけどビルドできると思う…?」って訊いたら「一度やったことがあるからやってみて出来たら教える」となって、その後すぐリリースに含めるようにしてもらえました(!) 最新版からAndroidビルドも入っています。ただ個人的に動かしたいのはPro-54とかで、これをやるにはchoc WebViewあたりから移植する覚悟が必要そうです。
  • JUCEの開発者に「AndroidプラグインのStandaloneをMIDI 2.0デバイスとして使えるようにしたんだから、同じことをデスクトップでやればいいじゃん?」と提案したら割と好感覚だったので、もしかしたら来るかもしれません。ちなみにuapmdでAllCtrlListを取得できるのを見せて「これもやればできるのでは?」と言ったら「それは方向性が違うかも…」となったので、そっちは来なそうです()

あと他人と雑談しているときはMML to Tracktionでコレからコレを生成したのとか、技術書を売るイベントがあって早く帰らなきゃいけないんだ…こういうイベントでな…とそのまま技術書典を説明したり(「CLAPの本もあるのか!?」みたいになっていました)、そろそろ小ネタは困らなくなってきました。

日本からはDreamtonicsとRolandが出展していて(どちらも半分くらいは「日本から」ではないかも)、あとYAMAHA方面から参加者が来ているという感じでした。Dreamtonicsブースは会場に近かったので空いてるときは便利に立ち寄らせてもらいました(!?)

ADCから帰国したらすぐ技術書典19で(当日来てくださった皆様ありがとうございました)、1週間前に日本にいなくて既刊を自宅から発送できなかったり、当日はスタッフ業もあって早朝から夜まで…という感じでしんどかったのですが、何とか生きてます。今回は「理想のオーディオプラグインフォーマット」にGUIの章を追加しただけで新刊扱いなのですが、在庫切れしたMIDI-CIガイドブックを後から印刷して出すので(紙で売っているのは技術書典期間中だけ)、それだけやれば完了です。おつかれさまでした。

今月は大きなイベントだけでもKotlin Festとか東京楽器博とかJJUG CCCとかFOMとかいろいろ遊びに行ってたので、妙に忙しかったような気持ちでしたが、そんな余裕があったということは割と暇だったということかもしれません(?)

UAPMD & midicci progress

今月はADC参加期間以外の大部分をUAPMD開発に費やしていたのと、GUI関連をAI agentsにやらせて割と「プラグインフォーマットのようにドキュメントのある仕様を実装する」のにはだいぶ役に立つことがわかったので(あと基本APIを自分で策定していて変なものを他所からまるっと持ってこられないことがわかっているので)、今月はさらにいろいろやらせてみていて、だいぶ進展がありました。

先月は「Serum2が動くようになった!」「NI製品がだいたい死ぬ」というレベルでしたが、現時点でremidy-plugin-hostは手持ちのプラグインの大部分をインスタンス生成できています。AUKontaktGUI表示でクラッシュするのが目下の最大の問題で、VST3版は問題なく使えています。

各論的に改善点を挙げていくと、こんな感じです:

  • だいぶ実装すべき機能が揃ってきたので、問題のある挙動をissuesに登録するようにしました。もう「自分が把握している問題だけでも多すぎて困る」状態ではなくなりつつあります。
  • ユーザー向けドキュメントを追加して、開発者用ライブラリ(なのにAPIが不安定)という位置付けから脱却しつつあります。
  • トランスポートコントロールが実装されたので、再生中の楽曲の位置情報などをプラグインに渡せるようになりました。remidy-plugin-host上で Play を指示するとSynthesizer V Studioが再生を開始するみたいなことが可能になります(これはGUIだけですが)
  • オーディオバスの接続処理が整理され、マルチチャンネルレイアウトも(可能な範囲で)正しい実装を作り直しました。
  • Stateのセーブとロードが(全フォーマットで)実装されました。VST3のIComponentとIEditControllerなど複数のStateデータを1つのファイルに含めるためのバイナリフォーマットは、今後変更があるかもしれません。
  • パラメーターの列挙型のサポートが整理され、remidy-plugin-host GUI上でも選択できるようになりました。discreteでない列挙値も、スライダーと併用して選択できます。
  • パラメーターの扱いを整理して、normalizedとplainのセマンティクスを整理しました。
  • プリセットのロードが全面的に可能になりました(ただしプラグインプラグインAPIに基づいて公開している場合のみ。実例は多くありません)
  • パラメーター変更の挙動を包括的に検証し、プラグインUI、プリセットロード、ホストからの直接操作を問わず、DSPを経由して、プラグインUIにもホストにも正しく通知・反映できるようになっています。
  • VST3のRestartComponentなどのインターフェースがいろいろ実装されました(パラメーター変更通知のついでのようなもの)
  • midicci-keyboardはMIDI-CIセッションの確立とAllCtrlListプロパティの取得、ProgramListの取得を別々の操作によって取得する方式に(暫定的に)移行しました。これが安定的なリストの取得に大いに貢献しています。
  • remidyで挙動が安定したパラメーター操作を、midicci-keyboardでも行えるようになりました(float - uint32のみ確認)

上記どの項目もどれももう少し細かく解説したほうが良い内容ではあるのですが、そこに無限に時間を割くわけにもいかないので、気が向いたら書けるものは書いていこうと思います。

だいぶいろいろ動くようになってはきたものの、GUIまわりでいくつか 問題が あるのと、MIDIイベントをどうプラグインに直渡ししつつ他のMIDIイベントをプラグインAPI呼び出しに変換していくのかという問題があるので、来月はその辺を片付けて、UAPMDをMIDI 2.0アプリケーションの基盤として使えるように周辺アプリケーションを整備していきたいところです。

10月の開発記録 (2025)

年初に「このフォーマットで書き続けるのもどうかと思う」とか書いておきながら気がつくとほぼ1年終わりつつあるわけですが、とりあえずこのスタイルで書きます。

今月はM3 2025秋がありましたが、結局同人誌の執筆には割かず、デモ展示という名目でオーディオプラグイン仮想MIDI 2.0デバイス化プロジェクトであるところのuapmdとUMPキーボードであるところのmidicciを大改造していました。uapmdの大改造というか、どちらかといえばプラグインホスティング機能の拡充がメインだったといえます。そんなわけで、今月はuapmd + midicciの話しか無いのでその各論を書きます。全体像についてはM3直前に記事を書いて公開したので、そちらを見てもらえればと思います。

zenn.dev

uapmd-service: GUIコントローラー

uapmdの当初の構想では、uapmd-serviceがheadlessな仮想MIDI 2.0デバイスの実態として動作して、それとは別にuapmd-setupというGUI管理コンソールがプラグインの列挙とMIDIバイス起動・終了などを担当する予定でした。しかしuapmd-serviceは将来的には全デバイスを1つのオーディオエンジンで処理したほうが良いですし、GUI機能はImGuiで作成したremidy-plugin-hostのものを使い回せそうだったので、CodexとClaudeにGUIを作らせることにしました。現状これはそれなりにうまく動作しています。

ただ、GUIアプリとしてUIとロジックの分離などをそれなりにきれいにやってきていたのですが、プラグインUIのサポートを追加してきたあたりから、この辺の切り分けに気を配らなくなっているので、どこかしらの時点できちんと仕切り直す必要がありそうです。

逆にこの辺が雑に作られていたJUCEは、次のバージョンでどうやらjuce_audio_processors_headlessという新しいheadlessモジュールを追加するようで、近年のJUCEのリアーキテクチャの動きは(たぶんJUCE6の時に加入したリードデベロッパーの采配だと思いますが)とても良いですね。これでjuce_audio_plugin_client_headlessも出来てくれれば(やや高望み)、オーディオプラグインのテストをCIなどのheadless環境で自然に行えるようになるかもしれません。

remidy: プラグインUIのサポート

今月の目玉は何と言ってもプラグインGUIサポートの追加でしょう。プラグインUIが無いと人間にはパラメーターを意図したとおりに操作できませんし、まだろくに動作確認できていないStateの操作も、GUIで操作することでようやく意味を持ってくるといえます。uapmd-serviceがGUIを表示するようになって、インスタンスの細かい制御が可能になったので、GUIサポートを追加する時が来た、という感じでした。

新しく作られたuapmd wikiには、これまでblueskyで投稿してきた数々のスクショが掲載されています。現時点で「GUIだけが正常に動作しない」ものは、手元で確認できているのはVaporizer2くらいです(と書くと大変完成度が高いように聞こえますが「そもそもプラグインインスタンス生成に失敗する」ものが多数なので、それほどではないです)。Vaporizer2は多分GL ContextをホストのImGuiとの間で取り合っているんだろうと思いますが、OpenGLベースのVitalなどは正常に動作しているので、要調査ではあります。

GUIサポートの実装は、実のところほとんどCodexとClaudeにやらせています。共通APIとしてCLAPライクなAPIを定義して、「これをVST3で実装して」といった感じで実現させています。当時はVST3SDKではなくDPFのTravestyのAPIで書かせていて、最初はVST3SDKを無理やりC APIで実現するTravestyの流儀にClaudeもかなりやられていましたが、きちんと説明すると理解できたようです。TravestyのAPIを使ったコードなどはほとんど存在しないはずなので(特にホスティングで利用していたのは自分だけではなかろうか)、agentic codingでもこの程度は実現できるんのか、と試験利用の可能性を広げることになりました。今月だけでVST3, AU, LV2, CLAPの全フォーマットで最低限のshow/hideとデフォルトのsizingを実現できていて、だいぶいい仕事をしたと思います(自分が)。

uapmd: UMPデバイスとしてのポート再編成 (ongoing)

uapmd-serviceはこれまで、DAWライクなトラック編成を内部的に想定していて(複数トラックのサポートが実装できているわけではないのですが!)、それぞれのトラックに複数のプラグインを差して、トラックごとに1つの仮想UMPデバイスを作成する、というスタンスで作られてきました。

これまでは空想上の産物でしかなかったのですが、uapmd-serviceで複数トラックを簡単に作成できるようになったので(AIに実装させることができたので)、このまま1つのトラック上に複数のプラグインDAWのトラックのように差せるようにしよう、と実装させてみました。それで気付いたのですが、このトラック単位でプラグインのパラメーターを操作するとなると、トラックの最初に差されたシンセのパラメーターしか変更できないことになってしまい、そのトラックの以降のエフェクトプラグインの操作ができません。

これらを操作できるようにするにはどうすればいいのか、設計を見直す必要がありました。AllCtrlListやProgramListといったプロパティによって、プラグインのパラメーターを取得できることが必要ですが、Function Blockを切り分ける必要があります。さしあたって現時点ではそもそも仮想MIDIバイスを複数立てて、操作はそれぞれの仮想MIDIバイスに対して行う、オーディオプラグインのトラックとしては1つ、というかたちになっていますが、プラグインインスタンス数だけ仮想MIDIバイスが生成されるとものすごいことになりそうなので(これは30トラックくらいの楽曲でもそうですが)、早々に元の1トラック1デバイス構成(あるいはそれ以上に圧縮)というかたちに戻すかもしれません。

プラットフォームMIDI APIバッファリングとの格闘 (ongoing)

uapmdとmidicciは、実のところktmidi-ci-toolと比べてだいぶMIDI-CIメッセージングが安定していません。下回りはどちらもlibremidiなので割と不思議ではあるのですが、midicciはそもそもagentic codingの産物なので、いろいろ完成度が低い可能性はあります。もっとも、ktmidi-ci-toolが試験的な用途でしか動作確認されていないのと比べて、uapmd-serviceもmidicci-keyboardもreal-world appsなので、単純な動作テストではハマらなかったような問題が発生することがあります。

たとえば現状でVitalのUMPデバイスを作成してAllCtrlListを生成すると、メガバイト単位のProperty Exchange JSONのやり取りが発生することになります。デフォルト値を極限まで最適化してMcoded7などを採用したりすれば、少しデータ量を抑えることが可能かもしれませんが、大前提として大容量データの送受信は問題なく行えるべきです。今月発見した問題のひとつは、プラットフォームのMIDI API(あるいはそれをラップしたlibremidi)の内部では有限のリングバッファが用いられているはずなので、それを超える大きさのチャンクを送受信することはできない、ということです。こちら側の実装は仮想UMPデバイスなので、31,250bits/sec.みたいな制約はありませんが(仮にMIDI 1.0の物理ケーブルを前提としたシステムのように1秒間に3000バイト超しか送信できないとすると、1MBのMIDI-CI JSONを送信するのに300秒以上かかることになり、実用性がありません)、プラットフォーム側でリングバッファを送信しないうちに次のチャンクを送りつけると死ぬ可能性があるので、現在はある程度のスロットリングを行っています。

いろいろな問題の切り分けと改善は行っているのですが、この領域は現在進行形でハマっている問題で、少しずつ改善していくしか無いと思っています。一番の問題はテスタビリティが無いことですね。CI環境にプラグインをセットアップする仕組みを作らないとなあと思っています(これはこれで大仕事)。

remidy: travestyからvst3sdkへの移行

10/20にVST3.8.0が公開されて、MITライセンスに変更されましたね。これについては今日、大掛かりな記事を書いて公開しています(だいぶ苦労した)。

zenn.dev

remidyはMITライセンスでリリースするためにvst3sdkを回避してtravestyを使い続けていたのですが、基本的にはC++のプロジェクトですし、vst3_public_sdkAPIなどもそのまま使えるほうが良いので、travestyに拘り続ける理由は特にありません。というわけで、勢いで全てvst3sdkに移行させました。Claude Codeで。travestyのAPIくらいならほぼ資料が無くてもVST3 APIからの推測でいろいろ自走できることは先のGUI実装で体験済みだったので、vst3sdk APIへの移行程度の「機械的な作業」なら処理できるだろうと踏んでやらせました。一部コードを読み落として移植コードが動かなかった等の問題は発生しましたが、人間がやっても同じことに(あるいは多分もっとひどいことに)なったでしょう。

プリセット実装の再整理

先月VST3 ProgramListの設計をいろいろこねくり回していて「これは無理かも」と半ば投げ出していたプリセットの問題ですが、今月LV2、AU、CLAPの3フォーマットでそれぞれ実装してみました。AUを除いてはプリセットのサポートは鬼門で、CLAPは特にpreset-factoryのAPIからしっかりサポートしているプラグインがほとんどありません。Six-Sinesですらソースコードのレベルで無効化されているレベルです。LV2もURIで識別するので安定的な番号を振れず、プログラムチェンジとの相性は良くありません。とりあえずAUだけは単なるAudioUnitGetProperty()の延長線上でいけるので、サポートしてあります。Dexedがmidicci-keyboardでプログラムリストから音色を選択できる状態です。

uapmd-serviceではなくremidy-plugin-hostのレベルでは、LV2プリセットもロードできています。Audible Planetsのプリセットなんかが選択可能です。VST3はプリセットは列挙できるもののロードに失敗する状態で放置されています。この辺もいろいろ手が空いた時に埋めていく感じになりそうです。

公開プロジェクト化

uapmdとmidicciは、ここに書いているのを除いては、ほぼ空気として進行してきたのですが、今月はM3の展示物として公開して、ついでに翌日行われていたAMEIのMIDI meetupでもちょろっと見せて回っていて、少しずつ公開プロジェクトっぽくなりつつあります。現状使えるプラグインが限られているので、まだまだ実験用という感じではありますが、動作するようになったプラグインも少しずつ増えてきています。今月は特にSerum2が問題なく使えるようになったのが割とターニングポイントだったと思います。これはGUIサポートつまりIEditControllerまわりの実装の足りない部分を埋めていた(埋めさせていた)時に改善された部分だったので、今月は空き時間を見てオーディオ処理やバス調整まわりも改善させてみようと思っています。今のところNative Instruments製品がだいたい死んでいるので、これを何とかしたいですね。

11月の予定

11月はADC 2025が開催されるので、ブリストル会場で参加してくる予定です。そこからダッシュで帰国して技術書典19で出展予定です。観光する暇が無い…! まあ今年はKotlinConf, LAC, ADCとカンファレンス参加しすぎなので、観光は来年どこか他でのんびりしよう…と思っています。

9月の開発記録 (2025)

DroidKaigiが終わって、自分が何らかのロールをもって参加するカンファレンスの類が一通りリセットされたのですが、「DroidKaigiが終わったらやる」予定だった作業がいろいろ襲いかかってきていて、あっぷあっぷしています。まあいつも台湾にいる間にやってたiOSDC 2025が今年は9月だったので遊びに行く、みたいな時間はありましたが。

Android Audio: Beyond Winning On It @ DroidKaigi 2025

予告通りDroidKaigi 2025でセッションやってきました。動画(会期中にもう公開されてた!)、スライド、それと事後公開の解説記事があります:

www.youtube.com

speakerdeck.com

zenn.dev

本当はさらに解説記事の英語版を用意したかったのですが、冒頭のような事情で後回しになっています(!)

セッションについての所感は解説記事にちょこっと書いたのでここで追記することはあまりないのですが、やはり全部解説するためには少なくとも2倍くらいは時間が無いと無理だな、という気持ちになりました。たとえばAndroidMIDI 2.0サポートの意義がどの辺にあるか、みたいな話は完全に省いていますが、90分あれば5〜10分くらいは「オーディオプラグイン機能の肩代わりが標準的な技術で可能になる」ことに言及できたでしょう(そのためにはオーディオプラグインAPIにどんなものがあるか、みたいな話もしなければならなくなるのですが、これも今回は言及していません)。

AAPのForeground Service対応

DroidKaigiではほとんどAAP自体の話をしなかったのですが(とはいえ「オーディオプラグインが無い」というトピックに大きく言及すること自体が現状ではほぼAAPの話をしていることになるでしょう)、会場でAAPがどんなものか見せられるようにしておくことは必要だと思って、一番わかりやすい例としてDAW = aap-juce-helioの動作クオリティを向上させる作業に取り掛かりました…が、ここしばらくuapmdとmidicciにかかりっきりだったせいで、アップデート作業がAndroidのメジャーリリースとKotlin 2.2のリリースを跨ぐことになってしまい、そこに時間を食われることに…

ここまではKotlin 2.0.x系列と互換性のあるKotlin 1.8のABIで続けてきたのですが、kotlinx-coroutinesがKotlin 2.1 ABIに移行してしまったのを無視し続けるわけにもいかないので、今回あきらめてABIをアップグレードすることにしました。

それからようやくAAPの作業に着手できたのですが、半年くらいノータッチだった間に自分もC++の書き方やAndroidオーディオでいろいろ学びを得たので、いろいろ書き直したい部分が噴出してきています。とはいえ時間が無かったので、とりあえず「最低限動作するDAW」を目指して、めちゃくちゃ今更感がある話ですが、AAPをForeground Service化させました。そうしないとバックグラウンド処理になって優先度が下がるし、最悪いつの間にか落ちてしまうので…。そういうわけで今AAPプラグインのいくつかは(全てではない)利用中に何をできるわけでもない通知アイコンが出るようになっています。

helio-workstation改めhelio-sequencerもひさびさに更新して、現状では最善のバージョンとして動作しています。このDAWプラグインインスタンスの扱いがいまいちよくわからないので(!)、もう少し直感的にトラックごとのプラグインを設定できるようになっているDAWがほしいところですが、Androidで動作するOSSDAWは他にほとんど無いので如何ともしがたいところです。新しく何か移植できるとして、JUCEホスティング依存になるのも微妙なので、uapmdみたいな独自ホスティング機構に基づくMIDI 2.0シーケンサーがほしいという話もあり…(気の長い話)

本当はこのタイミングでktmidi-ciを使ったMidiDeviceServiceのMIDI-CI対応を済ませようと思っていたのですが、ktmidi-ciのAPIがまだ成熟していないのと、Android Native MIDI APIのアプリケーションでも使えるようにするならmidicci統合で対応すべきかなと思ったこともあって、今回は見送っています。uapmdベースのアプリケーションが動くようになった時に、ネイティブ接続したときだけMIDI-CI前提の機能が使えないのはいただけないので…(!?)

ktmidi 0.11.2

2025年9月は、待ちに待ったJava 25リリースがありました。Javaに思い入れがあるという話では全くなく、Java 25は正式版のPanamaが含まれる初めてのLTSリリースなので、これでlibremidi-javacppを切り捨ててlibremidi-panamaを全面的に採用したktmidi(-jvm-desktop)を使ってくれと言えるようになったわけです。正確にはktmidi 0.11.0の時点でPanamaへの移行は済んでいたのですが、「JDK 22はLTSじゃないからやめてほしいんだけど…」と言われていて、こればかりはJavaリリースを待つしか無い、という状況でした。

Java25になったことで実装が大きく変わることはないのですが(Java 22でAPI自体はstableになっているはずなので)、libremidi-panamaはJava 25で動作させたjextractで生成したソースでビルドされています。

これは少し悩んだのですが、Kotlin 2.1 ABIに移行する必然性は無かったのと、Android専用でユーザーがほぼいないであろうAAPとは異なり、ktmidiはどれくらい古いKotlinが使われているかわからなかったので、とりあえずKotlin 1.8 ABI前提のktmidi 0.11系列の最後のリリースとなるでしょう。

本当はこのタイミングでktmidi-ciのAPIをもう少し使いやすくしたmusicdeviceというサブパッケージのAPIを使って、ktmidi-ci-toolを全面的に書き換えたかったのですが、そこまでの時間は作れませんでした。JUCEのjuce_midi_ciもそうですが、MIDI-CIライブラリはどれもまだまだアプリケーションに統合しやすいAPIにはなっていないので、もう1段階高レベルなAPIが構築されるべきだと思っています。現状どのベンダーも実現できていないです。

uapmd: パラメーターとプリセットの再整理

midicciでProgramListをサポートするためには、まずProgram ListをきちんとサポートできるMIDI 2.0デバイスが必要…というわけで、今月はuapmdのProgramListをきちんと使えるようにするための作業を進めていました。

ちなみにmidicciは現状ではProgramListを要求してデバイスからSysExとしては取得できているものの、それをUIに反映する部分が正しく実装されていない状態です。自分で書いているコードではなくClaude Codeに書かせているプロジェクトなので、Claudeがまともに修正できないうちは、(どうせろくでもないコードが生成されているので)自分では読みたくないので後回しです(!) Sonnet 4.5なら直せるようになっている可能性はあります(今日発表を見たばかり)

uapmdではprogram changeはpresetsに対応する機能ということになっているのですが、そもそもuapmdはプリセット対応機能をろくに実装していなかったので、プラットフォーム中立のAPIを模索するところからスタートしています。大抵のフォーマットではstate APIと連動するのですが、VST3には(MIDI 1.0のProgram Changeも無理やりVST3のAPIで置き換えさせていることもあって)ProgramListという独自のコンセプトもあって、これがパラメーターサポートと連動するものになっています。

VST3のパラメーターサポートを使うところで試行錯誤していて気付いたのですが、VST3以外のパラメーターAPIへのマッピングとそのプラグインでの実際の使われ方もきちんと把握できていなかったので、これを機に整理しました。remidy-plugin-hostというサンプルホストでは、パラメーター設定操作をuapmdの仮想MIDIバイスが利用するNRPNに置き換えてプラグインインスタンスに処理させていたのですが、これをそのままAUなどに渡しても処理してもらえない(前提が違う)ので、パラメーターAPIを呼ぶかたちに修正…といった、フォーマットごとの対応の確認をやっていました。それでも現状VST3のパラメーターAPIのサポートは機能していない状態で、まだ安心して使えるとは言い難い状態です。この辺は引き続き作業が必要でしょう。

uapmd: native plugin host GUI

それから、プラグインホストremidy-plugin-hostの開発を未成熟なWeb UIの荒野で進めるのはしんどいので、とりあえず各種の問題に目をつぶってImGuiで作り変えることにしました。といってもGUIをゼロから構築するのは面倒なので「既存のWeb UIのソースを見てImGuiに置き換えて」とClaudeにやらせています(それなりに仕事はしてくれたはず)。

ネイティブUIを使うようにしてから、GUIの機能追加は雑にClaudeに投げるようになってしまい、結果的にどっちが原因とは判断しがたいところですが、「GUIをいじるのがめんどくさいから…」という理由で作業が停滞することはなくなったので、これはやってよかったと思っています。

ちなみにImGuiは日本語入力ができないんだよなあ…とbskyでぼやいていたら作者の人から「使えるはず」というreplyが届いて、調べてみたら新しいSDL3バックエンドでは使えるようになっているようでした。ただSDL3は新しすぎてGitHub Actionsのubuntu-24.04 (latest) でも使えないので、i18n-readyになるにはまだ時間がかかりそうです。とはいってもubuntu 26.04が出る頃にある程度完成していればむしろ良いほうかな…?という感じのプロジェクトなので、とりあえずは現状でいきます。もっと高水準のGUIフレームワークに乗り換えるかもしれないですし。ただ、ImGuiを使うことによって、host as pluginとして動かすこともできるようになったはずなので、他に移行したとしても実装は残しておくかもしれません。

10月の予定

10月は差し迫って忙しいわけではないはずですが、M3-2025秋に出展するので新刊を作る作業が発生するでしょう。とはいえ、現時点ではAAP本のバージョンアップくらいで済ませようかなあと考えている程度です。むしろaap-juce-helioやuapmdなど展示できるものを充実させておきたいですね。

8月の開発記録 (2025)

台湾からの移動日にノートPCにコーヒーをひっかけて使い物にならなくなった状態でくそ暑い東京に帰ってきました。もうちょっとヒートアイランドを高みの見物していたかった…(時候の挨拶)

Can we build better music software ecosystems for more generative era? @ COSCUP 2025

今年のトークは例年よりエモい…というか今あるプラグイン技術でできていない「データの可搬性の無さ」の問題を取り上げて、プラグインフォーマット間の相互運用性を高めるにはどうすればいいか?みたいな議論を中心に話してきました。スライドは公開してあります。セッション動画はそのうちyoutubeに上がるんじゃないかと思います。

speakerdeck.com

COSCUP自体は今年も3000人くらい参加していたそうですが、今年はRubyConf Taiwanとくっついていたこともあってか、やたら日本人が多く来ていました。まあその面倒を見るのはOSC方面の人たちで、自分は「その他」野生の外国人勢ですが。

本当はもっと理想のプラグインフォーマット本に沿って、プラグインフォーマットごとの設計の相違点をネチネチと30分くらいしゃべれればよかったのですが、完全に主題が行方不明になってしまうのと、それをどこまで期待されているのかわからん(英語セッションの時点でこんな心配しても意味ないのですが)ということもあって、だいぶ絞って話しました。1時間あれば40分、2時間あれば1時間40分はその話をしたと思います…

midicci-keyboard

先月uapmdにktmidiのC++移植midicciを統合してAllCtrlListを取得できるようにするだけしましたが、やはりクライアントがktmidi-ci-toolやJUCEのCapabilityInquiryDemoくらいしかないのでは全く実用性が無いので、適当にMIDI 2.0キーボードのサンプルをでっち上げようと思って、またClaude Codeに作らせました。最初は独立リポジトリに作ってあったのですが、途中で面倒になってmidicciの一部として開発しています。

github.com

AI coding agentが書いたものなので全般的に中途半端ですが、動くときは動くようです。今はLinuxビルドがMIDI-CIのGet Property Data Replyの処理に失敗するようで、ここまで来るとなかなかAIでは先に進め難いところです。実行環境も特殊でローカルで動かすのが前提ですし。

ようやくAllCtrlListのプロパティをMIDI 2.0デバイス化したVitalから取得できるようになった…これじゃ使い勝手が悪いからツマミのリストくらいにしないとな。 ちなみに列挙値も取れるからJUCEのproperty grid的なものも作れなくもない。オーディオプラグイン並のことができるようにしたいところだな。

Atsushi Eno (@atsushieno.bsky.social) 2025-08-12T12:52:57.996Z
bsky.app

g0v.social

今月はこれに合わせて、uapmdで全フォーマット(VST3, AU, LV2, CLAP)でパラメーターリストを取得できるようにしたり(特にLV2がしんどい)、StateListをサポートするためにきちんとMIDI-CI Property ExchangeのresIdサポートを実装したり、その上でState APIをそもそもプラグインホスティングAPI (remidy) のレベルで実装して回ったりと、いろいろ目に見えない部分に時間がかかりました。まだProgramListの設計と実装の辺りができていないので、その辺がひと段落したらuapmdと合わせてもう少しまともにプロモーションしようと思います。

来月の予定

来月はDroidKaigiがあるので、発表資料をあーでもないこーでもないとこねくり回しながら作っています。コードもしばらくはAAP方面のメンテなどをやることになるでしょう。それが終わったらようやくひと息つける感じです。

7月の開発記録 (2025)

COSCUPのために台北に来ています(気が早い)。毎月のように出国していて、全然落ち着いて日本で活動できないですね…

uapmd MIDI-CI integration

先月書いたuapmdのMIDI-CI対応のためのktmidi-ciのC++化モジュールであるところのmidicciですが、今月も隙間時間を縫って手を加え続けていった結果、ようやくMIDI-CI receiverとして機能するようになってきました。Property Exchangeのやり取りが可能になれば、read-onlyでしか機能しないレベルであっても、AllCtrlListやProgramListは公開できることになります。そういうわけで、満を持してMIDI-CIデバイス化に着手することにしました。

ただ、ktmidi-ciとその応用としてのktmidi-ci-toolは、現状ほとんど混然としていて、ktmidi-ciの機能だけでMIDI-CIデバイスの機能をクリーンに追加できるほどうまく設計されているわけではありませんでした。動作確認用ツールの他にはアプリケーションが何も無く(これはどのMIDI-CI実装もそう)、uapmdのMIDI-CI統合は初の試みということになります。midicciはktmidiの移植コードとして成り立っているので、midicciに直接手を加えるのはあまり筋が良くありません。ということで、変更はktmidiのレベルで加えつつ、midicciの変更は「ktmidiの変更点をこっちでも反映しる」という感じの命令をClaude Codeに与えて進めています。

また、AllCtrlListやProgramListといった「標準プロパティ」については、そもそも明確に実装があったわけではありませんでした。これらについてはJSON Schemaの定義が存在しているので、これをマスターデータとしてコードを自動生成できればいいと思っていたのですが、JSON Schemaを解するKotlin Multiplatformのライブラリが無く、JSON Schemaライブラリを自作するのは面倒だなあ…MIDI-CIプロパティのJSON Schemaから手作業でコードを作るのも面倒だなあ…と、とにかく面倒がっていました。それが実際に使うコードを作るという段になると、さすがに多少やる気がでてきて、結局、手作業でコードを作成しました。この辺はいったん雛形を作ってからClaudeにやらせているところもあります。MIDI-CIプロパティのJSON Schemaはそんなに件数が多いわけではないので、コードジェネレーターを作る時間はもったいないところです。

この辺の作業を進めるためには、ktmidi-ciモジュールをktmidiモジュールに依存するように変更する必要があって(ktmidi-ciはktmidiに依存していなかったわけです(!) )、これはやや破壊的変更っぽい側面があるので億劫になっていたのですが、9月にPanamaがJava 25 LTSで使えるようになれば、最新のktmidiも抵抗なく破壊的変更を加えられるという見込みもあって(すでにv0.10.0のktmidi-jvm-desktopはPanama依存なのですが)、やや先走りですが変更に踏み切っています。

C++のほうはそもそもktmidiではなくlibremidiを使っているので、実装が一致しない側面があってやや座りが悪いところがあるのですが、とりあえず現状有姿で進めています。uapmdでも満を持してmidicciを依存モジュールとして追加して、とりあえずAllCtrlListはuapmdのパラメーターリストから生成できるようになりました:

仮想MIDI 2.0で演奏できるOPNplugのプラグインパラメーターをAssignable Controllerに割り当てて、その情報をMIDI-CIクライアントで取れるところまできた(内容はまだまだいろいろおかしいけど) ProgramListをpresetsに割り当てて、StateListをStateバイナリに割り当てたら、とりあえず当面の目的は達成だ

Atsushi Eno (@atsushieno.bsky.social) 2025-07-27T12:17:56.100Z
bsky.app

ktmidi-ci-toolだけでなく、JUCEのMIDI-CIモジュールのdogfoodであるCapabilityInquiryDemoでも取得できています:

VST3のOPNplugだとパラメーターが2100件くらいあって、それをAssignable Controllerで制御可能にすると、AllCtrlListの内容がそれだけのパラメーターが全部で500KB近いJSONメタデータになる。 gist.github.com/atsushieno/2... JUCEのCapabilityInquiryDemo取得させたら何分もかかってこれはちょっと使い物にならない。デモアプリにすぎないから真面目に最適化されていないっていう話はあるけど、現状これ以上に作り込まれたツールはあとは自作のktmidi-ci-toolくらいしかなさそう(これはちゃんと読める)。

Atsushi Eno (@atsushieno.bsky.social) 2025-07-27T15:23:28.755Z
bsky.app

ただ、blueskyの投稿でも書いている通り、MIDI入力を受け付けるVST3には悪名高い「(CC128件 + Program Change + Channel Aftertouch) x チャンネル数」の隠しパラメーターという問題があって、2100件近いパラメーター…から自動生成されたMIDI-CIプロパティを扱うことになります。JUCEのこのアプリは全然最適化されているわけではないので、およそ実用的な操作はできません。まあこのJUCEアプリのようなコードでMIDI-CIデバイスを使う場面は無いと思いますが、ktmidi-ci-toolにはこのような問題がなく、Compose Multoplatformを使うことでこの辺の問題が自然に解決できているありがたみが表出した感じです。

ちなみにAllCtrlListを実装するために仕様書の細かい部分をいろいろ読んでいたら、仕様書として不鮮明な箇所がパラパラと出てきて、これはまともに実装しないうちに規格化しちゃったんじゃないの…?と思いながらいくつかmidi.orgにレポートしています。正直あんまし良い仕様だとは思ってないです。

本当は続いてProgramListにも対応したかったのですが、そもそもuapmdがまだProgram Changeに対応できるようなPresetsアクセスの共通化を実現しておらず、そっちが解決してから対応することになりそうです。Presetsおよびその前提となるStateのAPIは、プラグインフォーマットごとの差異が強いので(これについては理想のオーディオプラグインフォーマット本で詳しく書いてあります)、十分な時間があるときに腰を据えて取り掛かりたい課題のひとつですが、例によって1箇所で落ち着いて作業できる状況はまだまだ先になりそうです。