VST作製~JUCEでShort Delay Pluginを作ったメモ
最初に
本記事は、以下のHaas効果なるステレオ効果を生じさせるVST Pluginを作製した際のメモになります。
偏ったDTM用語辞典 - ハースコウカ ハース効果:Haas Effectとは - DTM / MIDI 用語の意味・解説 | g200kg Music & Software
Haas効果を生じさせるには、ステレオ入力の片側チャンネルを数十ミリセカンドほど遅らせるようなVST(以下、Short Delay Plugin)を設計すればよさそうです。
Short Delay Pluginの仕様
以下のような仕様を満たすVSTを設計することにします。
- ステレオ入力の片側を0 ~ 50 msecの範囲でDelayさせて出力できる
- Delayさせるチャンネルは任意に選択可能
- Delayさせた入力とDelayさせてない素の入力を任意の量で混ぜ合わせたものを出力できる (Dry / Wetの設定が可能)
これに関しては、以下の完成したVSTのGUIを見ていただければ、何となく伝わるかと思います。
Delay EffectのDSP
ここで、Delay Effectとは、入力信号を一定の時間間隔で繰り返し再生するようなEffectであるとします。実際のDelay Effectorにおいて、上記の一定の時間間隔と繰り返しの回数は、それぞれ、Delay Time、Feedbackと言う名前のパラメータとして与えられていることが多いように思います。ここで、Delay TimeをT [s]、FeedbackをN [整数]としてやると、Delayされた出力成分は以下のように表現することができます。
ここで、y(t)とx(t)は時刻tにおける出力信号と入力信号になります。実際のDelay Effectorでは、Feedback回数に応じて、繰り返し音は減衰していくので、減衰量に関する定数を導入して、下記のようにした方がより現実的かと思います。
今回作製するShort Delay Pluginは、複数回の繰り返しと減衰の必要は無いのでN = 1、a = 1とします。さらに、Delay成分に素の入力(Dry)を混ぜ合わせたものを出力とする(仕様3より)ことを考慮して、全体として以下の処理をPuluginに実装してやることになります。
ここで、Delay成分の量を調整するパラメータを導入しています。m = 1でDelay成分のみ、m = 0の時にDry成分のみが出力されます。
DSPの実装
実装はJUCEを用いて行いました。実際のソースコードをここにベタ貼りするのもアレなので、詳細は下記のリポジトリ(Source/Modules/DelayComponent.h and cpp)を参照していただければと思います。また、実際に、ビルド済みのVST(32 bit / 64 bit)も置いてあるので、DAWに読み込ませたら動くと思います。ここでは、設計の要点のみを記載していきます。
DSP関連の実装部は以下の3つのClassに分けて行います。
- Parameters Class
- DelayComponent Class
- DelayProcessing Class
以下、順に要点のみを記載していきます。
Parameters Class
Short Delay Pluginには、3つの任意に設定可能なパラメータを持たせます。これらパラメータの値は、DAWホスト、VSTのDSP部、GUI部から参照・設定できるようにしたいので、パラメータ部だけを独立したClassにしています。
- delayChannel
- delayTime
- mixing
delayChannelは、EffectをかけるChannelを設定します(仕様2より)。0 or 1の値をとり、VSTの仕様上0がL channel、1がR channelに対応します。delayTimeは、0 ~ 1fの値をとるようにします。実際のEffect処理部でこれを0~50 ms範囲にスケール変換させます。mixingは、出力音のDelay成分(Wet成分)の量を調整するパラメータです(先の実装数式のmに相当)。
DelayComponent / DelayProcessor Class
これらClassが、Short Delat Effectの本体部分に相当します。DelayComponent Classには、過去の入力をDelayBufferに保持、そこから0 ~ 50 ms時間前の入力をDelay成分として取り出し、現在の入力(Dry成分)と混ぜ合わせてDAWのAudioBufferに出力するといったShort Delayに必要な各機能群を分けて実装しています。そして、DelayProcessing Classで、この一連の処理をそれぞれの入出力チャンネルのAudioBuffer全体に対して繰り返し適用する関数を実装します。
GUIの実装
割愛。自前で画像素材作れるセンスが無いので、全部JUCEの素材使って実装してます。詳細はソースコードを見ていただければ。
Wavetableを使ったオシレータ1
Wavetable Synthesis
Wavetable Synthesisとは
だそうです。
今回は、サイン波1周期分をテーブル化して、そこからピッチを可変できるウェーブテーブル方式のオシレータの実装方法について考えてみます。
※サンプルコードは後日追記するかも
テーブルの作製
Sine関数の1周期をLサンプルに分割したテーブルSinTableを作製します (Fig. 1)。
サンプルレート48 kHzの環境であれば、テーブルサイズLは512サンプルもあれば十分かと。
Fig. 1 SinTable (L = 512)
オシレータ部の実装
単一のSinTableから任意の周波数fを生成する方法を考えていきます。
周波数fの1周期あたりのサンプル数pは、ホストDAWのサンプリング周波数fsを用いて、p = fs / f となります。そして、SinTableにはSine波1周期分の波形データが入っていることを考えると、SinTableから任意の周波数fを持つSine波を作るということは、
pサンプル進む間に、SinTableの頭からケツまでをちょうど読み切ればよい
ということになります (Fig. 2)。
Fig. 2 1サンプル進む毎に、テーブルの読みだし位置はL / pずつ進む
従って、周波数fの波形g(n)は、次のように生成することができる(Eq. 1, 2)。
g(n) = SinTable[m] (Eq. 1)
ここで、m = (L * n / p) mod(L) = (L * f * n / fs) mod(L) (Eq. 2)
mod(L)は周期化処理です。
ここで、mは必ずしも整数になるわけでは無いので、線形補完処理によってEq. 1を以下のように拡張します (Eq. 3)。
g(n) = d * SinTable[int(m+1)mod(L)] + (1 - d) * SinTable[int(m)] (Eq. 3)
ここで、d = m - int(m)
以上、終わり。
L = 512であれば、単純な1次線形補完でも大丈夫な感じでした。実際、商用のVAシンセ等の場合はバンドリミット補完や、ラマジャンヌ補完が用いられてるとのことなのですが、どうなんですかね。
JUCE-AudioProcessorParameter Classを使ったパラメータ管理あれこれ 1
AudioProcessorParameter Class
JUCEには、パラメータを管理するクラスとしてAudioProcessorParameter Classが用意されている。どうやら、利用したいパラメータの数だけこのオブジェクトを用意してやれば、簡単にパラメータの数を追加できるっぽい。
パラメータの追加
今回は、整数値を取り扱うパラメータを追加してみる。int型のパラメータを取り扱うためのAudioParameterInt Classのオブジェクト (AudioProcessorParameterの派生Class)を作製し登録すると、パラメータが追加される (Source 1 and Fig. 1)。
Fig. 1: 1 ~ 3の値が設定できるパラメータparamAが追加された
パラメータテキストに設定した数値以外の値を返す
ここから本題。このパラメータ管理、デフォルトだとパラメータテキストの欄に設定した数値がそのまま返ってくる。すなわち、オブジェクト作製時に指定した、最小値1から最大値3までの整数しか表示されない。地味に困る。
そこで、雑なやり方ではあるが設定したパラメータ(値)に対応した文字列を返す方法をメモしておく。試しにparamA = 1のとき "a"、paramA = 2のとき"b"、paramA = 3のとき"c"の文字列を返すということを行ってみる(Source 2 and Fig. 2)。
- 各パラメータの表示用文字列を格納する配列textDeta[n]を用意。nは全パラメータ数。
- パラメータテキストをホストに伝える関数String getParameterText(int index, int)をoverrideし、指定されたパラメータ番号(index)に対応した、textDetaの要素をホストに返すようにする
- textDeta[index]に要素を代入する。paramObjPtr->get()で、paramObjPtrの現在の値が取得できる。また、paramObjPtr-> getParameterIndex()で、paramObjPtrのindex番号が取得できるので、この辺使えば何とかなる。
Fig. 2: スライダーの位置に応じた文字列を返す。
getParameterIndex()のおかげで、パラメータ数が増えてもいけるはず。
ステップシーケンサー実装メモ1
概要
JUCEでステップシーケンサーを実装します。とりあえず、音階指定は無しで各ステップのノートのON/OFFを切り替えるだけの原始的なやつ 。
機能的には、1) 現在のステップがON状態だったら、holdTime (sec)だけON音信号を出力し、2) sizeStep (sec)経過したら、次のステップに移る。この1), 2)を繰り返すだけ。
実装
解説することが無い。追記)JUCE関係ねぇはコレ。