年始にはこの月報スタイルでまとめていくスタイルもどうかと思う、変えるかも、などと書いておきながらまるっと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を含むバイナリとして指定できる」と書かれていることに気づき、現在はこのfullStateのStateのリクエスト1発で済ませています。
あと、もう1つ仕様の見落としがあった話になるのですが、当初の計画ではState Propertyの実装で得られたバイナリデータはSMF2Clipで保存できるつもりでした。しかしSMF2Clip仕様書では、Property Exchangeの内容はClip FileではなくContainer Fileに含めるべきものとして明言されている、ということに気付いたため、「じゃあSysExに変換して保存しなくてもいいか…」となって、むしろ想定していたよりもシンプルな、通信経路での変換のみを気にするという作業になりました。
そんな感じで、Stateサポートの作業が思っていたよりだいぶ軽くなったので(それでも主なメッセージングトラブルはここで発生するのですが)、リリースを早められたことになります。
Stateサポートは、Property Exchange実装の中でも特にresIdを使用する初めてのユースケースだったので、MIDI-CI実装をktmidi、midicciともに改善する機会になりました。また、StateプロパティはmutualEncodingにMcoded7を使用してmediaTypeにapplication/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つです。