aria2web: SFZ ARIA GUI extensions on Web UI (Part I)

目次

4月頃に、自作オーディオプラグインフレームワークのUI構想についていろいろ検討していて、SFZサウンドフォントのARIAまわりを取り込んでsfizzというSFZサンプラープラグイン)に統合したいという話を書きました

これをaria2webというプロジェクトとして開発して公開しています。まだ実用品ではないのですが、いろいろ知見があったので今のうちに書き連ねておこうと思います。

github.com

aria2webは、ひとことでまとめると、ARIA GUIをHTML(+SVG+JS)化して、WebViewが使える任意の環境でオーディオプラグインGUIとして汎用的に使えるようにしよう、というものです。

動機: なぜSFZを使うのか

オーディオプラグインフレームワークを作る目的は、音楽制作・再生のためのエコシステムを構築することです。古典的なDTMでは「楽器」に相当するのはMIDI音源でしたが、MIDI (1.0)の表現力では自由に思ったとおりの音楽を表現できないことが多いので、現在ではオーディオプラグインを使うのが一般的です。

オーディオプラグインには(ちょうざっくり分けると)楽器とエフェクターがあって、自分のフレームワークでは、とりあえずproof-of-conceptレベルではVSTのサンプルとして使われてきたmdaがLV2に移植されたmda-lv2が、すでに動いています(何しろLV2が実質無修正で動くので)。また、JUCEのプラグインにちょっとモジュールを追加するだけで対応プラグインがビルドできるので、それで取り込めるプラグインもいくつかあります。(UIをホスト側で出すのは現状では無理だけど模索中です。)

楽器のほうもfluidsynthをオーディオプラグイン化したjuicysfpluginsfizzを使えばいけるはずです。juicysfpluginはJUCEソリューションなのですが、内部的にはgtkにも依存していてちょっと使えないし(ここ後で触れる予定)、SF2/SF3のFluidsynthより表現力の高いSFZをサポートしたほうがより本格的な楽器プラグインを実現できる(何しろFluidsynthはMIDI 1.0のレベルなので)と考えてSFZとsfizzのほうを主なターゲットにしています。

SF2/SF3もSFZも同じ「サウンドフォント」ではないのか、何が違うのか、と思われそうですが、SFZにはキースイッチ*1のサポートなどで、SF2/SF3より柔軟な条件式で対応するサンプルを選択できるようになっており、本格的に楽器を表現できるフォーマットとして現在も進化しています。(独自拡張から標準化に進む世界であるようです。)

オーディオプラグインの世界では「サンプラー」というジャンルがあって、サウンドフォントの再生エンジンはこのカテゴリーに属することになります。サンプラーとして有名な音源はKontaktとかVSLとかです。Cubaseに付いてくるHALionとか、わたしが使っているWaveform付属のCollectiveなんかもそうですね。SFZにもこれらの商用音源に匹敵する機能を実現できるポテンシャルがあります。

自分のオーディオプラグインフレームワークKontakt並の音源を実現できるとしたら、だいぶ「エンドユーザーが使いたくなるもの」なのではないかという気がしてくるのではないでしょうか。

ARIA GUIとは何か

SFZはSF2/SF3より詳細な条件式でサンプルを選択できるという話を書きましたが、条件式の記述に用いられるのはopcodeと呼ばれる命令識別子であり、要するにごく単純なプログラムを書いているようなものです(プログラムの著作物とまでは言えないレベル)。SFZのopcodeは特に技術標準とまで言える仕組みではなく、これを各サンプラーのベンダーが独自拡張して使っていることがあります。

ARIAというのは*2、Plogue社のsforzandoというサンプラー製品で使われている独自拡張ですが、これにはSFZのopcode拡張の他に、GUIを定義するXMLマークアップボキャブラリーも含まれています。SFZの仕様にはGUIの定義が含まれていないので、SFZのサンプラー音源をいじるときは数値と格闘するか、標準的なコントロールを操作するしかないのですが、どのパラメーターが重要なのかはユーザーには分かりません。GUIがあればこの問題が緩和されます。

ARIA GUIの内容はごく単純です。

<GUI w="775" h="335">
 
  <StaticImage x="0" y="0" w="775" h="330" image="Main/Control.png" transparent="1" />

  <Knob param="20" x="53" y="168" image="Main/Window1.png" frames="101"   />

  <OnOffButton param="23"  x="660" y="282" w="52" h="21" image="Main/Button.png" />

x, y, w (width), h (height), image 程度のもので、GladeやXAMLなどに比べたら内容も多くが自明です。これで、例えばこんな画面ができます。(Unreal InstrumentsのMETAL-GTX)

f:id:atsushieno:20200601154625p:plain
UI METAL GTXのGUI

この画面では、ツマミの部分と数値入力っぽい部分がKnob要素で表現されています。Knobparamframesimageには説明が必要でしょう。まず簡単なframesのほうを説明しますが、これは数値の範囲に対応します。frames="101"というのは、数値が0〜100の101段階であることを意味します。paramは、サウンドフォントのどのパラメーターを操作するものなのかを、MIDIのコントロールチェンジのように数値で指定します。.sfzファイル側で規定しているはずです。

ツマミの部分は画像を自由に指定できます。それがimage属性の内容なのですが、リンク先を見てみてください。巨大な内容なので埋め込みはしません。

https://aria2web.firebaseapp.com/ui-metal-gtx/gen/GUI/Main/Main_Knob.png

あれ? 見えなくない…?ってなりますが、実はものすごい縦長の画像が、ブラウザウィンドウの幅に合わせて縮尺を調整した結果です。上方のごく一部だけを切り取るとこんな感じになっています。

f:id:atsushieno:20200601154706p:plain
ツマミ画像(抜粋)

ツマミですね…! ひとつひとつをよく見ると、ダイヤルが少しずつ上に移動しているのがわかります。縦長なのは、これが101個ぶんだけ縦に連結しているからです(!)。画面上には「今の値に対応する画像」を表示するだけです。とんでもないやっつけ仕様ですが、確かにこのフォーマットであれば、サンプラーのコードでは何も面倒を見る必要がないので、プラグインを作る側は自由なデザインでツマミを実装することができる、というわけです。

そして数値入力っぽく見えるのも実はダミーで、これはKnobコントロールです。マウスホイールで数値を変更できます。キーボード入力はできません。もちろん数字はツマミと同じ超縦長画像に描かれています。なんか…とんでもねー世界に来ちまったぞ…

webaudio-controlsを活用してHTML UI化する

GUI拡張と聞くとなんかものすごい技術努力によって実現しているかのように思えてしまいますが、ARIAはごく単純な仕組みとデータ形式でUIを表現していました。この程度であれば、HTML+JavaScript+CSS+SVGで表現できるでしょう。sforzandoはLinux版を提供していないので(もちろんAndroid版もありません)、ARIAGUIをロードできるようにするには自前で実装するしかありません。

aria2webのREADMEでも言及しているのですが、実はもう出来ていて、ここで試すことができます。

https://aria2web.firebaseapp.com/

今回これを作るにあたって活用したのは、この方面ですでに存在していたwebaudio-controlsという完成度の高いUIコントロールです。Web Componentsに基づいて作られていて、Javascriptから操作するのも簡単です。元コードの開発メンバーがだいたい日本のWeb Music Developers JPあたりにいそうです。

github.com

実のところ、ARIAがそこまでGUIの詳細な表示内容をデータ上に持っているとは考えていなかったので、ツマミやスライダーはこのwebaudio-controlsを使えば「それっぽいもの」が作れると思っていましたが、結果的には「ほぼ元通りのUIっぽいもの」を再現できるところまで出来ています。

ARIAXMLをHTMLに変換する処理は本当に簡単なもので、現状では100行も無いXSLTスタイルシートで実現しています。あの妙な画像も変換しなくていいの?と思うでしょうが、webaudio-controlsに含まれるwebaudio-knobに、完全に同じ機能が含まれています

これはどうやってARIA用にこういう画像を作るのかを調べていて割といろいろびっくりしたんですが、そもそもwebaudio-controlsを開発しているg200kgさんがKnobManというこのKnobコントロール画像を作成するツールの開発者で*3、これを使えばgimpとかでがんばって手作業でこういう画像を加工する必要はないわけです(最近はWebKnobManがメインらしい)。そもそもKnob Galleryに膨大な数のフリーKnob素材があるので、自分でゼロから作る必要はあんまり無さそうです。

もうちょっと調べてみると、KnobManの使い方はNative Instrumentsのフォーラムでも議論されていて、要するにこの画像フォーマットはもともとKontakt(たぶん)で決められていて、KnobManはそれを簡単に作るために作られたツールであって、ARIA GUIKontakt用音源のデータと同じ画像を再利用できるようにしたものっぽい、ということが把握できました。完全に謎のエコシステムが出来ている…!

ともあれ、当初予想していた以上にwebaudio-controlsを使ってUIを実現するというアイディアは的を射ていたことがわかりました。

ちなみにaria2webで使っているwebaudio-controlsのコードには少し手を加えてあります。具体的には、knobのサイズが不明な場合でも元画像のとおりによろしくやってくれるように変更しています*4XSLTで任意のARIAから変換してwebaudio-knobを生成している自分としては、画像のサイズがわからないので指定できないわけですが(これがXSLTでなければ画像をロードしてサイズを取得するだけなので瞬殺…)、webaudio-knobではサイズ指定がない場合はデフォルト値(120pxかなんか)で描画してしまうので、当初は常に変なサイズで描画されてしまう問題で悩みました。

なぜWeb AudioでもないのにWeb UIなのか

ここから先は現在も開発中なのですが、aria2webはそもそもオーディオプラグインのUIとしてARIAを流用するのが目的なので、活用するにあたってはこれをLV2やVSTのようなプラグインに統合することになります。最近ではどのプラットフォームにもWebViewがあるのでそれを使えば十分可能だろう、というのがわたしの理解でした。でした…とは!? この詳細は「Part IIに続く」(!)

…なのですが、もう少しこの設計について書きます。

先日も書いたのですが、LV2オーディオプラグインGUIの世界はバベルの塔が崩壊した状態で、各種のGUIフレームワークに分かれていて全部をサポートするのが難しい状態です。前回はsuilを紹介しましたが、suilでも解決できない課題があります(Part IIで書く)。

WebテクノロジーでUIを構築するというのは、この問題を解決するひとつの手段として考えたものです…というのは半分ウソで、そもそもWeb UIを使おうと思ったのはAndroidiOSのような特殊な世界でプラグインUIをどう構築するかという課題への解決策として思いついたものでした。つまりWeb UIならホスト側アプリでもロードできる、というわけです(詳しくはここで)。

いずれにしろ、ローカル環境を問わずにUIをホストする、一部で「ガワネイティブ」と呼ばれる技術が、オーディオプラグインUI開発の裾野を広げたり、あるいは一般的なアプリケーションのUI開発手法に近づけるために、有用だと思っています。個人的にはオーディオプラグインUIの開発手法は10年遅れている、juce_gui_basicsに技術投資する時代ではない、くらいの認識でいます。もっとも、ここは実のところ要件次第のところがあって、オーディオ処理ほどではないが演奏者が違和感を感じない程度に可能な限りレスポンシブに動くUIにはネイティブコードが最適でしょう。ただこれは設計次第なのと過渡的な問題だと理解しています。

ちなみに、この観点で紹介しておくべきものとして、JUCEのGUIをReactで開発できるBlueprintがあります。BlueprintはWebViewを使うのではなく、JUCE用のrendererを提供するreconcilerの実装になっている、すなわちもうひとつのReact Nativeのように振る舞っている、というところが特徴です。つまり、Reactのやり方で構築したUIが、JUCEのネイティブコンポーネントとして動作することになります。

これはこれでスマートなやり方なのですが、あくまでReact資産があったり、React開発でゼロから構築することを前提としないと難しいです。今回はwebaudio-controlsを使っていて、これは内部的にWeb ComponentsやSVGをDOMも含めて活用して実装されているため、React化するという選択肢はありませんでした。Blueprintはreconcilerを自前で実装していて、YogaやDuktapeを組み合わせて実装していてissuesを眺めてみてもそれなりに未実装の部分がいろいろあることがわかるので、あくまで出来る範囲のことをやる方針プロジェクトと理解しています。自分にはむしろCordovaやIonicのやり方が必要となったわけです。

今回は試験的にVue.jsを使っていますが、無くても良かったというレベルなので、今後依存関係として邪魔だな…となったらたぶん消します。(試験的なプロジェクトなので、ライブラリを開発している時みたいに「余計な依存関係はなるべく減らして…」とかは考えてはいません。)

ローカルプラグインからの利用については、Global空間に適当なWebViewからのフック用のJSオブジェクトを用意しておいて、webaudio-controlsからの操作通知はそこに送るように作ってあります。現在開発が進んでいるローカルのプラグインではこれでイベントを受け取っています。

ちなみに(言うまでもないかもしれませんが)Web上のデモはGUIのみです。音は出ません(デスクトップでも「まだ」音は出ませんが)。というのは、SFZサウンドフォントは大規模なものだと1GBにもなるような巨大なデータであるため、Web上環境には現状馴染まないためです。ダウンロードしてWeb Storage APIで格納するようなソリューションは実現可能かもしれませんが(あとはWeb Audio Pluginsと組み合わせたりとか)、個人的には現状あまり関心が無いです。

後半へ続く

本当は1回で全部まとめるつもりだったのですが、HTML化の部分だけでかなり長くなってしまったので、オーディオプラグインに組み込む部分はPart IIとして書きます。そもそもまだ出来ていないしな…!

*1:特定のキーのノートオンによって楽器のモードを切り替えることができる。ギターの奏法などで便利

*2:W3Cアクセシビリティ関連仕様であるWAI ARIAとは無関係です。

*3:webaudio-knob自体はもともとはagektmrさんが作ったもの

*4:変更が雑なのでgithubにPRを作れていない状態…

LV2のGUIサポートのトレンドに関する覚書

追記: zrythm作者からいただいたコメントがプラグイン開発者側の最新のトレンドについて詳しく言及しているのでそちらも参照されたい。

目次

オーディオプラグインGUI実装の一般論

一般的に、オーディオプラグインのUIはオーディオ処理の本質ではなく、DSPからは独立して構築できる部品だけど、どのプラグインフレームワークGUIが存在することは想定されている。DSPはリアルタイム処理のサイクルで実行可能であることが要求されるけど、一般論としてGUIをリアルタイムに処理することはできない*1

一般的に、オーディオプラグインはパラメーターやポートで入力を受け取ってDSPの処理に反映する。GUIの操作結果をパラメーターに反映できるようにすれば、オーディオプラグインの機能としては十分だ(パラメーターに反映できない部分はstateとして保存する)。また、一般的にGUIの処理はロジックと分離していることが期待されるが、GUIコードがパラメーターやポートを経由してのみオーディオプラグインの動作を制御する仕組みになっていれば、これは自然と実現できていることになる。すごい。XAMLだけでUIを書いていれば自然にMVVMになります!みたいないかがわしさがチョットある。いずれにせよ、GUI部分はリアルタイムをあまり気にせずに*2、一般的なGUIプログラミングによって実現することになる。*3

一方で、一般的なGUIプログラミングと同じということは、プラットフォームの壁が問題になるということでもある。VSTにはVSTGUIというGUIツールキットがあり(もちろんWindows APIを直接使うこともできる)、AUの場合は最初からApple OSでしか動かないのでCocoa (Touch)が使えることが前提にあると考えられるが、LV2については標準的なGUIツールキットは存在しない。LV2はクロスプラットフォームが前提だし、LinuxだけでもgtkとQtだけでも勢力が二分しているし、他にもX11ベースのツールキットがいくつか存在する(JUCEやWDLもそうだし、FLTKやその他GL系のソリューションもある)。これらを全てフレームワーク側が列挙し対応を表明するのは非現実的だし、それは標準的な技術(de facto standardも含む)で行うべきことではない。

LV2 UI feature

LV2では、コア仕様以外は全て拡張機能としてホストから提供したりプラグインで実装したりする仕組みになっている。LV2 UIについても拡張として仕様がまとめられている。

LV2拡張の仕組みを解説するためには本来RDFやらTurtle Syntaxやらも解説しないといけないのでしんどいのだけど、ここでは要点だけかいつまんで説明する。

LV2の拡張機能は全てマニフェストで判別できる。あるLV2プラグインがUI機能を提供しているかどうかは、http://lv2plug.in/ns/extensions/ui#ui のカテゴリの拡張の有無で判別できる。筆者が開発しているUIありプラグインを例として挙げると:

<https://github.com/atsushieno/aria2web>
  a doap:Project, lv2:Plugin, lv2:InstrumentPlugin ;
  ...
  ui:ui <https://github.com/atsushieno/aria2web#ui> .

<https://github.com/atsushieno/aria2web#ui>
  a ui:Gtk3UI ;
  ui:binary <aria2web-lv2ui.so> .

という感じだ。

doap:, lv2:, ui:プレフィックスで本来は名前空間宣言も載せるべきなのだけど、少しLV2プラグインを開発してみればこれらはほぼ自明なのでここでは省略する。これはlv2:Pluginである(かつdoap:Projectlv2:InstrumentPluginでもある)https://github.com/atsushieno/aria2webというオブジェクトに、ui:uiプロパティとしてhttps://github.com/atsushieno/aria2web#uiというオブジェクトを設定している。後者のオブジェクトはui:Gtk3UIで、aria2web-lv2ui.soというui:binaryに実装が含まれている。

LV2 UI拡張の中では、このGtk3UI以外にも以下のようなプロパティが定義されている:

  • WindowsUI
  • CocoaUI
  • X11UI
  • GtkUI
  • Qt4UI
  • Qt5UI

ひとつのプラグインで複数のGUIフレームワークをサポートする場合は、このui:uiプロパティのオブジェクトを複数定義することになる。

LinuxデスクトップでLV2をサポートするDAWプラグインをホストする場合は、プラグインごとに適切なUIをロードして実装するのが適切なやり方ということになる。Qt5アプリであるQTractorであればQt5UIを、Gtk3アプリであるzrythmであればGtk3UIをロードして使えばよい。

UI拡張API

LV2UI拡張のロード手順はLV2プラグインそのもののロード手順と似ている。共有ライブラリの中にはlv2ui_descriptor()というエントリーポイント関数が定義される。

const LV2_SYMBOL_EXPORT LV2UI_Descriptor *   lv2ui_descriptor (uint32_t index)

LV2UI_Descriptorには、ホストから呼び出され、ホストがUIを制御するために必要な関数などが含まれている。

trydef struct {
  const char *  URI;
  LV2UI_Handle(*    instantiate )(
      const struct LV2UI_Descriptor   *descriptor, 
      const char *plugin_uri, 
      const char *bundle_path,   
      LV2UI_Write_Function write_function, 
      LV2UI_Controller controller, 
      LV2UI_Widget *widget, 
      const LV2_Feature *const *features);
  void(*    cleanup )(LV2UI_Handle ui);
  void(*    port_event )(
      LV2UI_Handle ui, 
      uint32_t port_index,   
      uint32_t buffer_size, 
      uint32_t format, 
      const void *buffer);
  const void *(*    extension_data )(const char *uri);
} LV2UI_Descriptor;

ここでinstantiateで渡されるwrite_functionは、プラグインUIコードがユーザーからの入力をプラグインのポートに出力する時に使う。

LV2UI拡張には、他にもhttp://lv2plug.in/ns/extensions/ui#parentという拡張がある。上記のextension_data()にこのURIを渡すと、ホスト側でプラグインのUIコンポーネントの親コンポーネントが返ってくる(拡張なので、あくまでホストがサポートしている場合に限る)。ホストとプラグインのUI拡張は、これらを使えば最低限の制御が可能だ。この基本的なインターフェースには、GUIフレームワークに固有の部分が何ら存在しないというのがポイントだ。

GUIフレームワークのミスマッチ

オーディオプラグインユビキタスに提供するのは困難な仕事だ。Windowsだけ、Macだけという開発者が多い中、Linux版も頑張って提供する開発者は多くはない。オーディオ処理はクロスプラットフォームで記述できることが多いが、GUIはそうもいかない。なのでJUCEが幅広く使われることにもなるし、VSTGUIなどでクロスプラットフォームで実装することも出来るわけだが、それでは足りずGUIフレームワーク固有の機能を使って実装したい場合も多いかもしれない。

しかし…実のところ、そんな立派な目的でGtkやQtを使っているわけではないことも多いだろう。LV2はLinuxで使われるだけだし、自分の使っているArdourやQTractorで使えれば十分…という開発者は、自分の環境で使えるUIだけ提供することになるだろう。

実際、筆者が開発しているWebViewベースのプラグインUIはwebkitgtk3を簡単に使えるライブラリを使っている都合上、Gtk3UIしか提供できない。Qt5UIを提供するにはQtの使い方を勉強して、Qt5WebViewなどの使い方を調べないといけないし、とてもできるとは思えない*4X11UIを提供するにはもうCEFでも使って再実装するしかない。

もちろん、これはプラグインだけでなくホスト側についても言えることだ。QTractorはQt5UIだけをサポートするしArdourはGtkをホストするのみだろう。つまり実際にはこんな感じになる:

これで最終的に何が起こるかというと、DAWフレームワークでサポートされていないプラグインUIは表示されないことになる。ただでさえ狭いLV2プラグインの世界がさらに分断されているのが現状だ。また、プラグインのエコシステムがGUIフレームワークから切り離せなくなると、GUIフレームワークを乗り換えたり、新しいものを使っていく(たとえばFlutter on Desktopとか)といったチャレンジが難しくなる。

LV2はUIが無くてもポートの定義だけでもそれなりにパラメーター入力が可能なので、困らないことはそれなりにある。たとえば次の画面はsfizzをzrythmでロードした時に表示されるダミーUIだけど、ちゃんとファイル名まで渡せるようになっている。

f:id:atsushieno:20200510103512p:plain
sfizz on zrythm

QTracktorでも似たようなしっかりしたUIが出るし、VSTAUでもある程度は可能だろう。内部的にはプロパティグリッドを作っているようなものだ。しかしWindowsMacに比べて恵まれていない状況にあることは間違いない。

suil

プラグインUIの分断状態は好ましくないが、幸い解決困難な問題ではない。というのは、プラグインプラグインUIのインターフェースはLV2 UI拡張の規定する範囲に限られている。それであれば、他のGUIフレームワークに基づいてLV2 UIの機能を「ラップ」しつつ、ホスト側には対応するフレームワークに基づく情報を渡すような仕組みがあれば、どんなホストであっても任意のフレームワークGUIを呼び出すことが可能になる。プラグインのラッパーとおなじような発想だ。

これを実現しているのがsuilというライブラリだ。

gitlab.com

suilは自身がX11/Qt5/Gtk/Gtk3のLV2UI_Descriptorの内容をロードしUIホストのように振る舞い、X11やQt5やGtk3のプラグインホストが必要とする情報をホストに渡すことができる。suilがサポートするUIをホストする部分が整っていれば、ホスト側は比較的簡単にnon-nativeなプラグインGUIもサポートできるようになるというわけだ。

suilはLV2の開発者(標準仕様のように扱われているが、これを規定しているのは1人の開発者だ)が自ら開発していて、lilvなどと同様、半ば公式SDKの一部のような存在となっている。

ソースの構成を見れば、これがどういう泥臭い仕事をしているかがわかる:

f:id:atsushieno:20200510103540p:plain
wrappers in suil

前節では次のように書いたのだけど

QTractorはQt5UIだけをサポートするしArdourはGtkをホストするのみだろう。

これは実はウソである。数日前にリリースされた最新版のQTractorは、このsuilを組み込むことで、GtkUIとX11UIもサポートするようになったので(リリースノート参照)、いずれパッケージされて各種distroで使えるようになるだろう。それまではウソではない。

Gtk3サポートの課題とexternal UI

suilは銀の弾丸ではなく、ホストと繋ぎこむ部分はまだまだGUI種別ごとに面倒を見てやらなければならない。suilのAPIを使えばある程度問題が緩和できるということにすぎない。また、suilが直ちに全てのGUIフレームワークをサポートできるようにするわけではない。たとえば、最新版のQTractorでもGtk3はサポートされていない。コードならこんな感じでGtk3だけ見当たらない状態だ。

LV2 UIのドキュメントでは、X11UIについてはX11Windowをparentとしてホストから渡すことが想定されている書き方になっているが、Gtk3についてはGtkWindowを渡すとは書かれていない。実際、zrythmがGtk3UIに渡すのはGtkEventBoxになっていて、整合性が無い。zrythmがGtkEventBoxを渡しているのにQTractorがGtkWindowを渡すようになってしまうと、これはややこしい非互換問題になる。想定される挙動が不明なので、QTractorではまだサポートされていないのだろう。

このあたりでやや反則的に特別扱いされているのがCarlaなどを公開しているkxstudioで、zrythmも最新のQTractorも、kxstudioの名前空間を含むUI拡張が定義されていると "external UI" モードになって、独自のアプリケーションループを回す存在として処理されるようだ。

筆者のWebViewベースのプラグインUIも、使っているライブラリがGtkWindowを返すためにzrythmでGtk3UIとしてまともに動作しないので、このexternal UIモードで動作させたいのだけど(それで動くのかどうかはわからない)、LV2 UI標準に含まれていないので困っている状態だ。とはいえ、もともとLV2には存在していてむしろdeprecatedになったものらしい。zrythmが内部的に定義しているexternal UIの型などを見る限り、show/hide/runくらいしか定義されておらず、これではロジックとUIの分離が実現できていなかった、といった当たりの事情で廃止されたのだろう。

いずれにせよ、この辺りはどうやら未整備で、今後状況が変わってくるポイントかもしれない。

まとめ

LV2はGUIフレームワークに依存せずにプラグイン本体を制御するために必要な情報をRDFマニフェスト(ttl)に記載しているので、GUIプラグインの役割を自然に分離できているし、suilのようなラッパーを作って複数のUIフレームワークをサポートすることも可能にしている。もっとも理想と現実の間にまだギャップがあって、Gtk3サポートみたいな部分がこぼれ落ちることがある。この辺は2020年に現在進行形で起こっている出来事であり、近いうちに解決されていく可能性も十分にある。

*1:マルチスレッドで動作するGUIがすでに非現実的で、さらにそのうちのひとつがリアルタイムで排他処理を伴わずに一貫した状態をDSPに提供できなければならない

*2:プラグインのデータを変更する時にatomicな更新を心がける必要があるかもしれない程度だ。これはプラグインによる

*3:2020/5/10追記: 実のところこれはやや過度に一般化しているフシがあって、ホストからのウィンドウ表示への対応など一般的でない考慮事項がちょいちょいあるのだけど、今回はそこは主な話題ではないので割愛したい。

*4:難しくて理解できないという話ではなくて、他にやるべきことがいくらでもある

オーディオプラグインの動的ポートに関する覚書

最近自分がやっていることは多分もう自分以外の人にとっては完全にイミフだと思うが、後で歴史を発掘する作業も必要になってくると思うので、記録を残しておこうと思う。自分のオーディオプラグインフレームワーク最近ずっと悩みつつ進めている動的ポートのサポートについてだ。

目次

オーディオプラグインのポート

一般的なオーディオプラグインにはオーディオやMIDIの入出力チャンネルが存在し、DAWなどのホストはフィルターグラフとかオーディオグラフとかチェインとかいったものを形成して、そこにオーディオやMIDIのデータを流し込んで処理させる。プラグインにはそれぞれにさまざまなパラメーターが存在するが、一般的にはプラグインのパラメーターの数は状況に応じて増減したりはしない。

(LV2の場合はパラメーターという存在は全てポートとして処理されるので少し話がややこしくなって、ポートの数はDAWなどのホスト側の「バス」(Bus)の設定に依存してくる(ステレオとか5.1chとかambisonicとかいろいろある)のだけど、いったんバスが決まってしまえば、ポートの数はやはり普通は固定値となる。)

VSTAUでは、プラグインのパラメーターはプラグインインスタンス(ここではvst::IPluginFactoryも含むものとして考えてほしい)から取得するのだけど、LV2はこの点少し違っていてプラグインRDFマニフェストの中に記述する。たとえばGuitarixのマニフェストだとこんな感じだ(長いのでリンクにとどめる)。マニフェストに書いてあると、プラグインのプログラムを実際にロードしなくてもどんなポートが含まれるのか把握できる。ここから、たとえばMIDI入出力のあるプラグインかどうか、といった判断も可能だ(たとえばsfizzにはMIDI入力ポートがあることがわかる)。

動的ポートの存在

もう一度書くが、ポートの数は普通は固定値となる。しかし、このまわりで例外的なプラグインのやつらが存在する。ひとつの典型的なやつはプラグインの「ラッパー」と呼ばれるもの、の一種だ。ラッパーとは、本来サポート対象外である別のプラグイン規格のものをあたかも自分の実装するプラグイン規格のプログラムとして動いているかのように処理するもので、たとえばlv2vstというプログラムはLV2プラグインをまるでVSTプラグインであるかのように処理する。その実態は、ホストから受け取ったデータを単にロードしたLV2プラグインに渡して処理させ、結果をVSTホストに返すだけ、である(だけ、と書いたけど、実際には両規格の仕様には機能的な違いもあってそれなりに大変な作業だ)。ラッパーの多くは静的なものだけど、これが動的にプラグインをロードする仕組みになっていると、必要なパラメーター/ポートの数も変わらざるを得ない。

また、ラッパー以外でも動的ポートが必要になる類のプラグインが存在する。Dexedというプラグインは、往年のYAMAHA DX7などをシミュレートするプラグインだが(DX7部分はGoogleのエンジニアが作成したmusic-synthesizer-for-Androidで、詳しくは音楽ツール・ライブラリ・技術 Advent Calendar 2018の記事を参照されたい)、これはDX7Cartと呼ばれる楽器プログラムリストをロードして楽器として使うプラグインだ。そのパラメーターの数はロードしたCartによって異なってくる。Cartの問題なのか実際にサポートしている音源(DX7以外にもちょいちょい追加されているらしい)なのかは中身を追っていないのでわからないが、重要なのは状況によってポートの数が変わるという事実だ。

LV2 Dynamic Manifest拡張

LV2は事前にポートを列挙する仕組みになっているので、このままでは動的ポートのようなイレギュラーな存在をサポートすることはできない。しかしプラグインラッパーが実現できなかったりするのは不便だ。

LV2はこれを解決するために、Dynamic Manifestという拡張仕様を盛り込むことにした。本来プラグインのプログラムの一部として実装するものではないマニフェストRDFの提供を、プログラム上で動的に行ってホスト側に渡す、という、なんとも奇妙な仕様だ。ポート以外のメタデータも渡せるようだが、DAW側で既にプラグインのリストなどが生成されたりDAW上に表示されていたりするものが、いきなりプラグイン名が変わったりしたら混乱は必至だし、そもそも実現可能かもあやしい。ただ、プラグインのポートが増減する挙動は、ホスト側さえ実装できればこれで対応できる。

それにしても、ポートの数が動的であるのなら、最初からポートの情報はマニフェストに含めないほうが良かったのではないか、という気もするが、そうしないことにはメリットもある。まず、コードで表現すると、プラグインの「種類」を判別するために必要になるメタデータが際限なく増えてしまう。VST3であればPClassInfo, PClassInfo2, あるいはIPluginFactory/2/3と増えている。LV2の場合はPort Propertiesという拡張仕様にプロパティが増えるだけで、APIを増やすことはない(し、APIも拡張として提供してもよいが)。そして、ポート情報をホストとプラグインでやり取りするために、ポートをあらわす型情報が必要になってしまう。また、LV2ではポートについてはPort Propertiesという拡張仕様が規定されているが、もしAPIにしてしまうと、この拡張情報をどのようにやり取りするのか、といった問題も芋づる式に発生する。

こういった煩雑な問題が、「マニフェストのアップデート」であれば何も必要なくなるのである。LV2のDynamic Manifest拡張のAPIでホストからプラグインに渡されるのは、なんとFILE*である。そこに新しいマニフェストを書き出せ、そしたら後はホスト側でよろしく解析する、というわけだ。とんでもないやっつけAPIだな、と思ってしまうわけだけど、実はやっつけではなく、むしろ上記のようなややこしい問題を回避するためのハックなのだ。メタデータのパーサーは当然ホストにはあるはずなので(LV2ならlilvを使えばよい)、実装上のハードルは上がらないといえる。

ポートの追加はプラグインのパラメーターのやり取りの仕組みを利用すればよいのではないか、という考え方もありうるが、オーディオプラグインのパラメーターは、オブジェクト指向言語のプロパティグリッドで設定できるようなさまざまな型が用意されていることは通常はなく、全てfloat配列やbyte配列となっている。LV2のポートも同様で、複雑な構造体についてはAtomという拡張仕様が規定されている。これに既存のポート情報のやり取りを上乗せするAtom構造体を規定する苦労をするくらいなら、既にあるマニフェストのパーサーを使い回せるほうが楽だし覚えることも少なくなる。

プロセス分離モデルでの応用

わたしが開発しているのはAndroid用のフレームワークで、ホストとプラグインは別々のベンダーが開発して別々のアプリケーションとしてインストールするというモデルなので、ポート情報をやり取りする場合にはプログラム上のコードで表されたデータ構造としてそのままやり取りすることはできない(…というのは言い過ぎだが、Kotlin/JVMでもC++でも扱わないといけないので煩雑だ)。メタデータにはLV2同様にポートの情報が記述されているので、動的ポートをサポートするにはLV2のDynamic Manifest類似の拡張機能が必要そうだ。

実のところ、ポート情報をマニフェストに記載する必要はあるのか、という疑問が頭の中をぐるぐる回って、ある程度の解決に至る光明を見出すことがなかなかできなかった。LV2での実現方法とそれにまつわるメリット・デメリット、代替方法などを模索してみて、だいたい同じことをすれば良いだろうと考えるようになった。

実のところ拡張機能まわりは実装できていたと思っていたところがまだ全然足りていない側面があったこととか、LV2みたいに任意のポインタを渡してもらっても、たとえば関数ポインタとか渡されても困るから排除しないといけないということに気づかされた。おそらくプロセス分離モデルを前提とした仕様では同じような回避策を模索しなければならなくなるだろう(AUv3はApp Extensionなのでプログラム情報は共有できそうではある)。

このあたりはUI統合を実現する手法にも影響してくるので、まだ二転三転する可能性はあるのだけど、この辺は技術的に興味のある人が吸い寄せられてコメントしてくるくらい課題としてはけっこう面白いっぽいので、ちまちま実装を進めていこうと思っている。

PACEによるJUCEの買収とJUCE6開発計画が予告された件

4月になってからJUCE方面の動きが大きい。今日はついにPACE社がJUCEを買収したというニュースが出てきた。それなりにセンシティブな話題で、ある程度JUCEにコミットメントがある人には書きにくい話もあると思うので、完全に無責任な立ち位置で書いておこうと思う。

目次

PACEによる買収とjuce-framework/JUCEへの移行

JUCEは(ここでは何度も書いているので今さら感あるけど)クロスプラットフォームでクロスオーディオプラグイン規格の開発フレームワークで、今日まではROLI社が所有する製品だった。商用ライセンスとGPLv3で公開されている。

今回はROLI社が買収されるのではなく、JUCEの開発事業がPACEに買収されるらしい。JUCEはもともとJulian Storerという開発者がJule'sなんちゃら(忘れた)という自分の名前を冠して作ったライブラリで、彼はROLIに所属しているのだけど、最近はSOULというAPL(オーディオプログラミング言語)まわりの仕事をしていて、SOULは事業譲渡の対象ではないようだ。つまりJUCEはPACEのものになるけど、Juleは付いてこないということになる*1。ROLI社は楽器ハードウェアを販売している会社なので(Apple StoreなどでSeaboardやBLOCKSといったROLI製品が販売されているのを見たことがある人もいることだろう)、JUCE以外にもやるべきことはいっぱいある。

買収するほう、PACE社が分かる人がどれくらいいるか、多くはないんじゃないかと思うけど、iLokという製品を聞いたことがある/使っている人はDTMやってる人ならそれなりにいるかもしれない。USBドングルを売っているライセンシング・ソリューションを提供する会社だ。iLokは自分もADCで2年連続で見ているはずなのだけど、会社名までは全く覚えていなかった。最初 IP management company みたいな文言で書かれているのを見て「JUCE完全に身売りしないといけなくなったのか…」と暗い気持ちになったのだけど、製品開発にはノータッチノーサポートでIPだけ吸い取るような買収ではないようだ。

ROLIは実際のところかなり資金難に苦しんでいて、JUCE開発チームも2人か3人くらいで回している状態だった。JUCEにはいわゆる外部コントリビューターのようなものがいない。現時点ではROLIのポリシーなのかJUCEチームのポリシーなのかわからないが、当初はそもそもJuleの個人ユーティリティで彼は要望を受け付けてコードを取り込むのを嫌がって拒否していたようだ。つまりオープンソースではあるがオープン開発ではない。

ごく少人数で回しているJUCE開発は、実のところさまざまな方面で停滞していた。個人的に関心のある範囲だけでも、Projucerはビルドを全面的に面倒見るための機構なのに、痒いところに手が届かず、細々とした改善が求められていた。SteinbergがVST2 SDKを入手できなくしたので全面的なVST3サポートが必須だったのに、Linux版はVST3を全くサポートしなかった。

閑話休題、そこに現在のコロナ禍である。ROLI単独で状況が改善される見込みはほぼ無かった。資金難という話は昨年には出ていたので(昨年はNative Instrumentsなども大規模リストラを行っており、オーディオ開発ソフトウェアの世界は割としんどい年だったようだ)、PACE以外からもいくつか買収提案を受けていたようだ。PACEはJUCEからのシームレスなiLokサポートを実現できれば既成品の販売が強化できるので、それだけでもJUCEを手元に置いておく理由にはなるかもしれない。実のところiLok利用ベンダーとJUCEユーザーはある程度かぶっていたようだ。JUCEチームとしては資金の心配なく開発が継続できれば良いということだろう。JUCEユーザー(音楽ソフト開発者)としてはそれで得られる安心感は確かにある。

とはいえ…iLokである。もともとユーザーにとっては邪魔でしかないUSBドングルのライセンシングソリューションの会社でサポートの評判もいまいちらしく、全く歓迎されるべき事態には見えない。JUCE forumでこの話題が上がったのはこのスレッドだけど、リンク先のgearslutz.comの掲示板には残念がるコメントが散見される。まずタイトルがPace Ilok has bought the Juce Platform :-(である。散見ではあるけど、匿名掲示板でも大規模掲示板でもないことを考えると、十分「大きな」懸念が上がっていると思う。

PACEの買収はどんな影響をもたらすだろうか? 私見としては3つくらい今後の筋書きが考えられる。

  1. 何も変わらない。これが一番ありそうなシナリオだと思う。PACEはiLokの販路が拡大できれば十分なので、JUCEにiLokサポートを追加するような動きは見せるが、JUCEのライセンスモデルを変更したりすることはない。juce_product_unlockingモジュールはもしかしたらiLokサポート用に別モジュールが出来て消滅するかもしれない(が、そのためにはiLokがLinuxをサポートしなければならず、それ無しで移行するとは考えにくい)。
  2. 商用ライセンスに変更が加えられ、商用ライセンス版では特にソフトウェアプロテクションまわりでiLok以外の選択肢を排除するために独自の改良を加えられなくなる可能性が懸念される。こうなると独自にLV2サポートを追加するような変更が加えられなくなる。
  3. GPLv3ライセンスでの提供が廃止される。外部コントリビューションを受け付けていなかったので、PACEはJUCEのライセンスを恣に変更できる。もちろん、これまでにリリースされたバージョンは従前の商用ライセンスとGPLv3で利用できるが、誰かがOSSとしてforkしてメンテナンスする必要がある。幸い(?)本家の開発リソースは小さいのでそこまで劣勢にはならないだろう。ただLGPLで利用できないライブラリはライセンス的に使い勝手が悪いし、商用ユーザーにとってはそっちに協力してもメリットがないので、かなり閉じた世界になりそうではある。

(3)は一番問題のあるシナリオだけど、可能性は限りなく小さいと思う。競合製品を出しているわけでもないPACEにそんなことをするメリットは、悪意の深読みスペシャリストであるわたしにもちょっと思いつかない。JUCEの市場は小さくないけど、独占的とまで言えるほどではない。

…とまあ、懸念事項はあるのだけど、現時点で言えることとしては、少なくともJUCEチームにはいくつかの選択肢があったうえで一番よさそうなものを選んだのだということだ。PACEは少なくとも開発者にとっては親和的であるようだ。JUCEチームによる公式アナウンスには developer-focused company と書かれている。ただそれにしてはgithub organizationsも無いような会社なので、このコメントへはわたしは50%くらいは(良く言えば)リップサービスだなと思っている。

JUCEはROLIのorganizationからは外れて、新しくjuce-frameworkというorganizationの下で公開されることになったようだ。github teamsが価格改定されたこともあるし、タイミングとしては割とラッキーだったのかもしれない。

JUCE6の予告

ともあれ、資金的な困難が解消したこともあってか、JUCEチームは大規模リリースに向けて本格稼働を始めた(あるいはそれを公開するようになった)ようだ。今から4日前、JUCE6のリリースが告知された。個人的には嬉しい事だらけの内容だ。

forum.juce.com

JUCE6は現在すでにある程度コードがgithub上でjuce6というブランチで公開されている。以下の項目以外にもいくつか改善があるので、詳しくは上記アナウンスやgithub上のコミットを眺めてみてほしい。

CMakeサポート

JUCE6最大の変更はProjucerからCMakeへの全面的な移行だ。CMakeサポートはもともと「CLionサポート (beta)」というかたちで実装されていたわけだけど、あくまでビルドが可能というレベルで再利用性は低く、ファイル追加などはProjucer上で行うことが前提となっていた。JUCE6ではこれを全プラットフォームで全面的に使うということを意味している。Projucerは、なくなるわけではなく、CMakeLists.txtを生成するための補完的なツールとして生き残り続けるようだ(そもそも他にもいくつか機能がある)。

昨年12月にJUCE Advent Calendarでわたしは以下のように書いたのだけど、完全にその通りになってくれた。

わたしの個人的な感想としては「もうProjucerのビルドシステムはあきらめてCMakeを使おう」なのですが(パッケージ参照もちゃんと解決できるしVisual StudioでもAndroid StudioでもCLionでも開けるんですよコレ)

完全に余計な話だけど、CMakeサポートはJUCEのオリジナル開発者Julian Storerがやりたがらなかった仕事のひとつなので(「CMakeが嫌だからJUCEを作ったんだ」)、彼が組織上は居なくなるタイミングでCMakeサポートが追加されるというのは何とも象徴的ではある(タイミングの問題であって、CMakeが理由で決別ってことはまあないだろう)。PACEの買収のポジティブな側面として、CMakeサポートは新しくコミュニティからスカウトされてJUCEチームの一員となった開発者が書いている。

これを書いている時点では、examples/CMake/README.mdに詳しいドキュメントがまとめられている。「ドキュメントはわかりやすい場所に置いてほしい」というユーザーフィードバックが開発者に届いていたので、そのうち移動するかもしれない*2

Run Headless (Linux)

JUCEアプリケーションがLinux上ではheadlessでGUIなしで動作するようになった。GUIが無くても良いということは、CIサーバ上で非常に動かしやすくなったということだ。CircleCIを使う記事を去年書いたときにも言及したけど(CIまわりはわたしが自分で書かないと英語圏でも誰も書いていないような状況だった)、これまではX server相当の何かをセットアップしないと何も出来なかった。

atsushieno.hatenablog.com

GUIコードが一切存在しなくても、Projucerでファイルを生成するためにはJUCEアプリであるProjucerが実行できなければならなかったし、これがheadlessで動作しなかった。ビルドファイルをコミットする覚悟があるプロジェクトでも、プラグインのルックアップすらjuce_eventsなしにはできなかったし、juce_eventsだけ使ってjuce_gui_basicsを使わないコードを書くことはほぼ無かっただろう。headless化が具体的にどこまで基盤を変更することになったのかはわからないけど(ソースツリー差分で調べようと思えば調べられそうではある)、juce_events依存部分が整理されたことを期待している。

なおLinuxのみらしい。CIサーバ上でheadlessのメリットがあるのはLinuxくらいなので、これで十分だ(GUIなしのWindowsmacOSがあれば話は別だけど)。

Linux VST3サポート

ついにLinux上でもVST3がサポートされるようになった。だいぶ前にホスト側だけでもと思ってパッチを作ってpull requestを送っていて、最近も「VST3対応しなさそうだから自分で作って公開するか…?」となっていたのだけど、公式対応が出てきたのでこれでひと安心だ(まだリリースされたわけではないが)。

JUCEのアイデンティティクロスプラットフォームでオーディオプラグイン等をビルドできることであり、VST3サポートが無い問題はLinux系音楽開発クラスタにとっては致命的だった。VST2が利用できなくなった結果、VST3という出口のない状態で、JUCEのAPIを使って書いたコードがLinuxでは何の価値も無くなってしまったのだ。LADSPA v1サポートはホストだけだし、そもそもv1にはMIDIサポートが無い。そしてLV2サポートも無い。

これが理由でかつてJUCEを使っていたLinux系のオーディオプロジェクトのいくつかが本家JUCEを捨ててLV2サポートのあるforkやDISTRHOに切り替えたり(dexedやADLplugなど)、あるいはJUCEを捨ててLV2に移行する(sfizzなど)といったトレンドが発生していた*3し、わたしも別のものに移行するかJUCEでないものを作るしか無いと思っていた*4

なお、昨日の時点で関連コードがjuce6ブランチに追加されている。

DSPのビルディングブロック

DSPにはディレイ、コンプレッサー、フェイザーなどいくつかの定番フィルターがあって、それらはもちろん単体でもオーディオプラグインとして機能しうるものだけど、実際のオーディオプラグイン製品とくに楽器の類では、これらをオプションとして最初から組み込んでいるものが多い。そういったアプリケーションを簡単に作れるように、DSPのビルトイン部品が追加された…ということだろう。

WebViewまわりの刷新

最新のWebViewが各プラットフォームで利用できるようになるらしい(一度も使ったことがないのでわからない)。blueprintなど、JUCEでも最近のアプリケーションのGUI部分をjuce_gui_basicsではなく独自に作成するトレンドもあり、WebViewサポートによっていわゆるガワネイティブ的な使い方も出てくるようになる可能性がある。

総括

PACEによる買収は、まだ今後どうなるか分からないのである程度注意しながら様子を見ていきたいけど、とりあえず資金が確保できたことで開発が先に進むようになった側面があるので(OracleによるSun買収もそうだったとは思う)、短期的にはJUCE6のポジティブな計画をたのしみに待ちたい。

*1:まあそうは言っても、JUCEはもともとTracktion社から出てきたもので、彼はtracktion_engineにもcommitしていたりするので、今後もそんな関係かもしれない

*2:わたしが眺めているtheaudioprogrammer discordにbuild-systemというチャンネルがあって、そこで観測した

*3:これはJUCEをLinux方面で使っていないと滅多に目にすることはなかったと思う。iPlug2などLinuxサポートの無いライブラリに比べたらJUCEは比較的マシなほうなのだけど、それでは不十分だったということだ

*4:今でもちょっと思っている節はある

3月の開発記録

4月ももう1/3くらい過ぎているのに3月かよ?ってなりますが、PCを修理に出したり、そこに至るまでそもそもPCの不調にやられたりでいろいろ止められていた感じです。

3月からいよいよ世間がコロナ一色になって落ち着かないですね。わたしは世間一般でようやく自分の在職時代の日常だった在宅勤務が普及するようになったのを横目で眺めながら、家でゲームしたりしていました(半分くらいは↑のせい)。3月はどちらかといえば生産少なめです。

目次

aap-juce

先月も書いていたAndroid用オーディオプラグインのプロジェクトにJUCEを統合するやつ、あまりにも手作り作業が多かったので、これを誰でも手順通りにやれば作れるように作業フローをまとめてCIできるところまでもっていこう、というhigh意識でちょいちょい作業しました。

github.com

Android用に組み込むと言いながら、実際にはAndroid用とemscripten用の2つをビルドしていて、これはだいぶ面倒なことではあります。ひとつには、先月書いたjuce_emscripten app gallaryのページもCIでリリースしたいという希望があったのと、もうひとつは後で詳しく書くUI統合で利用する目論見があったためです。juce_emscriptenのデモは、2月にやりたいと書いていたtracktion_engine統合の実験も兼ねて移植したStepSequencerDemoも追加してあります(統合サンプル自体は未着手)。tracktion_engineの開発者もTLで見つけたらしく、質問されたりしていました。

juce-demos.atsushieno.dev

それはさておき、ビルド自動化にあたって一番問題だったのはやっぱりProjucerが生成するMakefileemscripten用)を手作業で加工していた部分なので、ここは諦めてProjucerに手を加えました。

その過程で気づいたのですが、juce_emscripten自体はソースツリーがJUCEからのforkになっていなかったものの、実際には本家からのforkとして公開するのも全然難しくはありませんでした。なので現在はそういう構成でJUCEの独自forkとして発展させています。(juce_emscriptenの本家Dreamtonicsは現在進行中の製品開発で忙しいようなので、この辺はあっちが落ち着いたら連携して統合する予定です。)

結果的に以下の5つを移植してビルド自動化して、半分くらいはまだ謎クラッシュしたり音が出なかったりといったところまではやってある感じです。

  • AudioPluginHost
  • andes
  • SARAH
  • dexed
  • Magical8bitPlug2

それぞれどんなものなのかは、(Androidではありませんが)juce_emscripten app gallaryで見ることができます。

ちなみにビルドはGitHub Actionsで行っています。これは3月になってようやくactionsのAndroid SDKコンポーネント不足が解消されることによって実現しました。Android Studio 4.0の想定するAndroid Gradle Plugin 4.0.x系列が使用するCMakeが3.6から3.10.2にバージョンアップしたのですが、去年12月に試したときは、GitHub ActionsのVM (ubuntu-18.04) ではAndroid SDKに含まれるcmake 3.10.2がインストールされていなかったので、actionsに直接プルリクを送りつけて対応を求めたのですが全然反応が無く、2月になって他のユーザーも問題に気づき始めてコメントされるようになってから、ようやく3月にマージされました。

また、GitHub Actionsで1つのサンプルをビルドするのにかかる時間は20〜30分、それが5本あるので、1ビルド150分くらいかかっています(!) 1つのアプリをビルドすると500MBくらい消費してしまうので、intermediatesを削除して回ることでようやく可能になっています… 500MBのfree tierでもビルドできるようにしたいのですが(今はProの1GBを使えている状態)、500MB削るのは困難そうです。

UI統合の再検討

aap-juceでjuce_emscriptenを使っている主な理由は、UI統合のスキームとして使えるんじゃないかという目論見があったためでした。ロジックとなるオーディオ処理はプラグイン側のアプリケーションで処理するが、UIはjuce_emscriptenで生成したHTMLをホスト側のアプリケーションでWebViewとしてロードする、という設計が最も合理的なのではないかと思ったためです。

ただ、実際にjuce_emscriptenのアプリをAndroid WebViewで実行してみると、途中でエラーが出て止まってしまいます。ChromiumのDevToolsにあるremote devicesの機能を使ってデバッグしてみると、emscriptenが内部的に使用しているAtomicsAndroidでサポートされていないことがわかりました。GUI部分にatomicを使う必要はないはずなのですが2020/6/1追記: GUIコードもオーディオプラグインパラメーターを直接いじる時にはatomicsを使う必要がある場面があります、これがJUCEの膨大なコードベースから呼び出されるlibc等のどこで使われているかは、emscriptenの標準ライブラリに踏み込まないとわからなそうなので(juce_emscripten自体はJSのAtomicsを参照していない)、ちょっとこれは無理ゲーの香りがして中断しています。

UIをHTMLで実現するというアイディアはjuce_emscriptenでなくても可能なので、このアプローチ自体は引き続き考慮しています。たとえば、JUCE方面ではUIをReactで記述するblueprintというプロジェクトがあります。

sfizzからARIAまわりを探る

2月にはtracktion_engineを移植して音楽を鳴らせるようにしたい、という話を書いていましたが、音楽を鳴らすためには再生エンジンの他に楽器として使えるプラグインが必要です。JUCE方面ではjuicysfpluginというFluidsynthを使ってサウンドフォントベースのinstrumentプラグインがあって、最新のFluidsynthがAndroidでも動くことはよく知っているのですが(何しろ自分が移植したコードが公式なので)、これ自体はgtkを使ったりしていて、移植するより自前で作ったほうが早いかな…でもfluidsynth相当ならMIDIプレイヤーでもいいんだよな…みたいなことを考えていました。

それでふとsfzならkey switchとかもサポートしているし、もう少し高機能なんじゃないかと思って、OSSの合成エンジンを探して(何しろLinux方面では一番ポピュラーなLinuxSamplerが非OSSなので)、まずはJUCE統合もあるSFZero-Xを見つけました(実際にはSFZeroを見つけてその中で最も新し目のforkを見つけたわけですが、細かいことはおいといて)。それで、sfzformat.comからリンクされているsfzを試してみたのですがサッパリ期待通りに鳴らない。どういうことだろうと調べてみて、どうやらSFZeroはkey switchサポートも含めていろいろ機能が足りないということがわかりました。それで他にもいろいろ試した結果、OSSではsfizzが良さそうだということになりました。

sfizzはLV2をサポートしていて、QTractorで試しに使っている範囲ではちゃんと音が出るようなので、Androidに移植するのも現実的であるように思えます。ただ、sfzによってはSforzandoの独自拡張として定義されているARIAというUIマークアップW3Cとは無関係、仕様も未公開)もサポートしているものがあって、これをまずsfizzでサポートしたい気持ちがちょっとあります(ほぼ未着手)。ARIAは単なるXMLマークアップで内容もシンプルそうなので、SVGあたりに変換すればWebViewで表示できそうです…というあたりでさっきのUI統合の話と繋がります(!)

ちなみに、sfzで移植したら面白いことになりそうだと思っているのは、sfzformat.comからもリンクされているこの辺のギター音源とかです。keyswitchやvelocityいかんで各種奏法を切り替えられるし音色も使い勝手が良さそうです。

unreal-instruments.wixsite.com

あと、関連してLV2をVSTとして使うlv2vstというプロジェクトがあって、コレがあるとTracktion WaveformのようなLV2をサポートしていないLinuxDAWでもLV2音源が使えるのですが、LV2 featureまわりの解決に問題があってsfizzがロードできなかったので、その辺を修正するパッチを書いて取り込んでもらいました。最新版ではsfizzもロード出来ますが、lv2vstはnon-X11 UIをサポートしておらず、sfizzがファイル名を受け取るためのAtomポートにファイル名をバイト列として渡せるルートが無いので、sfizz側に手を入れないと実質使えない状態です。わたしは諦めてsifzzを組み込んだJUCEプラグインをちょっとだけ作ってその後放置しています…

guitarixとそのUIを眺める

これはどちらかといえばデスクトップ側でsfizzとギター音源を組み合わせてからの話なのですが、ギター音源があってもそれ単体だとありがたみがありません。本格的なオーバードライブやアンプを組み込んで音を作りたくなるはずです。Collectiveとかで適当にプリセット音色をいじっているときは組み込みエフェクトだけでもそれなりの音が作れますが、Androidには移植できないのでどこかから持ってくる必要があります。

Native InstrumentsのKompleteで遊んでいた時はGuitar Rigっていう製品があったけどああいうものはLinuxには無いのか…と思って探したら、ありました。知ってました。Guitarix。今でもsourceforgeで(!)開発されていて、LV2もサポートしています。これなら移植しがいがありそうです。

しかもソースを覗いてみるとなにげにwebuiなんてディレクトリまであります。コレもHTML UIの仕組みに乗っけやすそうなやつなので、もう少し深入りしてみようと思っています。ただ、enyo.jsとかいうやや古そうなフレームワークなのと、今guitarix自体がUIをgtkなどからx11に移植しつつあって、これはもしかしたらemscriptenで直接サポートできるUIになるのではないかという期待もあり、とりあえず移植が終わるまで待っています。

Androidネイティブバイナリビルドの動向

sfizzにしろguitarixにしろ、LV2プラグインなので、LV2がプラグインフレームワーク開発の出発点であるわたしとしては着手しやすいものです…となるはずなのですが、実際にはLV2サポートの根幹となるネイティブライブラリのビルドを整備して、aap-juceのリポジトリと同じくらい手順フローを整備してプラグインを簡単に移植できるようにしたいところです。

そんな中、AndroidオーディオチームがOboeのパッケージを新しく"prefab"フォーマットのaarで配布するよ、という話が流れてきました。prefabというのはAndroid Studio 4.0(用のGradle Plugin)で新しくサポートされる形式です。

google.github.io

詳しくは今回は省きますが、これまでのaarとは、拡張子こそ同じだけど根本的に違う内容になっています。そしてこのprefabをビルドするための仕組みとして、Googleはndkportsという仕組みを併せて公開しました。

android.googlesource.com

1つ以上のネイティブライブラリのソースtarballとビルド記述ファイルとなるport.ktsがあれば、後はndkportsのビルドスクリプトがNDK各種ツールチェインを駆使してaarまでビルドしてくれるという代物です。という目標はいいのですが、現実にはまだまだ機能が全然足りない感じです。さいわいprefabはndkportsが無くてもビルドできるので(自前でzipアーカイブすれば!)、ある程度の省力化・ビルド自動化はできそうです。

もっともこれでは開発がスムーズになる気がしないので(フレームワークやLV2ビルドに手を入れたらアプリケーションをAndroid StudioからF9一発でデバッグできるようにはならない)、パッケージング以外の場面ではまだ取り込めないかもしれません。いずれにしろ、4月はこの辺に手を出すタイミングなのかなと思っています。

2月の活動記録

早くも冬が終わりそうで、基本的に冬型人間のわたしとしてはこのままずっと冬が続いてほしいし皆さんには永遠に春が来ないでほしいと思っているのですが、いかがお過ごしでしょうか。(時候の挨拶)

というわけで2月の活動記録です。

目次

Music Tech Meetup #1

まず2/6に無事開催できたMusic Tech Meetup #1。今さら書くのはかなり遅いのですが、参加してくださった皆さん、その他関係者の皆さんありがとうございました。今月は後半になって勉強会やカンファレンスの類が全滅に近い状態ですが、こちらは結果的にはだいぶギリギリのタイミングで開催できた感じになりました。これもひとえに日頃の行い…! (今ほとんどのイベント運営者を敵に回した気がする)

初開催だったこともあって、公開前には「10人くらいになるかもしれないし50人埋まるかもしれないけど、どんな人数でも対応できるようにしておこう…」くらいの気持ちで開催したのですが、翌日にはwaitlistになってしまうくらいの申込みをいただいてテンションが上がりました。

当日のついったらんどの様子はtogetterでまとめておきました。

togetter.com

また、最初のADC2019振り返りセッションについては動画を公開しました。動画上げる前に一応チェックしなきゃな…となって後回しにしてしまって、いざチェックしてみたら音量とかがイマイチで加工を繰り返すことに…

www.youtube.com

また微分音セッションについては、時間制約のないかたちで再演されている動画が公開されているので、こちらを見てください。これはお二人にセッションをお願いして大正解だったと思っています(自画自賛

www.youtube.com

以下KPT

50人規模の勉強会を自分たちで回すのは初めてだったので、わからないことがそれなりにあったりしました。今回は特に他のスピーカーを探してきてお願いするというタスクがあり、イベント全体の時間と帳尻を合わせたり、内容のバランスをどうするかで悩んだりしました。LTで本編に足りなかった成分がある程度補給できたこともあって、結果的にはとてもバランスがとれて良かったと思っています。可能なら次回もバランスよくいきたいです。

タイムキープ。3セッションにしたので余裕があるつもりだったのですが、逆に全体的にざっくりやっても大丈夫だろうと慢心した結果、あろうことか自分たちのセッション時間が伸びて余裕がなくなるという恥ずかしい事態になっていまい、大いに反省しています…(他の登壇者の皆さんにはご迷惑をおかけしました) 今度平日夜に開催するなら、30分トークx3、5分LTx3、30分くらい懇親会、くらいの流れにしたいと思います。

予算も無い草の根勉強会なので、フードはセルフで…!という方式にしましたが、実際には用意していった15人分くらいのお菓子が切れた時点で終わったようでした。まあこれはやむなし…とはいえお菓子ならまあそんなに高くつかなさそうなので次回もこうするかもしれません。ピザとか注文すると1人3000円とかになっちゃうので、当日なんとかするのもやめました。一方で飲み物はソフトドリンクをArt Teknikaさんにご提供いただけたので大変助かりました。

当日までwaitlistがほとんど解消せず、何件か「自分は参加できそうか?」と訊かれたのですが、当日キャンセルの見込みが全くわからず、会場キャパとしても50人はギリギリという認識だったので(多分合ってる)、無理そう…ゴメンナサイ…という感じで応対していました。結局15人を超える事前処理なしの無断キャンセルがあって、入ろうと思えば全員入れた計算になります。さすがに無断キャンセルを無条件でスルーするのは、入れなかった皆さんには心象が悪いと思うので、無断キャンセル勢の皆さんは次回申し込んでもwaitlistが出来たら直前にこちらからキャンセル処理する処置をとる予定です*1。このサンクションの設計は他の勉強会でも有効な措置なんじゃないかなと思うので公開で書いておきます。

次回も開催したいと思っていますが(セッションをお願いしたい人リストはけっこう溜まっているので…!)、数カ月は先とするつもりです。もともとそのつもりだったのですが、COVID19の流行もあって、今後数カ月はイベントの企画が厳しくなりそうだと思っています。今度は土日祝に半日開催としたいのですが、会場を貸してくださる会社でも外部の人への土日の会場貸出はやっていないことが多く、なかなか難しそう…(!?)

ADCセッションメモ

Music Tech Meetupのセッションに先駆けて、数多のセッション動画を眺めていた時に、ある程度メモ書きを残しておいて公開したほうが有益そうだなと思ったので、見ながらちょいちょいまとめていました。まとめたものはとりあえず demo.codimd.org に公開しています(名前もdemoだしいつまであるのかわからないサービスなので、いずれどこかに移転するかもしれません)。まとめきれなかったものもいくつかあるので、ちょいちょい追加するかもしれません。

勉強会参加/LT発表未遂

今月は自分としては珍しく小咄のネタがいくつかあったので、後半にWebAssembly Nightやshibuya.apkでLT登壇するつもりで登録していたのですが、全てイベント自体がキャンセルとなってしまいました。さらにDroidKaigiの中止と技術書典の中止が重なって、今月は完全に自宅警備員モードということになってしまいました(あれ…平常運転では…)。今回は技術書典もほとんど手伝っていなかったのですが、仕掛かりの作業を停止して積みゲーの消化に勤しんでいました(!?)

現時点ではだいぶ知見が広まったと思いますが、この種の大規模なイベントは安易にキャンセルできるものではなく、実施しても中止しても大損害が生じるので、個人的には税金で運営でもしているんでない限りは中止であれ開催であれ支持する立場です。M3は何とか開催されそうなので、楽しみにしています。本来だったらまさかの技術書典8とかぶって参加できないはずだったので…(!)

もしイベント運営者の事情をきちんと知りたいという人がいたら、DroidKaigiについて運営メンバーの人たちがかなり詳しく語っている動画が公開されているので、見てみると良いでしょう。

www.youtube.com

発表するつもりだったネタは主にjuce_emscriptenまわりやAndroid NDKまわりだったのですが、まあ鮮度が落ちるというほどのこともないので、いずれ内容を最新情報に改めて喋ろうと思っています。

juce_emscriptenでオーディオプラグインをWebAssemblyで動かす

今月はちょいちょいjuce_emscriptenに手を加えて、まずJUCEで一番よく使われているであろうAudio Plug-inのプロジェクトのStandaloneアプリケーションをjuce_emscriptenでも動かせるように手を加えました。最新のmasterに取り込まれています。

これまでは実質的に "GUI Application" しかビルドできなかったので、ごく一部のアプリケーションだけがwasm化できただけのように見えましたが、オーディオプラグインのプロジェクトはgithub等に少なからずあるので、これまでに比べるとだいぶ気軽に楽しめるというか実験の成果が出やすくなったと思います。とりあえず成果をいろいろtwitterに上げていたので引っ張り出しておきます。

RtMidiとjuce_emscriptenにWeb MIDI APIサポートを追加

juce_emscriptenではMIDIサポートが実装されていません。なので、MIDI入力を前提としたオーディオプラグインのStandaloneプロジェクトでは、実行できたとしても音を出す手立てがありません。EmscriptenならWeb MIDI APIを使うことも出来るはずだし、実装はそんなに難しくはないのではないかと思っていました。(まあEmscriptenを使ったことは例によって数日しか無いのですが)

C++クロスプラットフォームMIDIをサポートするのによく使われるのはportmidiやRtMidiですが、これらにもWeb MIDI APIサポートはありません。どうせならいったんRtMidiでWeb MIDI APIをサポートするようにして、それをもとにjuce_emscriptenでWeb MIDI APIサポートを実装するようにすれば、今後のメンテナンスは半分くらいはRtMidiコミュニティに投げられるのではないか(!?)と思って、RtMidi経由で実装しています。

RtMidi単体でemscriptenを活用して動かせるMIDIアプリケーションは何一つ知らないので、juce_emscriptenはいいネタになったと思います。

最終的には繋がるようになったので、仮想ポートを自動生成するxmmkからjuce_emscriptenで動くオーディオプラグインのstandaloneアプリにMIDI入力メッセージを送りつけて、(臨むなら)それをローカルで起動しているfluidsynthにMIDI出力メッセージとして送る、なんてこともできます。(画像は入力のみ)

JUCEオーディオプラグインAndroid上でプラグインとして動かす

1月までの時点でJUCEアプリケーションで自分のAndroidプラグインフレームワークをサポート出来るようになっていたので、今月は逆にJUCEを使ったオーディオプラグインを自分のフレームワークで動作するプラグインとしてビルド出来る仕組みを構築していました。そもそもJUCEはAndroid用にAudio Plug-inプロジェクトをサポートしてこなかったので、そこから手さぐりでJUCE本体には手を入れずにサポートを追加する方法を模索しました。この辺の話は、実装が出来た段階で先日のエントリーでまとめてあります。

JUCEオーディオプラグインプロジェクトはいろいろあるのですが、Androidでも動作する比較的簡単なものとして、とりあえずandesSARAHを移植してあります。関連して、他にもADLplugなどいくつかのオーディオプラグインAndroidで動かしています(自作フレームワーク統合はまだ)。

この辺は、先のjuce_emscriptenのサポートと合わせて、自分のforkとしてビルドすることが多くなりそうなので、いずれWebサイトを作ってまとめておこうと思います。特にwasmビルドはWeb上にデプロイできるわけですし。

emscriptenはさておき、Androidで自前のプラグインフレームワークでJUCEプラグインが動くようになると、こういうことが出来るようになります。

今回はどちらもJUCEプロジェクト、別々のAndroidアプリケーションインスタンスです(まあ先月もLV2プラグインを動かしていたわけで、それも別々のアプリケーションインスタンスですが)。

What's next?

次は…いよいよtracktion_engineあたりを動かしますかね。tracktion_engineが動いたら自作MML環境で作った音楽がAndroid上で再生できる用になる日も近い…?

それと別に、UI統合についてそろそろ真面目に検討しようと思っています。ホスト部分はtracktion_engineが動けばコンセプトとしては十分に目的を達成できていると思うので、足りない部分を継ぎ足していきたいですね。

*1:再申込拒否まではしない

JUCEベースのオーディオプラグインを自分のフレームワークに輸入する

こちらは二部構成の後編になる。前編はこちら。

atsushieno.hatenablog.com

タイトルは対照的ではないけど、今度は「JUCEでカスタムAudioPluginFormatをホストする」の反対側、すなわちJUCEを使って自分のプラグインフレームワーク用のオーディオプラグインをビルドできるようにするために、JUCEモジュールを作成する。

JUCEの本質的な特徴のひとつは、1つのコードベースで複数フォーマットのオーディオプラグインを簡単にビルドできることだ。すなわちオーディオ処理の部分はコード共有できるので、自分が独自にオーディオプラグインフレームワークを開発しているときは、これを活用できるようにしたい。

ホスト側をサポートするだけでは、DAWの基盤で自分のフレームワークをサポートすることは出来ても、オーディオプラグインのほうは実例が増えないので、JUCEをプラグイン作成のほうでサポートできるようにするのが手っ取り早いやり方だ。

目次

JUCEでサポートで必要になる機能追加

JUCEは、ごく大まかに言えば、フレームワークライブラリと、ビルドシステムであるところのProjucerの2つから成り立っている。オーディオプラグインが単一の共有ライブラリで完結していることはおそらく多くなく、一般的には何らかのバンドル(パッケージ)になっている。ライブラリを利用してコードを実装できるのはフレームワークの部分だけであって、パッケージングの部分ではビルドシステムが重要になる。これらは切り離して考えたほうがよい。つまり:

  • オーディオ処理コードの自家製フレームワークへの繋ぎ込み → フレームワーク側の拡張
  • リソースの生成とパッケージング(バンドル作成) → Projucerのビルドシステムの拡張

この2つのうち、Projucerの拡張は、あまり推奨できないし、自分でもやらなかった。ただそれがなぜなのかを説明するためには、フレームワークの方から先に説明する必要がありそうだ。

JUCE本体に手を加えずに機能を拡張する

どんなライブラリやフレームワークも、いったん安定版をリリースして広く使われるようになったら、可能な限りAPI互換性を維持するようになる。互換性が破壊されると、自分が書き換えられないユーザーのコードがビルドできなくなるからだ。開発者はある日突然自分が使っていたライブラリのアップデートのせいでビルドが通らなくなると、アップデートしなくなるかもしれない。つまりユーザーが減る。これはユーザーベースが無くても良い開発者の場合は問題にならないが、そんなケースは相当まれだろう。

Androidや.NETの開発者としてのキャリアが長い自分から見ると、JUCEは比較的APIを破壊的に変更しているほうだと思うが(基本的には良い特徴ではない*1)、それでも破壊的変更は原則として不必要には行われないはずだ。

JUCEに何かしらの機能を追加する場合によく見られるのが、JUCE本体をforkして、必要なコードに手を加えることだ。これは実のところあまり持続性が無いやり方で、可能な限り避けたほうが良い。いったんforkを維持する方針を採ってしまうと、ROLIが新しいバージョンをリリースするたびに、コードの変更からコンフリクトを解決しなければならなくなる(これは変更が大きければ大きいほど問題になる)し、実行時の意図しない挙動の変更によって問題が生じる可能性も広がる*2

もっともJUCE本体にかかるさまざまな特徴*3が、これを困難にしている。だからJUCEのforkは他のフレームワークに比べたらずいぶん多い。どれも開発者にとっての関心の対象外になった時点で追従しなくなっている。

JUCEはなるべくforkせずに、基本的にはJUCE APIに沿って、破壊的に変更されないAPIを使ったモジュールやライブラリとして実装したほうがよい。

Projucerサポートの部分をあきらめる

さて、ここでもうひとつの本題であるところのProjucerに戻ろう。非破壊的変更の原則はフレームワークにしか適用されない。生成されるビルドスクリプトMakefileなど)には常に破壊的な変更が入りうる。だからこれに依存する仕組みは入れられない。

アプリケーション開発者に公開されていて破壊的変更が無いことを暗黙的に約束されているのは.jucerファイルにおける設定部分のみだ。それ以外ではProjucerにはAPIは無いので、外部から利用する手立てがない。自前フォーマットのサポートがProjucerに入っているはずはないので*4、Projucerで行われている処理に機能を追加するためにはforkするしかない。しかしforkしたら持続性が無いことは説明したとおりだ。

ではどうすべきか? Projucerで行われている部分はもうあきらめて、自分でパッケージバンドル作成ユーティリティを作ったほうがよいだろう。幸いなことに、われわれ(誰)に必要なのは、多様なプラットフォーム向けにさまざまなパッケージングを求められるVST3のようなフォーマットに手を加えることではなく、あくまで自分のフォーマットに沿ったパッケージングだ。

「理想的な」ビルドシステムへの繋ぎこみを考えるのであれば、ProjucerはCMakeをサポートしていて(CLionサポートなどと称しているが、要するにCMakeサポートである。JUCEは伝統的にCMakeを毛嫌いしていたので正面から認めたくないだけだ)、CMakeは現状では最も幅広くクロスプラットフォームでサポートされているビルドシステムなので、CMakeモジュールとして自分のパッケージフォーマットをビルドする仕組みを作るのが良いかもしれない。

ProjucerのCMakeサポートはそこそこやっつけ仕事感があるので(たとえばファイルパスをLinuxMakefileから取り込む部分が雑でCMakeではビルドできないなんてことがざらにある)、話半分で聞いておいたほうがいい話かもしれない(「理想的な」というのはエクスキューズだ)。

次節からコードに踏み込んで解説することになるのだけど、Projucerでやっている仕事をどうやって実現するかについても後ほど改めて触れていく。

プロジェクト構成 - Audio Plug-inプロジェクトの概要

さて、コードの話に踏み込むまでにすっかり説明が長くなってしまった。JUCEのオーディオプラグインプロジェクトの「コードを」取り込む話に入っていこう。

最初に、そもそもJUCEのオーディオプラグインプロジェクトについて書いておこう。JUCEのアプリケーション テンプレートには、GUI ApplicationやOpenGL Application、Audio Applicationなどと並んでAudio Plug-inというものがある。JUCEでオーディオプラグインを開発する人はほとんどこれを利用しているはずだ。

プロジェクト テンプレートによって何が異なるかというと、Projucerで表示され.jucerで保存されるオプションが変わってくる。Audio Plugin-inの場合は、ここにプラグインの基本情報(名前とかベンダーとか)やVSTAU・AAXなどに固有のオプションが入ってくる。これが生成される.vcxprojや.xcodeproj、Makefileなどにも影響するというわけだ。

オーディオプラグインプロジェクトではstandaloneアプリケーションをビルドすることもできる。プラグイン実装のロジックをデバッグする時には大抵これを使うことになるだろう。繋ぎこみの部分にバグが入ってくることは通常はあまり考えなくてよいし、それは基本的にはJUCE本家の開発者がやるべきことだ。もっとも、われわれ(?)は独自のフレームワークへの繋ぎこみを実装する立場なので、この意味ではJUCE開発者のポジションに近い。

独自のライブラリへのglueコードを構築する手段として一番便利そうなのは共有ライブラリで、ProjucerではStatic LibraryやDynamic Libraryのプロジェクトも作成できるが、これではプラグインメタデータ生成が行えないし、standaloneプロジェクトをテンプレートから自動生成してくれるAudio Plug-inのほうが便利だ。何より既存のプラグインを取り込んでサポートしたいのであれば、こちらをサポートする必要がある。

AudioProcessorの"Wrapper"を作る

Audio Plug-inプロジェクトを新規作成すると、コードとしてはjuce::AudioProcessorの派生クラスが定義される。これを自分で実装することになる。API自体はそんなに難しくはない。ホストを実装する時に出てきたjuce::AudioPluginInstanceprocessBlockと同じだ。そもそもAudioPluginInstanceAudioProcessorから派生しているので、同じ関数をプラグインを作るときにもoverrideするということだ。

Audio Plug-inプロジェクトのコードの部分は大半がこの実装になるので、実装すべきコードについての説明はもうほぼ終わりだ(!)。あと他にAudioPluginEditorを実装している場合もあるが、これを繋ぎこむ処理が必要になる場合については話がややこしくなる(そもそも繋ぎこむ先のプラットフォームGUI固有の話になる)ので、基本的には何もしなくてもJUCE側でよろしくやってくれることを期待することにする。

自分のフレームワークに繋ぎこむためには、むしろ自分のフレームワーク側でどのようなプラグインのエントリーポイントを用意すべきか、どんなコードでオーディオ処理を行うべきか、といったところから考えなければならない。これはしかし自分のフレームワーク向けのプラグインを開発するのと、基本的には何も変わらないだろう。自分のフレームワークには自分なりのオーディオバッファデータ構造があるはずだが、概ねfloat配列などに帰着するだろうから、多くの場合はフォーマットの変換処理までは必要ないだろうし、なるべくコストのかからない方式で変換してやると良いだろう。実行時にメモリ確保しないようにすることが重要だ。もちろんMIDIメッセージに関してはこれ単純変換で済むという話は当てはまらない。

JUCEがVSTやらAUやらのサポートをビルドするときに何をしているかというと、基本的には.jucerで指定されたオプションに基づいて、C++コードに何を有効にしているかを宣言する#defineを追加して、その状態次第で各プラグイン向けのコードがコンパイル対象になる。たとえばVST3の場合はJucePlugin_Build_VST3が有効になるとjuce_VST3_Wrapper.cppのコードがコンパイル対象になる。この中にVST3プラグインで必要になるGetPluginFactory()が定義されているので、VST3用にビルドされたコードはVST3プラグインのコード部分として機能する、というわけだ。

これらは公開APIになっている必要はないので、juce_audio_plugin_clientのAPIリファレンスを眺めても、プラグイン固有のAPIは何も出てこないというわけだ。なのでコードも別にWrapperという名前を付けなくても良いのだけど、伝統的に他のプラグインフレームワークAPIの処理を自分たちのAPIでwrapする感じになるのでwrapperという名前がよく使われるようだ。

ちなみにJUCE側にはwrapperの基底クラスとして使うべきものは何もないが、wrapperを実装するためにはプラグインプロジェクトで作成されるAudioProcessorインスタンスを取得出来る必要がある。これはcreatePluginFilter()という、Projucerが自動的に生成する非公開の関数を呼び出すことで使えるようになる。

作ったwrapperはJUCEモジュールとして再利用できるようにしておくと、プラグインを移植する際にモジュールを追加するだけで済む…可能性が上がる。実際に可能かどうかは、次節で説明する残りの作業の内容次第だ(プラグインフォーマットによって変わる)。

Projucerに代わってメタデータをコードから生成する

さて、ここまでコードの実装を頑張ったところで、このプラグインについてビルドして利用できるのはStandaloneビルドのアプリケーションとSHARED_CODEと呼ばれるstaticライブラリのみだ。自分のプラグインフォーマットのためのビルドスクリプトでは、これをリンクして共有ライブラリなどを完成させなければならない。前述の通りProjucerは使えないので、これは自前で実装する必要がある。

もう一つ、残されているのは一般的にプラグインに必要とされるメタデータ生成だ。個別のプラグインAPIでは、コード中のAPIとして実行時に取得可能になっている可能性もあるが、JUCEではそのようなAPIは用意されていないので、ユーザーもそのようなコードを実装してはいない。

ではどうやってVST3やAUメタデータが生成されているかというと、.jucerファイルで指定された作者情報などは、ProjucerがC++#defineディレクティブに変換していて、各wrapperではこれを利用しているのである。juce_audio_plugin_clientの公開APIには含まれていないので、あくまで自分のモジュール内で、該当するディレクティブを見つけて、その内容を処理してメタデータを生成するコードを実装するしかない。

Projucerはもちろん独自プラグイン形式のメタデータを生成してくれないので、この枠組みの中で自分にもできることは、wrapperコードの一環としてメタデータを生成する関数を定義して、これを利用する外部ツールをビルドすることだ。ビルドされたSHARED_CODEのstaticライブラリをリンクすれば、このコードを呼び出せる。

このアプローチを採用しているのが、ADLplugというプラグインのプロジェクトで使われているJUCEのforkに含まれるLV2サポートの実装*5。このコードで定義されているLV2リソース生成のためのコードは、プラグイン本体としては全く実行されないが、LV2のTurtle形式のメタデータを生成する際に使われる。JUCEでLV2をサポートするためにはこういうアプローチが採用されている。

まとめ

ここまでの流れを踏まえて、Audio Plug-inプロジェクトを移植する全体的な作業をまとめる。

  • もしまだ作っていなければ、独自プラグインフレームワークへ繋ぎこむモジュールを実装する
  • 移植対象のプラグインのソースの.jucerをProjucerで開いて、そのモジュールを追加する
  • ビルドする
  • もし必要かつまだ作っていなければ、繋ぎ込みモジュールにメタデータリソース生成のためのコードを実装して外部から呼び出せるようにする
  • メタデータリソース生成(無理がなければパッケージバンドル生成)のためのツールを用意する(エントリーポイントからその関数を呼び出すようにして、Audio Plug-inのライブラリにリンクするだけで十分)

これでいけるはずだ。出来てしまえば難しいことはあまり無いだろう。

*1:ゼロ・トレランスで破壊的変更を拒絶するAPIは成長に対するコミットメントが無く未来もないことが多いので、「基本的には」という留保を付けている。C++ライブラリがそもそもそういうものかもしれない。

*2:挙動に変更に起因するバグは外部モジュールにしていても当然発生するけど、意識のすり合わせのない「変更」ではその可能性がぐんと上がる

*3:伝統的に外部モジュールによる機能拡張をあまり歓迎しなかったC++コード基盤とか、多様な環境をより簡単にサポートするためにソースから全てビルドするがゆえにバイナリ配布によるAPIを考慮してこなかったとか、そもそも「Juleの個人的なユーティリティだから改造したいやつは好きに使え」だった歴史とか

*4:ちなみに今回コードを書いていて発見したのだけど、公式のProjucerでLV2サポートを追加しようとしていた痕跡が今でもコードベースにある

*5:これ自体はDPFという別のフレームワークの開発者による実装かもしれない