以下の内容はhttps://atsushieno.hatenablog.com/entry/2026/01/31/194746より取得しました。


1月の開発記録 (2026)

昨年12月に引き続き、1月もintensive uapmd hack monthでした。今週はだいぶ稼働していないので、成果の大半は最初の3週間半くらいで出てきたものです。

他ではKotlinのライブラリのパッケージメンテナンスをちょっと進めたくらいです。uapmdでMIDI 2.0音源が実用的に使えるようになってきたので、特に(現状Tracktion Engine前提の)augene-ngをMIDI 2.0中心にシフトして進めようとも思ったのですが、やはり単一のUMPデバイスだけではまだまだ楽曲の打ち込みができるとは言い難いので、もう少しuapmdを先に進める必要があります。

uapmd/midicciの進展

uapmdとmidicciは先月末の時点でほぼパッケージングまで完了していましたが、年頭に正式にv0.1をリリースしました。これに合わせてKVR product pageも作成・公開しています。

www.kvraudio.com

uapmdもmidicciも、ここ1ヶ月でいろいろ大きく進展しています。最初にどんな変更が加えられたかを列挙して、後から各論的に細かい部分を詳説しています。時間が無くて書ききれないので、細かい論点は別途切り出して覚書として別投稿するかもしれません。

uapmd improvements

  • UMPのバッファリングの処理が安定し、現在ではuapmdとmidicciあるいはMIDI2.0Workbenchとの間でSysExのやり取りが中断されることはなくなりました。
  • 巨大なJSONのやり取りが必要になっていたAllCtrlListのデフォルト値の出力を大幅に削り落としました。それでも数十チャンクのGetPropertyDataのやり取りが必要にはなりますが、1/4くらいになったのではないでしょうか(未計測)
  • AUv3サポートをAUv3のAPIに沿ってネイティブ実装しました。(後述)
  • uapmd-appのImGuiにPatitotective/ImThemesを利用してBootstrapっぽくしてみたり、Robotoフォントを取り込んでテキストのレンダリングがまともに見えるようにしました。ImGuiのデフォルトの陰鬱な配色も嫌いではないのですが、メリハリが無いのはUXとして改善の余地があると思っています。デフォルトフォントはまあファイルサイズが小さいからとサウンドフォントにTimGM6mbを使うような合理性があるのでしょう。
  • GUIでは表示のカスタムスケーリングにも対応しました。ある程度は正常に動作してくれるはず…現時点でMIDIキーボードが拡大されないことは把握済みなので、そのうち直すと思います。
  • per-note controllersをmidicci-appのUI上に出して調整できるようにしました。ただ音源側がper-note controllerをちゃんと適用できることを確認できたものがなく、UI上は選択できるという以上のものではないです。ちなみにGUI上はGroupやChannelも選択可能になってはいるのですが、Groupは不要そうな気もしますし、ChannelはVST3やAUにそういうコンテキストがあるとはいえるものの、これらを使う音源は何1つ知らないので未確認です。
  • Windowsビルドが動作するようになりました。とはいえ、Windows MIDI Servicesサポートがまだです。これはまずWMS自身のバグでWMSサービスの動作チェックに失敗する問題に引っかかって、それが直ったら今度はlibremidiにwinmidiのopen_virtual_port()実装がない問題に引っかかった、という感じで現状では実装待ちです。
  • JSサポートの位置付けを変更して、uapmdのAPIを外部からFFIでnodeのみサポートする方式ではなく、uapmd-appからQuickJSを使用してJavaScript APIを公開するという方式に切り替えました(後述)
  • シーケンスエディターの構築が始まりました。uapmd 0.1の時点ではオーディオファイルは全てのトラックに流し込まれていましたが、現在ではトラックごとにオーディオクリップを選択して、クリップも複数作成できて、位置も自由に調整できるようにしてあります(その関係でオーディオ入力のスペクトラムアナライザーは現状動いていません)。ここにMIDI2Clipも配置できるようにしたいのですが、現状MIDI2Clipファイルはどこにも存在していないので、これらを作る仕組みを作るところから必要です(道半ば)
  • UMPエンドポイントのFunction Block管理機能を整理しました(後述)
  • シーケンサーエンジンAPIを大幅に再編成しました(後述)
  • パラメーターリストの動的な変更に対応しました。AUv3のMela、VST3/CLAP/AUのBYODなど、ロードされたプリセットに応じてパラメーターリストを切り替えるタイプのプラグインが、最新状態のパラメーターリストを表示できるようになりました。
  • LinuxIRunLoopの実装を全面的に見直し、remidy本体が提供するEventLoop実装が使われていないアプリケーションでWaylandサポートが使えていなかった問題を解決しました(後述)
  • LinuxのFlatpakセットアップが追加されました。ただオーディオ機能はALSAがバイパスされているので、PipeWireのみが動作する、みたいなことにはならないようです。

midicci improvements

今月はmidicciにもさまざまな改善が施されました:

  • midicci-keyboardとmidicci-guiの機能をmidicci-appという1つのアプリに統合しました。MIDI-CIを活用したプロパティの可視化も、MIDI-CIデバッグ作業も、1つのバイナリ実行ファイルで済ませたほうが楽です。
  • 先月UAPMDのバイナリパッケージを作成したのに倣って、midicciもパッケージを作成することにしました。Linuxではdebrpm, macOSではhomebrewとdmgを公開しています。
  • UAPMDに合わせて、こちらもWindows GitHub Actionsビルドのartifactを含めるようになりました。ただ動作は完全に未確認です。Wndows版uapmd-appはまだUMP仮想デバイスを登録できないので…
  • per-note controllersをmidicci-appのUI上に出して調整できるようにしました。ただ音源側がper-note controllerをちゃんと適用できることを確認できたものがなく、UI上は選択できるという以上のものではないです。ちなみにGUI上はChannelも選択可能になってはいるのですが、ChCtrlListのやり取りは行っていないので(してもいいのですが、現状uapmdでChCtrlListを発行しないので)、何も反映されません。
  • uapmdとの間でSysExのやり取りがほぼ安定して動作するようになったので、Send Discoveryボタンはデフォルトで折り畳まれたエリアを作って対比することになりました。デバイス詳細情報もデフォルトで非表示にしています。通常は名前だけで十分なので。
  • uapmdとの間でAllCtrlListやProgramListのやり取りが安定するようになったので、これらのMIDI-CIプロパティはResourceListに含まれていたらRequestボタンが押されるのを待たずに自動的にリクエストするようにしました。
  • uapmdにMIDI2Clipサポートを追加したい関係で、ktmidiのktmidi本体(ktmidi-ciではない部分)をMidiAccess API以外すべて取り込むことにしました。ライブラリ・モジュール・名前空間の構成をどういう扱いにするか悩みましたが、新しいモジュールはUMPサポートが中心になるのでumppiという新しいモジュール・名前空間に切り分けることにしました(midicciに近いネーミング)。これまでcmidi2で行っていた処理もこのumppiで全部書き換えています。

embedded JSスクリプティングのサポート

v0.1.0リリースを公開してからすぐに取り掛かったのがJSサポートのやり直しです。先月少し言及したのですが、先月までのJavaScriptサポートはuapmd APIの公開インターフェース全体にJS shimを自動生成して、JSコードだけでinstrumentationが可能になることを目指していました。これだとuapmdを起動して、GUIコンテキストを用意して、プラグインをスキャンしてリストを構築・管理して…といった作業をJSでやることになり(あるいは「できるようにする」ことになり)、使い勝手が悪く、実用性が無かったといえます。しかもJSバインディングはnodeを前提に作られていて、Webブラウザ上で動かしてWebCLAPみたいなものもサポートしたいとなった場合には使えません。そういうわけでこの方向性は無かったことにしました。

代わりに導入されたのは、一般的なDAWに見られるスクリプティングランタイムの仕組みの導入です。これはchocのQuickJSサポートを組み込んで、chocのJS関数登録の仕組みを活用して実現しています。APIは目下のところ「AIにuapmdのAppModelクラスに含まれていた関数に対応するものを全部作る」というレベルで自動生成させたものだけですが、これで「JS変数にちょっとリストアップしたプラグインインスタンスを生成してプラグインUIを表示する」というスクリプトを自動生成させて、それが現状うまく動作しているので、この段階で満足しています。もう少しやる気が出たら、起動直後に自動実行するスクリプトをuapmd-appにコマンドライン引数として渡すとuapmd-appの初期化が完了したら自動実行する、みたいな機能を作れば、いろいろできることが増えるかもしれません。

組み込みスクリプティングAPIがあればMCPサーバーの追加も難しくはないと思いますが、現状スクリプティングAPIがあれば、それをAIエージェントに食わせるだけで十分いろいろできるはずなので、どちらかといえば外部に接続できてembedded JSランタイムと通信できるWebSocketsなどのproxyサーバーを組み込むほうが有用そうです。最近の話題でいえば、iPlug3が(まだ机上の存在ですが)プラグインMCPサーバーとして動作するものとして設計されているようですが、その動作環境において通信経路を自由に作れるとは限らないプラグインSDKとは異なり、uapmdは普通にライブラリとして利用できるので、一見して使い方が分かるようなAPIが公開されていれば、あとはAIのほうがよろしく解釈してくれるでしょう。

Linux GUIサポートの改善

uapmdの大きな特長のひとつは、uapmd自体はコーディングAIエージェントにプラグインフォーマットのAPIのインターフェースをガンガン実装させて先に進める、というアプローチを全面的に採用していることです。iPlug3が「われわれはAI時代を念頭に置いてフルスクラッチで設計している」みたいなことを宣言していますが、uapmdは(まあまあ抵抗感がありましたが)とっくに採用しています。

ただその弊害で、AI任せであんまし良くない設計のまま放置していた(いる)のがGUIサポート、特にVST3のIRunLoopIWaylandHostIWaylandFrameといった部分でした。ClaudeやCodexはAPIの意図を解釈して実装する分には問題ない仕事をするのですが、uapmdがイベントループのAPIremidy::EventLoop)をどのように使うかは明示的な仕様が無いので、IWaylandHostを実装するよう指示した時にLinuxEventLoopなるクラスを独自に作成してその中でのみIWaylandHostをサポートする、みたいなことをやっており、自分も深く吟味せずに取り込む、みたいな状態になっていました。

EventLoopはremidy「が」実装を規定すべきものではなく、プラグインホストが自身のGUIフレームワークに合わせて提供すべきものとして作られていて、実際uapmd-appはImGuiEventLoopで動作しているので、(その中ではIWaylandHostはサポートされておらず)、Waylandサポートが利用される場面は全く存在しなかったといえます。

Waylandサポートの問題はもう1つあって、プラグインのほとんどはWaylandをサポートしない(IPlugView::isPlatformTypeSupported(kPlatformTypeWaylandSurfaceID)がfalseを返す)ので、ホストのデスクトップ セッションがWaylandでも(XDG_SESSION_TYPE=waylandでも)引き続きX11プラグインUIもサポートしないといけない、ということです。uapmd-appは「現在のデスクトップに合った1つだけのplatform type」のみをチェックしていたので、前述のEventLoopの問題を修正した直後は、(Waylandセッションで動作している場合はWayland UIとして動作するかどうかをチェックし、当然のようにほとんどのプラグインがそのチェックを通らずにUI生成に失敗するため)UI生成が何一つ成功しないという問題に陥っていました。

現在ではX11が通ればX11でUIを生成することにしていますが、これはもしかしたらWaylandオンリーの環境では(特にWaybackなども組み込んでいないため)失敗するかもしれません。とりあえず現時点ではuapmd-appは一般的なLinuxデスクトップのみをターゲットにしていて、あまり現実的な懸念ではないと思って放置しています。Waylandオンリーの環境でも動作するオーディオプラグインが有意に出現してきたら考えます。

AUv3サポートのネイティブ実装

AUv3プラグインはAUv2のホスティングAPIを利用してインスタンス生成が可能です。ただ、そのホストが利用できる機能は、当然ながらAUv2が前提とするものに限られます。今月になってノート別コントローラーの実装を作り込もうとして気付いたのですが、AUv2はAudioUnitGetPropertyというAPIの上ではノート別パラメーターを取得できるようになっているものの、実際にこれでパラメーターを取得できるように作られたプラグインは存在せず、AUv3になってから初めてAUParameterTreeAPIによって使えるようになった機能という位置付けになっているようです。

こうなったらAUv2のAPIではなくAUv3のAPIを使用してAUホストを書き直すしかない…と思ってひと通り実装しました。 これまでは「AUv2もAUv3もAUv2のAPIからアクセスできるからそっちで統一的にやる」というスタンスでやっていましたし、最終的にはAUv2実装が不要になるはずだからAUv3実装に統一するか…と考えていたのですが、今度はper-note controllerなどがAUv3のAUParameterTree経由でないと取得できず、一方でAUv3 APIだけで実装するとAUv2のパラメーター変更が取得できないというややこしい問題に引っかかり(このcommitだけでも確か結局は解決せず)、最終的に2つの実装が併存しています。

Appleがこの辺をきちんとAUv2Bridgeで実装してくれていればAUv3 APIだけで問題なく統一できていたはずなのですが、現実はそうはなってはいなかった、ということで、自分もいろいろ原稿(とは)を書き換えないといけないな…と思っているところです。

Function Block管理機能の再編成

uapmdはここまで、SequenceProcessorがリストを保持するAudioPluginTrackが中でAudioPluginGraphをもち、そのノードであるAudioPluginNodeが保持するプラグインインスタンスのそれぞれについて、UMP Function Blockとしてプラットフォーム仮想MIDIバイスを(libremidiを使って)構築する、という構成になっていました。

UMPメッセージは、uapmd-appのAppModelAPIでは個別のプラグイン インスタンスに対して送られるようになっていた一方で、シーケンサーのトラック管理が進化して1つのトラックに複数のプラグインを直線で繋げるようになった時点で、UMP入力キューはトラックごとに編成されることになりました。プラグインへのUMPイベントの受け渡しは、最初は先頭ノードがUMPを受け取って処理し、次のノードは前のノードの出力を受け取って処理するような設計になっていました。これは失敗でした。MIDIメッセージの何がそのノードで処理され、何が次のノードに「受け継がれる」べきかはわかりません。MIDIイベントストリームの連結はユーザーのホスト操作次第であるべきで、この仕組みでは後続のエフェクトパラメーターすら操作できません。NRPNの番号が衝突するので、最初のUMP入力をそのまま次のプラグインにも渡すというわけにもいきません。

この問題を解決するために、各プラグイン インスタンスには途中からFunction Block ≒ groupアサインされるようになり、uapmd-appはプラグインインスタンスIDを指定してnoteOn()などの命令を呼び出すのに、途中でgroupがアサインされ、それを受け取ったAudioPluginTrackはそのgroupに対応するインスタンスに渡す…といったややこしい実装になっていました。

ややこしいのはともかく、根本的な問題として、AudioPluginTrackにUMP Function Blockを割り当てるという構図がある限り、UAPMDはほぼフル機能のDAWシーケンサーエンジンを実装しなければならないことになってしまいます。それはやりたくないので、Function Blockの管理をシーケンサーエンジンから切り離せるようにしました。この部分は設計が詰められていなかったので、APIから作り直しています。

過去にUapmdMidiDeviceという名前で存在していた仮想MIDIバイスの実体は、その実態に合わせてUapmdFunctionBlockとなり、これらを複数まとめたものが新たにUapmdFunctionDeviceという名前になりました。プラットフォーム上のUMPエンドポイントはUapmdFunctionBlockごとに作成されます。uapmd-engineのAudioPluginTrackは1つのUapmdFunctionDeviceを持つことになるでしょう(プラグインが9件以上刺さって全てが有効となる事態は、現状では想定していません。Kontaktのmulti-outなんかも8つまででしたし)。

シーケンサーを導入するためのモジュールの切り分け

1月になってから、embedded JSランタイムを追加して、またオーディオトラックとMIDI2クリップをカバーするシーケンスエディター機能(トラック編成・編集機能)を構築しようとしたのですが、これらがどんどん複雑なタスクとなっていき、特にuapmd-appAppModelAudioPluginSequencerという2つのクラスが巨大な神クラスと化してきて、機能拡張を進める前にいろいろコードとモジュールの切り分けをしっかりしておくべきだと思うようになりました。特に以下の2点が大きな課題でした。

uapmdのリポジトリには、現状作りかけで開発が進んでいないuapmd-applyという小型ツールがあって、これは基本的にはプラグイン名とオーディオファイルやSMFファイルを渡すと、それを時系列に沿ってプラグインにそれらを入力として適用して、オフライン レンダリングでオーディオファイルを生成する、というものを意図しています。これを実現するにはembedded JSエンジンのようなものをheadlessで動かす必要があるので、たまにuapmdに破壊的変更を加えるとこれがビルドできなくなるので、シーケンサーAPIの切り離しと再編成においてsanity checkの役割を果たしています。

プラグインホスティング、Function Block(仮想デバイス)管理の機能をシーケンサーエンジンから切り離すというのは、uapmdというモジュールをremidyやuapmd-app固有のものではない、再利用可能なライブラリとして位置付け、uapmd-appが最終的にDAWに匹敵する機能を自ら作り込まなくても何とかなるようにする、という目的が込められています。1月末の時点でどのような切り分けが実現しているかをまとめると、以下のようになるでしょう。

  • remidyはプラグインホスティング機能に特化する
  • uapmdはプラグインホストが管理するプラグインインスタンスMIDI 2.0仮想デバイス = Function Blockとしての公開コントローラー機能だけを提供する
    • プラグインホストやプラグインインスタンスの機能はAudioPluginHostingAPIAudioPluginInstanceAPIといったインターフェースのみ規定し、実装はアプリケーションが提供する
    • 仮想MIDIバイスの作成やMIDI I/OのフックはMidiIOFeatureMidiIOFeatureManagerといったインターフェースのみ規定し、実装はアプリケーションが提供する
  • UAPMDのシーケンサー部分はuapmd-engineというモジュールで実装する(SequencerEngineクラスがその大部分を担う)
    • miniaudioとlibremidiを組み込むのはこの部分
    • プラグイン操作APIはこのモジュールに含まれるRemidyAudioPluginHostRemidyAudioPluginInstanceが実装している
  • uapmd-appはuapmd-engineを操作できるGUIを実装する

AudioPluginInstanceAPIの抽象化はまだまだ不完全で、たとえばPluginParameterSupportインスタンスがそのまま返されるような設計になっています。これではまだremidyから切り離されているとは言い難いところです。ただ現状remidy以外の実装たとえばjuce_audio_plugin_clientを組み込む優先度は高くないです。)

切り離しの一番大きな部分は、シーケンサーエンジンが前提とする「トラック」が、Function Blockを編成する主体ではなくなり、またトラックのオーディオグラフのノードがプラグイン インスタンスを保持(管理)する存在でもなくなった、という部分です。具体的には…

  • シーケンサーはオーディオとMIDIイベントの入力を時系列に沿ってオーディオグラフに流し込んで結果を取得するだけの存在になり、そのオーディオグラフがどのような実体になっているかは感知しません。
  • オーディオグラフはプラグインインスタンスのノードを扱いますが、その操作はuapmdモジュールのインターフェースでのみ操作し、実装がどうなっているかは関知しません。
  • プラグイン インスタンスにはFunction Blockを割り当てることができ、複数のFunction Blockが1つの「UMPデバイス」を構築できますが、その編成はシーケンサーのトラック(のオーディオグラフ)と連動する必要はなく、それらのFunction Blockのコンフィグレーション(デバイス名など)をどうするかはuapmdのアプリケーション(uapmd-appなど)に委ねられています。

v0.2と来月の見通し

先月末にはシーケンサー機能を実装してAndroidサポートも追加して…みたいなことを考えていて、実際に年初にはv0.2 milestonesとしても列挙していたのですが、今月はだいぶ方向性の違う作業やシーケンサーのための下回り作業に追われていたこともあって、このままv0.3 milestones扱いにしてv0.2はそろそろ公開してもいいかな…と思っています。まあこの辺のナンバリングは単なるフィーリングですが。

2月も引き続き個人開発にフルコミットできそうな感じなので、シーケンサーを安心して作り込める体制をコード上で構築していくことになると思います。あまりDAWのフルエンジンを作り込むような作業ではなく、あくまでDAWのオーディオエンジンで使えるプラグインホスティング機能を実装するものとして進めていきたいところです。




以上の内容はhttps://atsushieno.hatenablog.com/entry/2026/01/31/194746より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14