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