VST3プラグインのパラメーター実装方法3
前回はいろいろな値を表示すると文字列リストのパラメーターを実装しました。
しかし、「デジタルフィルタのカットオフ周波数」ようなパラメーターでは、パラメーターの値が大きくなるにつれて効き目が変わってくるものもあります。
例えば、デジタルフィルタのカットオフ周波数の場合、同じ1,000Hz分をカットオフ周波数を操作するにしても500Hz~1,500Hzと15,000Hz~16,000Hzではフィルタの影響が大きく異なります。(15,000Hz~16,000Hzはほとんど変化がないように感じます。)
このようなの場合、どの位置を動かしても同じような影響がでるパラメーターを実装したほうが使いやすくなります。
これはパラメーターの操作に対して指数的(非線形)に値が増加するパラメーターを実装すれば実現できます。
今回は「パラメーターの操作に対して、値が指数的(非線形)に値が増加するパラメーター」を実装する方法について記載したいと思います。
今回作成するプラグインは下記のとおりです。
- ローパスフィルタをかける機能を持つエフェクター
- パラメーターは2つ
- 周波数(50.0~22000.0Hzの範囲のパラメーター)
- Q値(0.5~10.0の範囲のパラメーター)
- 入力バス、出力バスは1つで、共にステレオ(2ch)
今回の内容は前回と前々回の説明を参考にしています。パラメーターの追加方法などについては、前回の説明と前々回の説明をご参照ください。
なお、「バス」についての概念はこちらに記載したとおりです。
このVST3プラグインのサンプルソースファイルはこちらからダウンロードできます→vst3dev_20210403
ZIPファイルの中の「vst3dev04_パラメーター実装方法3」フォルダが今回のサンプルソースファイルになります。
コンパイル・ビルドの方法は簡単にこちらでご説明しております。ご参考までに。→サンプルソースファイルのビルド方法
また、今回ローパスフィルタをかけるにあたって、自作のフィルタクラス(CMyFilter)を使用しています。
フィルタクラスについては「簡単なデジタルフィルタの実装」と「簡単なデジタルフィルタのサンプルコード」にて説明しております。
指数的な変化をするパラメーター
指数的な変化をするパラメーターを実装するためには、Parameterクラスを継承し自作のパラメータークラスを作成します。
Parameterクラスを継承して自作のパラメーターを使用するには、下記手順を行います。
表示値と正規化値の意味については前回の表示値と正規化値をご参照ください。
- Parameterクラスをpublicで継承
- コンストラクタで継承元クラスの初期化と自作パラメータークラスの初期化を行う
- toString()関数、fromString()関数、toPlain()関数、toNormalized()関数をオーバライドし、それぞれの関数に応じた処理を行う
(各関数の詳細は後述)
今回の自作パラメータークラスはMyParameterとします。MyParameterは下記のようにして定義しています。
【myparameter.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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#ifndef __CMYPARAM_H__ #define __CMYPARAM_H__ // VST3 SDKのインクルードファイル #include "pluginterfaces/base/ftypes.h" #include "pluginterfaces/base/ustring.h" #include "public.sdk/source/vst/vsteditcontroller.h" // 自作VST用のインクルードファイル #include <cmath> // VST3作成に必要なの名前空間を使用 namespace Steinberg{ namespace Vst { // ============================================================================================ // 指数的な変化をする自作パラメータークラス // ============================================================================================ class MyParameter : public Parameter { private: ParamValue min; // パラメーターの表示値の最小 ParamValue max; // パラメーターの表示値の最大 public: // コンストラクタ(継承元メンバー変数などの初期化を行うためほぼ必須) MyParameter(const TChar *title, ParamID tag, const TChar *units = 0, ParamValue minPlain = 0.0, ParamValue maxPlain = 1.0, ParamValue defaultValuePlain = 0.0, int32 flags = ParameterInfo::kCanAutomate, UnitID unitID = kRootUnitId); // 正規化された値(0.0f~1.0fの値)を表示値の文字列にする関数(必須) virtual void toString(ParamValue valueNormalized, String128 string) const; // 表示されている文字列から正規化された値(0.0f~1.0fの値)を取得する関数(必須) virtual bool fromString(const TChar* string, ParamValue& valueNormalized) const; // 正規化された値(0.0f~1.0fの値)から表示されている値にする関数(必須) virtual ParamValue toPlain(ParamValue valueNormalized) const; // 表示されている値から正規化された値(0.0f~1.0fの値)にする関数(必須) virtual ParamValue toNormalized(ParamValue plainValue) const; // Parameterクラスの各種設定を自作パラメータークラス置き換えるマクロ(必須) OBJ_METHODS(MyParameter, Parameter) }; }} // namespace SteinbergとVstの終わり #endif |
コンストラクタや各メンバー関数の引数などは下記のとおりとなります。
- MyParameterコンストラクタ
概要 | MyParameterのコンストラクタ。メンバー変数や継承元クラスの初期化などを行う (引数についてはRangeParameterを参考に作成している。) |
||
---|---|---|---|
戻り値 | 型 | 概要 | |
戻り値はありません(コンストラクタのため) | |||
引数 | 型 | 変数名 | 概要 |
TChar* | title | パラメーターの名前。 (TCharはwchar_tの再定義。文字コードはUTF-16で設定する) |
|
int32 | tag | パラメーターのタグ。重要。他のパラメーターのタグと重複しないほうがよい。 | |
TChar* | units | パラメーターの単位(dB、sec など)。省略可。省略した場合 NULL になる。(単位が表示されない) (TCharはwchar_tの再定義。文字コードはUTF-16で設定する) |
|
minPlain | ParamValue | 表示値の最小値。省略可。省略した場合 0.0 とする。(ParamValueはdoubleの再定義) | |
maxPlain | ParamValue | 表示値の最大値。省略可。省略した場合 1.0 とする。 | |
defaultValuePlain | ParamValue | 表示値の初期値。省略可。省略した場合 0.0 とする。 | |
int32 | flags | パラメーターのフラグ。省略可。 省略した場合 ParameterInfo::kCanAutomateになる。基本的に省略でいいはず。 |
|
UnitID | unitID | 詳細不明。省略可。省略した場合 kRootUnitId になる。基本的に省略でいいはず。 |
- toString()関数
概要 | 正規化された値(0.0~1.0の値)を表示値(50~22050など)の文字列に変換する関数 継承元のParameterクラスからオーバーライドした関数 |
||
---|---|---|---|
戻り値 | 型 | 概要 | |
戻り値はありません | |||
引数 | 型 | 変数名 | 概要 |
ParamValue | valueNormalized | 変換したい正規化値 | |
String128 | string | 変換後の文字列を格納する文字列バッファ (String128は長さ128の2Byte文字配列。__wchar_t[128]と同じ。文字コードはUTF-16で設定する) |
- fromString()関数
概要 | 表示値(50~22050など)の文字列を正規化された値(0.0~1.0の値)に変換する関数 継承元のParameterクラスからオーバーライドした関数 |
||
---|---|---|---|
戻り値 | 型 | 概要 | |
bool | 正常に変換出来た場合はtrue。失敗した場合はfalse。 | ||
引数 | 型 | 変数名 | 概要 |
const TChar* | string | 変換したい文字列 (TCharはwchar_tの再定義。文字コードはUTF-16で設定する) |
|
ParamValue& | valueNormalized | 変換後の正規化値を入れるためのポインタ |
- toPlain()関数
概要 | 正規化された値(0.0f~1.0fの値)から表示値(50~22050など)にする関数 継承元のParameterクラスからオーバーライドした関数 |
||
---|---|---|---|
戻り値 | 型 | 概要 | |
ParamValue | 表示値を返す | ||
引数 | 型 | 変数名 | 概要 |
ParamValue | valueNormalized | 変換したい正規化値 |
- toNormalized()関数
概要 | 正規化された値(0.0f~1.0fの値)から表示値(50~22050など)にする関数 継承元のParameterクラスからオーバーライドした関数 |
||
---|---|---|---|
戻り値 | 型 | 概要 | |
ParamValue | 正規化値を返す | ||
引数 | 型 | 変数名 | 概要 |
ParamValue | plainValue | 変換したい表示値 |
指数的な変化をするパラメーターの実装
MyParameterの実装はは下記のようにしています。
【myparameter.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 34 35 36 37 38 39 40 41 42 43 44 45 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 |
#include "myparameter.h" // VST3作成に必要なの名前空間を使用 namespace Steinberg{ namespace Vst { // コンストラクタ(継承元メンバー変数などの初期化を行うためほぼ必須) MyParameter::MyParameter(const TChar *title, ParamID tag, const TChar *units, ParamValue minPlain, ParamValue maxPlain, ParamValue defaultValuePlain, int32 flags, UnitID unitID) : Parameter(title, tag, units, 0.0f, 0, flags, unitID) // 一部の引数は継承元クラスにそのまま渡し、初期化する { // 最小値、最大値を設定 min = minPlain; max = maxPlain; // デフォルト値を設定 // valueNormalizedは継承元(Parameter)クラスで定義された正規化された現在の値 // toNormalized()関数を使って引数のデフォルト値(表示値)を正規化する valueNormalized = toNormalized(defaultValuePlain); } // 正規化された値(0.0f~1.0fの値)を表示値の文字列にする関数 void MyParameter::toString(ParamValue valueNormalized, String128 string) const { // 正規化された値を文字列にするにはUString128クラスを使う // UString128クラスは内部に文字列用バッファ(長さ128)を持ったクラス // 詳細は割愛するが、文字列をいろいろ変換するための関数が用意されている UString128 wrapper; // printFloat()関数はfloatの値を文字列にし、wrapperの内部文字列用バッファに保存する // 正規化された値なので、toPlain()関数を使い表示値に変換してから保存する。 // precisionは少数第何位まで表示するかを示す変数(継承元クラスで定義済み) wrapper.printFloat(toPlain(valueNormalized), precision); // 内部文字列用バッファからstringにコピーする wrapper.copyTo(string, 128); } // 表示されている文字列から正規化された値(0.0f~1.0fの値)を取得する関数 bool MyParameter::fromString(const TChar* string, ParamValue& valueNormalized) const { // 表示値の文字列から正規化値を取得にするにはUString128クラスを使う // まず、wrapperの内部文字列用バッファにstringを設定する UString wrapper((TChar*)string, strlen16(string)); // 表示値の文字列から表示値を取得する ParamValue plainvalue; if (wrapper.scanFloat(plainvalue)) { // 表示値取得に成功したら、toNormalized()関数で正規化し、valueNormalizedに代入 valueNormalized = toNormalized(valueNormalized); // 変換に成功したらtrueを返す return true; } // 変換に失敗したらfalseを返す return false; } // 正規化された値(0.0f~1.0fの値)から表示されている値にする関数 ParamValue MyParameter::toPlain(ParamValue valueNormalized) const { // 正規化値を表示値に変換して返す // toNormalized()関数とtoPlain()関数で可逆であることが必要 // 指数関数としてはとりあえずvalueNormalizedを3乗する関数にしておく(適当) // あわせて、最小値~最大値の範囲に収めるよう計算する。 return ((max - min) * pow(valueNormalized, 3.0f)) + min; } // 表示されている値から正規化された値(0.0f~1.0fの値)にする関数 ParamValue MyParameter::toNormalized(ParamValue plainValue) const { // 表示値を正規化値に変換して返す // toNormalized()関数とtoPlain()関数で可逆であることが必要 // toPlain()関数では3乗しているので、plainValueの3乗根を計算する return pow((plainValue - min) / (max - min), 1.0f / 3.0f); } } } // namespace SteinbergとVstの終わり |
指数的な変化をするパラメーターの利用
次に自作したMyParameterパラメータを実際に利用します。
利用方法は前回のRangeParameterやStringListParameterと同様にnewで作成したあと、MyParameterクラスのポインタaddParameter()関数に渡します。
- 自作クラスのヘッダファイルmyparameter.hをインクルード
- newでMyParameterクラスのインスタンスを生成
- パラメーター操作クラスにparameters.addParameter()関数で追加
【controller.cpp】
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
tresult PLUGIN_API MyVSTController::initialize(FUnknown* context) { // まず継承元クラスの初期化を実施 tresult result = EditController::initialize(context); if (result == kResultTrue) { // 以下固有の初期化を実施。 // 自作パラメーターを作成 MyParameter* param1 = new MyParameter(STR16("Freq"), PARAM_FILTERFREQ_TAG, STR16("Hz"), 50.0f, 22000.0f, 22000.0f); param1->setPrecision(2); // 小数第何位まで表示するか parameters.addParameter(param1); // 範囲パラメーターを作成 RangeParameter* param2 = new RangeParameter(STR16("Q"), PARAM_FILTERQ_TAG, STR16(""), 0.5f, 12.0f, 0.5f); parameters.addParameter(param2); } // 初期化が成功すればkResultTrueを返す。 result = kResultTrue; return result; } |
パラメーターのタグ(PARAM_FILTERFREQ_TAGとPARAM_FILTERQ_TAG)は事前に定義しております。
また、今回もsetPrecision()関数で小数第何位まで表示するかを指定しています。(前回参照)
setPrecision()関数の呼び出し自体を省略しても問題ありません。(省略時は小数第4位まで表示されます。)
音声処理クラスでのパラメーター値の受け取り
次は追加したパラメーターの値を音声処理クラスのprocess関数内で受け取る部分です。
前回説明の通り、渡されるパラメーター値は必ず正規化された値(0.0~1.0)になります。
受け取ったパラメーター値は使用しやすい値に再変換して各メンバー変数に格納しています。
また、今回はデジタルフィルターを使うため、メンバー変数freqとqをセットで利用する必要があります。
各メンバー変数へ格納後、Filterクラスの再計算を実施しています。(processor.cppの44~46行目)
赤く強調された部分が今回変更した部分になります。
【processor.h】
21 22 23 24 25 26 27 28 29 |
class MyVSTProcessor : public AudioEffect { protected: ParamValue freq; ParamValue q; CMyFilter filterL, filterR; // フィルタクラス 説明は省略 参考:https://www.utsbox.com/?page_id=728 public: MyVSTProcessor(); |
【processor.cpp】
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 112 113 114 115 116 117 118 |
// パラメーター変更の処理 // 与えられたパラメーターがあるとき、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 PARAM_FILTERFREQ_TAG: // Freqを変更する // プロセッサクラスに渡されるときは正規化(0.0~1.0)された値となってしまう // 自分でMyParameterに応じた範囲に変更する必要がある freq = ((22000.0f - 50.0f) * pow(value, 3.0f)) + 50.0f; break; case PARAM_FILTERQ_TAG: // qを変更する。 // 同様にRangeParameterに応じた範囲を設定する必要がある。 q = ((12.0f-0.5f) * value) + 0.5f; // 0.5~12.0の間に変更 break; } // 変更されたパラメータに応じてフィルタ係数を再計算する filterL.LowPass(freq, q); filterR.LowPass(freq, q); } } } } |
ローパスフィルタをかけるフィルタクラス(CMyFilter)は「簡単なデジタルフィルタの実装」と「簡単なデジタルフィルタのサンプルコード」にて説明しておりますので詳細は割愛いたします。
音声処理クラスでの音声信号の処理
パラメーター値を受け取り、再変換した後は音声信号を処理します。
今回は単純に入力にフィルタをかけて出力しております。
赤く強調された部分が今回変更した部分になります。
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
// 入力・出力バッファのポインタをわかりやすい変数に格納 // 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] = filterL.Process(inL[i]); outR[i] = filterR.Process(inR[i]); } |
最後に
以上で指数的に値が増加するパラメーターを追加することができます。
今回はデジタルフィルタのカットオフ周波数を例としましたが、シンセサイザーのエンベロープジェネレータなどで利用することがあると思います。
なお、前々回から今回までで作成したVSTは終了してしまうとパラメーターがデフォルト値に戻ってしまいます。
次回は作成したパラメーターを保存する方法について記載したいと思います。
次回→パラメーターの保存方法
このVST3プラグインのサンプルソースファイルはこちらからダウンロードできます → vst3dev_20210403.zip
コンパイル・ビルドの方法は簡単にこちらでご説明しております。ご参考までに。→サンプルソースファイルのビルド方法
VST3プラグイン作りの情報はこちらにもございます → はじめてのVST3プラグイン作り
ご指摘やご質問などがございましたら、コメント欄か掲示板、Twitterでご連絡いただければと思います。
■掲示板
■Twitterアカウント:@vstcpp URL:https://twitter.com/vstcpp