1月の開発記録(2023)

1月はひさびさに開発作業に専念できた気がします。

rebranding AAP: Audio Plugins For Android

今月からAndroid Audio Plugin FrameworkをAudio Plugins For Androidに改名しました。GitHubリポジトリもatsushieno/android-audio-plugin-frameworkからatsushieno/aap-coreに変えてあります。

改名自体に喫緊の理由は無く、名前の先頭に他所の商標が入っていると問題になりそう、っていう予防的な対策です(喫緊ではないもののずっと気になっていた問題ではあります)。GoogleAndroid関連商標っぽく見えるので、公開イベント等で話す時に名前を出しにくくなるともったいないですし、今ならそんなにブランディングの変更も大して面倒ではないと思って着手しました。後ろにFor Androidを付けるのは割と一般的な回避策だと思います(Mono for AndroidWindows Subsystem for Android)。略称としては引き続きAAPを使いますし(AP4A、APfAでもいいけど)、コード上の命名に変更はありません。ここを変えるとしたら大々的に非互換変更を加えるときでしょう。

昨年末から少しずつプロジェクトとして回していくための作業が進行しつつあります。

AAP: previewing presets & MIDI settings

JUCEやLV2のプラグインは、ものによってはMIDIのプログラムチェンジに対応して音色…に対応するプリセット…を切り替える機能を実装しているものもありますが、そうはなっていないプラグインも多いです。それでも、プラグインAPI経由でプリセットを変更する機能が実装されているものは少なくありません。また、エフェクトプラグインについては、MIDIプログラムチェンジでプリセットを切り替えるというものはほぼありません。

モバイル環境でオーディオプラグインのパラメーターを細かく調整するのは作業として面倒ですが、プリセットを選択する程度であればカジュアルにドロップダウンリストのようなものを表示するだけなので、現実的な解だと思います。そういうわけで、とりあえずはaaphostsampleにプリセット選択機能を実装してみました。今月はBYODを移植したので、BYODのプリセットを選んでいろんなディストーションを適用して遊べるようになったのですが、割といい体験になったと思います…ちゃんと動けば。JUCEのプリセットサポートはややクセがあって(プログラムチェンジみたいに番号で選ぶようになっている)、AAPでもやや無理をしている部分があるので、割とちゃんと動かないことが多く、今後見直す必要がある課題みたいになっています。

aaphostsampleは、今は完全にやっつけプレビュー機能しかもっていませんが、MIDIマッピング設定などをshared preferenceで保存できるようになりましたし、今後はだんだん管理機能のようなものを追加していくことになると思います(aaphostsampleである必要は全く無いですが)。

ちなみにaaphostsampleでプリセットを選択できる機能をスクショしてmastodonに流したのですが(twitterにもforwardされる)、その数日後にJUCEのAudioPluginHostにも同じ機能が実装されましたね。いいところはどんどんパクってほしいですね(自信過剰)

new AAP ports

今月はしばらく前に移植を試みて動いていなかったJUCEプラグインが2つ動くようになりました。

(1) aap-juce-os251: OS-251のAAP移植が動くということはすなわちreact-juceプラグインが動くということです。最初に移植したときに起動時エラーで落ちていて、1年以上放置していたのですが、多少JUCE on Androidの罠がわかるようになってきて(主にこの 辺りなど)、今年改めてAAPの最新版に更新して改めてビルドして既知の起動時エラーも対処してみたら動くようになりました。

(2) aap-juce-byod: BYODはRTNeuralの応用事例であり、ギターエフェークターのリアルな実用例としても大いに移植のしがいがあるプラグインです。RTNeuralで使われているxsimdが古くてAndroid上でSIMD最適化が行われていなかったので、RTNeuralで最新のxsimdを使えるように多少コードの修正を加えて、Android上でも使えるようになりました。これも実は半年くらい前に試しに移植してみて動かなかったやつです(今でもUI起動前にクラッシュするのでちゃんと動くとは言い難いですが)。

去年は新しいプラグインをほとんど増やしていなかったので、だいぶ豊作な印象があります。実のところ、去年移植が全然増えなかったのは、これ以上増やしても大変っていうこともまああるのですが、あまり新しいJUCE OSSプラグインが出てこなかったということもあります。このBYODとOi Grandadくらいしかパッと思いつきません。LV2プラグインもそんなに増えているわけではないですが、master meとかが新しいかな。プラグインというとちょっと違うけどIldaeilも新顔ですね。

AAP parameters and presets on MidiDeviceService

MidiDeviceServiceとなったオーディオプラグインでは、入力がMIDIメッセージのみになってしまうので、パラメーターやプリセットの指定がそのままではできません。これがDAWであれば、MIDI mappingを使ってDAWMIDI入力メッセージをパラメーター設定やプリセット指定に置き換えたりできるわけで、同じような機能がMidiDeviceServiceにもほしくなります。特にAAP V2プロトコルではMIDI2が入力として使えるので、マッピングポリシーさえ事前に規定しておけば、それなりにストレートなやり方でマッピングが実装できます。

というわけで、AAP拡張機能の一部として、以下のようなマッピングを調整できるようにしました:

  • CCをパラメーター設定として使う
  • Assignable Controller (NRPN) およびPer-Note Assignable Controllerをパラメーター設定として使う
  • ユニバーサルsysex8で所定のビットパターンをもつものをパラメーター設定として使う
  • プログラムチェンジをプリセット設定として使う

一方で、VST3のようにマッピングを強制した結果オーディオプラグインでまともにMIDIのプログラムチェンジが受信できなくなったり、ノートとパラメーターチェンジが別々のシーケンスになって順序がおかしくなったりする問題の原因にもなるので、プラグイン開発者が「このメッセージは受信したら独自に処理する」と宣言しておくことで、MidiDeviceServiceのようなホスト側がそれらを「マッピング対象としない」ことが可能になっています。

実装当時はparameters拡張機能の一部としていましたが、プログラムチェンジの設定は明らかに「パラメーター」ではないので、今は「midi拡張機能」の一部としています。もともとmidi2拡張というのがあったのですが、イベント処理の内部実装が全てMIDI 2.0 (UMP)による実装になったので、拡張もへったくれもない状態で、それなら今回ついでにMIDIイベント処理に関する拡張機能として再利用しちゃえ、ということでこうなりました。

LMN-3-DAW-Android

2年前にJUCE+Android+CMakeのネタを書いてから、JUCE on Androidに詳しい輩とみなされるようになっていて(実際にはそんなにJUCE開発の経験は無いんですが)、ちょろちょろとメールで相談がやってくる日々です。今月は、LMN-3-DAWAndroidで動かしたいんだけど…みたいな相談がやってきたので、(自分では開発に関わるつもりは無かったので)とりあえずブートストラップとしてプロジェクト構造があればいいかと思って、1日でAndroid版を作ってみたら、案外そのまま起動できるところまでいったので、そのまま渡した(教えた)ら、翌日には公式リポジトリ持っていかれていました。

ただ引き渡してからが問題で、Windowsでビルドできない…という話になって、(もう1年以上Windowsをまともに起動していない)、調べ方だけいろいろ伝えて、某オーディオDiscordでも喧々囂々の議論が行われていたのですが(といっても自分はほぼ眺めていただけ)、あの記事は思いのほか勘違いされている(たとえばパッチが無いと動かないような修正を加えていると思われている)ということがわかり、ちょっとエゴサしてみるとforumやらdiscordやらでリンク出されては「難しくて読めねえ」みたいな感じの反応ばかり出ている状態だったので(!?)、気が向いたらそのうち何とかしよう…と思いました。

最近も「MIDI入力が期待通りに取れないんだけど…」みたいな相談を受けては「そもそもJUCEは入出力を取り違えているレベルでまともにAndroid MIDIを扱っていないので…」みたいな話をしていて、これもそのうちJUCEを直すしかねーかな…みたいな感じになっています(自分のコードで使う予定が全く無いのでモチベーションが低い)。

このプロジェクト、DAWのエンジン部分にはtracktion_engineが使われていて、AAPも組み込めれば利用事例になるといえばなるんですが、USBで接続するハードウェアコントローラーが無いとまともに使えないので(デスクトップ用にはコントローラーのエミュレーターになるJUCEアプリもあるのですが、Androidでそのまま使えるわけがない)、とりあえず今のところ実験用に使う予定はないです。

AAP APK Installerとパッケージングの全般的な統一

今年はAAPをある程度は実用性のあるオーディオプラグインのソリューションとして使えるようにしていきたいと思っているのですが、パッケージが多数のリポジトリに分散していて全く試せる状態になっていなかったので、何かしらのベータテスティング的なデプロイメントを実現しようと考えました。それで紆余曲折を経て、AAP APK Installerという仕組みが出来上がりました。AAP専用にするのはちょっともったいないので、AAP以外でも使えるようにある程度一般化して、自分でパッケージカタログ等を用意すれば何でもインストーラーに渡せるようなActivityを含むライブラリにもなっています。詳しくはzennのほうに書いています。

zenn.dev

それでAAPプラグインのカタログを自分でまとめていて、パッケージ名などが割とバラバラで散逸していたので、この機に全部統一的な名前にしようと考えて、全てorg.androidaudioplugin.ports.lv2.*org.androidaudioplugin.ports.juce.*みたいな名前にして、アプリケーション名も全部 "AAP xxx" の形式にしました。これで関連パッケージが全部インストール済みアプリケーションの先頭に AAP xxx としてまとまるのでスッキリしました。

あとaap-lv2-*リポジトリ群、aap-juce-*のCMakeビルドのリポジトリ群、aap-juce-*のProjucerビルドのリポジトリ群、の3つのカテゴリーで、それぞれGitHub Actionsのワークフローが同一の設定をほぼ使い回せる程度に共通化されていたので、Reusing workflowsの機能を使って全部まとめることにしました(現在進行形で移行中)。

aap-juce hosting fixes, and Helio Workstation now works

AAP 0.7.4をリリースしてから気づいたのですが、aap-juce-plugin-hostが何やらインスタンス生成で固まるようになっていました(自動テストしたいんだけどできないやつ)。AudioPluginHostは現状aap-juceの唯一的な実装例なので、これが動かないのでは見本として体裁が悪いので、ちゃんと調べて直すことにしました。結論からいえばAndroidContext.bindService()がメインスレッドで動くようになっていたのとJUCEのjava.lang.Threadサポートのバグのコンボだったわけですが、無事また使えるようになりました。

ホスティング実装が直ったので、この機会にと思ってHelio WorkstationAndroid版へのAAP統合をアップデートして試してみたのですが、相変わらずオーディオルーティングのところでオーディオポートが検出されないという問題が直っていなかったので、これも調査してみて、どうやらjuce::BusesPropertiesの設定を含むjuce::AudioPluginInstanceしか認識されないということがわかったので、バス設定を適当にでっち上げるようにしたら(!?)、ちゃんと実行できるようになりました。

実行はできるものの、オーディオクオリティは全然良くないので、その辺は今後の調査次第という感じですが、そもそもHelioに最初から含まれているプリセットプラグインでも音飛びが生じているので、AAPの問題とHelioの半々という感じです。まだまだ問題はありますが、AAPが見込んでいるオーディオプラグインフォーマットらしい使い方が、これでようやく示せたのではないかと思います。何とか実用できるところまでもっていきたいところですね。

Web技術とクロスプラットフォームオーディオプラグイン開発アーキテクチャの今後を検討する(2023)

最近、Web技術を利用したクロスプラットフォームのオーディオプラグイン開発アーキテクチャについて考えを巡らせている。といっても対象はWebプラットフォーム(だけ)ではない。Web以外の環境における利用を念頭に置いている。

オーディオプラグインという文脈では、従来のクロスプラットフォームWindowsMacLinuxに限られる言及が多かった。しかし2022年現在、クロスプラットフォームといったらモバイルやWebを考慮に入れるべきだし、Windows 11やChrome OSのようにLinuxコンテナやAndroidコンテナが動作する環境も登場してきている。

iOSにはAudioUnit v3があって、macOSと基本的には同一のアーキテクチャで構成できると考えても良さそうだ。Androidにはオーディオプラグイン規格がないが、筆者が開発しているAAPが実用段階に入れば(まだ待ってほしい)、あるいは他所から同様の機構が出現すれば、検討すべきものになる。WebではWAM2 = Web Audio Modules 2.0がこの分野を開拓しているが、まだ発展途上にある(AAPよりはだいぶ進んでいる)。WebAssemblyはC/C++/Rustといった言語のソースからWebアプリケーションをビルドするための基盤として確立しており、wasmランタイムによってネイティブ実行も可能になりつつある。

モバイルやWebにおける音楽制作アプリは、他の分野と比べると、デスクトップにまだまだ追いついていない。Webアプリで「ネットワークを跨いでリアルタイム演奏したい」といったデスクトップでも実現できていないような要望を叶えるのは無理があるが、今できることとできないことを整理して、どのような開発アプローチで向き合っていけるかを本稿では検討したい。

Web技術を取り込むというトレンド

筆者はオーディオプラグインとホストの開発で、昨年いくつかのプロジェクトがWeb技術を取り込もうとする動向を観測した。

これらは、必ずしも最先端のトレンドを作ろうとしている先駆者というわけではない。CmajorよりもSOULよりも前から、FAUSTではコードをWebアプリケーションとして動かすことができたし、ReaperのCockos WDLから派生したiPlug2では数年前にWebAssemblyビルドを実現しており、WAMのビルドも可能になっていた(WAM2に対応しているかは筆者は把握していない)。

また、開発用言語としてJavaScriptを使用しつつも、実際にはネイティブコードにコンパイルして動作させるElementary AudioのDSPのようなソリューションもあり(と筆者は理解しているが、使ったことはないので誤解している可能性は否定しない)、これは筆者の理解ではどちらかというとFAUSTやCmajorに近い。あるいは、ChiselでScalaから複数のパイプラインを経てネイティブコードにコンパイルするのと同じように、JSからDSPのネイティブコードを出力していると考えたほうが近いかもしれない。

Plugin UI, to and from Native and Web

Web技術をオーディオプラグインに取り込むというのと、Webにオーディオプラグインを持ち出すというのは、逆方向の概念だ。これらは整理しないと混乱を招く可能性があるので、ここで一覧にまとめておきたい。

Technology UI API/code binary platform コメント
react-juce React.js Native + JS Native ReactでUIを作ってJUCEプラグインとして動かす
Elementary JS,HTML JS, Native/WebView Web, Native ネイティブDSPはJS runtimeで動かさない
FAUST FAUST Native, wasm Native, Web オーディオDSP用言語でWebもサポート。GUIはFAUST
c-major JS,HTML Native/WebView, wasm Native, Web オーディオDSP用言語でWebもサポート。GUIはHTML
iPlug2 iPlug2/C++ Native, wasm Native, Web x-platプラグイン開発ツールがwasmに対応
DPF wasm DPF/C++ Native, wasm Native, Web x-platプラグイン開発ツールがwasmに対応
juce_emscripten JUCE/C++ wasm Web x-platプラグイン開発ツールがwasmに対応
WebAudioModule2 * wasm, JS Web WebAudio用のオーディオプラグインフォーマット

Wasm as ABI(s)

CLAP開発者の技術研究目的はオーディオプラグインのバイナリコードを再利用できる可能性に関するものであって、これは筆者の関心事と重なるところがあって興味深い。バイナリコードとしてwasmを使えるようになれば、新しいCPUアーキテクチャが登場したときにネイティブコードを新たに生成する必要がなくなり、同じwasmコードを流用できるというわけだ。新しいプラットフォームでも、DSPコードが分離していればコードの再利用が可能になるし、なんならDSPだけであればGUIなしでそのままwasmランタイム上で実行できる可能性がある。

これは世のwasmランタイムが目指している「ネイティブコードとしてのwasm」あるいは「ABIとしてのwasm」の構想をオーディオプラグインでも実現しようというものだ。C/C++/Rustコードからwasmをビルドできて、それがどのアーキテクチャのどのプラットフォームでも実行できるようになれば、あとはプラグインフォーマットの違いだけ意識すれば済むことになる。実際にはwasm32とwasm64で別々にコードを生成しなければならないだろう。これは特にLinuxデスクトップなど、機能的完全性に比べて正当に評価されていない環境での制作環境の大幅な改善に繋がりうる。

ネイティブコードとしてのWebAssemblyというのは、実のところ他の領域でも利用が検討されている。Android NDK開発チームから昨年公開された提案のひとつに、wasmをNDKのABIとして実装してみるのはどうか、というissueがある。これについてはTechBoosterから12/31に発売される「プロと読み解くモバイル最前線~アプリを支える最新技術~」に寄稿した「Android NDKはどこへ向かうのか」という記事で数ページ割いて解説したので、興味のある読者にはそちらを参照されたい。

DSPGUIのコード分離(1)ネットワーク越しのプラグイン操作

DPFの開発者であるfalktxが実験している目的は少し異なる。彼はMOD Devicesというハードウェアデバイス上に実装されたLV2プラグインを外部から制御するためにWeb GUIを利用しようとしている。つまりGUIDSPは別々のプロセスどころか別々のハードウェア上で動作しているというわけだ。実のところ、ADC22でも組み込み用Linuxでオーディオプラグインを搭載するハードウェアデバイスの開発を行っているElk Audioの開発者が、DSPとUIの分離に関するセッションを行っていて、アーキテクチャとしてはMOD Devicesに近いといえる。セッション動画をリンクしたいところだがまだ公開されていない(去年は遅いやつだと11ヶ月も待たされた動画があったしまともな時期に公開される期待値は低い)。

オーディオプラグインなのにネットワーク越しに操作するなんてナンセンスじゃないか、と考える読者もいるかもしれないが、オーディオプログラミングで最も厳密にリアルタイム性が求められるのはオーディオ処理の部分であって、GUIは一般的にはリアルタイムではあり得ない。GUIの更新は(2022年の一般的なアーキテクチャとしては)プラットフォーム上のGUIスレッド上で動作しなければならないし、それらはリアルタイムスレッドではない。

もちろんハードなリアルタイム性が要求できないからといって、いくら遅延してもかまわないということにはならず、合理的な範囲でユーザー操作が遅延なくDSPに反映されることが求められる。ユーザーによるポインターイベントの開始から実際にアプリケーション上で入力イベントがGUIアプリケーションループに乗るまでの遅延も厳しく求められる世界ではある。

DPFとFAUSTでは、複数のWeb UIがひとつのDSPをコントロールできる。これはGUIDSPのコードが適切に分離していないと実現できない。

DSPGUIのコード分離(2)プロセス分離モデル

AudioUnit v3はApp Extensionとして実装されており、DAWのアプリケーション上でプロセスを分離した上でプラグインを起動・実行できるようになっている。iOSでも、GUIDAWの上にデスクトップのオーディオプラグインのようにオーバーレイ表示できるようになっている。Android…というよりAAPの場合、GUIサポートはまだ実装されていない。Overlay WindowでDAW UIの上にプラグインUIを表示できる可能性もあるが、筆者は現時点ではプラグインから提供されるWeb UIをホスト側でロードして表示する仕組みを検討している。この場合、ホスト側のプロセスで表示しているUI上で発生したプラグインへのイベント通知は、Binder IPCの仕組みに基づいて送られる想定だ。このモデルでは、DSPGUIのコードが分離することになる。

モバイルではなくデスクトップDAWでも、Bitwig Studioのようにプラグイン毎に分けるプロセス分離モデルをサポートしていると、そのDAWではホストとプラグインの間でIPCに基づいてオーディオグラフが処理されることになるはずだ。AndroidのAAPもだが、オーディオバッファ全体をIPCのメッセージで送受信するような無駄なことはせず、共有メモリを利用することになるだろう。

これらのような環境では、プラグイン拡張機能をライブラリとしてホスト側で実行することは(少なくともインプロセスでは)できないので、プロセス境界をまたぐFFIを実現する必要があり、プラグインとホストのやり取りをAPIというよりは通信プロトコルのようなかたちで実現する必要がある。

ホットリロードを可能にするためのコード分離

FAUST、react-juce、Cmajorなどが実現しているのは、ホットリロードを前提としたRADといえる。C++やRustで開発していたら、全てを静的にビルドしなければならず、待機時間が長い。GUIをWeb技術で構成するものは、GUIのみホットリロードできるし、DSPを分離していれば、その部分だけはフルコンパイルしても大したビルド時間にならずにすむかもしれない。どちらか一方だけでも、伝統的なJUCEプラグインのように全部コンパイルするモデルに比べたら格段に速いだろう。

ホットリロードという課題は、Web技術にすれば何でも解決する問題ではないということを示すものだ。 juce_emscripten を使うと、Webブラウザ上でもJUCEプラグイン(のstandaloneアプリ)が動作するが、これはホットリロードからはほど遠い技術だ。

コード分離の境界線

オーディオプラグインのコードが設計上の制約によって分割される場合、次のような境界線が考えられる。

  • DSP: このモジュールは、主としてリアルタイム優先度をもつオーディオスレッド上で行われるオーディオ処理を担う。他のモジュールとのやり取りはatomic operationsによって行われる。
  • GUI: このモジュールは、ホストプロセス上にもロードできるようなUI処理を担う。DSPモジュールとのやり取りは(もしサポートするのであれば)atomic operationsによって行われる。リアルタイム性は期待されない。メインモジュールとのやり取りはイベントメッセージングによって行う(一般的にはunidirectionalな処理フローになろう)。
  • メインモジュール: このモジュールはプラグインプロセスでしか行えないDSP以外の処理を行う。DSPGUIからリクエストされたファイルI/OやネットワークI/O、リアルタイムでは処理できない計算処理などが想定される。(この名称はやや迷ったが、メインスレッドで動作することになるのでこれがある意味妥当だと考えることにした。)

GUIを表示するホストとDSPを処理するプラグインが別々のプロセスで動作する場合、DSPとメインモジュールは同一のコードベースでも問題ないが、メインモジュールとGUIは別々のコードベースで動作する必要があると考えられる。プラグインのアプリケーションで利用するファイルリソースにはプラグインのプロセスからしかアクセスできないので、メインモジュールはプラグイン側のプロセスで動作する必要がある。一般的にはGUIモジュールとDSP・メインモジュールの2つに分割することになると考えられる。

実はこの境界線モデルが既に確立しているプラグインフォーマットがある。LV2だ。LV2は仕組みとしてはメインのライブラリとGUIのライブラリを別々にロードすることが想定されていて、両者のやり取りはプロセス分離を前提とした考慮できるLV2 UIのAPIに基づいて行われることになっている。

※「プロセス分離を前提とした」という書き方は不正確(でわりと重要)なので訂正: LV2 UIはGUIの共有ライブラリを別途(LoadLibrary()dlopen()によって)ロードするので、コードの構造を共有しない前提にはなっていない。opaque pointerとして相互にやり取りすることは不可能ではない(当然ながらbad practiceであり仕様上は推奨されていない)。推奨されるコーディング プラクティスに沿って作られていれば、プロセスが分離していても問題ないという点は変わらないと思う(反例があるかもしれない)。

※2023/2/9追記: 上記追記のほうが間違っていた。LV2 UI仕様には次のように"MUST NOT"と明記されている: "Note that the process that loads the shared object file containing the UI code and the process that loads the shared object file containing the actual plugin implementation are not necessarily the same process (and not even necessarily on the same machine). This means that plugin and UI code MUST NOT use singletons and global variables and expect them to refer to the same objects in the UI and the actual plugin."

CLAP 1.0はこのアプローチを採用しなかったが、これは開発モデルの問題であり、CLAP 1.0の上に追加要求事項としてコードの分離を前提としたプロトコルを実装することは可能だ。もちろん同じことがVST3やAUにもいえる(AUをweb readyにするというのはちょっと考えられないが)。

ネイティブプラグインはWeb技術のラッパーで作れる?

プラグインフォーマットとは、複数のDAWと複数のプラグインを互換性を損ねることなく結びつけるための規格だ。DAWは複数プラグインフォーマットをサポートすることが期待される。プラグインフォーマットのベンダーが販売しているDAWではこれが期待できないかもしれない(AppleがLogic ProでVST3をサポートする、SteinbergがCLAPをサポートする、Bitwig StudioがLV2をサポートする、etc.)けど、そういうのは無視して、複数プラグインフォーマットで最大公約数の機能を実現することはできるだろう。それを地でやっているのがJUCEであり、iPlug2であり、DPFだ。あるいはMatlabやCmajor、Elementary Audioかもしれない。

デスクトップの全プラグインフォーマットの機能の最大公約数まで拡大しないにしても、一般的なDSP機能の範囲でクロスプラットフォームのオーディオプラグインAPIを実装することは可能だろう(もともとDSPのコードはクロスプラットフォームで実現できることが多い)。そしてGUIもWeb技術で実現できるのであれば、いっそ全てWeb技術で開発してしまって、各ネイティブプラグインAPIの境界でホストとのやり取りさえ実現できれば、複数の環境向けにコードをビルドする必要がなくなるように思える。これは実質的にプラグインラッパーを作るというのと変わらない。

これはもちろん、DSPJavaScriptで動作させようというものではない。WebAudioModules2がそうであるように、「そうすることもできる」が、そうする必要はない。DSPはwasmであればhard RT safeであり続けられるし、Elementary AudioのようにJSで書かれたコードがJSランタイム上で動作「しない」仕組みがあれば、一般的なlatencyの問題は生じない。

まとめ

2023年のオーディオプラグイン開発においては、デスクトップ、iOSAndroid、Web、組み込みなどさまざまな場面で利用できるコードがビルドできることが求められる。そのために、主にリモートプロセスのためのGUI、ホストと分離したプラグインのプロセス、ホットリロードといった機能を実現する手段としてWeb技術が模索されている。wasmとWeb UIはどの環境でも利用できるポータブルなコードになるポテンシャルがあり、これを具体的に実現する技術が新しく出てくる可能性があると思う。

追記

aikeさんのリアクションがそれなofそれなだったので言及しておきたい。

mastodon.cloud

12月の開発記録(2022)

晦日、1年分の振り返りを書く時間がなさそうなので、いつも通りひと月分だけのアップデートです。

Zrythm/DAW勉強会の下準備

12/11にはZrythm DAW勉強会を開催しました。いつも20人いれば多いほうかな?みたいなノリでやっていたところに、最終的に50人近く参加申し込みいただいて、何かえらいことになったな…!? と思いながら当日まで準備していました。 Zrythmは謎の勢いがあるプロジェクトで、いずれ調べてみようと思っていたから開催したわけで、普段使っていて詳しいから開催したと感じではありません。普段使いは強いて言えばWaveformです(が、そもそもDAWを使う作業を最近ほとんどやっていない…)。今回はalexteeせんせいをお招きしていたので、内容が間違っていても訂正はしてもらえるという期待がありましたが、当日しゃべる内容がちゃんと実態とマッチしていることは重要なので(たとえば「最先端のDAWです」みたいな話をするわけにはいかない)、動かしながら問題が発生したらバグレポートを作ったり原因を調べたりする感じでした。どちらかというと資料作成より再現性のあるバグレポートを作る作業でいっぱいいっぱいになったともいう…(資料作成はいつも通りそんなに気合を入れるつもりはなかったですし)

スライドはspeakerdeckに公開してあります。

speakerdeck.com

当日は配信やら録画やらでホスト側として失敗しすぎたのでいろいろ反省するところがあり、次はもう少しコミュニティの皆さんにヘルプをお願いして回すことになると思います。次は自分がしゃべるのではなく1回目(オフライン)のような複数本トークの集まりにしたいですね。

Android NDKはどこへ向かうのか (C101)

TechBoosterの新刊「プロと読み解くモバイル最前線~アプリを支える最新技術~」に「Android NDKはどこへ向かうのか」というタイトルで記事を1本寄稿しています。「NDK与太話」という仮題で書いていたのですが、こっちの題を思いついてしばらく悩んだ末にこっちにしました…

techbooster.booth.pm

NDKのネタを書くつもりは当初なかったのですが、Zrythm勉強会の準備で余裕がなかったのと、10月までも(M3で)文章書きタスクが多くてこれ以上開発作業から離れるのは良くないと思って、比較的ライトなネタを選びました。NDKの最新情報は多分割と地味になるので候補ではなかったのですが、そういえばGitHub issuesにはおもしろネタがたくさん転がってるんだよな…という視点で「将来」の話としてまとめています。ただしRustサポートなど、公式には「やらない」と明言しているので注意が必要です(本書でもそう言及しています)。具体的には次の3本です。

  • Rustサポート? - Rust方面の開発者以外には刺さらないと思いますが、ネイティブコード開発者としては誰もがふんわりと関心をもつトピックだと思います。筆者もRust系開発者ではないのでふんわりと調べてまとめています。ネタ元は主にissue #1742
  • wasm as IR? - WebAssemblyをIRというかABIとして活用しよう、というアイディアは個人的には想像の範囲を超えていたのでかなり刺さっています。wasmランタイムとかにふんわりと関心のある層向きです。ネタ元は主にissue #1771
  • Jetpack NDKライブラリ? - これはまあ完全に宣伝文句の問題で、単に公式ライブラリとしてPrefabを配布するよという話なのですが、公式ライブラリとしてパッケージを配布する基盤はちゃんと整備されているか?という観点で標準ライブラリ(libc++)の話を書いています。ネタ元は主にissue #1314libcxx-provider

AAP state of union updates

11月にAAP v0.7.4 / AAP-LV2 0.2.4 / AAP-JUCE 0.4.4と比較的安定したバージョンをリリースしたので、12月はひとまず外部の人向けに現状をいろいろドキュメントとしてアップデートする作業にかかりました。とりあえず2022年の更新まとめと、今できている機能のリスト、今あるプラグインとホストのリストがあります。

もう少し情報を足していかないといけない気もするので、1月にも更新が続くと思います。roadmapも2023年版が必要だし。個別にチャットとかで相談を受けたりはするのですが、来年はコミュニティ的なものを形成していったほうがいいかなあと思っています。そのためにはちゃんと複数人で開発できる体制が必要なのでコードも整備しないと…という感じでやることが芋づる式に出てきます。

まあでもこのプロジェクトを1人で続けるのはスケールしないですし(プロジェクトの立ち位置としてあまり適切な体制でもないし)、少しでもタスクをオフロードできる可能性を広げていきたいと思っています。

AAP testing updates

今月後半に主にやっていたのがテストまわりで、来月も続くと思うのですが、テストがまともに機能していないのをどうにかしようと格闘しています。個人的には割とtest firstに近いところで生きてきたのですが、AAPはクライアント/サービス + NDK環境という、割と安定解の無いところからスタートしていて(そもそも最初はJUCEプロジェクトだったし)、ものすごく治安が悪い状態です。一応gradlew connectedCheckは昔からあって、なんならaosp-atdの設定すらあるのですが、これがまともに動作したことがほとんどないし(コードの問題ではなくAGPの問題)、そもそもGitHub Actionsがnested virtualizationをmacosでしかサポートしていないので、その辺でCIへの統合はストップしている感じです。ただローカルでconnectedCheckで確認できることは増やしておきたい…と思って、ここにインスタンス生成とオーディオ処理まわりのテストを加えては期待通りに動かないのを発見してコードを直す…みたいなことをやっています。

この辺のテストを含めた開発体験が、Android Studio + NDKでは(Kotlin onlyのアプリに比べると)かなり悪く、バグレポートを作ろうと何日か格闘しても再現条件が難しい(自分のプロジェクトは複雑すぎて切り分けにも時間がかかる)…という感じで、割と時間を食わされていて、どうにかしたいなあ…とは思うところです。まあKotlinだけでも、複数スレッドが絡むとデバッグオブジェクト評価がまともに評価できない問題とかあるし、AS自体がいま割と不安定ですよね。Dolphinがそんな状態のままstableになっているので、今後もしばらく不安定なんだろうなと思っています。

その他/総括

今月はアドベントカレンダー月間だったのでこれらの他に2本記事を公開していますが、どちらもここに上がっているので言及するほどでもないでしょう(書き上げたのも先月だし)。それとは別にzennに1本Cmajor first impressionというのを出しています(先月書き溜めて中途半端だったのでそのまま忘れていた)。

…そんな感じで、12月は開発アップデートは少な目です。まあ今年は全般的にそういう月が多かったですね。来年はもう少し開発側にコミットしていきたい…(フラグっぽい気がする)

JUCE 7.0で追加されたLV2サポートについて

JUCE Advent Calendar 2022、2日目のエントリーです。

2021年の半ば頃からJUCE開発者がサポート計画を公言して以来、コミュニティで話題になっていたLV2プラグインフォーマットのサポートが、ついにJUCE 7.0のリリースで実現しました。

LV2プラグインなんて知らなかったという人向けに軽く説明すると、主にLinuxデスクトップ環境でデファクトスタンダードとして使われているオーディオプラグインフォーマットです。近年ではVST3もクロスプラットフォームで使えるようになって、Linux上でのシェアはLV2と同程度かそれ以上に大きくなりつつありますが、以前はVSTクロスプラットフォームではなかったため、「WindowsではVSTMacではAULinuxではLV2」のような状況がありました。JUCEがLinuxでVST3をサポートするようになったのも2020年にリリースされたJUCE 6.0からです。

LV2もクロスプラットフォームの仕組みであり、WindowsMacでも利用可能ですが、現実的にLV2をサポートするホストはLinuxを中心に成長してきたDAWがほとんどであり、Linux環境以外でのバイナリパッケージの配布例も多くはないでしょう。

LV2プラグインについては過去に「LV2オーディオプラグイン開発ガイド」という、世界でも他に類を見ない同人誌を書いたので、興味のある方はそちらをどうぞ(宣伝 (まあ英語では開発者直筆のlv2 bookが充実しているので不要ともいえます。)

従来のLV2サポート: コミュニティプロジェクト

JUCEはマルチプラグインフォーマットのフレームワークであり、そのエクスポーターとしてLV2を追加することは決して不自然でも不可能ではなく、実際DISTRHO/JUCEというforkではLV2プラグインのビルドが追加されていました。JUCEからLV2版プラグインを作るときはほぼこれが使われていたと思います(他にもいくつかLV2サポートのforkがありましたが、とりあえずこのforkに収束していたと考えてよいでしょう)。

一方で、JUCEはプラグインを作るだけのフレームワークではなく、プラグインホスト(DAWなど)をビルドできるフレームワークでもあります。DISTRHO/JUCEはこの実装を持っておらず、lvtk/jlv2というプロジェクトがLV2ホスティングをサポートしていました(こっちはそんなに需要が無かったのか、contributrorも自分しかいませんでした)。

JUCE7.0でのLV2サポートの追加によって、これらのプロジェクトは今後は不要になるはず…というわけにはおそらくいかず、少なくともDISTRHO/JUCEは残り続けるでしょう。オーディオプラグインのパラメーターなどは、打ち込まれた楽曲の内容に影響を与えないために、後方互換性を維持しなければなりません。DISTRHO/JUCEとJUCE7のLV2サポートで生成されるプラグインメタデータに相違があれば、後方互換性が損なわれる可能性があります。実際に内容の異なるメタデータが生成されるものなのか(= DISTRHOが後述するPatchの方式もサポートしていたりしないか)は筆者は把握していませんが、JUCE7にDISTRHOとの後方互換性を維持する動機は全く無いので、期待値はさほど高くありません。

一方でホスティングのほうは、JUCEでは(フレームワークとしての制約が無い限り)原則としてあらゆるLV2プラグインをロードできる必要があり(VST3やAUと同じことがいえます)、またホスト側には後方互換性を維持しなければならない理由は特に無いので、lvtk/jlv2はその役割を終えたといえますし、実際プロジェクトもarchivedとなっています。

LV2プラグインをビルドする

既存のオーディオプラグインプロジェクトにLV2ビルドを追加するには、CMakeのjuce_add_plugin()FORMATSオプションにLV2を加えるだけです。one liner changeです追記: もうひとつLV2URI "..."というプロパティを追加する必要があるのでtwo liner changeでした。とはいっても、JUCE 7.0以降を使う必要があるので、APIの破壊的変更には追従しないといけないでしょう。

もし今でもProjucerを使っているプロジェクトであっても、LV2サポートは追加されています。他のプラグインフォーマットと同様、プロジェクトのconfigオプションで指定できます。

LV2 export option on Projucer ホスト側のプロジェクトも、他のプラグインフォーマットと同様、juce_audio_processorsのオプションで指定できます。

LV2 hosting option on Projucer 試しに筆者がjpcima/HeraをLV2対応にしたときのパッチを置いておきます(CLAP対応も数行混ざっているのでクリーンなパッチではないです): https://gist.github.com/atsushieno/36b8963db9ba93b6e00a27008ae476ee

エクスポートされたLV2プラグインの見どころ

JUCE 7.0のLV2プラグインは、筆者の私見でいえば、2022年時点でのJUCEコードベースから構築できるLV2プラグインのベストプラクティスが実現しているといえます。その特徴をいくつか挙げます。LV2の基礎的な理解が必要な話なので、興味がない人は読み飛ばしましょう。

(1) LV2実装を実現するために、LV2のSDKとしてserd, sord, lilvがそのまま使われています。仕様上、LV2プラグインAPIを実装するために必要なのはlv2/lv2に含まれるLV2ヘッダーファイルのみですが、LV2仕様で特にserd/sordなしでTurtle Syntaxのパーサーを自前実装するのはやや無謀ですし、lilvを使わずにプラグインローダーを実装するメリットもほぼ無いでしょう。

(2) プラグインパラメーターそれぞれにPortを割り当てる伝統的な方法ではなく、パラメーターごとにLV2 Parameterを定義して、パラメーターの更新はAtom Sequence入力ポートにLV2 Patchを送ることで実現し、パラメーターの変更通知もAtom Sequence出力ポートにLV2 Patchを送るように作られています。Atom Sequenceを使うと、sample accurateなパラメーター変更/通知を実現でき、またMIDI入力と順序を維持した指示をプラグインに送信できます。CLAPのイベント入力ポートも同様の仕組みに則っているといえます(参考)。

先のHeraのLV2ビルドから生成されたdsp.ttlには、以下のようなパラメーター定義が含まれます(抜粋):

plug:VCADepth
    a lv2:Parameter ;
    rdfs:label "VCA depth" ;
    rdfs:range atom:Float ;
    lv2:default 0.5 ;
    lv2:minimum 0 ;
    lv2:maximum 1 .

plug:VCAType
    a lv2:Parameter ;
    rdfs:label "VCA type" ;
    rdfs:range atom:Float ;
...

これが後にpatch:writableおよびpatch:readableとして定義されます(数百行にもなります):

 patch:writable
        plug:VCADepth ,
        plug:VCAType ,
        ...

そしてpatch:messageを処理できるAtom Input portが後から定義されます:

     a lv2:InputPort , atom:AtomPort ;
        rsz:minimumSize 10064 ;
        atom:bufferType atom:Sequence ;
        atom:supports
            midi:MidiEvent ,
            patch:Message ,
            time:Position ;
        lv2:designation lv2:control ;
        lv2:index 2 ;
        lv2:symbol "in" ;
        lv2:name "In" ;

(3) レイテンシーを通知するControl Output Port、有効・無効を制御するControl Input Port、Free Wheelingを入力できるControl Input Portが別途作成され、一般的な共通コントローラーとして利用できます。

ベストプラクティスを実現できているのか疑問が生じる技術的選択として、LV2UIの実体はDSPと同一の共有ライブラリのバイナリになっています。LV2では「DSPGUIは分離しているべき」とされるので、ベストプラクティスに反する実装といえますが、そもそもGUIが分離していないjuce::AudioProcessorを使ってプラグインを作っている時点でコードの分離は実現しようがないので、LV2エクスポートの実装には期待できません。LV2ポートのみを経由したGUIDSPのインタラクションは実現しているので、LV2仕様が想定するバッドプラクティスのパターンには陥っていないとはいえるでしょう。(この辺りの問題は以前に少しCLAPのGUI拡張に関連して書いたことがあります。)

実例

Pianoteqは7まで従来型のLV2サポートを提供していましたが、Pianoteq 8でJUCE 7.0の標準的なLV2サポートに切り替えたと考えられます。Pianoteq 7までのパラメーターはLV2 ControlPortによるものでした。Pianoteq 8はLV2 Patchの方式に変更されています。両者はプラグインとして別々のインスタンスなので(~/.lv2/Pianoteq 7 vs. ~/.lv2/Pianoteq 8)、互換性を維持する必要がない事例ですが、Pianoteq 7のなまのLV2打ち込みデータをPianoteq 8に流用することはできなくなっているはずなので注意したほうがよいでしょう。とはいっても、大抵のDAW = ホスト側ではどちらも「パラメーター」として扱うでしょうから、ここに違いが生じることはない気もします。

ホスト側は対応できているのか

JUCEでLV2プラグインがビルドできるようになったとして、そのプラグインが正しくホストでロードできるかどうかは別の問題です。本当は別の問題であるべきではないところですが、LV2は仕様上の制約が小さい上にプラグインの数がVSTなどと比べると多くないため、ホストにとって想定外のメタデータをもつプラグインが出現する可能性が高い仕様です。

というわけでホスト側をいくつか実験したいところですが、その前にまずJUCE7のLV2プラグインを用意する必要があります。そんなわけで、上記Pianoteq 8のほか、いくつかLV2ビルドを作ってみました。

  • Monique - CMakeLists.txtjuce_add_plugin()呼び出しの部分でFORMATSLV2を足して、その下の行にでもLV2URI "(適当なURL)"を追加するとビルドできます
  • Dexed - Moniqueと同様の修正を加えるとビルド自体はできます(ただしCMakeLists.txtSource以下のもの)。ただ、少なくともJUCE 7.0.2以前では生成されるdsp.ttl「パラメーター名に含まれる.を正しくエスケープしない」問題があって、パラメーター名から.を消して回る必要があります。それが出来たら、ロードできるようになるでしょう。

というわけでプラグインが揃ったので、いよいよLV2をサポートしているホストをいくつか試してみました。Reaperはv6.71、QTractorとZrythmは11月末頃のmasterブランチ(commit hashを特定するほどでもないでしょう)、AudioPluginHostはJUCE 7.0.2に含まれているものです。

DAW Pianoteq 8 Monique Dexed
Zrythm *1 NG*1 *2
QTractor *3 NG*4 *4
Reaper OK OK OK
AudioPluginHost OK OK OK
  • *1 音は出るけど打ち込みを演奏できず https://todo.sr.ht/~alextee/zrythm-bug/1013
  • *2 ほぼ問題なく動作する。プラグインUIのウィンドウサイズが意思疎通できていないっぽい
  • *3 プラグインUIのダイアログ表示状態管理が甘いのか、頻繁にクラッシュする。クラッシュしなければ音は鳴るし演奏もできる
  • *4 音は出るけど(打ち込みプレイバックも可能)、再生速度が2倍くらいになる?

どうも実際に正常に利用できる環境とプラグインの組み合わせはまだ限られるようです。VST3も安定しない時期はこうだったんじゃないかという印象があります。

JUCE7によるLV2サポートの展望

これまでもDISTRHO/JUCEなどでLV2プラグインのビルドは可能だったといえますが、JUCE7でLV2が公式にサポートされたことによって、さらにLV2サポートの可能性が広がることが期待されます。特に期待できるのはTracktion WaveformなどJUCEを利用して構築されたDAWでLV2プラグインがついに使えるようになると見込まれるところです。Helio Workstationや、何ならZenBeatsでも使えるようになるかもしれません(Rolandのやる気次第でしょうか)。

プラグインのバイナリパッケージ配布も、前述したとおり従来はLinuxのみのものが大半でしたが、JUCEからカジュアルにビルドして配布できるのであれば、今後はWindows/Mac用LV2プラグインが公開され、プラットフォームを跨いで利用できるポータビリティの高いプラグインが増えてくるかもしれません。今後のJUCE7/LV2採用事例がたのしみですね。

VitalにCC0ライセンスのWavetableを大量に取り込む

DTMテクニック集 Advent Calendar 2022、初日が空いていたのでトップバッターをつとめることにしました。

1月にVitalのウェーブテーブルとして使えるフリー素材を大量にVital用ファイルとして取り込んだリポジトリを作ったので、その話を書きます。CC0ライセンスで誰でも使えます(大量に自作したのではなく、大量のCC0リソースを取り込んだものです)。※ウェーブテーブルのみであって、音色プリセットはありません。

github.com

※今後CC0以外のリソースを取り込んだらライセンスが変わる可能性もあります

以降は「何でこれが必要になったのか」「オープンソース互換で公開できるウェーブテーブルを生成するにはどうすればいいか」を説明します。ユーザーとして使うだけなら、すでにこのリポジトリにあるものをVitalのユーザーディレクトリ(~/.local/share/vital/Userなど)にコピーすればいいだけです(ディレクトリ構成は注意が必要です。README.mdを見てください)。

ちなみにVitalってそもそもどんな音源?という人は、この辺の日本語記事から読むと良いかもしれません。

dtmer.info

VitalのOSS版で使えるプリセットがほしい

ちょっとだけ自分のやっていることを紹介すると、オーディオプラグインフレームワークAndroidで作ろうとしていて(何しろAndroidにだけ存在しないので!)、その関係でオープンソースプラグインをいくつかAndroidに移植して遊んでいます。

去年の今頃、VitalもAndroidでビルドして動かして遊んでいたのですが、すぐに「OSSのVital(GitHubで入手出来るVitalのソースコード)には音色のプリセット(*.vitalファイル)が何も入ってない」ということに気づきました。Vitalのプロプラエタリ版はそこで商売になっているわけですね。無償版(OSS版とは違うことに注意)はその入口というわけです。コードはGPLv3、音色データは商売道具、という切り分けは、フリーソフトウェアと親和的でよくできたビジネスモデルだなと思います。

しかしAndroid版を自作して(GPLv3に基づいて)公開するのであれば、少なくとも配布データとしてプロプタエタリ音色を含めることはできないので、少なくともアプリケーションとして配布するならそれなりの音色プリセットを自作するしかありません。無償版Vitalユーザーがその音色をAndroid版に持ってきて使うのは別に問題ないのですが、わたしが配布するわけにはいかないのです。

これは大変な作業になりそうだ…でも、そういえばVitalはフリーソフトウェア界隈ではVitaliumという(ストアアクセスなどのプロプタエタリな要素を含まない)独自forkがあったはずだけど彼らはどうしているのだろう…その音色データを取り込めば解決では…?と考えましたが、結論からいえば、彼らも特に大した音色プリセットをもっているわけではない、ということでした

さらなる根本的な問題として、Vitalはウェーブテーブルシンセサイザーなので音色プリセットを作るためにはウェーブテーブル音色定義が必要になるわけですが(それをもとにさまざまなパラメーターで音色を作ることになるわけです)、VitalのOSS版にはウェーブテーブルが含まれていません(プロプタエタリなので)。ここから用意する必要があります。

Serum用のウェーブテーブルはフリー素材がたくさんある

ところで、ウェーブテーブル音源は(そういうシンセのジャンルがあることからもわかるように)Vital以外にも数多く存在しますし、その歴史も長いです。Vitalが出現する以前の代表的な音源は概ね異論なくSerumだったと言っていいでしょう。VitalはSerumの機能を数多く取り込んで実現しており、ウェーブテーブルも2048フレームまでのデータを処理できる設計になっています。

ウェーブテーブルは*.wavファイルとして配布されます。サンプリングデータ以外にもデータがいくつかあるのですが、それらはWAVファイルフォーマットの一部であるRIFFヘッダチャンクに含まれます。Serumの情報はclmというヘッダが使われるらしい、ということがあるKVR Forumのスレッドから読み取れます(Xfer Recordsの開発者が自ら書いている情報もあります)。そして、VitalではSerum用のウェーブテーブルを取り込むことができます。このclmヘッダの情報も読み取ります。clmヘッダの情報を取り込めるのはVitalだけでなく、Surgeなどでも可能なので、このジャンルでは概ね共通フォーマットと考えてもよさそうです。

ちなみにVitalのウェーブテーブルは*.vitaltableというJSONファイルになります。サンプリングデータは「JUCEのBASE64文字列」として保存されます。「」が付いているのは、これは標準的なBASE64と互換性が無いためです。

さて、Serum用のウェーブテーブルデータは、実は無料しかもオープンライセンスで大量に手に入ります。 waveeditonline.comにはいろんな人が作ってCC0ライセンスで登録したウェーブテーブルが大量にあります。

WaveEdit Online もうひとつ、kimuratato.comからも大量のウェーブテーブルを ダウンロードできます。日本語でGNU Octaveを使ったウェーブテーブルの作成方法なども公開されています。

vitaltableファイルへの自動変換

WAVファイルとclmヘッダが公知情報なら、その情報をもとにこれらの大量のWAVファイルを自動的に変換するツールを作るのは難しくないはずです…が、実際にはclmの情報をどう取り込むかはVitalの実装次第かもしれないし、前述のJUCE BASE64のような罠が他にもあるかもしれないことを考えると、自前で変換ツールを作るよりも、Vitalのコードベースを使って*.vitaltableに変換したほうが、実装としては安牌です。

そういうわけで、コンバーターは「Vitalのソースに手を加えて一括変換用のコマンドを追加する」というかたちで実装しました。つまり、Vitalのforkとなっています。

github.com

ソースの変更内容はこれだけです: https://github.com/mtytel/vital/compare/main...atsushieno:vital:batch-import-wavetables

プラグインでもよいのですが、make standalone等で実行ファイル版をビルドして起動するのが一番簡単でしょう。ビルドされるのはVialという実行ファイルであることに注意してください(README.mdを読むとわかりますが、Vitalという名称はMatt Tytelのものなので、OSSでは混乱を防ぐためにVialという名前になっています)。ビルドして起動できたら、Wavetableエディタの画面に移動して、メニューを開くと、"Batch Import Wavetables"というVital本家にはないコマンドが出現します。

Batch Import Wavetable

これを実行すると、ディレクトリ選択ダイアログが出現するので、変換元wavファイルを含むディレクトリを指定すると、その下にある*.wavから*.vitaltableが生成されます。

冒頭の繰り返しになりますが、すでにatsushieno/open-vital-resourcesリポジトリで公開されているウェーブテーブルを使用するだけであれば、ここまでやる必要はありません。リポジトリの内容をダウンロードして、Vitalのデータディレクトリ(~/.local/share/vitalなど)にしかるべきディレクトリ構成でコピーするだけです。

あと、もちろんホントにほしいのはWavetableじゃなくて音色プリセットなんですが、これはさすがに一朝一夕では大量には作れないので、どこかから湧いてこないかな〜って思っています(他力本願)

オープンリソースの可能性

最後に与太話を書きたいのですが、わたしがAndroid版をビルドしているのは「どこで音楽を打ち込んでもどこでも再生できる」ような世界を作りたいと思っていることがまあまああります。VitalだけだとOne synth challengeか??みたいな状態になってしまいますが、他にもOSSプラグインはたくさんあるので、さまざまなプラットフォーム上で使えるようになるといい世界になると思いませんか? ふとんにくるまってAndroid端末で音作りして、それをPCに持っていけたり、同じデバイス上でDAWから呼び出せたりしたいですよね。(まあそうは言ってもVitalのUIは現状モバイルでは到底使い物にならないわけですが…!)

ちなみにVitalの公式Discordでは「iOS版とAndroid版を作っている」という情報も見かけたので、別にわたしがやらなくてもそのうち出てくるかもしれません(書いていた人はコア開発者ではなく真偽は不明です)。いずれにしろAndroidにはオーディオプラグインの仕組みが存在しないので、あくまでstandaloneで使えるものということになるでしょう。

FLOSSで音楽制作ツールを作っていると、サンプルやテスト用に楽曲がほしくなるのですが、その音源データがプロプラエタリだと、リポジトリに取り込むにも抵抗があります(不可能だ、と言ってもいいのかもしれません)。そういう場合にはオープンリソースで作成した楽曲データが有用なのです。たとえばわたしは自作MMLコンパイラのサンプル楽曲で著作権の切れたオーケストラ楽曲の打ち込みデータを試験的に公開しているのですが、submoduleにはsfzのフリー音源のみが使用されていて、GitHub ActionsでMP3レンダリングまで実現しています。つまり自分のPC以外でも再現できたということです。

オープン音源リソースのエコシステムが拡大すると、こういうことをやりやすくなるので、この世界がもっと広まってくれるといいなと思っています。

11月の開発記録(2022)

毎年「えっ、11月って31日まで無かったっけ?」って思ってしまう人です(時候の挨拶)

勉強会: Zrythmから読み解くDAWの作り方(12/11)

music-tech.connpass.com

10月に「あと11月のうちに1回オンライン勉強会をやっておきたいですね。」などと書いていたのですが、その頃考えていたのがこのZrythm勉強会です。Zrythmは新進気鋭のDAWで開発に勢いがあってプロジェクトが成長するのを見ているのも面白いし、開発者のalexteeせんせいは日本語もいけそうなので「もしかして日本語勉強会が出来るのでは…?」と思いついたのは天才的だったと思います(自画自賛) 「ちゃんと勉強会として提案できる内容になるかな…」といろいろ下調べをしていたら時間がかかってしまって、12月になっちゃいましたが、参加を快諾していただいたalexと、開催コミュニティのおふたりの協力をいただいて開催の目処が立てられました。

Zrythmはプラグインのサポート作業をCarlaにオフロードしているので、他の部分に注力できている一方で、GTKアプリケーションという(この分野では)割と特殊な全体構造なので、そこにあまり踏み込まずに話せるだろうか…とか、今もスライドを作りながらいろいろ落とし所を探っているところです。Zrythmに終始する勉強会にはせず、使い方の話で終わらせもせず、Zrythmへのcontributionにも道を拓くようにする…くらいの立ち位置を目指します。

ちなみに勉強会自体はMastodonTwitterFacebookでお知らせしていたら1日ちょっとで30人枠全部埋まってしまいました(今までにない流れ…!)。すでにwaitlistが出来ていて、たぶん何かしらの救済策を立てますが、これからのフル参加は難しいと思います(すみません)。

ADC2022(オンライン参加)

audio.dev

先月分を書いたときはまだ現地参加するかも…みたいなノリでしたが、その直後にオフライン参加が締め切られてしまったので、今年もオフライン参加になりました。まあ正直ハイブリッド開催におけるオフラインオンライン参加のメリットは小さいですね。gather.townに来るのは多くても100人くらいで、スピーカーなど主な参加者はオフラインにいる間はオンラインには絶対来ません(自分もよほど物足りなかったりしない限り見に行かないと思います)。セッション中にその場で質問するとか、あとはgather.townでスポンサーブースにいる人たちと会話するのが主な「参加」の楽しみ方となるでしょう。

もちろんセッションコンテンツを見られること自体も意味があると考えることはできますが、正直有料で参加していても「さっさと動画を公開してくれたほうがいい」と思います。ADC2021の動画はなかなか公開されず(youtubeにずっとunlistedで上がっていただけ)、結果的に参加したわれわれが他の人と動画鑑賞会も開催できないうちに興味/関心が薄れてセッション録画の価値が下がっただけだなー、というのがわたしの所感です。

それはさておき、今回の話題はこの辺だったかな、というのをいくつか挙げるとしたらこんな感じです:

個人的にはMIDI 2.0のセッションを聞いてアップデート情報を見て「MIDI-CIのprotocol negotiation無くなるの? マジ?」とかいいつつ、MIDI協会のブースに行って中の人としゃべったりして、ADC後にミーティングを設定してもらったりして、謎のコネクション(?)が増えました。聞いたところによると、MIDI 2.0の話とか書いたりTwitter(いつまであるかわからんけど)に書いたりしていると漏れなくヲチされるらしい…(適当) 「このブログを読んだんだけど…」と言われてここが英訳された画面が出てきたりとかして、心臓に悪かったです(これも読まれる可能性がある)。日本語もそこそこ通じたので今度はMIDI 2.0勉強会とかやったら来てくれないかな…(無理筋)

書き物

今月は「Android NDKでAPIレベルによる条件分岐を実現する」という割と長めのネタを書きました。

本文中でも触れていますが、「NdkBinderがminSdk 29で使えなくなっているんだけど…」っていうissueを春先に立てていて、(変更が複数コンポーネント = 複数チームに渡るので割と重く)ようやく解決したっぽいので、その過程で得られた知見をまとめておこうと思ったのでした。

あと、公開されるのは12月ですが、明日12/1はDTMテクニック集 Advent Calendar 2022atsushieno/open-vital-resourcesの話を、明後日12/2はJUCE Advent Calendar 2022でJUCE 7.0のLV2サポートの話を出すので、今月はそれらを書いていました。これで来月書くものは少なくなったはず…!

AAP: update plugins to work as AudioPluginService.V2 plugins

今月は結局ADCをリモート参加にしたのでその分自分の時間が取れたはずで、開発もそれなりに進んだはずなのです…が、成果を見るとそうでもないような…。AAPのパラメーターサポートの実装をやっていました。

先月はプラグインパラメーターの出入り口を(オーディオ・MIDIと同じ)Portから(独自の)Parameterに変更する調整作業を今月に持ち越していました。それはaap-lv2、aap-juceともに何とか片付いたのですが、UMPからMIDI1へのtranslatorを実装していて、「UMPのAssignable Controllerをパラメーター変更に使っているとプラグイン側でNRPNを受け取れないな?」ということに気付きました。もう少し具体的に説明すると、UMPのAssignable Controller (AC)はMIDI 1.0でいえばNRPNに相当するもので、UMP単体で見たらACはMIDI 1.0には存在しないメッセージなので既存のMIDI 1.0に基づくプラグインMIDI入力とかぶることはないのですが、MIDI 1.0とMIDI 2.0の間で変換処理を行うと、NRPN-DTEの組み合わせはMIDI 2.0ではACに変換されることになるので、やっぱりこのメッセージもMIDI 1.0用プラグイン潜在的に処理するメッセージと競合することになるわけです。

ACを使ったパラメーターの変更という設計は賢いと思っていたので、捨てるのがもったいなくてしばらく悩みましたが、これを残していたらVST3のCCと同様に使えない仕様になってしまうと考えて、やはり変えることにしました。新しいメッセージフォーマットではSysEx8を使います。SysEx8であれば今度こそ問題ないはず…と思いますが、manufacturer IDやsub IDは割と雑に00だったりするので、もしかしたらマイナーチェンジが必要になるかもしれません。ともあれ、この変更のためにcmidi2から微妙に手を入れ直して、aap-lv2もaap-juceも全体的にプロトコルを修正する作業でいっぱいいっぱいになりました。

ちなみにパラメーター変更にSysEx8を使うアプローチは実は初めてではなくて、MMLからUMP経由でTracktion Engineのパラメーター変更を実現する際にも利用していました。この頃は「音源別パラメーターってSC-88Proのインサーションエフェクトみたいなもんだからシステムエクスクルーシブでしょ」って雑に考えてやっていました。ただし、今回のフォーマットはもう少し情報を付加することになりました。MIDI 2.0のAssignable Controllerはノート別(Per-Note)パラメーターもサポートしていますし、プラグインとしてもCLAPのようなフォーマットではパラメーター変更メッセージにノートナンバーが含まれます。また、そもそもNRPNはchannel voice messageであり、チャンネル情報がa prioriに付いてきますが、SysExにはこれが無いんですね。そういうわけで、チャンネル情報とノートナンバー、あと今後ノートがkeyではなくnote IDの方式で渡される仕組みが一般化するような事態を考慮してextraフィールドを作ったので、16ビットのSysEx8単体パケットはほぼ使い切ってしまいました(いざとなればもう1パケット増やして32ビットにしてもいいのですが、パラメーターの変更ひとつに32ビットか…という気持ちは正直あります)。まあ、そんな感じでこの辺の設計にもいろいろ試行錯誤があります。

LV2のlv2:Patchサポートを除いて実装上はひと通り対応したはずなんですが、まだバグフィックスが必要になったりaap-juceを実装しているうちにaap-lv2がリグレッションを起こしたりしていて(実装してみるとわかりますが、client/serverが別アプリで非常にテストしづらいし、リポジトリを合体させるわけにもいかないのでビルドも統合できなくて、だいぶ悩ましいやつです)、めでたく12月に持ち越しです。

lv2:Patchサポートは、まだこれを必要とするLV2プラグインが無いので優先度が低いというのが正直なところですが、やらないわけにはいかないやつです。実のところJUCE 7.0のLV2ビルドではlv2:Patchが使われることになるので(この辺はJUCE Advent Calendarの記事に詳しく書きます)、それらを「LV2プラグインとして」AAPに取り込むなら必須になるのですが、JUCEプラグインならストレートにaap-juceを使えばいいじゃん…となります(!)

あとは、プラグイン単体だと機能するけどMidiDeviceServiceとして使おうとすると音が出ない、みたいな問題も大きく、今月はまだ安定的に使えるバージョンをtag打ちできないなあ、ってなっています(3ヶ月くらいできていない)。今年中に一区切りつけたいですね。

Mastodon

最近は、日常的な技術的発見の多くはmastodonで書いています(英語は @atsushieno@g0v.social 、日本語は @atsushieno@mastodon.cloud )。Twitterは概ね反応器としてしか使ってない感じです(まあイベントの宣伝とかには使うと思いますが)。可能なら正常な治安が回復してほしいですね。Twitterはほとんどリアル知り合いばかりをフォローして狭い世界を構築していたのですが、今どきそんな古臭いことをしなくてもいいだろうということでMastodonはもっとカジュアルに使っています。

10月の開発記録(2022)

M3 2022秋の出展

10/30のM3 2022秋 (50回目だったらしい)に「オーディオプラグイン研究所」として出展参加しました。技術書典13で発行した「CLAPオーディオプラグイン開発ガイド」に加筆修正を加えた「正式版」の印刷版・電子版と、「Linux DTMガイドブック」の電子版が新刊ということになります。前者はboothで取扱開始しました。技術書典のときに「紙版がほしかった」という方はこちらからどうぞ。本当は無償配布にしたいところですが、印刷代だけでも「売れば売るほど赤字になる」設定なので、ひとつよしなに…

xamaritans.booth.pm

Linux DTMガイドブックも同様に「完成版」を発行したかったのですが(たとえば、サンプラーなど「器」だけ言及していて楽器サンプルへの言及がゼロなのを何とかしたい)、時間的に厳しくあきらめました。M3では新刊の頒布に合わせてAndroidプラグインの展示も行う計画で開発を進めていて、そこまで手が回らなかったためです。(Androidプラグインの展示に合わせて最新版を安定させる計画は間に合わず、少し古いバージョンにしたのですが…!)

このLinux DTM本と既刊の「MIDI 2.0エコシステム構築術」「MML to MIDI 2.0 to DAW」も含め、展示では幻の「印刷版」を見本として用意しましたが、これらの印刷版はありません。デジタル版でお求めください。(この2冊は割と「自由研究をまとめてみた」性格の本で、現状ではそれなりに時代とともに価値が薄れていく過渡的な内容だと思っています。)

DroidKaigi2022(遊びに行っただけ)

10月上旬にはDroidKaigi2022もありましたね。今回はトーク応募すらしなかったので、単純に東京ドームシティまで遊びに行きました。開催地が遠くなってしまったこともあって、宗教上の理由で午後からの参加のみでしたが、いつものDroidKaigiのふいんきがだいぶ戻ってきていて(ランチとかコーヒーとか夜のパーティが戻ってくるのはまだ無理そう)、開催してくれてありがたい〜という感じでした。年単位で会っていなかった知人のみなさんとも久々に顔合わせできたし、新しく開発者のみなさんとも知り合いになれました。

Gradle Managed Virtual Devicesで変化するエミュレータ活用術」の外山さんに「GitHub ActionsのLinuxホストで使えるようになってほしい(KVMのnested virtualizationをサポートしてほしい)んだよ〜」とか詮無きことを言ったり、「Jetpack Composeを用いて、Canvasを直接触るようなコンポーネントを作成する方法」を見て自分がComposeで作っていたWaveform描画コードを直したり、「Android "を" ビルドしてAndroid Systemを覗いてみよう]」みたいなそこそこ低レイヤーのセッションが聞けるのうれしい〜とか言いながらAOSPアプリのビルドに手を加えるのを眺めたり、「Considerate App Update Delivery」で相変わらず多種多様な他人のアプリをビルドしないといけない仕事やべえ知見がたまりそう〜とか思いながら眺めたり、「人の声を可視化する」でオーディオAPIの話したい〜と思ってask the speakersでお話ししてみたらここの読者の方だと判明したり(そんなことあるんだ…!ってなりました)、「Android アプリの内と外をつなぐ UI」を見ながら自分のアプリのonBackPressed()onBackPressedDispatcherで書き換えたり、あと他の人と雑談していて気がついたらセッションを見逃したり()みたいないつもの体験を楽しみました。

全部かはわからないけどYouTubeセッション動画も公開されているようなので、裏番組とかで見られなかったやつを少しずつ追っかけようと思います(昨日まではさすがにそんな余裕なかった)。

ktmidi 0.4.0

今月は先月から続いていたktmidiの実装を直す作業がひと段落して、新しくバージョン0.4.0を公開しました。 Kotlin Native環境用にRtMidiのcinteropバインディングを作って、RtMidiNativeAccessという実装を追加して(JNAを使ったKotlin/JVMバインディングもあることを考えるとだいぶ二度手間感…)、Midi2Playerをネイティブでも意味あるものにしたという変更が大きいですが、UMPベースの楽曲データのフォーマットやAPIに破壊的変更を加えたからバージョンを0.1上げたというのが建前上の理由です。

楽曲データの問題はlong-standing issueで、元々トラックヘッダーにUMPの件数を指定していたのですが、これでは読み飛ばしができないのでバイト数に変更したものです。楽曲のMETAイベント…に対応するSysex8データ…も間違っていて、テンポ指定が適切に反映できなかった問題があり、今回新たに追加したUMP to MIDI1 translatorの実装と合わせて、ようやくMIDI 1.0と同等のMidi2Playerの動作がreal-worldなやつとして確認できました。mugene-ngもこれを反映しているので、MMLからMIDI 2.0楽曲を引き続き生成できます。

この過程でAPIドキュメントジェネレーターのdokkaがビルドエラーを引き起こす問題にしばらく悩まされて、結局Kotlin Slackで開発者にいろいろ助けてもらって何とかしました。自分の中でdokkaの評価がだいぶうなぎのぼりに上がりました(!?)

AAP: プロトコル変更"V2"の再設計

8月にオーディオプラグインのパラメーター変更にMIDI 2.0 UMPを使ってプロトコルを再設計する話を書いたのですが、その手助けとなるはずだった上記のMidi2Playerをプラグイン試用のアプリに組み込もうとしたものの、ネイティブコード側の実装には使えず、デバッグに使うにも微妙なので、そっちの路線は一旦留め置いて、まずmidi2プロトコルで従来どおりの固定のメッセージを処理できるPoCを(今度はUMPで)作って、それをaap-lv2やaap-juceに適用していこうと考えました。

それで、最初は従来の「midi1ポートもmidi2ポートもサポートする」方向で実装を練っていたのですが、早晩「これは開発体験が悪い」と気づきました。これまではむしろ「MIDI2ポートはいらない。UMPをサポートしたければ、MIDI1ポートでMIDI-CIのSet New Protocolを送って切り替えればよい」と考えていたのですが、プラグイン開発者が「どっちで来るかわからないから両方サポートする」ことになるのは理不尽ですし、「MIDI2を処理できるプラグインは現状ほぼ無い」にしても、「パラメーター変更はMIDI2でしか受け付けられない」、「パラメーター変更をサポートしないプラグインはほぼ無い」、「MIDI2をMIDI1に(可能な範囲で)変換するのは難しくない」…という状況を鑑みて、むしろ「MIDI2ポートのみ存在すれば良い」という考えに至りました(ここまでで何日かかかった)。

これは相互運用性の観点ではドラスティックな変更になる(完全に別物になる)ので、AAPのプロトコルのバージョンを上げて対応すべきところですが、現行バージョン0.7.4までで "V2" として作り直しが進んでいるので、そのままV2プロトコルがこの設計を反映したものになるでしょう。

一方でAndroidAudioPluginMidiDeviceServiceがUMPを前提にするわけにはいかないので、MidiDeviceServiceからの入力を8月に実装したUMP Translatorを使ってUMPに変換し、逆にプラグイン実装側ではUMPからMIDI1にダウンコンバートするTranslatorの真面目な実装を新たに作って使ったり、プラグインプレビューのアプリではパラメーター変更入力をポートへの直接入力から新しいUMPのAssignable Controllersメッセージに変更したり(これでようやくCLAPやAtom Sequenceのような新しいイベント処理のスキームに切り替えられたといえます)、同じダウンコンバーターをaap-lv2の実装にも適用したり…といったことをやっていました。実のところまだ完了しておらず、LV2 portとAAP portのマッピングどうしよう…みたいなところで現在でも泥沼にハマっています。先にaap-juceで対応したほうが早かったかも…

11月の予定

11月は正直まだどうするか決めていません。「どうする」というのは具体的には例年11月にロンドンでやっているアレです…多分オンライン参加で妥協すると思います。去年より自由になったとはいえ、まだ一生後遺症が残り得るLong-Covidの問題が続いていることに変わりはなく、現地で自己アッピール()に使えるようなソフトウェアはまだ完成していない状態なので、今行くと「オーディオクラスタにしては妙にAndroid開発に詳しいやつ」というビミョい位置付けで固まってしまう気がします(正しいのですが)。まあ何か理由があれば現地参加する可能性もあります。

あと11月のうちに1回オンライン勉強会をやっておきたいですね。ネタはあるといえばあるのですが、状況次第です。何か準備できたら改めてTwitter(いつまで使うかわからないけど)などで告知します。