LoginSignup
1
0

More than 5 years have passed since last update.

D言語でVST/AUプラグイン開発2 (エフェクト自作)

Last updated at Posted at 2018-09-22

前回 https://qiita.com/ShigekiKarita/items/e67d26b4cfaffd648033

今回はDplugにおけるVST Instrumentの解説をするといいましたが、一からVST Effectを作りたくなったので予定変更します。具体的にはdelayエフェクトを作ろうと思います。エコーとか呼ばれるやつですね

環境などは前回と同じです。

アルゴリズム

我流で書いていたら思いのほか難しかったので、このページを参考に実装してみました
https://www39.atwiki.jp/vst_prog/pages/64.html

雑に説明すると、delayされる過去入力をdelayTimeFrame分の容量を持ったキュー(FIFO, 待ち行列)に1フレームずつ出し入れするだけです。処理時にメモリ確保・解放をしない効率よいキューの実装としてリングバッファを使います。具体的にはbuffer[readIndex..$], buffer[0..writeIndex]の中に過去のdelayしているframeを書き込み、一フレーム新たに出力・入力するごとに++readIndex, ++writeIndexとしてずらしていくだけです。

実装

基本的には前回解説したMSEncodeクラスを変更するだけで作れます。今日までの更新はGitHubにあります

reset 関数について

delayを作るとき、遅延量としてUI上では直感的な「秒」を使って、処理上は「フレーム数」を使いたいことがあります。その変換には1秒あたりのフレーム数であるsampleRateが必要です。しかしながらDplugではVST以外にもAudioUnitsやAAXといった色んなプラグイン規格を抽象化しているので、sampleRateの取得などがよくあるVSTの例とは違うようです。なんとなく前回の例ではreset関数が唯一sampleRateを取得できる方法のようでした。

    override void reset(double sampleRate, int maxFrames, int numInputs, int numOutputs) nothrow @nogc
    {
        // DAW側でsampleRateが変更されたときに呼ばれる?
        if (this._sampleRate != sampleRate)
        {
            // 処理用のsampleRateを変更
            this._sampleRate = sampleRate;
            // RingBufferのサイズも変更
            this._buffer[0] = RingBuffer!float(this.maxDelayTimeFrame);
            this._buffer[1] = RingBuffer!float(this.maxDelayTimeFrame);
            // 新しいsampleRateにおけるUI上のdelayTimeをRingBufferに反映
            this.resetInterval();
        }
    }

前回の記事に触れたreset関数のコメントにあったように、delayに使っているring bufferの再初期化などを行っています。

unittest を書く時の注意

module ringbuffer;

struct RingBuffer(T)
{
     ...
}

unittest
{
    auto buf = RingBuffer!float(3);
    assert(buf[] == [0.0, 0.0, 0.0]);
    buf.setInterval(2);
    assert(buf.readIndex == 0);
    assert(buf.writeIndex == 2);
    assert(buf.front == 0);
    buf.pushBack(1.0);
    buf.pushBack(2.0);
    buf.pushBack(3.0);
    assert(buf[] == [2.0, 3.0, 1.0]);
}

上記のようにD言語は気軽に unittest が書けるのがウリですが、DLLやVSTのmainがあるので普通に dub test とコマンドしても動かないです。そこでD言語のversion conditionを使ってunittest時はコンパイルから外します

module main;
import dplug.core, dplug.client, dplug.vst;

version (unittest)
{}
else
{
    // This create the DLL entry point
    mixin(DLLEntryPoint!());
    // This create the VST entry point
    mixin(VSTEntryPoint!SimpleDelay);
}

複数チャネル処理の注意

基本的には参考ページを愚直にD言語で書いただけですが、L/Rで独立したdelay timeを設定できるようにしたところが違います。あとprocessAudioメソッドで楽をしてL/R channel -> 各frameの処理をすると左右のチャネルで遅延が生じてずれて聞こえます。

// 悪い例, 左右がズレて聞こえる
override void processAudio(const(float*)[] inputs, float*[]outputs, int frames, TimeInfo info) nothrow @nogc
{
    foreach (ch; 0..2)
        foreach (t; 0 .. frames)
            ...     

おそらくDAWが非同期的にこのoutputsバッファを読み出して、再生してしまうのではと予想しています。なので、できるだけLチャネルを処理した直後にRチャネルも処理する必要があるようです。

// 良い例,左右のズレはなさそう
override void processAudio(const(float*)[] inputs, float*[]outputs, int frames, TimeInfo info) nothrow @nogc
{
    foreach (t; 0 .. frames)
        foreach (ch; 0..2)
            ...     

Tracktionでの動画

前回でできたsimple-mono-synthで2分音符を鳴らして、短めのdelayを左右違う長さでかけています。


まだ続くのかはモチベーションと暇次第ですが、GUIか、VST Instrumentについて学習したメモを書こうと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0