VST3ホストを作ろう!
VST3ホストを作ろう!
@hotwatermoningです。
この記事はC++ Advent Calender 2014の参加記事です。
昨年の記事では、VSTという音楽制作プラグインの規格に対応したVSTホストアプリケーションを作成しました。
しかし、前回使用したVST2.x系列のSDKは開発元であるSteinberg社からの配布が終了してしまいました。 (実は今回取り上げるVST3のSDKの中にVST2の開発に必要なソースファイルは同梱されていますが、VST2のドキュメントは含まれていません)
そのような背景から、今回はVST3に対応したホストアプリケーションを作成します。
VST3の仕様について筆者が誤って理解しているところがあるかもしれません。ご了承下さい。(指摘いただけるとありがたいです)
VST3とは
VSTという音楽制作アプリケーション用のプラグイン規格の概要ついては、前述の記事ですでに紹介してありますので、VSTについてご存じない方はそちらをご参照下さい。
VST3はこれまで標準的に使われていたVST2を更に進化させたバージョンです。
公式サイトでは次のように紹介されています。
"Steinberg は12年に渡って、VST により世界のネイティブプラグインフォーマットを牽引してきました。 最新版の VST3 は、完全に書き換えられたコードにより数々の新機能を搭載すると共に、VST 史上最高の安定性を備えています。 VST3 はバーチャルエフェクト、バーチャルインストゥルメント (VSTi) の可能性をより高め、音楽創造を更に豊かにするテクノロジーです。
VST3 : | https://www.steinberg.net/
VST3ではVST2にはなかった多くの機能が機能が追加されています。
- Audioバスの構成を柔軟に変更できる仕組み
- パラメータやそのプリセットをツリー構造で管理できる仕組み
- バスのバイパス機能
- オートメーションの精度の向上
- などなど
これまでのVSTのプラグインのタイプはVST Effect
かVSTi
かという分類でしたが、VST3では少し状況が変わりました。
プラグインに指定するsubcategories
という|
文字区切りの文字列にFx
が含まれていればエフェクトプラグインとして、Instrument
が含まれていればバーチャル・インストゥルメントとして見なされます。
そして両方含まれていれば、どちらのタイプとしても使用できるプラグインと見なされるようになります。
詳しくは、VST3 API DocumentationのFAQのWhy do Plug-ins need subcategories?
の項をご参照下さい。
さらに、VST2.4のドキュメントにはVST MIDIエフェクト
のドキュメントが付属していましたが、VST3ではこれは廃止されたようです。VST3の仕組みでVST MIDIエフェクト
が実装できるのかもしれません。
VST3はVST2から全く新しく設計された規格のため、VST2との互換性はありません。
モジュールの拡張子もこれまではWindows用バイナリは.dll
, Mac用のバンドルは.vst
でしたが、VST3規格では統一されて.vst3
となります。
このため、VST3として作成されたプラグインはVST3対応のホストでなければ使用できません。
こちらのサイトによれば、最近のCubase, Nuendo, Sonar, Studio One2などではVST3がサポートされていますが、Ableton Live, Bitwig Studioなどではまだサポートされていないそうです。
Supported Hosts | Support | Waves
また、VST3では、Windows環境でのプラグインのインストール先が固定されました。
VST2の時は、Windows環境では、HKEY_LOCAL_MACHINE\SOFTWARE\VST\VSTPluginsPath
に記載されたパスが標準ということになっていました。 *1
しかし、このパスは最初にインストールされるDAWによって勝手な場所が指定されたり、VSTPluginsPath
にパスが設定されていたとしても問答無用でC:/Program Files/VstPlugins
へインストールされるプラグインがあったため、特定場所がVSTプラグインを配置する標準パスとは言えませんでした。
VST3からは以下のパスが標準となります。
------------------------------------------------------------------------------------------------------------------- Priority Location Path Comment ------------------------------------------------------------------------------------------------------------------- 1 Global /Program Files/Common Files/VST3/ native bitdepth: 32bit Plug-in on 32bit OS, 64bit on 64bit OS) 1 Global /Program Files (x86)/Common Files/VST3/ 32bit Plug-ins on 64bit Windows 2 Application $APPFOLDER/VST3/ -------------------------------------------------------------------------------------------------------------------
Macではこれまでより、/Library/Audio/Plug-ins/VST3/
と/Users/$USERNAME/Library/Audio/Plug-ins/VST3/
がVSTプラグインのインストール先としてデファクトスタンダードになっていましたが、これもVST3 SDKでは明記されるようになりました。
------------------------------------------------------------------------------------------------------------------- Priority Location Path ------------------------------------------------------------------------------------------------------------------- ! User /Users/$USERNAME/Library/Audio/Plug-ins/VST3/ 2 Global /Library/Audio/Plug-ins/VST3/ 3 Global /Network/Library/Audio/Plug-ins/VST3/ 4 Application $APPFOLDER/Contents/VST3/ -------------------------------------------------------------------------------------------------------------------
詳しくは、VST3 API DocumentationのVST 3 Locationsのページをご参照下さい。
音楽制作のためにプラグインを使う側からしても変更点の多いVST3ですが、プラグインやホストの製作者側から見た時には更に変更点が多く、内部の実装はほとんどVST2と互換性がありません。
このようにVST2から大きく様変わりしたVST3ですが、個人的にはやり過ぎ感なあるような気がしています。
同じくVSTという名前が付けられていますが、実質別物と言えるほどの仕様の違いがあり、開発の複雑さもVST2の頃より増しているように思います。
VST2よりもフリーのプラグインが少ないのもおそらくVSTプラグイン開発のハードルが上がったためだろうと思います。
Sonarで有名なcakewalkのブログにもそのような発言が見られました。
The Cakewalk Blog » Blog Archive » Developer Notes: SONAR X3 VST Enhancements
とはいえ、VST3のプラグインも、VST3対応のDAWも徐々に充実してきているため、今後はVST3がもう少し活発になっていくかもしれません。
VST3プログラム開発の情報
VST3プラグイン、及びVST3プラグインに対応したホストを開発するには、VST3 SDKを使用します。
SDKは開発元であるSteinbergから入手可能です。(Steinbergの開発者登録が必須となります。)
Developers : | http://www.steinberg.net/
現在最新のSDKはvstsdk360_22_11_2013_build_100.zip
ですので、今回の開発でもこのバージョンを使用しています。
VST3のSDKに付属しているドキュメントの内容はあまり親切ではありません。
そのため開発に際しては、ドキュメントとともにSDKのヘッダーファイルも参照したり、付属しているサンプルプラグインのソースも参考にすることになるでしょう。
SDKのドキュメントは大きく3つのセクションに分かれています。 * VST3 API: VST3に特有のクラスや仕様のドキュメント * Base Module: Steinberg社で使用されている様々なクラスやテンプレートやコンテナ。VST3のクラスもこれらのクラスを使用している。 * VST-MA: VST Module ArchitechtureというCOMのような技術に関する情報。VST3プラグインの仕組みはこの技術をベースにして構築されている。
VST3プラグイン/ホスト作成には、VST3 APIのセクションだけを見ても書かれていない情報があるため、必要に応じて他のセクションを横断的に参照する必要があるでしょう。
ネットで見つかるVST3の開発に関する情報は、VST2のものと比べ非常に少ないです。
[12/02追記]
こちらのサイトでVST3プラグインの作成方法が紹介されていました。解説が丁寧で分かりやすそうです。
C++でVST作り
[12/02追記終わり]
他のプラグイン規格(AAX, RTAS, Audio Unitなど)と合わせてVST3にも対応したクロスプラットフォームのライブラリが公開されていますので、その実装を参考にすると良いかもしれません。
昨年の記事で紹介したVstHostですが、現在最新のバイナリはVST3にも対応しています。
しかし公開されているソースはVST2.4を対象にしていた頃のものなので、VST3部分の情報はありません。
今回作成したプログラム
今回作成したサンプルプログラムのソースはこちらです。
今回作成したプログラムはこちらにあります。(無保証、自己責任でお願いします。)
このサンプルプログラムを試すには、VST3プラグインが必要です。 VST3プラグインを持っていない方は、u-heのzebraletteなんかが無料で公開されているのでオススメです。
ソースコード解説
ここから、今回作成したプログラムのソースコードを解説していきます。また、VST3の仕様に絡む解説もありますので、その部分では見出しに[VST3]を設定しています。
おおまかな構造
プラグインを扱う部分以外の大きな構造は以前のVST2対応ホストと変わっていません。
鍵盤を押すとノート情報が生成され、それがプラグインに送られて、プラグイン内部で合成された波形をオーディオデバイスに送信します。
追加機能としては、VSTiではない、エフェクトプラグインもロード可能になりました。プラグイン内部で加工した波形を確認するために、ダミーの波形をプラグインに入力しています。
また、VST3は一つのモジュール(.vst3ファイル)のなかに複数のプラグインをふくめる機能があるので、そのようなモジュールが指定された場合はどれか一つ選ぶためのダイアログを出す機能が追加されました。
VST2でも一つのモジュール(.dllファイル)のなかに複数のプラグインをふくめる機能はあったようですが、これを利用しているプラグインがほとんどなく、筆者自身その機能に詳しくなかったのでVST2対応Hostではその機能を入れていませんでした。
プラグインを扱う部分は、VST2の頃と大きく変わっています。
以下では、VST3プラグインを扱うための3つのクラス
- Vst3PluginFactory
- Vst3Plugin
- Vst3HostCallback
について、順に解説してきます。
しかし、VST3の仕組みをそのまま扱うとプログラムがややこしくなってしまうため、今回はユーティリティクラスを用意しました。
上記のクラスの解説に移る前に、VST-MAの仕組みとそれを扱うユーティリティクラスについて紹介します。
[VST3] VST3-MAの仕組みについて
前述のとおり、VST3のシステムはVST-MAというCOMのような仕組みをベースにしています。
VST-MAについてVST Module ArchitectureのドキュメントIntroductionには次のように記載されています。
VST-MA is a component model system which is used in any Steinberg host application as the basic layer for Plug-in support as well as for internal application components. It is object-oriented, cross-platform and (almost) compiler-independent. The basics are very much like Microsoft(R) COM, so if you are familiar with this technology, understanding VST-MA should be quite easy.
VST3のプラグインやホストの開発では、VST-MAおよびVST3 APIで定義された抽象的なインターフェースを、VST-MAの仕組みに則って実装していくことで、独自のプラグインやホストを作成します。
VST3で定義されたインターフェースが、プラグイン側/ホスト側のどちらで実装されるべきかについては、VST3 API Documentationの
に定義されています。
VST-MAでもっとも重要になるインターフェースがFUnknownです。
これはVST-MAで最もベースとなるインターフェースであり、他の全てのVST-MAおよびVST3のインターフェースはすべてFUnknownを継承しています。
FUnknownは、参照カウントの仕組みと、自分の実体であるコンポーネントが実装している他のインターフェースを取得する機能を提供しています。
また他に重要な要素として、VST-MAではIIDとCIDという2種類のIDの仕組みがあります。
- IID: インターフェースIDと呼ばれ、VST-MAの特定のインターフェースを取得するために使用する。
- CID: コンポーネントIDあるいはクラスIDと呼ばれ、何らかのインターフェースを実装した特定のクラスを指定する時に使用する。
IIDとCIDを使用するのは、FUnknown::queryInterface()
の呼び出しと、ファクトリークラスのcreateInstance()
の呼び出しです。
あるコンポーネントがなんらかのインターフェースを実装していて、そのインターフェース型のポインタを得るとき、C++ではキャストによって型変換を行いますが、VST-MAではFUnknown::queryInterface()
という関数を使用します。
目的のインターフェースを表すIIDを渡してqueryInterface()
を呼び出すと、引数に渡したポインタにそのインターフェースのポインタがセットされ、ステータスを表す戻り値が返ります。
queryInterfaceの例
// なんらかのインターフェース void doSomethingWithISomeAnotherFeature(ISomeFeature *p1) { ISomeAnotherFeature *p2; // p1からISomeFeature2のインターフェースを取得。 Steinberg::tresult const result = p1->queryInterface(ISomeAnotherFeature::iid, (void **)&p2); if(result == kResultTrue) { p2->doSomething(); p2->release(); // インターフェースを使い終わる時はreleaseで参照カウントを下げる }
ファクトリークラスから、特定のコンポーネントを構築する際には、ファクトリークラスのcreateInstance()
に、目的のクラスのCIDと、どのインターフェースのポインタとして取得するかを指定するIIDを渡します。
構築に成功すると、指定したインターフェースのポインタとしてそのコンポーネントを取得できます。
createInstanceの例
void createComponent(IAwesomeFactory *factory, FUID target_cid) { IAwesomeComponent *component; Steinberg::tresult = factory->createInstance(target_cid, IAwesomeComponent::iid, (void **)&component); if(tresult == kResultTrue) { component->doSomething(); // インターフェースを使い終わったらreleaseで参照カウントを下げる。 // この時はこのコンポーネントを参照してるポインタが存在しないため、ここでdeleteが行われる component->release(); } }
上記のコード片では、使い終わったポインタに対してrelease()
を呼び出し、参照カウントを下げています。このような参照カウントの操作は、複数のポインタで一つのメモリ領域を管理するために必要となります。
以下はVST-MAではなくCOMの資料ですが、参照カウントについての解説で参考になります。
COM オブジェクトの有効期間の管理
ユーティリティクラス
VST-MAのqueryInterface()
とcreateInstance``はどちらも関数呼び出しの結果を受け取るための変数をvoid**型の引数で渡し、ステータスを
Steinberg::tresult`型の値で返します。
これらの関数はよく使われるのに対して、関数のインターフェースは使いにくいものになっています。
また、取得したポインタの参照カウントをプログラマ自身で管理する仕組みはバグの原因となります。
そのため今回のプログラムでは、VST-MAのインターフェースを表すスマートポインタと、queryInterface()
, createInstance()
を使いやすくするラッパー関数を用意して使っています。
struct SelfReleaser { template< class T> void operator() ( T * p) { if( p) { p->release(); } } }; template <class T > std:: unique_ptr< T, SelfReleaser > to_unique(T *p ) { return std:: unique_ptr <T , SelfReleaser >(p); } //! 失敗か成功かどちらかの状況を返すクラス //! is_right() == trueの時は成功の状況 template<class Left, class Right> struct Either { //...略 }; //! pに対してqueryInterfaceを呼び出し、その結果を返す。 /*! @return queryInterfaceが正常に完了し、有効なポインタが返ってきた場合は、 Rightのオブジェクトが設定されたEitherが返る。 queryInterfaceがkResultTrue以外を返して失敗した場合は、そのエラーコードをLeftに設定する。 queryInterfaceによって取得されたポインタがnullptrだった場合は、kNoInterfaceをLeftに設定する。。 @note 失敗した時に、そのエラーコードが必要になることを考えて、Boost.Optionalではなく、Eitherを返すようにした */ template<class To, class T> Either<Steinberg::tresult, std::unique_ptr<To, SelfReleaser>> queryInterface_impl(T *p, Steinberg::FIDString iid) { typedef Either<Steinberg::tresult, std::unique_ptr<To, SelfReleaser>> either_t; To *obtained = nullptr; Steinberg::tresult const res = p->queryInterface(iid, (void **)&obtained); if(res == kResultTrue && obtained) { return either_t(to_unique(obtained)); } else { if(res != kResultTrue) { return either_t(res); } else { return kNoInterface; } } } namespace prevent_adl { template<class T> auto get_raw_pointer(T *p) -> T * { return p; } template<class T> auto get_raw_pointer(T const &p) -> decltype(p.get()) { return p.get(); } } // prevent_adl template<class To, class Pointer> Either<Steinberg::tresult, std::unique_ptr<To, SelfReleaser>> queryInterface(Pointer const &p, Steinberg::FIDString iid) { return queryInterface_impl<To>(prevent_adl::get_raw_pointer(p), iid); } template<class To, class Pointer> Either<Steinberg::tresult, std::unique_ptr<To, SelfReleaser>> queryInterface(Pointer const &p) { return queryInterface_impl<To>(prevent_adl::get_raw_pointer(p), To::iid); } template<class To, class Factory> Either<Steinberg::tresult, std::unique_ptr<To, SelfReleaser>> createInstance_impl(Factory *factory, Steinberg::FUID class_id, Steinberg::FIDString iid) { typedef Either<Steinberg::tresult, std::unique_ptr<To, SelfReleaser>> either_t; To *obtained = nullptr; Steinberg::tresult const res = factory->createInstance(class_id, iid, (void **)&obtained); if(res == kResultTrue && obtained) { return either_t(to_unique(obtained)); } else { return either_t(res); } } //! なんらかのファクトリクラスからあるコンポーネントを取得する。 template<class To, class FactoryPointer> Either<Steinberg::tresult, std::unique_ptr<To, SelfReleaser>> createInstance(FactoryPointer const &factory, Steinberg::FUID class_id, Steinberg::FIDString iid) { return createInstance_impl<To>(prevent_adl::get_raw_pointer(factory), class_id, iid); } //! なんらかのファクトリクラスからあるコンポーネントを取得する。 template<class To, class FactoryPointer> Either<Steinberg::tresult, std::unique_ptr<To, SelfReleaser>> createInstance(FactoryPointer const &factory, Steinberg::FUID class_id) { return createInstance_impl<To>(prevent_adl::get_raw_pointer(factory), class_id, To::iid); }
以下の各クラスの解説にもこれらのユーティリティクラスを使用したコードが含まれていますが、やっていることはqueryInterfaceの生の呼び出しや、自分で適切にaddRef/releaseを呼び出すような処理と大差ありません。
[VST3] VST3の名前空間について
VST3 SDKで定義された型や変数は、Steinberg名前空間に含まれています。 その中で特にVST3 APIのドキュメントに含まれるようなVST3特有の型はSteinberg::Vst名前空間に定義されています。
Vst3PluginFactory クラス
ところどころにVST3の仕様の解説を挟みながら、個々のクラスの解説をしていきます。
Vst3PluginFactory
クラスは、プラグインのモジュール(.vst3
ファイル)をロードして保持し、VST3プラグインを表すVst3Plugin
クラスを構築するためのクラスです。
Steinberg::IPluginFactory
というクラスのラッパークラスになっていて、
* コンポーネント情報へのアクセスを簡単にする。
* 指定されたコンポーネント情報からVst3Plugin
クラスを構築する仕組みをカプセル化する。
という役割を持っています。
VST3プラグインのファクトリーとVST3プラグインのコンポーネントについて少し補足します。
[VST3] VST3プラグインのファクトリー
VST-MAでは、モジュール(.vst3
ファイル)はこのような関数をエクスポートしている必要があります。
IPluginFactory* PLUGIN_API GetPluginFactory ();
モジュールをメモリ上にロードし、エクスポートされたこの関数を呼び出すと、IPluginFactory
インターフェースが得られます。
このインターフェースのgetFactoryInfo()
を呼び出すとベンダー名や連絡先の情報が取得できたり、getClassInfo()
を呼び出すと、このモジュールが保持しているプラグインの情報(正確にはプラグインのコンポーネントの情報)が取得できます。
getClassInfo()
で取得したSteinberg::ClassInfo
にセットされているCIDを元にIPluginFactory::createInstance()
を呼び出すと、目的のプラグインを構築できます。
[VST3] VST3プラグインのコンポーネント
VST3のプラグインは、基本的にProcessor
とController
という2つのコンポーネントから構成されます。
Processor
: 実際にMidi情報や音を受け取ったり加工したりする役割をもったコンポーネント。オーディオを扱う機能を提供するIAudioProcessor
と、より汎用的な機能を提供するIComponent
からなる。Controller
: GUI向けの機能やパラメータを扱う仕組み提供するコンポーネント。
プラグインが2つのコンポーネントに分かれていることによって、例えばホストアプリケーションでProcessor
のみを使用し、Controller
の機能は使用しないというような運用が可能になっています。
詳しくはVST3 API DocumentationのBasic Conceptionをご参照下さい。
このように一つのプラグインを表すコンポーネントの情報が2つ存在するため、IPluginFactory
から取得できるコンポーネント情報のリストにはProcessor
の情報とController
の情報がそれぞれ含まれています。
ところが、Processor
側でController
のインターフェースも一緒に実装してしまっているようなプラグインでは、コンポーネント情報がProccessor
側だけしか存在しません。
そのため、VST3プラグインを構築する際には、IPluginFactory::createInstance()
にProcessor
のCIDを渡すようにします。
Vst3Pluginクラス
Vst3Plugin
クラスは、Processor
とController
という2つのコンポーネントをまとめたり、その他さまざまなインターフェースをプラグインから取得して、VST3プラグインを扱いやすい形にしたクラスです。
Vst3Plugin
クラスはコンパイラファイアウォールとしてImplクラスを持っています。
これはVst3Plugin
のヘッダー側に不必要に多くのVST3 SDKの実装を公開したくなかったためです。
ですので、実質的にはVst3Plugin
が持っているImpl
というクラスがVST3プラグインを管理する実体となっています。
VST3プラグインの構築
Vst3PluginFactory::CreateByID()
やVst3PluginFactory::CreateByIndex()
を呼び出すと、その過程でVst3Plugin::Impl
クラスのコンストラクタが呼び出されます。
VST3プラグインはこのImpl
のコンストラクタ、更にその中のLoadPlugin()
の中で構築され、続いて初期化処理が行われます。
void LoadPlugin(IPluginFactory *factory, ClassInfo const &info, host_context_type host_context) { LoadInterfaces(factory, info, host_context.get()); Initialize( std::move(queryInterface<Vst::IComponentHandler>(host_context.get()).right()) ); //! End of use of host context, delete it here. }
ここで、factory
はVst3PluginFactory
から渡されたファクトリーのインターフェース、info
は構築したいプラグインのコンポーネント情報です。host_context
は後述するVst3HostCallback::Impl
クラスです。
LoadPlugin()
は内部で2つの関数を呼び出しています。
最初のLoadInterface()
では、VST3プラグインを実際にファクトリーから構築し、構築したプラグインからVst3Pluginクラスで必要となるインターフェースを順次取得し、保持していきます。
その次のInitialize()
では、取得したインターフェースを元に、プラグインの出入力バスの設定やパラメータの情報を構築したり初期化処理を行っています。
LoadPlugin()で呼び出しているそれぞれの関数を詳しく見て行きましょう。
LoadInterfaces()関数
void LoadInterfaces(IPluginFactory *factory, ClassInfo const &info, Steinberg::FUnknown *host_context) { Steinberg::int8 cid[16]; for(size_t i = 0; i < 16; ++i) { cid[i] = info.cid()[i]; } auto maybe_component = createInstance<Vst::IComponent>(factory, cid); if(!maybe_component.is_right()) { throw Error(ErrorContext::kFactoryError, maybe_component.left()); } auto component = std::move(maybe_component.right()); tresult res; res = component->setIoMode(Vst::IoModes::kAdvanced); if(res != kResultOk && res != kNotImplemented) { throw Error(ErrorContext::kComponentError, res); } status_ = Status::kCreated; res = component->initialize(host_context); if(res != kResultOk) { throw Error(ErrorContext::kComponentError, res); } status_ = Status::kInitialized; BOOST_SCOPE_EXIT_ALL(&component) { if(component) { component->terminate(); component.reset(); } };
はじめに、Vst3PluginFactory
から渡されたIPluginFactoryのcreateInstance()
に対象のプラグインのCIDを渡して、VST3プラグインを構築しています。
この時、構築したプラグインはProcessor
側のコンポーネントの一つであるIComponent
インターフェースとして取得しています。
次いで、setIoMode()
でプラグインのIoモードを指定します。これはCubaseの'simple instrument tracks'という機能をサポートするための仕組みのようです。詳しくは、VST3 API DocumentationのComplex Plug-in Structures / Multi-timbral InstrumentsのページにあるThe Simple Modeの項をご参照下さい。
その次にIComponent::initialize()
を呼び出し、IComponent
を初期化します。
ここで渡しているhost_context
とは、VST2におけるAudioMasterCallback
のようなもので、プラグインからホスト側へ要求や情報を伝えるためのコールバック先です。
次に、Processor
側のもう一つのコンポーネントであるIAudioProcessor
インターフェースを取得します。
auto maybe_audio_processor = queryInterface<Vst::IAudioProcessor>(component); if(!maybe_audio_processor.is_right()) { throw Error(ErrorContext::kComponentError, maybe_audio_processor.left()); } auto audio_processor = std::move(maybe_audio_processor.right()); res = audio_processor->canProcessSampleSize(Vst::SymbolicSampleSizes::kSample32); if(res != kResultOk) { throw Error(ErrorContext::kAudioProcessorError, res); }
インターフェースが取得できたら、オーディオデータを処理する精度を問い合わせています。今回はfloat
精度でオーディオデータを扱うので、float
での処理ができないプラグインの場合はエラーにしています。
その次は、Controller
側コンポーネントのインターフェースであるIEditController
を取得しています。
// $(DOCUMENT_ROOT)/vstsdk360_22_11_2013_build_100/VST3%20SDK/doc/vstinterfaces/index.html // Although it is not recommended, it is possible to implement both, // the processing part and the controller part in one component class. // The host tries to query the Steinberg::Vst::IEditController // interface after creating an Steinberg::Vst::IAudioProcessor // and on success uses it as controller. auto maybe_edit_controller = queryInterface<Vst::IEditController>(component); bool edit_controller_is_created_new = false; if(!maybe_edit_controller.is_right()) { FUID controller_id; res = component->getControllerClassId(controller_id); if(res == kResultOk) { maybe_edit_controller = createInstance<Vst::IEditController>(factory, controller_id); if(maybe_edit_controller.is_right()) { edit_controller_is_created_new = true; } else { //! this plugin has no edit controller. } } else { if(res != kNoInterface && res != kNotImplemented) { throw Error(ErrorContext::kComponentError, res); } } } auto edit_controller = std::move(maybe_edit_controller.right()); if(edit_controller_is_created_new) { res = edit_controller->initialize(host_context); if(res != kResultOk) { throw Error(ErrorContext::kEditControlError, res); } } BOOST_SCOPE_EXIT_ALL(&edit_controller, edit_controller_is_created_new) { if(edit_controller_is_created_new && edit_controller) { edit_controller->terminate(); edit_controller.reset(); } };
前述のとおり、VST3のプラグインは基本的にProcessor
とController
のコンポーネントに分かれています。
構築したProcessor
に対応するController
のコンポーネントIDを取得するにはIComponent::getControllerClassId()
を使用します。
ただし、Processor
側でIEditController
インターフェースを実装してしまっているようなプラグインはすでに取得したIComponent
からIEditController
のインターフェースを取得できるはずなので、先にそれを試行しています。
どちらの方法でもIEditController
が取得できないようなプラグインがあるかどうかは不明ですが、その場合はIEditController
がないプラグインとしています。。
plugin_info_ = info; component_ = std::move(component); audio_processor_ = std::move(audio_processor); edit_controller_ = std::move(edit_controller); edit_controller2_ = std::move(edit_controller2); edit_controller_is_created_new_ = edit_controller_is_created_new; }
必要なインターフェースが揃ったらそれをメンバー変数に保持して、LoadInterfaces()
は終了します。
Initialize()関数
LoadInterface()
に続くInitialize()
では、プラグインの初期化処理を進め、パラメータのリストを構築したりします。
void Vst3Plugin::Impl::Initialize(std::unique_ptr<Vst::IComponentHandler, SelfReleaser> component_handler) { tresult res; if(edit_controller_) { input_changes_.setMaxParameters(edit_controller_->getParameterCount()); output_changes_.setMaxParameters(edit_controller_->getParameterCount()); if(component_handler) { res = edit_controller_->setComponentHandler(component_handler.get()); if(res != kResultOk) { OutputDebugStringW(L"Can't set component handler"); } } auto maybe_cpoint_component = queryInterface<Vst::IConnectionPoint>(component_); auto maybe_cpoint_edit_controller = queryInterface<Vst::IConnectionPoint>(edit_controller_); if (maybe_cpoint_component.is_right() && maybe_cpoint_edit_controller.is_right()) { maybe_cpoint_component.right()->connect(maybe_cpoint_edit_controller.right().get()); maybe_cpoint_edit_controller.right()->connect(maybe_cpoint_component.right().get()); }
Initialize
の引数に渡しているcomponent_handler
は、先述のhost_context
から取得したIComponentHandler
インターフェースです。
初めに、edit_controller_
が有効なら、edit_controller_->setComponentHandler()
を呼び出して、Controller
の操作をホスト側に通知するコールバックを登録しています。
続いて、component_
とedit_controller_
それぞれからIConnectionPoint
というインターフェースを取得し、2つのコンポーネントを接続しています。これによって、Processor
とController
という2つのコンポーネントが接続されたことになります。
このあたりは筆者の理解が弱い部分ですが、やっている処理は、VST3 API DocumentationのInitialization of communication from Host point of view
を参考にしています。
次に、プラグインの出入力バスの設定を行っています。
component_
からバス情報を取得し、それぞれのバスを有効にして、Vst3Plugin::Impl
のバスのバッファinput_buses_
とoutput_buses_
を初期化しています。
OutputBusInfo(component_.get(), edit_controller_.get()); input_buses_.SetBusCount(component_->getBusCount(Vst::MediaTypes::kAudio, Vst::BusDirections::kInput)); for(size_t i = 0; i < input_buses_.GetBusCount(); ++i) { Vst::BusInfo info; Vst::SpeakerArrangement arr; component_->getBusInfo(Vst::MediaTypes::kAudio, Vst::BusDirections::kInput, i, info); audio_processor_->getBusArrangement(Vst::BusDirections::kInput, i, arr); input_buses_.GetBus(i).SetChannels(info.channelCount, arr); component_->activateBus(Vst::MediaTypes::kAudio, Vst::BusDirections::kInput, i, true); } output_buses_.SetBusCount(component_->getBusCount(Vst::MediaTypes::kAudio, Vst::BusDirections::kOutput)); for(size_t i = 0; i < output_buses_.GetBusCount(); ++i) { Vst::BusInfo info; Vst::SpeakerArrangement arr; component_->getBusInfo(Vst::MediaTypes::kAudio, Vst::BusDirections::kOutput, i, info); audio_processor_->getBusArrangement(Vst::BusDirections::kOutput, i, arr); output_buses_.GetBus(i).SetChannels(info.channelCount, arr); component_->activateBus(Vst::MediaTypes::kAudio, Vst::BusDirections::kOutput, i, true); } for(size_t i = 0; i < component_->getBusCount(Vst::MediaTypes::kEvent, Vst::BusDirections::kInput); ++i) { component_->activateBus(Vst::MediaTypes::kEvent, Vst::BusDirections::kInput, i, true); } for(size_t i = 0; i < component_->getBusCount(Vst::MediaTypes::kEvent, Vst::BusDirections::kOutput); ++i) { component_->activateBus(Vst::MediaTypes::kEvent, Vst::BusDirections::kOutput, i, true); } input_buses_.UpdateBufferHeads(); output_buses_.UpdateBufferHeads();
その後、edit_controller_
からパラメータを一覧を取得したり、プログラムリストを構築したり、再生時にIAudioProcessor
とパラメータの変更情報をやりとりするバッファの初期化を行ったりして、Initialize()
は終了します。
//! 可能であればこのあたりでIPlugViewを取得して、このプラグインがエディターを持っているかどうかを //! チェックしたかったが、いくつかのプラグイン(e.g., TyrellN6, Podolski)では //! IComponentがactivatedされる前にIPlugViewを取得するとクラッシュした。 //! そのため、この段階ではIPlugViewは取得しない PrepareParameters(); PrepareProgramList(); input_changes_.setMaxParameters(parameters_.size()); output_changes_.setMaxParameters(parameters_.size()); }
コメントにある通り、当初はここでIPlugView
というプラグインのエディターGUIを提供するインターフェースを取得して、プラグインのエディターGUIが存在するかどうかを判定したかったのですが、それによってクラッシュするプラグインがあったため、ここではIPlugView
を作成していません。
Vst3Pluginのセットアップ
LoadPlugin()
の呼び出しが正常に終了すると、Vst3PluginFactory
からVst3Plugin
が取得できます。
続いて、このプラグインを使用してオーディオ処理をするために、セットアップを行います。
VstHostDemo.cpp(290)
vst3_plugin_->SetBlockSize(block_size_); vst3_plugin_->SetSamplingRate(sampling_rate_); vst3_plugin_->Resume();
SetBlockSize()
はVST2と同じように、一度の再生フレームで合成可能な最大のサンプル数を設定する関数です。
Vst3Plugin::SetBlockSize()
とVst3Plugin::SetSamplingRate()
は値をセットするだけで、実際にVST3プラグインにこれらのパラメータをセットするのは、次のResume()
になります。
Resume()
の定義は以下のようになっています。
void Vst3Plugin::Impl::Resume() { BOOST_ASSERT(status_ == Status::kInitialized || status_ == Status::kSetupDone); tresult res; if(status_ != Status::kSetupDone) { Vst::ProcessSetup setup = {}; setup.maxSamplesPerBlock = block_size_; setup.sampleRate = sampling_rate_; setup.symbolicSampleSize = Vst::SymbolicSampleSizes::kSample32; setup.processMode = Vst::ProcessModes::kRealtime; res = GetAudioProcessor()->setupProcessing(setup); if(res != kResultOk && res != kNotImplemented) { throw std::runtime_error("setupProcessing failed"); } status_ = Status::kSetupDone; } res = GetComponent()->setActive(true); if(res != kResultOk && res != kNotImplemented) { throw std::runtime_error("setActive failed"); } status_ = Status::kActivated; is_resumed_ = true; //! Some plugin (e.g., TyrellN6.vst3, podolski.vst3) need to create its plug view after the components is active. tresult const ret = CreatePlugView(); has_editor_ = (ret == kResultOk); hwm::dout << "Latency samples : " << GetAudioProcessor()->getLatencySamples() << std::endl;
事前に設定したブロックサイズとサンプリングレートを渡してIAudioProcessor::setupProcessing()
を呼び出し、VST3プラグインに、これらのパラメータを設定しています。
setupProcessing()
が成功すると、次にIComonent::setActive()
を呼び出しています。
この段階で、IComponent
がアクティブになったため、ここでIPlugView
インターフェースを取得し、プラグインがエディターGUIを持っているかどうかをチェックしています。
次にIAudioProcessor::setProcessing()
を呼び出しています。
ただし、プラグインによっては、setProcessing()
がkNotImplemented
を返す場合があります。
ここでは、kNotImplemented
が返されても正常にResume()
は成功したという状態にしています。
//! 略 /*! 略 */ res = GetAudioProcessor()->setProcessing(true); if(res == kResultOk || res == kNotImplemented) { status_ = Status::kProcessing; is_processing_started_ = true; } else { hwm::dout << "Start processing failed : " << res << std::endl; }
このセットアップの流れと、また、逆の停止処理の流れはVST3 API DocumentationのWorkflow Diagramに記載がありますので、詳しくはそちらをご参照下さい。
Vst3Pluginのオーディオ処理
ここまででプラグインのセットアップは完了したため、プラグインを使用できるようになりました。
次にVst3Plugin
のオーディオ処理の部分を見ていきます。
VST3プラグインのオーディオ処理は、VST2と同じくフレーム単位で行います。
今回のプログラムでは、VstHostDemo.cpp(419)
から再生スレッド内で呼び出している関数がオーディオ処理を行う部分です。
float **synthesized = vst3_plugin_->ProcessAudio(pos, num_samples);
この呼び出しはVst3Plugin::Impl
のProcessAudio()
へ転送されます。
float ** Vst3Plugin::Impl::ProcessAudio(size_t frame_pos, size_t duration) { ClassInfo &cinfo = *plugin_info_; double const tempo = 120.0; double beat_per_second = tempo / 60.0; Vst::ProcessContext process_context = {}; process_context.sampleRate = sampling_rate_; process_context.projectTimeSamples = frame_pos; process_context.projectTimeMusic = frame_pos / 44100.0 * beat_per_second; process_context.tempo = tempo; process_context.timeSigDenominator = 4; process_context.timeSigNumerator = 4; process_context.state = Vst::ProcessContext::StatesAndFlags::kPlaying | Vst::ProcessContext::StatesAndFlags::kProjectTimeMusicValid | Vst::ProcessContext::StatesAndFlags::kTempoValid | Vst::ProcessContext::StatesAndFlags::kTimeSigValid;
最初に、IAudioProcessor
に渡すための今回の再生フレームのコンテキスト(再生位置やテンポやループ再生状態の情報)を設定してします。
今回はサンプルプログラムなので、テンポ120.0
, 4/4
拍子, 再生位置は再生フレームごとに前進しつづける、というような設定にしています。実際のDAWでは、DAW内部の再生状態に合わせてここを設定することになるでしょう。
続いて、IAudioProcessor
にMIDI情報を送るEventList
を設定しています
Vst::EventList input_event_list; Vst::EventList output_event_list; { auto lock = boost::make_unique_lock(note_mutex_); for(auto ¬e: notes_) { Vst::Event e; e.busIndex = 0; e.sampleOffset = 0; e.ppqPosition = process_context.projectTimeMusic; e.flags = Vst::Event::kIsLive; if(note.note_state_ == Note::kNoteOn) { e.type = Vst::Event::kNoteOnEvent; e.noteOn.channel = 0; e.noteOn.length = 0; e.noteOn.pitch = note.note_number_; e.noteOn.tuning = 0; e.noteOn.noteId = -1; e.noteOn.velocity = 100 / 127.0; } else if(note.note_state_ == Note::kNoteOff) { e.type = Vst::Event::kNoteOffEvent; e.noteOff.channel = 0; e.noteOff.pitch = note.note_number_; e.noteOff.tuning = 0; e.noteOff.noteId = -1; e.noteOff.velocity = 100 / 127.0; } else { continue; } input_event_list.addEvent(e); } notes_.clear(); }
IAudioProcessor
は、再生時にMIDIイベント情報をIEventList
インターフェースとして受け取ります。
VST3 SDKには、IEventList
インターフェースを実装したEventList
クラスが用意されていますので、ホスト側ではこのクラスを利用して、MIDIイベントの情報をIAudioProcessor
へ渡せます。
また、IAudioProcessor
からホスト側へMIDIイベントを返す仕組みもあり、ここではoutput_event_list
という変数がそのイベントを受け取るバッファーになります。
今回はサンプルプログラムなので、鍵盤画面から送られてきたノートのオン/オフの情報のみをEventList
型の変数であるinput_event_list
にセットして合成処理を行います。
続いて、プラグインに渡す/プラグインから受け取るオーディオバッファの設定です
std::vector<Vst::AudioBusBuffers> inputs(input_buses_.GetBusCount()); for(size_t i = 0; i < inputs.size(); ++i) { inputs[i].channelBuffers32 = input_buses_.GetBus(i).data(); inputs[i].numChannels = input_buses_.GetBus(i).channels(); inputs[i].silenceFlags = false; if(inputs[i].numChannels != 0) { for(int ch = 0; ch < inputs[i].numChannels; ++ch) { for(int smp = 0; smp < duration; ++smp) { inputs[i].channelBuffers32[ch][smp] = wave_data_[(wave_data_index_ + smp) % (int)process_context.sampleRate]; } } wave_data_index_ = (wave_data_index_ + duration) % (int)process_context.sampleRate; } } std::vector<Vst::AudioBusBuffers> outputs(output_buses_.GetBusCount()); for(size_t i = 0; i < outputs.size(); ++i) { outputs[i].channelBuffers32 = output_buses_.GetBus(i).data(); outputs[i].numChannels = output_buses_.GetBus(i).channels(); outputs[i].silenceFlags = false; }
今回作成したプログラムは、バーチャル・インストゥルメントではないオーディオエフェクトプラグインもロードできる仕組みになっていますので、プラグインで加工した音を確かめるために、ダミーの波形入力バッファに書き込んでいます。
ここで使用しているAudioBusBuffers
クラスは、先ほどのIEventList
インターフェースとEventList
クラスの関係と同様に、IAudioProcessor
が受けとるIAudioBusBuffers
インターフェースに対応するクラスです。
次は、プラグインへ送るパラメータ変更の通知です。
input_changes_.clearQueue(); output_changes_.clearQueue(); TakeParameterChanges(input_changes_);
ホスト側のオートメーションの操作やプラグインのエディターGUIの操作によるパラメータの変更は、任意の場所から直接IAudioProcessor
に適用することはできません。
必ずプラグインの再生フレーム処理の中で、IAudioProcessor
に送信される必要があります。
Vst3Pluginは
、パラメータの変更を一時的に保持しておくparam_changes_queue_
というキューを持っています。
プラグインのエディターGUIから送られてきたパラメータの変更情報は、一度このキューに溜められた後、次回の再生処理の時にTakeParameterChanges()
によってinput_changes_
に取り出され、IAudioProcessor
に送信されます。
また、EventList
の場合と同様に、パラメータの変更をIAudioProcessor
の側からをホスト側へ返す仕組みもあり、それを受け取るためにoutput_changes_
という変数を用意しています。
ここで使用しているinput_changes_
, output_changes_
の型はParameterChanges
というクラスであり、これは、IEventList
とEventList
の関係と同様に、IAudioProcessor
が受け取るIParameterChanges
インターフェースに対応するクラスです。
ここまでで、1フレームの再生に必要なデータが揃ったので、Vst::ProcessData
というクラスに情報をまとめて、
IAudioProcessor::process()
を呼び出します。
Vst::ProcessData process_data; process_data.processContext = &process_context; process_data.processMode = Vst::ProcessModes::kRealtime; process_data.symbolicSampleSize = Vst::SymbolicSampleSizes::kSample32; process_data.numSamples = duration; process_data.numInputs = inputs.size(); process_data.numOutputs = outputs.size(); process_data.inputs = inputs.data(); process_data.outputs = outputs.data(); process_data.inputEvents = &input_event_list; process_data.outputEvents = &output_event_list; process_data.inputParameterChanges = &input_changes_; process_data.outputParameterChanges = &output_changes_; GetAudioProcessor()->process(process_data); for(int i = 0; i < output_changes_.getParameterCount(); ++i) { auto *queue = output_changes_.getParameterData(i); if(queue && queue->getPointCount() > 0) { hwm::dout << "Output parameter count [" << i << "] : " << queue->getPointCount() << std::endl; } } return output_buses_.data(); }
呼び出しが成功して正常に音の合成や加工が行われていれば、渡したオーディオバッファにデータが書き込まれているはずなので、そのポインタを返して、Vst3Plugin::Impl
のProcessAudio()
は終了します。
Vst3PluginのエディターGUI
読み込んだVST3プラグインにエディターGUIが用意されている場合は、Vst3Plugin::OpenEditor()
を呼び出してエディターGUIを作成できます。
内部的には、Vst3Plugin
のIEditController
から取得したIPlugView
というクラスを使用します。
bool Vst3Plugin::Impl::OpenEditor(HWND parent, IPlugFrame *frame) { BOOST_ASSERT(HasEditor()); tresult res; res = plug_view_->isPlatformTypeSupported(kPlatformTypeHWND); if(res != kResultOk) { throw std::runtime_error("HWND is not supported."); } plug_view_->setFrame(frame); res = plug_view_->attached((void *)parent, kPlatformTypeHWND); bool const success = (res == kResultOk); if(success) { is_editor_opened_ = true; } else { MessageBox(NULL, L"Failed to attach", NULL, NULL); is_editor_opened_ = false; } return success; }
isPlatformTypeSupported()
で、現在のプラットフォームでのエディターGUIが開けるかどうかを確認します。
その後setFrame()
で、IPlugFrame
インターフェースを設定しています。
VST3ではエディターGUIのサイズが変更できるようになりました。IPlugFrame
インターフェースはエディターGUIのリサイズ情報をホストのウィンドウ側に通知するためのコールバックの機能を提供します。
setFrame()
の呼び出し後、attached()
でエディターGUIの親ウィンドウとなるハンドルを渡すと、エディターGUIが開かれます。
今回のプログラムでは、EditorFrame.hpp
で定義しているEditorFrame
クラスがエディターGUIの親ウインドウ兼IPlugFrame
になっています。
エディターGUIを破棄するには、removed
を呼び出します。
void Vst3Plugin::Impl::CloseEditor() { if(is_editor_opened_) { plug_view_->removed(); is_editor_opened_ = false; } }
[VST3] VST3プラグインのパラメータ変更
Vst3Plugin
のオーディオ処理の項で紹介したとおり、VST3では、エディターGUIで操作したパラメータの変更はそのままプラグインのIAudioProcessor
には適用されません。
エディターGUIからのパラメータの変更はまず、IComponentHandler
インターフェースを通じてホスト側へ通知されてきます。
このインターフェースは、プラグイン構築時にhost_context
として渡したものです。
そしてホスト側では、受け取ったパラメータの変更をキューに貯め、次回の再生フレームでIAudioProcessor
へパラメータの変更を通知します。
Processor
側は大抵の場合専用の再生スレッドで動作し、エディターGUIの操作はGUIスレッドで操作されますが、このようにController
側のパラメータの変更を非同期にProcessor
側へ通知する仕組みによって、Processor
とController
の側で同期処理を考慮する必要がなくなります。
逆にホストからプラグインへパラメータの変更を指示する場合は、IEditController::setParameterNormalized()
を呼び出して、Controller
側に変更を通知し、
IAudioProcessor
へ送るキューにもパラメータ変更の情報を追加して、次回再生フレームでProcessor
側に変更が適用されるようにします。
[VST3] VST3プラグインのプログラム(パラメータのプリセット)
VST3では、プログラム(パラメータのプリセット)の仕組みがVST2から変わりました。
VST3では、プラグインが複数のプログラムのリストを持つことができるようになりました。また、それぞれのリストは、Unit
という構造に紐付いています。
Unit
とは、プラグインのパラメータをより細かく管理するために新たに導入された仕組みです。
階層構造で管理され、一つのUnit
の下に複数のUnit
を持つことが可能です。また、ID0
をもつグローバルのUnitが存在します。
それぞれのUnit
はprogramListId
という値を持っており、この値によってUnit
とプログラムのリストが紐付いています。
今回の実装ではUnit
を検索し、programListId
に有効な値が設定された最初のUnit
から、使用するプログラムのリストを決定しています。
またプログラムをプラグインに適用する方法もVST2から変わりました。
これまでのようなプログラムを変更するための専用のAPIは用意されず、プラグインのパラメータのリストに、現在のプログラムのインデックスをあらわすパラメータが追加されました。
そのようなパラメータはParameterInfo::flags
にkIsProgramChange
が設定されています
Vst3PluginImpl.cpp(739) void Vst3Plugin::Impl::PrepareProgramList() { parameter_for_program_ = -1; for(auto const ¶m_info: parameters_) { if((param_info.flags & Vst::ParameterInfo::ParameterFlags::kIsProgramChange) != 0) { parameter_for_program_ = param_info.id; } }
ホストがプラグインのプログラムを変更するときは、このパラメータに対して、新たなプログラムのインデックスを0.0 - 1.0
の範囲に収まるように正規化して、通常のパラメータの変更処理と同じようにController
とProcessor
に設定します。
Vst3PluginImpl.cpp(293)
void Vst3Plugin::Impl::SetProgramIndex(size_t index) { if(parameter_for_program_ == -1) { //nothing to do } else { current_program_index_ = DiscretizeProgramIndex(GetEditController()->getParamNormalized(parameter_for_program_)); } if(index == current_program_index_) { return; } current_program_index_ = index; auto const &program = programs_[current_program_index_]; auto const normalized = NormalizeProgramIndex(current_program_index_); param_value_changes_was_specified_ = false; //! `Controller`側、`Processor`側それぞれのコンポーネントにプログラム変更を通知 if(parameter_for_program_ == -1) { GetEditController()->setParamNormalized(program.list_id_, normalized); EnqueueParameterChange(program.list_id_, normalized); } else { GetEditController()->setParamNormalized(parameter_for_program_, normalized); EnqueueParameterChange(parameter_for_program_, normalized); } }
上の関数は、setParamNormalized()
で、Controller
側へのプログラム変更を通知し、EnqueueParameterChange()
によって次回の再生フレームの処理でProcessor
側にプログラムの変更を通知しています。
Vst3HostCallbackクラス
Vst3HostCallback
クラスは、プラグインからホスト側へ処理や要求をコールバックするためのクラスです。
このクラスもImpl
クラスを持っており、Impl
クラスの側でIHostApplication
やIComponentHandler
などのインターフェースを実装しています。
IHostApplication
インターフェースはBasic Host Callback Interface
と定義されており、ホストアプリケーションの名前をプラグインに返したり、プラグインで内部的にコンポーネント間の通信を行うためのIMessage
インターフェースをプラグイン側に渡す機能を提供します。
IComponentHandler
は、プラグインのController
で行われたパラメータの変更をホスト側に通知するコールバックです。
Vst3HostCallback::Impl
では、以下のようにIComponentHandler
インターフェースの関数をオーバーライドしています。
/** To be called before calling a performEdit (e.g. on mouse-click-down event). */ tresult PLUGIN_API beginEdit (Vst::ParamID id) override { hwm::dout << "Begin edit [" << id << "]" << std::endl; return kResultOk; } /** Called between beginEdit and endEdit to inform the handler that a given parameter has a new value. */ tresult PLUGIN_API performEdit (Vst::ParamID id, Vst::ParamValue valueNormalized) override { hwm::dout << "Perform edit [" << id << "]\t[" << valueNormalized << "]" << std::endl; parameter_change_notification_handler_(id, valueNormalized); return kResultOk; } /** To be called after calling a performEdit (e.g. on mouse-click-up event). */ tresult PLUGIN_API endEdit (Vst::ParamID id) override { hwm::dout << "End edit [" << id << "]" << std::endl; return kResultOk; } /** Instructs host to restart the component. This should be called in the UI-Thread context! \param flags is a combination of RestartFlags */ tresult PLUGIN_API restartComponent (int32 flags) override { hwm::dout << "Restart request has come [" << flags << "]" << std::endl; request_to_restart_handler_(flags); return kResultOk; }
プラグインのController
側で操作が行われると、これらの関数呼び出されます。
beginEdit()
は、エディターGUIつまみが動かされるなどして、あるパラメータの変更が行われる直前、endEdit()
はそのパラメータの一連の変更が終了した直後に呼び出されます。
performEdit()
はbeginEdit
とendEdit()
の呼び出しの間で、パラメータが変更がされ続ける度に呼び出されます。
今回のプログラムではperformEdit()
が呼ばれた時に、事前に設定した関数parameter_change_notification_handler_
を呼び出しています。
ここには、VstHostDemo.cpp(93)
で定義されたラムダ式が設定されており、performEdit
の呼び出しに渡された引数をVst3Plugin::EnqueueParameterChange()
に渡して、次回再生フレームが来た時にパラメータの変更がProcessor
側に通知されるようにしています。
また、Vst3HostCallback::Impl
クラスでは、IComponentHandler2
クラスも実装しています。
IComponentHandler2
は複数のパラメータを同時に変更するための機能や、プラグイン側のDirtyフラグの状態を受け取る機能を提供しています。
今回のプログラムでは、これらの関数を利用する機能はありません。単にデバッグ用に呼び出しのログを取るだけになっています。
まとめ
このようにして、VST3プラグインをロードして、エディターGUIの情報を受け取ったり、実際にノートを送って合成できるようになりました。
VST3に関しては本当に情報が少なく、自分でも正しく理解できているかどうか怪しいところが多々あるのですが、それでもなんとか形になって動くものができて良かったです。
最近はVST3の上に構築されたARAというプラグインの規格も登場しているので、今後はVST3と合わせてこの規格も調べてみたいです。
明日は、oniprogさんです。よろしくお願いします。