D言語でVST/AUプラグイン開発1 (環境構築・検証編)

tl;dr

- Dplugを使うと例外やGCを使わないD言語でVST/AU/AAXが簡単に作れる
- ついでにD言語はOS依存が少ないので色んな環境で動く
- 需要が多そうなWindows VSTでビルドや動作を検証

https://github.com/AuburnSounds/Dplug

## 筆者の環境

- Dコンパイラ DMD2.082.0
- Dplug 7.1.2
- VST SDK vstsdk3610_11_06_2018_build_37
- Tracktion 7
- Windows 10 64bit

## 準備

VSTというのはSteinberg社が作った、PC上の音楽制作に使うエフェクトやシンセの規格で、共通して色んなOSや録音ソフトで使えるようになります。とりあえず動作確認環境が欲しいので、Linux/Mac/Windowで無料のVSTプラグイン対応DAWであるTracktionをインストールします。

https://www.tracktion.com/products/t7-daw

今回はWindowsでの64bitビルドがハードル高かったので(追記参照)、とりあえず32bit版を使いますが、Windowsは使わないよとか64bitビルドできるよという人は別の選択肢でもいいです。もし以下の公式サポートがあるDAWをお持ちの方はそれを使えば良いです。

https://github.com/AuburnSounds/Dplug/wiki/Host-Support

最後にD言語コンパイラは公式のインストーラなどを使って入れました。

https://dlang.org/download.html

## Getting Started

https://github.com/AuburnSounds/Dplug/wiki/Getting-Started

上記の資料に従って最初に VST SDK を入手します。VST3と書いてありますが、Dplugがサポートしている昔の規格VST2も同梱されています。よくあるDownloadディレクトリに展開した場合、次のように環境変数を設定しましょう。以降この記事ではpowershell上でコマンドを打ち込んでいる想定です。

``` powershell
# powershell
set-item env:VST2_SDK C:\Users\skarita\Downloads\vstsdk3610_11_06_2018_build_37\VST_SDK\VST2_SDK
```

次に Dplug 本体を clone して example をビルドします。チュートリアルではdistortやcliptなどの例をビルドするように書いてありますが、ここでは専用GUIもなく最も単純なms-encodeを使ってみます。アルゴリズム自体はとても単純なエフェクトなので下記のWikipediaをご覧ください

https://ja.wikipedia.org/wiki/M/S%E3%83%97%E3%83%AD%E3%82%BB%E3%83%83%E3%82%B7%E3%83%B3%E3%82%B0

以下がビルド手順です、基本的にどのOSも同じだと思いますが、ただし最後の行を `--arch=x86_64` とすると64bit版のプラグインがビルドできます（うまいこと環境構築できていれば）

``` bash
git clone https://github.com/AuburnSounds/Dplug -b 7.1.2
cd Dplug/examples/ms-encode
dub build --arch=x86
```

成功した場合は現在のディレクトリに `ms-encode.dll` というプラグインの動的ライブラリファイルができているはずです。

## 動作確認

次にTracktionを起動して動作確認します。

1. Tracktionでプラグインのパスを通す: Settingsタブ(上) > Plugins (左) > Scanning and Sorting (下) > Scan for new or updated VST plugins (ポップアップ) > + ボタンでパスを追加できます。先ほどの作業ディレクトリ `Dplug/examples/ms-encode`を設定します
2. 適当なプロジェクトをDL：Projects (上) > Get Demo Songs (左下) > 2BITs
3. 適当なプロジェクトを開く：Projects (上) > Active Projects (左) > 2BITs > Edits (中央)> 2Bits ダブルクリック
4. 適当なトラックで`ms-encode`エフェクトを読み込む >  各トラックの右側を適当にクリック (画像) > Add new plugin > ms-encode

![image.png](https://qiita-image-store.s3.amazonaws.com/0/37859/e5de407b-a44c-681e-df09-02d8d7671e0e.png)

以上の手順で下のようなシンプルなプラグイン画面がでてきます。真ん中のYesと書いてるフェーダを左にドラッグするとNoに変わり、MSから普通のLRステレオに戻り音が変わるはずです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/37859/c01e5390-89da-c7d3-38c3-234f53c109bb.png)


## ms-encode 解説


ソースコードはこれだけです。
- オーディオ処理のコード https://github.com/AuburnSounds/Dplug/blob/v7.1.2/examples/ms-encode/msencode.d

``` d
/**
Copyright: Guillaume Piolat 2015-2017.
License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
*/
import std.math;
import dplug.core, dplug.client, dplug.vst;
/** VST用のボイラープレートを挿入 */
// This create the DLL entry point
mixin(DLLEntryPoint!());
// This create the VST entry point
mixin(VSTEntryPoint!MSEncode);

enum : int { paramOnOff } // パラメータの識別子

/// あなたが作れる最もシンプルなVSTプラグイン
final class MSEncode : dplug.client.Client {
public:
    /// (必須)
    override PluginInfo buildPluginInfo()
    {
        // PluginInfoはplugin.jsonをコンパイル時にパースして作る
        // 設定は一か所にまとめる方がよい、手動の初期化はお勧めしない
        static immutable PluginInfo pluginInfo = parsePluginInfo(import("plugin.json"));
        return pluginInfo;
    }
    /// (オプション) Parameter の構築
    /// 返り値のメモリ確保は GC による new ではなく、mallocによるメモリ確保をしなくてはいけない
    /// GC管理ではないのでメモリ開放は手動でfreeするか、dplug.core.vec.Vecにfreeを任せる
    /// std::vector相当の型 https://github.com/AuburnSounds/Dplug/blob/v7.1.2/core/dplug/core/vec.d
    /// malloc系の関数 https://github.com/AuburnSounds/Dplug/blob/v7.1.2/core/dplug/core/nogc.d
    override Parameter[] buildParameters()
    {
        // DAWのGUIで「on/off」というパラメータを表示
        auto params = makeVec!Parameter();
        params.pushBack( mallocNew!BoolParameter(paramOnOff, "on/off", true) );
        return params.releaseData();
    }
    /// (必須) IO組み合わせが対応していると通知するメソッド。ここでも返り値にGCを使ってはいけない
    override LegalIO[] buildLegalIO()
    {
        auto io = makeVec!LegalIO();
        io.pushBack(LegalIO(2, 2)); // 2in-2out のみ対応
        return io.releaseData();
    }
    /// (必須) 状態（例：リサイズや遅延線）や確保したバッファのクリアに使う
    /// 重要：オーディオ用スレッドから呼ばれるのでGCを使ってはいけない (nothrow, nogc)
    override void reset(double sampleRate, int maxFrames, int numInputs, int numOutputs) nothrow @nogc
    {
    }

    /// (必須) オーディオ処理を行うメソッド
    /// float*ポインタはプラグインがlegalIOとしたチャネルで正常なポインタであることが保証され、未接続のチャネルもゼロ埋めされる
    /// framesは最後にreset()が呼ばれたとき以上の値をとることが保証される
    /// inputsとoutputsは最後にreset()が呼ばれたときと全く同じであることが保証される
    /// 警告 outputs のポインタを変更してはいけない (値のみを変更する)
    override void processAudio(const(float*)[] inputs, float*[]outputs, int frames, TimeInfo info) nothrow @nogc
    {
        if (readBoolParamValue(paramOnOff)) // MS処理をするとき
        {
            // M チャンネル、パワーをLRのときと同じにするために 1/sqrt(2) 倍している
            outputs[0][0..frames] = ( (inputs[0][0..frames] + inputs[1][0..frames]) ) * SQRT1_2;
            // S チャンネル
            outputs[1][0..frames] = ( (inputs[0][0..frames] - inputs[1][0..frames]) ) * SQRT1_2;
        }
        else // 普通にLRをバイパスするとき (注意：ポインタを付け替えてはいけない、愚直にコピーする)
        {
            outputs[0][0..frames] = inputs[0][0..frames];
            outputs[1][0..frames] = inputs[1][0..frames];
        }
    }
}
```

あとは設定ファイルです
- プラグインの設定、[構造体](https://github.com/AuburnSounds/Dplug/blob/v7.1.2/client/dplug/client/client.d#L83)の定義、[スキーマはこちら](https://github.com/AuburnSounds/Dplug/blob/v7.1.2/plugin-schema.json) https://github.com/AuburnSounds/Dplug/blob/v7.1.2/examples/ms-encode/plugin.json
- ビルド用の設定、[仕様はこちら](https://code.dlang.org/package-format?lang=json) https://github.com/AuburnSounds/Dplug/blob/v7.1.2/examples/ms-encode/dub.json

``` javascript
{
    // 仕様
    "$schema": "https://raw.githubusercontent.com/AuburnSounds/dplug/master/plugin-schema.json",
    // ベンダーの名前
    "vendorName": "No Name Audio",
    // 4文字のユニークなベンダーID
    "vendorUniqueID": "NoAu",
    // プラグインの名前
    "pluginName": "MSEncodator",
    // 4文字のユニークなプラグインID、互換性が破壊されたときは変更する必要がある
    "pluginUniqueID": "NAms",
    // majorバージョンが一致する場合は互換性がなくてはならない
    "publicVersion": "1.0.0",
    // OSX用の何らかの設定
    "CFBundleIdentifierPrefix": "com.nonameaudio",
    // GUIの有無
    "hasGUI": false,
    // VSTインストルメントかどうか
    "isSynth": false,
    // MIDI入力をとるか
    "receivesMIDI": false,
    // プラグインのカテゴリ。列挙値はschemaを見る
    "category": "effectImaging"
}
```

---

### 追記:AUなどの同時対応

これはものすごい便利で、上記のおまじない的な部分を次のようにすればよいみたいです

```d

mixin(DLLEntryPoint!());

version(VST)
{
    import dplug.vst;
    mixin(VSTEntryPoint!ClipitClient);
}

version(AU)
{
    import dplug.au;
    mixin(AUEntryPoint!ClipitClient);
}
```

例: https://github.com/AuburnSounds/Dplug/blob/v7.1.2/examples/clipit/main.d

ビルド方法の詳細は`dub.json`を見てほしいですが、`dub build --config=VST` や `dub build --config=AU`でいけると思います。

### 追記:VSTインストルメント

https://github.com/AuburnSounds/Dplug/blob/v7.1.2/examples/simple-mono-synth/simplemonosynth.d

今回は単純なエフェクトだけを紹介しましたが、Dplugではインストルメントも非常に単純に作れるようです（わずか173行！）。これなら以前Visual C++でVSTインストルメントを作ろうとして挫折した私でも何か作れそうです。次回はインストルメントについて解説したいと思います。


### 追記：Windows での VST 64bit ビルドの手引き

DMDやLDCのインストールだけでなく、Visual Studio Community 2017 で「C++によるデスクトップ開発環境」をインストールするといけます。12GBくらいあるので注意。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/37859/a4ae80ee-0b04-8195-5c81-38d03493363f.png)