【サルベージ】 オーディオプラグインフレームワークを設計する

昔からいろいろな思いつきを書き溜めたまま放置してしまって、そのままお蔵入りになることが多いのだけど、これは一度考え方のたたき台的に出しておいたほうが良いかと思ったので公開することにした。書いたのは最後の2段落以外は2020年11月なのだけど、最近CLAPも話題に出てきたのでその流れでも読めるかもしれない。

以下本文。


これは自分が現在開発しているAAP (android-audio-plugin-framework) の設計方針を見直すために書いている。

オーディオプラグインフレームワークの乱立問題

オーディオプラグイン規格をきちんと作り出すのはたぶん簡単ではない。

  • 「仕様が乱立してどれを使うのが最適とも言えないから最強の仕様をひとつ作ろう」の罠にハマる。作り出された新しい規格には誰も乗ってくれないので結局無駄な作業になってしまう。
    • とはいえ、AAPのように「そもそもインプロセスでライブラリをロードできる前提で設計されたオーディオプラグイン規格はどれも使えない」という状況では、新しいものをゼロから設計するしかない。
  • 後方互換性を維持するのが重要だが、オーディオプラグイン規格のトレンドの動きは非常に緩やかで、一般的な開発のトレンドとの乖離が著しい。今の技術的課題のひとつはVST2からVST3への移行だが、VST3がすでに10年以上前の仕様だ(あくまで3.0が10年前なのであり、現在の3系列の最新版は3.7である)。
  • フレームワークの採用のトレンドが緩やかであるにもかかわらず、技術の進歩には追いつかないといけない。10年前に重要なトピックとして存在しなかった技術の例を挙げるなら、MIDI 2.0, BLE MIDI, モバイルプラットフォーム、3Dオーディオなどがある。

オーディオプラグインの実行環境とSDKの分離思考

従来は、オーディオプラグインフレームワークとはランタイムとSDKの両方を曖昧に含む関係だったが、JUCEやWDL/iPlug2、DPFなど、オーディオプラグインフレームワークそのものではなく、オーディオプラグインを開発できるSDKがポピュラーになっている。Carlaのように複数のプラグインフレームワークやファイルフォーマット(sf2/sfzなど)に対応する機構もある(が、まだポピュラーとまではいけない)。新しいプラグインのランタイムも、これらにプラグインバックエンドやホストバックエンドを追加すれば対応できるので、フレームワークが乱立することそのものについての弊害はある程度縮小している。

これを前提として考えると、新しいオーディオプラグインフレームワークを構築するのは、必ずしもそこまで無価値ではない。

初期段階ではそのフレームワークAPIが安定していることよりも、それらの開発フレームワークのサポートが重要であると考えられる。一般的に、長期的に開発されていてポピュラーなSDKAPIが安定している。

安定的なAPIと安定的なABI

プラグイン開発者あるいはホスト開発者として懸念すべきは、「API」の安定性だ。APIとABIの維持に関する各ステークホルダーインセンティブをまとめておく。

  • ホスト開発者が実際に気にするのは(べきは)APIではなくABIのほうだ。プラグインを動的にロードしてそのABIが期待したものと異なっていたら使えない。ホストを開発する際に必要になるAPIは破壊的でないほうが望ましいが、絶対ではない。
  • プラグイン開発者は最新版のプラグインが古いホストでも使えれば十分、という程度にはABIが維持されていないと困る。プラグイン開発時のAPIは破壊的でないほうが望ましいが、絶対ではない。
  • 作曲家・DAWのユーザーは、古いプラグインも古いホストも、新しいプラグインや新しいホストと組み合わせて動作してもらわないと困る。ABIの維持が最も恩恵を受ける層はユーザーである。APIの破壊的変更に関心は無い。
  • 楽曲を演奏するアプリケーションのユーザーはほぼDAWのユーザーと同視できる。

バージョン1.0をリリースしてsemantic versioningを意識する段階になったら、プラグインAPI自体が破壊的変更を要求しないようにしなければならない。もっとも、破壊的変更のニーズは常に存在する。LV2設計者はrun()にオーディオバッファを渡す仕様にせずにconnect_port()でポインタ上の接続を確立してrun()に何も渡さない仕様にしたことを後悔しているが、このようなレベルで非互換問題が生じる(これを生じさせたい)可能性は常にある。VST3の仕様が策定された時に、プラグインがプロセス分離された空間でないとロードできないiOSAndroidのような環境が出現することは想像できなかっただろう。

(AAPではNdkBinderとAIDLの制約上、個別のashmemポインターをParcelFileDescriptorとして送受信せざるを得ないので、LV2で反省しているようなことが実現できるわけではないし、JUCEサポートにおいてはashmemポインターが動かないことを前提としている。)

現状AAPは破壊的変更の過ちを犯すルートにある(APIの追加が破壊的変更になっている)。VST-MAのCOMライクなクエリーインターフェースのほうが賢いこともある。LV2でこれをやろうと思ったら拡張を使うしか無いし、全てのホストに拡張に対応させるしかない。

一方でVST3のクエリーインターフェースはまだるっこしいのでVST3そのものが採用されない、みたいな側面はあった。このあたりはABIが課題になるような低レベルではなく、一段上の、APIの破壊的変更があり得るeasing API SDKを用意することで対応するのが適切かもしれない。

GUIとの連動・分離

WindowsではWin32 APIMacOSではCocoaでほぼ統一できるが(ホントかな…CarbonとCocoaを同一視してるレベルみたいな気もしてきた。まあとりあえずいいか)、LinuxデスクトップではGtk2/Gtk3/Qt4/Qt5で乱立していて、しかも相互運用性が無かった。現在はX11を使いそれ以上のフレームワークを使わないのがトレンドになっている。結果的に、オーディオプラグインGUIフレームワークはプラットフォーム標準やUI技術から乖離した、貧弱なゲームUIフレームワークに近いものになってしまっている。

これは伝統的なオーディオプラグインの立ち位置であり、モダンなUIに合わせてプラグイン機構を構築できる可能性も十分にある。そのためにはプラグイン機構とUI機構の十分な分離が必要になる。これを強制的に実現できているのはLV2のみだ。LV2の場合、オーディオプラグインコード(classなど)を直接参照することもできないため、ポート経由でUIの更新を反映するしかないため、この分離点をより強力に(AOPのように)制御できる。AUVST(あるいはそれを前提にしたJUCE)ではこれが自然にはできない。

とはいえ、LV2でもGUIX11の貧弱なUIフレームワークしか使えない事態に変わりはない。LinuxGUIで相互運用性がないというのは、具体的にはGUIのmain loopの設計の相違で複数のGUIフレームワークが両立し得ないという状態だ。X11を使っているのは最大公約数としての消極的な対応でしかない。真面目に解決するなら、プロセスレベルで分離した上で、UIをホストとの間で制御する仕組みを構築する必要がある。

プロセスを分離するとパフォーマンスに影響が出るが、そもそもUIスレッドとオーディオスレッドは分離していなければならないし、UIスレッドにリアルタイム要件は無い。UIからオーディオパラメーターを操作するときはアトミックであることが求められるし、オーディオのプロパティはUIプロセスからオーディオプロセスへのリクエスプロトコルによってクエリできればよい。