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


2月の開発記録 (2026)

先月に続き2月も自分の開発に100%集中できていて、生産的な時間を過ごしています。といっても生産性の亡霊みたいに生きたい願望は皆無ですし、生産性を最大化して日々過ごしているわけでもないですが。

あとADC Japan 2026がいつの間にかCFPを出していたので1つだけ投稿しましたが、3/2まで時間があるのでもしかしたらもう2つくらい作るかもしれません。

audio.dev

正直どちらかといえばLinux Audio Conference 2026のほうが自分のあり方に近いので、そっちでしゃべったほうがいいのかもしれません。スケジュールを見る限りADCの採択からLACの締切まで1日の猶予があるので、落ちても再利用できそうですが、ちゃんとスケジュール通りに動いていれば…という前提がありますね。

lac26.mucs.club

UAPMD v0.2.0 and MIDICCI v0.2.0 released

2月中旬にUAPMDとMIDICCIのv0.2.0をリリースしました。実際には、v0.1.4を2月初頭にリリースして、その2週間後にv0.2.0をリリースして、実際にはちょっとやらかしててuapmdはv0.2.1をリリースしているのですが、まあ誤差でしょう。

UAPMD v0.1の主要なアチーブメントが「任意のオーディオプラグインをMIDI 2.0デバイス化する」にあるとしたら、v0.2は「マルチトラックのオーディオ / MIDI (2.0) クリッププレイヤー」を実現できたことをもって決定されたリリースです。SMF2 Container Formatはこういうフォーマットとして作られているはずなので、それを実現したものである、ということもできます。これを実現するために、UAPMD v0.2.0にはいくつか新機能が追加されました:

  • プラグインにUMPイベントを送信できるSMF2クリップをトラックの一部として再生できるようになりました。SMF2クリップの送信対象はプラグインであり、オーディオクリップと並列に走らせることもできるので、エフェクトプラグインオートメーションに相当する処理もSMF2クリップで実現します。
  • UAPMDプロジェクトファイルのロードとセーブ: SMF2 Containerフォーマットはまだ仕様が公開されていないので、独自形式で保存されています。
  • SMF importer: SMFで制作された楽曲の各トラックをSMF2Clipに分解できる(保存はプロジェクト保存と同時に行われる)。これで既存のSMFファイルからSMF2ファイルをカジュアルに作成できるようになりました。
  • マスタートラック: SMFでは明確な仕様ではないのですが、テンポや拍子の管理は1つのトラックに吸収して管理されています。管理といっても現状では全トラックから吸い上げているだけで、正直これを編集可能にするときにどうしたものかはわかりません。MIDI 2.0の場合テンポや拍子の設定はUMPの一部 (Flex Data) なので情報の保持は難しくないのですが。
  • トラックのタイムラインのコンセプトがほぼクリアになりました。具体的には、オーディオI/Oからのオーディオグラフにおける位置付けが、再生時にはいったんトラックの内容から「今回のオーディオ処理に含まれるサンプルとイベント」を切り出して、プラグイングラフに渡す(プラグイングラフの構築と処理は実装次第)、というかたちが明確になりました。
  • SMF2クリップの内容を表示するMIDIダンプリストを表示できるようになりました。

MIDI 2.0楽曲ファイルの作成と再生については、ktmidiにSMFをそのまま延長したような構成のAPIがあったのですが、UAPMDというかたちで音源というかプラグインホストを作成してみると、明らかにこの構成では足りないことが明白になったので、先月ktmidiからumppiとして取り込んだばかりのMidi2Musicクラスは消えて無くなる事になりました。

UAPMD & MIDICCI app design polish

UAPMDは元々GUIアプリケーションにするつもりが無かったこともあって、GUIとしての見てくれはUIテーマをBootstrapっぽいやつにしたりフォントをRobotoにした以上のことはしてこなかったのですが、MIDICCIと併せて今回はもう少し手を加えることにしました:

  • Lightテーマ対応: ImGuiにデスクトップからlight/dark themeを判別する機能は用意されていないようなので手動切替になりますが、一応ホワイト中心の配色を用意しました。ただImTimelineの中は調べきれていなくてそこだけ暗いです(逆効果かもしれん)
  • GUI上の機能が増えすぎて文字ボタンがスペースを取りすぎるのが無視できなくなってきたので、FontAwesome + fontaudioを使って画像で代用することにしました…のつもりが、fontaudioはまだ出番が来ていません(取り込みはしましたが)。カスタムフォントとしてImGuiに統合するのはjuliettef/IconFontCppHelpersと組み合わせてC++ヘッダー化したフォントファイルのリソースを使えば簡単…というわけでAIにC++ blob headerを自動生成させていたのですが、最近公開されたeyalamicmusic/ResEmbedを使うとさらにクリーンなかたちで統合できたので、公開が告知されたその日のうちに対応しています。
  • アプリアイコン: v0.1の時点ではアイコンの類を何も用意しなかったのですが、アプリケーションとしてインストールできるようにしているわけだしもう少し実用品っぽくしておこう…と思って用意することにしました。とはいえ考えるのめんどいしどうしようかなあ…とぼやいていたらmuojpせんせいから天啓が…

最近ClaudeでAndroid用のXMLアイコン描かせてるんですが、妙に味があって気に入ってしまい困る事案にw

muo (@muo.jp) 2026-02-14T06:19:49.867Z
bsky.app

DroidKaigi 2025の関連セッションなどを見ているとわかりみが出てくると思いますが、完全にそれっぽい…!と思って真似してみました。おかげさまでそれっぽいものが生成できています:

VST3/AU host on macOS: every plugin loads

UAPMDの大きな弱点のひとつはremidyのプラグインホスト機能がjuce_audio_plugin_clientなどと比べると安定的にあらゆるプラグインを利用可能になっていなかったことでした。具体的には、Absynth5のAUがインスタンス生成に失敗したり、Kontakt 6/7/8のGUIがAUでのみクラッシュしたり、KORG M1やVienna EnsembleのVST3がインスタンス生成時にクラッシュしたりといった問題が起こっていました。

これらについては毎月のように修正を試みていたのですが、今月はついに自分の手元にあるプラグインについては全て解決しました(そんなに大量に抱えているわけではありませんが):

  • Absynth5 AUとKontakt 6/7/8のUIを妨げていたのはAUのインプロセスモードの明示が原因でした(!) プラグインインスタンスの生成は時間がかかるので非同期のAudioComponentInstantiate()で行うべきですが、その引数にkAudioComponentInstantiation_LoadInProcessを指定すると、インプロセスモードで実装されていないプラグインの機能が全て死にます。
    • Absynth5 AUが死んだ理由はこれがarm64ではなくx64のABIでビルドされた旧時代のプラグインであり、Rosetta2経由で動いていたため、別のプロセスでロードしなければならず失敗していた(クラッシュまではしなかった)ということになります。
    • Kontaktのほうは、おそらくmulti outの機能を実現するうえで1つのGUIプロセスが複数のプラグイン インスタンスとやり取りするかたちになっていて、DSP側のインスタンスだけなら正常に動作できてもGUIを表示しようとした瞬間にAUのフレームワーク側から想定外のブロックを食らって死んでいたのでしょう。
  • VST3のM1とViennaのほうは、単純にremidyのオーディオバス調整まわりでプラグインとの不一致が原因でした。

そんなわけで、VST3とAUのプラグインホスト機能は少なくともmacOS上ではだいぶ安定感が出てきました。とはいえ何もかもが動くようになったというわけではなく、CLAPプラグインではFloeが動かず、またLinuxではyoshimiが自分の知る限りでは動いていません。この辺はもう少し修正が続いていきそうです。とはいえ互換性地獄のプラグインホスト開発でここまで順調にいろいろ使えるようになったことを考えると、だいぶいい戦果を出しているのではないかと思います。

MIDI Message Report: The Latest Parameter Value Saga

先月の作業の最大の成果は、プラグインのパラメーターリストの動的な変更とホスト側への最新パラメーター情報の通知を何とか実装したことだったのですが、これがuapmd-appからmidicci-appへの通知となるとまだまだ完成していませんでした。これは、簡単にいえば「仮想MIDIデバイス生成時にはプラグインパラメーターの値が既に初期値から動いてしまっているため」です。同様の問題はuapmd-app自身がホストとしてプラグインのパラメーターの現在値を通知してもらう流れでも発生していました。

この問題を解決するために、仮想MIDIデバイスにMIDI-CI接続が新たに発生した時に、MIDI-CIの機能の一番目立たないMIDI Message Reportという機能を使って、現在のプラグインのパラメーター…に対応するAssignable ControllerとPer-Note Assignable Controllerのリストをmidicci-appからリクエストできるようにして、またuapmd-appもMIDI Message Reportに応答できるようにしました。

Process Inquiryの機能を実装しているのはktmidiとmidicci(とuapmd)だけなので、juce_midi_ci(と呼ばれていたモジュール)を使っていると実現できません。

あと関連項目として、UAPMDのAllCtrlListプロパティでは、パラメーターの最大値と最小値を指定するminMaxという項目を生成しなくなりました。これが指定されると、パラメーターのnormalized valueの値域が0..1からシンプルに0..UINT32_MAXに対応しなくなってしまい、ユーザーに余計な混乱を与えることになります。MIDIデバイスとしてやり取りするのは常にnormalized value、enumなどのdiscrete valueに変換するときも0.0..1.0にマッピングしていたのと同じことをする、という前提で再構成しています。

midicci diverged: UMP type migration from uint8_t to uint32_t

midicciはそのアイデンティティが「C++プロジェクトに組み込めるktmidi相当のMIDI-CI機能を実現するライブラリ」にあって、これまではずっと「ktmidiに新機能を実装し、それをコーディングAIエージェントに移植させる」というモデルで進めてきました。

このアプローチは現状でも破綻しているわけではないのですが、今月になってmidicciはついにAAPにも組み込まれるようになり…さまざまな問題が噴出しました。現時点でもAAPのMIDI-CI統合は期待通りに動作していません。uapmdとmidicciは2026年の今を生きる人類として開発が進められているライブラリで、libremidiなど2026年のライブラリを想定して設計されています。

ktmidiがUMPサポートを追加したとき、あるいはAAPがMidiUmpDeviceServiceサポートを追加したとき、それらのAPIが念頭に置いていたのは「MIDI 1.0とのシームレスな互換性」でした。そのため、これらのAPIはMIDI 1.0のAPIと同様、1バイト単位(kotlin.Byte, uint8_t)でUMPを受け渡しています。これがlibremidi、ALSA、CoreMIDIなどとデータのやり取りをする場面ではuint32_tなど4バイト単位の受け渡しが必要になります。この変換は単なるポインタのキャストではうまくいきません。なぜならエンディアンネスの問題が発生するためです。UMPのuint8_t*のストリームとしては40 90 2B 00 F0 00 00 00のように渡さないといけないのに、4バイトのlittle endianのメモリには00 2B 90 40 00 00 00 F0のように格納されているためです。こういった複雑な前提の違いは、特にコーディングAIがすぐ間違えます。人類も間違えるでしょう。

そういうわけで、midicciはUMPを扱うAPIの一般的な潮流に合わせて、uint8_tからuint32_tの単位でUMPを表現することにしました。これは結果的にktmidiに追従する開発方針からの転換ということになります。まあ実際これで困ることはもう無いでしょう。midicciの実装は、可読性はともかく、挙動としてはだいぶ信頼できるコードになってきました。

ktmidiもそのうち追従するかもしれませんが、ktmidiのほうをいじっている暇がないので、しばらくは現状の1バイトAPIを維持することになりそうです。

UAPMD: U is for Ubiquitous

UAPMDは特にWindowsがMIDI 2.0をサポートしていない現状ほぼLinuxとmacOSで動かすために作られたものですが、次世代プラグインホストに相応しい立ち位置を実現するために開発しています。これは特にAndroidを含め「デスクトップ限定のホスティングライブラリで終わらない」という側面があります。UAPMDのUはubiquitous(遍在)のUであり、元々は「どんなオーディオプラグインでも」UMPデバイス化する、という意図だったものですが、今月はさまざまなプラットフォームで動かせる可能性が見えてきました。

uapmd-android

まずはAndroid版です。リポジトリはuapmdに統合される可能性がありますが、とりあえずは独立して作られています(雑にAndroid Studio Pandaで作られていますがAGPのバージョンさえ調整すればNarwhalでもいけるはず):

github.com

どのプラットフォームでも一番問題になるのは「ImGuiが動くかどうか」ですが、Android上ではSDL3 on Androidがそこそこ本気で作られているので、この上で動かしています。SDL3 on Androidは特にSDL3のメイン開発者が積極的にコミットしていることもあって、今後が期待できそうです。ただ現時点では「SDL3のメインスレッド」がAndroidのメインスレッドではないという問題があって、挙動が安定しません。Flutterがv3.29で大改修を実現したやつと同根ですが、こちらは単なるリグレッションのようなので、そこまで時間がかからずに直るかもしれません。

Android版のuapmd-appは、ImGuiアプリケーションとしては概ね問題なく動いていたので(最近はやや不安定でしたがこれはuapmd-app本体が不安定という可能性が高いです)、新たにPluginFormatAAPのAPIを作ってmainブランチにも取り込まれていますが、まだプラグインの列挙から先は未確認です。というのも、現状uapmd-appはファイルをロードできないとプロジェクトに取り込むことすらできないわけですが、AndroidでファイルI/Oというわけにはいかないので…

uapmd on Windows

次にWindows版です。Windows MIDI Servicesがついに出る出る詐欺を脱して正式リリースされそうということもあって、だんだんこの界隈が賑やかになってきました。先月libremidiの仮想ポートサポートがまだ出来ていないという話を書きましたが、今月になってこのissueにmicrosoft/MIDIの設計開発者も参戦してきて、実装されたけど動作確認できない〜、みたいな状態で来月に持ち越しそうです。

Windows版はまだまだ全体的に安定しないのですが、プラグインホストまわりでは大きく2つの修正を加えた結果、そこそこプラグインが使えるようになりました:

  • Native Instrumentsのプラグインが全て使えていなかったのですが、これはVST3のバンドルで呼び出すべきInitDll() ExitDll()の関数名が間違っていたのが原因で、これらを修正したら正しく使えるようになりました。
  • 先月までImGuiのバックエンドがGLFWだったのですが、まずSDL3を使うようになり、さらにDirectX11を使うようになり、その後安定性の観点でSDL3に戻ったものの、プラグインUIの描画に問題が出たため、現時点ではDX11に戻っています。SDL3の描画問題はかつてはmacOSでも出ていたので、似たような問題で解決可能かもしれませんが、現状DX11もSDL3も同じくらい不安定なので、今後DX11からさらに動くかはわかりません。

uapmd WebAssembly build

現状まだ本気で取り組むつもりはないのですが、uapmdにはEmscriptenビルドも存在していました。ただ古いブランチのままで、その後のuapmdの成長に全く追従していなかったため、mainブランチに取り込めるようなものではありませんでした。今週ちょっとCodexのトークンが余りすぎていたので一念発起して「ちゃんとmainブランチに取り込んで維持できる」前提のものを作ろうと思い至って、何とかCodexのweekly limitの範囲内で実現できました。

wasm版では意外にもchocのAudioFileReaderが(基本的にファイルI/Oで動いていたのでそのまま動かないのは当然として)iostreamでも期待通りに動かないという問題が出て、miniaudioの組み込みAPIでようやく再生できる(miniaudio自体はWeb Audioバックエンドもサポートしているのでそのまま使える)が、flacは再生できてもoggが再生できない…という事態に陥っています。

また当然ですがwasm版では現状利用可能なプラグインフォーマットが無く、WebCLAPかもしかしたらWAM2が今後サポートできるかどうかわからない状態にある、という感じです。どちらもプラグインを列挙するAPIがまだ無いはず。

AAP midicci integration

AAPには元々ktmidiを利用してMIDI-CI機能を実現するつもりでした。これを実行に移していなかったのは、Kotlinで実装してしまうとMIDI-CI処理をAAPから操作したい場合にややこしい事態になると想定されたためです。この考え方がmidicciの成長を見ていくうちに変わってきました。midicciを使ってAAPのC++実装から調整したほうがuapmdに近いことが出来そうな目論見が立ってきたので、ktmidiの代わりにmidicciを使うルートで実装することにしました。

ただ、midicci-appが存在しているデスクトップ環境とは異なり、midicci-appはまだAndroidに移植されていないので、接続確認にはktmidi-ci-toolを使っています。これがまだAAPのmidicci統合と相互接続できず、まだ試行錯誤している段階です。AAP側にも、インスタンス生成時にパラメーターリストを取得できない問題などがあり、AAPもいろいろ再設計したいという気持ちが高まってきました。ただそれは抜本的な再設計になるので、UAPMDが落ち着いてからがっつり時間を取って着手したいところです。

3月の予定

11月から3ヶ月ほどuapmdとmidicciに集中できていたのですが、4月には技術書典20とM3 2026春があるので、新刊の準備をしないといけなそうです。プラグインホストの設計解説書あたりが良いかと考えていたのですが、Windows MIDI Services正式公開の流れに乗るならMIDI 2.0総合開発本あたりが良いのかもしれません。まあ来月末には告知できる段階にあろうかと思います。

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のオーディオエンジンで使えるプラグインホスティング機能を実装するものとして進めていきたいところです。

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など展示できるものを充実させておきたいですね。




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

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