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

前回 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にあります

https://github.com/ShigekiKarita/dplug-simple-delay/tree/v0.0.1

### reset 関数について

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

```d
    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 を書く時の注意

```d
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](https://dlang.org/spec/version.html#version)を使ってunittest時はコンパイルから外します

```d
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の処理をすると左右のチャネルで遅延が生じてずれて聞こえます。

``` d
// 悪い例, 左右がズレて聞こえる
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チャネルも処理する必要があるようです。

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

## Tracktionでの動画

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://t.co/OhtSsgl5s2">https://t.co/OhtSsgl5s2</a> 左右独立のdelay timeとfeedbackもつけた</p>&mdash; カリテク (@kari_tech) <a href="https://twitter.com/kari_tech/status/1043513677972832256?ref_src=twsrc%5Etfw">September 22, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

https://youtu.be/34pHOZG1tFI

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

---

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