Python で DAW 用の VST プラグインを呼び出して MIDI データから WAV ファイルを生成する (dawdreamer)

# やること

- python のプログラムだけで、SMF (`some.mid`みたいなファイル) から、音声として再生可能な`some.wav` ファイルを生成します。

- この変換のときに DAW で使える VST プラグイン（サンプラーやシンセサイザ）を、ソフトウェア音源として使います。

変換には DawDreamer ライブラリを使います。

- [DawDreamer](https://github.com/DBraun/DawDreamer)

Windows 11 / Mac で動きました。

# 準備

## ライブラリのインストール

**python は 3.10** を使います。3.9 以下だと動かないプラグインがありました。

あとは必要なライブラリヲインストールします。

```bash
$ pip install dawdreamer numpy scipy
```

numpy / scipy の代わりに torch / torchaudio でもいけます。

## プラグインの用意

適当な VST プラグインを用意します。下のコードでは Native Instruments のシンセサイザの Massive を使っています。

# コード

```vst.py
import dawdreamer as daw
import numpy as np
from scipy.io import wavfile

SAMPLE_RATE = 44100
BUFFER_SIZE = 128

SYNTH_PLUGIN = "C:/Program Files/Common Files/VST3/Massive.vst3" # for windows
#SYNTH_PLUGIN = "/Library/Audio/Plug-Ins/VST3/Massive.vst3" # for mac
MIDI_PATH = "./sample.mid"
OUTPUT = "./massive.wav"

engine = daw.RenderEngine(SAMPLE_RATE, BUFFER_SIZE)
synth = engine.make_plugin_processor("Massive", SYNTH_PLUGIN)
assert synth.get_name() == "Massive"

synth.open_editor()
synth.save_state('./massive.state')

synth.load_midi(MIDI_PATH, clear_previous=True, beats=False, all_events=True)

graph = [
  (synth, []),
]

engine.load_graph(graph)
engine.render(8) # WAVに出力する秒数を指定。無指定ではエラーになる。
output = engine.get_audio()

wavfile.write(OUTPUT, SAMPLE_RATE, output.transpose())
```

上記コードを実行すると、シンセサイザのウィンドウが開きます。そのウィンドウ上でプリセットを読み込んだり、パラメータをいじったりしてからウィンドウを閉じると、その設定の状態で mid ファイルの中身を演奏（レンダリング）した wav ファイルが出力されます。

毎回ウィンドウひらくのが面倒な場合は、state ファイルをロードさせることもできます。

```vst.py
#synth.open_editor()
#synth.save_state('./massive.state')
synth.load_state('./massive.state') # これで設定をロードできる
```

# プラグインいろいろ

プラグインによって、うまく動かなかったり、出力ファイルが再生できないとかあったので、メモしておきます。

エフェクタは試していません。

## Mac の場合

- ◎ Massive: 問題なく動く
- ◎ Battery 4: 問題なく動く
- ◎ BFD3: 問題なく動く
- ◎ Addictive Keys: 問題なく動く
- 〇 Massive X: コンソールにエラーが大量に出るが、wav は正常に出力される。
- 〇 Loopcloud: ウィンドウは開くので多分いける（サブスクリプションが切れてて試せなかった）
- △ Komplete Kontrol: Instruments, Loops, One-shots のいずれも動く。ただし、Windows 版の Komplete Kontrol と同じ問題が起こる（後述）。
- △ Kontakt 6 Player: 動く。ただし、Windows 版の Komplete Kontrol と同じ問題が起こる（後述）。
- △ Synthesizer V: ウィンドウが開くので、UI上で音符を入力すると wav は出力される。ただし、入力した譜面の最初の1音しか出力されない（気がする）。
- 〇 ~~× Kontakt 7: ウィンドウが開かない（使えない）。~~ python のバージョンを 3.10 にしたら動きました。3.9 では動きませんでした。

## Windows の場合

- ◎ Massive: 問題なく動く
- ◎ Battery 4: 問題なく動く
- 〇 Massive X: コンソールにエラーが大量に出るが、wav は正常に出力される。
- △ Komplete Kontrol: Instruments, Loops, One-shots のいずれも使える。ただし、出力されたファイルが 32 ch になってしまい、そのままでは再生できない。後述する方法でチャンネル数を2に減らす必要がある。
- 〇 ~~× Kontakt 7: ウィンドウは開くが、Instruments の読み込みのときにフリーズしてしまう（使えない）~~ ← Kontakt のライブラリの表示モードを Instruments Rack にしたら問題なく動きました。Content Browser モードだとフリーズすることがあるようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/a94b0a57-b7af-792d-cf22-ca872825a486.png)

見た目が上のように、左側に instruments が縦に表示されるモードのことです。KONTAKT 7 というロゴのすぐ右側にある、本みたいなアイコンを押すと切り替えられます。


## Komplete Kontrol / Kontakt 6 Player への対応

プラグインを呼び出すときにチャンネル数を 2 に指定できればいいのだけど、やり方が分かりませんでした。出力されたデータ側を修正することで、一応対応はできます。

```komplete_kontrol.py
wavfile.write(OUTPUT, SAMPLE_RATE, output[0:2,:].transpose())
```

出力されたデータを見ると、3 チャネル以降には何もデータが入っていない（無音）状態になっていました。なので、単純に最初の2チャンネルだけ取り出しています。これで一応いけてました。