VST3プラグインのパラメーター実装方法1
前々回ははじめてのVST3プラグインを作成いたしました。
今回は前々回のVST3プラグインにパラメーターを追加します。
今回作成するプラグインは下記のとおりです。
- パラメーターは1つ(ボリュームコントロール)
- 入力のボリュームをパラメーターにより調整して出力する
- 入力バス、出力バスは1つで、共にステレオ(2ch)
「バス」についての概念は前回記載したとおりです。
このVST3プラグインのサンプルソースファイルはこちらからダウンロードできます→vst3dev_20210403
ZIPファイルの中の「vst3dev02_パラメーター実装方法1」フォルダが今回のサンプルソースファイルになります。
コンパイル・ビルドの方法は簡単にこちらでご説明しております。ご参考までに。→サンプルソースファイルのビルド方法
パラメーター操作クラスの作成
まずはパラメーター操作を行うクラスを作成します。
このパラメーター操作クラスを定義して、後述するinitialize()関数内でパラメーターの追加処理(parameters.addParameter()関数を呼び出す)を行うことで、パラメーターを追加できます。
上記の図のパラメーターはホストアプリケーション(Cubase,Sonar等のDAW)が標準で持つUIを使用しています。
独自のUIをの作り方は、「ツールを使ったVST GUIの作成方法」や「最小構成のVST GUIコード」「VST GUIコードでのつまみ(ノブ)等の実装方法」などに記載しております。
パラメーター操作クラスはVST SDKにあるEditControllerクラスを継承して下記のように宣言します。
(VST3の開発では名前空間を指定する必要がありますので忘れないようご注意ください。)
【controller.h】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// VST3 SDKのインクルードファイル #include "public.sdk/source/vst/vsteditcontroller.h" // 自作VST用のインクルードファイル #include "myvst3define.h" // VST3作成に必要な名前空間を使用 namespace Steinberg{ namespace Vst { // =================================================================================== // VSTのパラメーターを操作するためのControllerクラス // =================================================================================== class MyVSTController : public EditController { public: // クラスを初期化する関数(必須) tresult PLUGIN_API initialize(FUnknown* context); // 自作VST Controllerクラスのインスタンスを作成するための関数(必須) static FUnknown* createInstance(void*) { return (IEditController*)new MyVSTController(); } }; } } // namespace SteinbergとVstの終わり |
【controller.cpp】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// 自作VST用のインクルードファイル #include "myvst3define.h" #include "myvst3fuid.h" #include "controller.h" // VST3作成に必要な名前空間を使用 namespace Steinberg{ namespace Vst { // クラスを初期化する関数(必須) tresult PLUGIN_API MyVSTController::initialize(FUnknown* context) { // まず継承元クラスの初期化を実施 tresult result = EditController::initialize(context); if (result == kResultTrue) { // パラメーターを追加 parameters.addParameter(STR16("param1"), STR16("..."), 0, 1, ParameterInfo::kCanAutomate, PARAM1_TAG); // 以下固有の初期化を実施。 // 今回は何もしない } // 初期化が成功すればkResultTrueを返す。 result = kResultTrue; return result; } } } // namespace SteinbergとVstの終わり |
initialize()関数は名前のとおり、クラスの初期化を行う関数です。
継承元のinitialize()関数を呼び出した後、継承元のプライベート変数parametersクラスのaddParameter()関数を呼び出し、パラメーターを追加しています。
addParameter()関数は下記のとおりです。
- addParameter()関数
概要 | パラメーター操作クラスにパラメーターを追加する関数 パラメーターの値は必ず0.0~1.0の間でなければならない。 |
||
---|---|---|---|
戻り値 | 型 | 概要 | |
Parameter* | 追加したパラメーターのポインタ | ||
引数 | 型 | 変数名 | 概要 |
TChar* | title | パラメーターの名前。 (TCharはwchar_tの再定義。文字コードはUTF-16で設定する) |
|
TChar* | units | パラメーターの単位(dB、sec 等)。省略可。 (TCharはwchar_tの再定義。文字コードはUTF-16で設定する) |
|
int32 | stepCount | 何段階のパラメーターか? 例: 3を指定した場合…0.0、0.5、1.0の3段階 5を指定した場合…0.0、0.25、0.5、0.75、1.0の5段階 0を指定すると0.0~1.0を滑らかに動く。 省略した場合 0 になる。 |
|
ParamValue | defaultValueNormalized | 標準のパラメーターの値。0.0~1.0で指定する。省略可。 (ParamValueはdoubleの再定義) |
|
int32 | flags | パラメーターのフラグ。省略可。 基本的にParameterInfo::kCanAutomateでいいはず。 |
|
int32 | tag | パラメーターのタグ(ID)。重要。省略した場合 1 になる。 | |
UnitID | unitID | 詳細不明。省略可。基本的にkRootUnitIdでいいはず |
パラメーターの名前や単位はUTF-16の文字コードで設定する必要があります。
文字コードがUTF-16であることを明確に指定するため、引数に与える際にSTR16()マクロを使用しています。
また、パラメーターのタグ(第6引数int32 tag)はパラメーター操作クラスと音声処理クラスの間でパラメーター情報をやり取りするために必要となってきます。
例えば、パラメーター操作クラスでtagが100のパラメーターを操作し値を0.5にすると、音声処理クラスには「タグ(ID) 100のパラメーターが0.5になった」というような情報が送信されます。
(パラメーターのタグ(ID)はVST SDKのマニュアルでも”タグ”だったり、”ID”だったりと表記がバラバラのため、本サイトではできるだけタグで統一したいと思います。表記間違いがあればご指摘いただけると助かります。)
タグ情報は数値で指定すればよいのですが、パラメータ-の数が多くなると「どのタグの値がどのパラメーターか」を管理するのは大変になります。
今回は#defineを使用してタグをわかりやすいように定義するとともに、パラメーター操作クラスと音声処理クラスのソースファイルから読み込めるよう、ヘッダファイルを分けで下記のように定義しています。
【myvst3define.h】
1 2 |
//パラメーター用のタグ #define PARAM1_TAG 100 |
上記のパラメーター操作クラスを定義することで、ホストアプリケーション標準のGUI画面を表示させることができます。
下図のような画面で、ひとつのパラメーターにひとつのスライダーが割り当たるような画面になります。
ホストアプリケーションはまず、パラメーター操作クラスを呼び出し、パラメーターの数やタグ(ID)情報などを取得します。その後、上記のような標準のGUI画面を作成します。
標準GUI画面のパラメーター(スライダー)が操作されると、操作(変更)したパラメーターのタグ(ID)と値(スライダーの位置)を取得し、音声処理クラスにその情報を渡します。
音声処理クラスでのパラメーターの処理
パラメーターを追加した後は、パラメーターの操作情報を受け取り、処理する機能を音声処理クラス側に追加します。
パラメーターの操作情報の受け取りと処理はprocess()関数で行います。
パラメーター(GUI画面のスライダー)が操作されるとprocess()関数の引数dataのinputParameterChangesクラスに必要な操作情報が格納されて渡されます。
まず、ヘッダファイルではivstparameterchanges.hを追加でインクルードしておきます。
これは、音声処理クラスでパラメーター操作情報を受け取る際に、IParamValueQueueというクラスを使用るためです。IParamValueQueueクラスの使い方は後述いたします。
次にパラメータ操作情報を受け取るための変数を定義します。
ここではParamValue型のvolumeを定義しています。(ParamValueはdoubleを再定義したもの。)
【processor.h 追加分】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// VST3 SDKのインクルードファイル #include "public.sdk/source/vst/vstaudioeffect.h" #include "pluginterfaces/vst/ivstparameterchanges.h" // VST3作成に必要なの名前空間を使用 namespace Steinberg{ namespace Vst { // =================================================================================== // 音声信号を処理するProcessorクラス // =================================================================================== class MyVSTProcessor : public AudioEffect { protected: ParamValue volume; public: // コンストラクタ MyVSTProcessor(); // クラスを初期化する関数(必須) tresult PLUGIN_API initialize(FUnknown* context); // バス構成を設定する関数。 tresult PLUGIN_API setBusArrangements(SpeakerArrangement* inputs, int32 numIns, SpeakerArrangement* outputs, int32 numOuts); // 音声信号を処理する関数(必須) tresult PLUGIN_API process(ProcessData& data); // 自作VST Processorクラスのインスタンスを作成するための関数(必須) static FUnknown* createInstance(void*) { return (IAudioProcessor*)new MyVSTProcessor(); } }; } } // namespace SteinbergとVstの終わり |
インクルードファイルの追加と変数の定義が終われば、パラメーター操作情報を受け取る処理を追加します。
パラメーター操作情報を受け取る処理はprocess()関数内に下記(51~90行目)のように記載します。
【processor.cpp 追加分】
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
// =================================================================================== // 音声信号を処理する関数 // =================================================================================== tresult PLUGIN_API MyVSTProcessor::process(ProcessData& data) { // パラメーター変更の処理 // 与えられたパラメーターがあるとき、dataのinputParameterChangesに // IParameterChangesクラスへのポインタのアドレスが入る if (data.inputParameterChanges != NULL) { // 与えられたパラメーターの数を取得 int32 paramChangeCount = data.inputParameterChanges->getParameterCount(); // 与えられたパラメーター分、処理を繰り返す。 for (int32 i = 0; i < paramChangeCount; i++) { // パラメーター変更のキューを取得 // (処理するサンプル内に複数のパラメーター変更情報がある可能性があるため、 // キューという形になっている。) IParamValueQueue* queue = data.inputParameterChanges->getParameterData(i); if ( queue != NULL) { // どのパラメーターが変更されたか知るため、パラメーターtagを取得 int32 tag = queue->getParameterId(); // 変更された回数を取得 int32 valueChangeCount = queue->getPointCount(); ParamValue value; int32 sampleOffset; // 最後に変更された値を取得 if (queue->getPoint(valueChangeCount - 1, sampleOffset, value) == kResultTrue) { // tagに応じた処理を実施 switch(tag) { case PARAM1_TAG: // volumeはメンバー変数としてあらかじめ定義・初期化しておく。 volume = value; break; } } } } } // 入力・出力バッファのポインタをわかりやすい変数に格納 // inputs[]、outputs[]はAudioBusの数だけある(addAudioInput()、addAudioOutput()で追加した分だけ) // 今回はAudioBusは1つだけなので 0 のみとなる // channelBuffers32は32bit浮動小数点型のバッファで音声信号のチャンネル数分ある // モノラル(kMono)なら 0 のみで、ステレオ(kStereo)なら 0(Left) と 1(Right) となる Sample32* inL = data.inputs[0].channelBuffers32[0]; Sample32* inR = data.inputs[0].channelBuffers32[1]; Sample32* outL = data.outputs[0].channelBuffers32[0]; Sample32* outR = data.outputs[0].channelBuffers32[1]; // numSamplesで示されるサンプル分、音声を処理する for (int32 i = 0; i < data.numSamples; i++) { outL[i] = volume * inL[i]; outR[i] = volume * inR[i]; } // 問題なければkResultTrueを返す(おそらく必ずkResultTrueを返す) return kResultTrue; } |
IParameterChangesやIParamValueQueueは基本的にこの部分でしか使用しないため、簡単に説明いたします。
パラメーター操作情報の受け取り時に、まず、data.inputParameterChangesがnullptrでないかを確認します。
次に処理するサンプル(numSamples)内で操作(変更)されたパラメーターの数をdata.inputParameterChangesのgetParameterCount()関数で確認します。
処理するサンプル(numSamples)内にパラメーターの操作(変更)がなければ0となり、操作(変更)があれば1以上の値が返ります。
例えば、ボリューム・フィルターカットオフ・レゾナンスの3つのパラメーターがあった場合、一つも操作しなければgetParameterCount()関数の戻り値は0、3つ同時に操作すれば戻り値は3となります。
次に操作(変更)されたパラメーターの数だけ、処理(内部の変数など)を更新します。
操作(変更)されたパラメーターのタグ(ID)や値はdata.inputParameterChangesのgetParameterData()関数で取得します。
getParameterData()関数の戻り値であるIParamValueQueueクラスはパラメーターのタグ(ID)と値の変更情報を持っています。
パラメーターの操作(変更)は処理するサンプル(numSamples)内で複数回 操作されていることがあるためキュー(queue)という形式をとっています。
例えば、「タグ(ID) 100のパラメーター」が処理するサンプル(numSamples)内で「10サンプル目に値が0.1」「20サンプル目に1.0」「30サンプル目に0.5」と操作された場合、下記のような形でIParamValueQueueクラスに変更情報が格納されます。
IParamValueQueueクラスの取得後は、getParameterId()関数を使用し、パラメーターのタグ(ID)を取得しています。
そして、パラメーターの操作(変更)された値をとgetPoint()関数を使用して取得し、変数volumeに格納しています。
概要 | パラメーターキューに保存されているパラメーター変更情報を取得する関数 | ||
---|---|---|---|
戻り値 | 型 | 概要 | |
tresult | パラメーター変更情報の取得に成功したかどうかの結果 取得に成功した場合…kResultTrue、取得に失敗した場合…kResultFalse |
||
引数 | 型 | 変数名 | 概要 |
int32 | index | 取得するパラメーター変更情報のキュー番号を指定する。指定できる範囲は「0からgetPointCount() – 1」となる。 | |
int32& | sampleOffset | 取得したパラメーター変更情報の位置(何サンプル目か?)を格納するための変数 | |
ParamValue& | value | 取得したパラメーター変更情報の値(0.0~1.0の間)を格納するための変数 |
前述の通り、パラメーターの操作(変更)は処理するサンプル内で複数回 操作されていることがあるのですが、今回のサンプルプログラムでは、最後に変更された値のみを反映しています。
これは、一般的に処理するサンプルの長さが2~5ミリ秒程度(※)のため、キュー(queue)の内容に合わせて1つ1つを処理(反映)しても、最後に変更された値のみを処理(反映)しても、聴覚的な差がほとんどないためです。
(※一般的にnumSamplesは100~200程度の値になる。サンプリングレートを44100Hzで換算すると2~5ミリ秒程度)
FUIDの追加
パラメーター操作クラスにもFUID(GUID)を定義する必要があります。前々回に作成したmyvst3fuid.hにFUIDクラスを追加します。
FUIDの取得方法も音声処理クラスと同じでVisual StudioのGUID生成ツールなどを使用して取得します。(音声処理クラスのFUIDを使いまわさないようにしてください。)
【myvst3fuid.h 追加分】
10 |
static const FUID ControllerUID(0xDC821BA2, 0x3DCA437F, 0x85046563, 0x196B2BFD); |
音声処理クラスとパラメーター操作クラスの関連付け
FUIDを追加したら、音声処理クラスとパラメーター操作クラスの関連付けを行います。
音声処理クラスにコンストラクタを定義し、下記のようにsetControllerClass()関数を呼び出します。
【processor.cpp 追加分】
10 11 12 13 14 15 16 17 |
// ================================================================================= // コンストラクタ // ================================================================================= MyVSTProcessor::MyVSTProcessor() { // コントローラーのFUIDを設定する setControllerClass(ControllerUID); } |
プラグイン情報の追加
最後にパラメーター操作クラスを呼び出すための情報を追加します。
前々回同様マクロを使って下記のように定義・作成します。(BEGIN_FACTORY_DEFとEND_FACTORYの間に追記します。)
なお、事前にcontroller.hをインクルードする必要があります。
【factory.cpp】
51 52 53 54 55 56 57 58 59 60 |
// MyVSTControllerクラスの作成を行う DEF_CLASS2(INLINE_UID_FROM_FUID(Steinberg::Vst::ControllerUID), PClassInfo::kManyInstances, kVstComponentControllerClass, MYVST_VSTNAME " Controller", // 自作VSTの名前に"Controller"を付与したもの。 0, // 使わないので必ず0にする "", // 使わないので必ず""にする MYVST_VERSION, kVstVersionString, Steinberg::Vst::MyVSTController::createInstance ) |
最後に
以上でパラメーター操作可能なVST3プラグインを作成することができます。
なお、今回の方法で作成したパラメーターは0.0~1.0の範囲の値しか表示できません。
ディレイエフェクターの遅延時間のように、0.0~1.0に収まらない(例えば1.0~4.0秒等)パラメーターや、シンセサイザーのフィルタータイプのように「Lowpass」「Highpass」等の文字列一覧を表示したいパラメーターもあります。
次回は、そういったパラメータの表示方法について記載したいと思います。
次回→パラメーター実装方法2
このVST3プラグインのサンプルソースファイルはこちらからダウンロードできます → vst3dev_20210403.zip
コンパイル・ビルドの方法は簡単にこちらでご説明しております。ご参考までに。→サンプルソースファイルのビルド方法
VST3プラグイン作りの情報はこちらにもございます → はじめてのVST3プラグイン作り
ご指摘やご質問などがございましたら、コメント欄か掲示板、Twitterでご連絡いただければと思います。
■掲示板
■Twitterアカウント:@vstcpp URL:https://twitter.com/vstcpp
いつも更新お疲れ様です。
ブログ楽しく読ませていただいています。
最近、管理人様とともにVST3.6の勉強を始めさせていただきました。
近々VST2.4のSDKの配布が中止になるという話も小耳に挟んだため、そろそろ以降時期なのかなと感じたのもあります。
最新記事を参考にVST3.6のビルドを試してみたのですが、どうにもprocess関数内でのポインタ周りや変数でエラーが出てしまいます。
もしよろしければ、参考にさせていただきたいので全体のソースコードを前wikiのようにあげていただけないでしょうか?
厚かましい要望をしてしまい恐縮なのですが、お時間がありましたらよろしくお願いいたします。
これからもブログ更新楽しみにしています。
コメントありがとうございます。
実はVST3の中に書かれているコードは動作確認していないものもあります…
いずれ動作確認の上、ソースコードのアップロードや整理する予定でしたが、早めに行うようにします。
今は平日は少し忙しく、更新の余裕がないので、土日ぐらいになると思います。
最後に、エラーの発生したコードや使用したDAWや環境等も可能な限り教えていただけると幸いです。
日本にはほとんど情報がなく、私もぜひ参考にさせていただければと思います。
返信ありがとうございます。
私の環境だとエラーが発生したコードはprocessor.cppとcontroller.hでした。
どうやらパラメータを保管する変数がヘッダファイル側で定義されていなかったり、ヘッダを二度読みしていたことなどが原因でした。
私自身もいろいろいじってしまっていたのでそれも原因かもしれません。
無事ビルドは完了しました。
DAWは使わずに公式から配布されているテストアプリを使って動作確認をしたのですが
現在はパラメータの変更に伴ってエフェクトがかからないといった状態です。
また、いろいろ試してみようと思います。
また、重ねて質問になってしまい申し訳ないのですが
以前使用していたVSTHostアプリケーションのバージョンアップの関係で、VisualStudio側からプロセスアタッチを使ったVSTSDK2.4プラグインのデバッグができなくなってしまい困っています。
もしよろしければ管理人様が使っているVSTホストアプリケーション、デバッグ環境を教えていただけると幸いです。
解決してよかったです。
とりあえずソースコードは早めにアップロードするようにします。
私の使っているVST3ホストアプリケーションはVST 3.6 SDKに同梱されているVST3PluginTestHostとVSTHostです。
実はデバッグはやったことがありません…
ソースコードをアップロードいたしました。参考にしていただければと思います。
お忙しい中ありがとうございます。
参考にさせていただきます!
いつも更新お疲れ様です。
一つ質問があります、手順通りタグを設定したけど、デバッグで確認する時、queue->getParameterId();で取得したタグは定義した100ではなく、0です。(101を定義したら、1を取得しました)、これについて何かわかりますでしょうか。
書き込みありがとうございます。
動作確認につかったホストアプリケーション(DAWなど)は何でしょうか?
VSTHostだと正常にタグを指定できない不具合があった気がします。
返信ありがとうございます!
使ってるホストは「VSTHost」です(リンク:https://www.hermannseib.com/english/vsthost.htm)。
なるほど、ホスト側が問題あるか…、あとで他のホストアプリを使って試してみます、今の対策としてはタグを100を0に変更し、問題が回避してます。
ありがとうございます!
やっぱりVSTHostでしたか…。
VST SDKに同梱されているホストアプリ「VST3PluginTestHost」がSteinberg社製なので、これで動けば大体のDAWで動きます。
一つの基準になるので個人的にはおすすめです。