JUCE Advent Calendar 2024、4日目のエントリーですが、最近すっかりAndroidではなくデスクトップでJUCEを使わないプラグインホストを作る遊びにふけっているatsushienoです。
オーディオプラグイン開発者は、どのフォーマットであれ、さまざまなDAWでプラグインをロードできなかったり正常に動作しなかったりといった問題に悩まされてきたことでしょう。これはJUCEのようなプラグイン開発フレームワークを使っていると、JUCEにさまざまな部分で面倒を見てもらえている側面があります。特定のDAWでのみ動作しない問題が、何年にもわたってJUCEチームに分析され、回避策が実装されているということです。
具体的にJUCEにどんな面倒を見てもらえているのか、興味が出てきませんか?
実はこれを、全部ではないにしても、大部分(多分)を調べる手段があります。juce_audio_processors/utilities/juce_PluginHostType.hというクラスがあります:
class PluginHostType
{
public:
....
enum HostType
{
UnknownHost, /**< Represents an unknown host. */
AbletonLive6, /**< Represents Ableton Live 6. */
AbletonLive7, /**< Represents Ableton Live 7. */
AbletonLive8, /**< Represents Ableton Live 8. */
AbletonLive9, /**< Represents Ableton Live 9. */
...
そしてisXXX()関数(XXXにはAbletonLiveなどのホスト名)が40件強含まれています:
/** Returns true if the host is the JUCE AudioPluginHost */
bool isJUCEPluginHost() const noexcept { return type == JUCEPluginHost; }
/** Returns true if the host is Apple Logic Pro. */
bool isLogic() const noexcept { return type == AppleLogic; }
/** Returns true if the host is Apple MainStage. */
bool isMainStage() const noexcept { return type == AppleMainStage; }
/** Returns true if the host is any version of Steinberg Nuendo. */
bool isNuendo() const noexcept { return type == SteinbergNuendo3 || type == SteinbergNuendo4 || type == SteinbergNuendo5 || type == SteinbergNuendoGeneric; }
このisXXX()関数の利用箇所を追跡していくと、プラグインをロードしたホストによって、どのような回避策が取られているのか、調べることができます。各DAWにどのような問題があるのか簡単に調べられるわけです。このクラスだけがすべての特別ルールを掌握しているわけではありませんが(たとえばJuceVST3EditControllerにはisBlueCatHost()という関数があり、BlueCat's PatchWorkなどに特化した処理が見られます)、大部分はここから調べられるでしょう。
JUCEを使っていない人も、「JUCEでは対策済みの問題」かどうかを調べられますし、プラグインホストを開発している人も、どんな問題が発生しうるのか知ることができます。
さて。このisXXX()の関数は(本稿執筆時点で)40件以上もあるので、全部を追っかけていると大変そうに見えますが、実は(CLionのclang analyzerで見ている限り)半分以上が定義だけされていて使われていません。実際に使われているのは次の関数だけです。一言で説明が終わるものはついでにこのリストで済ませちゃいましょう:
isAbletonLive()isAdobeAudition()isBitwigStudio(): VST3 on LinuxでchildBoundsChanged()が発生した時にrepaint()も呼び出すように動作を変更しますisCubaseBridged()// "Steinberg Cubase 5 Bridged"isFruityLoops()// FL StudioisLogic(): AudioUnitでバスの情報を取得するとき、Logicだけは最大でも8チャンネルまでしか無い前提で処理されますisPremiere()isReceptor(): VST2のUIでマウスイベントに対応するaddMouseListener()を呼び出しますisReaper()isSonar()// CakewalkisSteinberg()(isCubase()、isNuendo()、isSteinbergTestHost() を含む)isWavelab()(isWavelabLegacy()を含む)
(上記のほかに)どんな問題が回避策として実装されているのか、具体的に見てみましょう:
isWavelab() || isCubaseBridged() || isPremiere(): これらのホストでは、VST2のGUIがUIスレッドから作成されないことがあるようです。isAdobeAudition() || isPremiere(): これらはVST3のGUIをIEditControllerのAPIから正常に取得できないのか、常にGUIを生成できるものとしてコーディングされていますisFruityLoop(): FL PatchはsetActive()を呼び出す前(あるいは呼び出し中)にprocess()を呼び出す実装になっていて、それを防ぐ仕組みが必要なのだとかisAbletonLive() || isSonar(): これらのDAWはMIDI-only pluginをロードしてくれないので、オーディオ出力をでっち上げて対応する必要があるようです(これはJUCE本体ではなくMidiLoggerPluginDemoのコードで実装されているので、JUCEプラグインでも個別に対応する必要がありそうです)
VST3ホストウィンドウのリサイズも個別に対処されていますが、条件がややこしいので、コードをそのまま引用します:
#if JUCE_MAC if (host.isWavelab() || host.isReaper() || owner.owner->blueCatPatchwork) #else if (host.isWavelab() || host.isAbletonLive() || host.isBitwigStudio() || owner.owner->blueCatPatchwork) #endif setBounds (editorBounds.withZeroOrigin());
だいたいこれくらいでしょうか。GUIインスタンスを生成するのにUIスレッドから呼び出されない等は割とビックリする案件ですが、幸いVST2固有の問題のようなので、現代では概ね整理されプラグインの仕組みが開発者に理解されてきたということでしょう。
JUCEが回避策を提供しているDAWの問題としては、これらの他にも例えば「GarageBandやLogic Proがパラメーターのリストを取得する時にパラメーターIDでなくパラメーターリストのインデックスを使用してしまっていて、不要になったパラメーターをプラグインの新バージョンで安心して削除できない」というものがあります。これを解決するのがjuce::AudioProcessorParameterのコンストラクターに渡されるversionHintなのですが、先日の技術書典17で公開した新刊でもう少しだけ詳しく解説しているのでそちらを見て下さい(無料です):
以上ちょっとした小ネタでした。今回紹介してきたのはあくまでプラグイン側(ホストの挙動の違いの影響を受ける側)の実装に関する問題なので、プラグインホストの実装にかかるバッドノウハウの類は、また別途いろいろあります。ただPluginHostTypeクラスのような分かりやすいフラグ集があるわけではないので、収拾して体系的にまとめるのはやや骨が折れそうです。書けそうだったら機会を見てまとめようと思います(期待値低)。