pythonでVSTをいじれる"Dawdreamer"を試してみる

Dawdreamerとは?

2021年のISMIR-LBDで発表された、オーディオ処理用のPythonフレームワークです。詳細は以下の引用を見て下さい
論文:https://arxiv.org/abs/2111.09931

DawDreamer is an audio-processing Python framework supporting core DAW features and beyond:

・Composing graphs of multi-channel audio processors
・Audio playback
・VST instruments and effects (with UI editing and state loading/saving)
・FAUST effects and polyphonic instruments
・Time-stretching and looping, optionally according to Ableton Live warp markers
・Pitch-warping
・Parameter automation at audio-rate and at pulses-per-quarter-note
・Parameter automation saving in absolute audio-rate time
・MIDI playback in absolute time and PPQN time
・MIDI file export in absolute time
・Rendering and saving multiple processors simultaneously
・Support for the Faust Box and Signal APIs
・Transpiling Faust code to JAX/Flax and other target languages
・Full support on macOS, Windows, Linux, Google Colab, and Ubuntu Dockerfile

https://github.com/DBraun/DawDreamer

大きくは

  • FaustのPythonラッパーかつ、Faust to JAX /Flaxをサポート

  • VSTi等プラグインのパラメタ操作やオフラインレンダリングが可能

という2つかなと思います。JUCEのprocessorを読み込める様ですがまだ見れてないです。過去にRenderManというものがあり、これを発展したものだと記載があります。(恐らくVSTi対応部分についてのみ)

後者のPluginのパラメタ操作がPythonからできると、深層学習のデータセットを作成したり、既存のソフトウェアシンセを用いてパラメタ推定のタスクを解かせる事ができます。

今回はDawdreamerからPluginを操作する機能について試してみようと思います。

備考:
VSTのパラメタを自動的に動かしてデータセットを作る事は、Max等でもできるが、リアルタイムで処理が進められていくので場合によっては非常に時間がかかってしまう。(Maxの場合は商用製品で品質がしっかりしているし使いやすいけれど)
結構読み込めないプラグイン多い。Maxは悪くない、プラグインの規格が整備されてないだけだと思う。
Dawdreamerはノンリアルタイムで処理を行なっていくので、大規模なデータセットを作るのはこういったものが良いのかも

パラメタ推定のタスクの例は、以下の論文など
https://dafx2020.mdw.ac.at/proceedings/papers/DAFx20in21_paper_46.pdf
この方はRendermanを使っているようです。


How to use

Wikiの中に、Plugin Processorについてのサンプルがあり、基本的には公式ドキュメントを見ながら進めれば問題はないのですが、躓いた部分や分からなかった部分のメモを残しておきます。

動作確認済のプラグインは以下URLから確認可能です。
私はMacで試したのですが、対応しているものが少なく、限定した使い方が強いられそうです。
リストに無いプラグインをいくつか試してみましたが、読み込みでエラーとなりました。(Neuton (VST3)、VCV Rack (VST)、Vital (VST)、Synth1(Component)はうまく動作しませんでした。)

対応プラグインリスト:
https://github.com/DBraun/DawDreamer/wiki/Plugin-Compatibility

Utilities and imports部

import dawdreamer as daw
import numpy as np
SAMPLE_RATE = 44100

def make_sine(freq: float, duration: float, sr=SAMPLE_RATE):
  """Return sine wave based on freq in Hz and duration in seconds"""
  N = int(duration * sr) # Number of samples 
  return np.sin(np.pi*2.*freq*np.arange(N)/sr)

基本的なimportとサンプルレートの定義、make_sine関数は後にパラメタを操作するLFOの様な形で使用

Examples部

長いので少しずつ区切って解説していきます。

プラグインのパスの定義など

BUFFER_SIZE = 128 # Parameters will undergo automation at this buffer/block size.
PPQN = 960 # Pulses per quarter note.
SYNTH_PLUGIN = "C:/path/to/synth.dll"  # extensions: .dll, .vst3, .vst, .component
REVERB_PLUGIN = "C:/path/to/reverb.dll"  # extensions: .dll, .vst3, .vst, .component
MIDI_PATH = "C:/path/to/song.mid"

Macの場合であればプラグインは標準で”/Library/Audio/Plug-Ins”以下にインストールされる。 例えば以下の様に定義します。(Dawdreamer全体に言える事ですが、絶対パスでないと受け付けてくれません)

SYNTH_PLUGIN  = "/Library/Audio/Plug-Ins/VST/TAL-NoiseMaker.vst"
REVERB_PLUGIN = "/Library/Audio/Plug-Ins/VST3/RoughRider3.vst3"

※ RoughRiderはリバーブではないですが、変数名はサンプルコードに準拠しています。

設定の読み込み等

engine = daw.RenderEngine(SAMPLE_RATE, BUFFER_SIZE)

# Make a processor and give it the unique name "my_synth", which we use later.
synth = engine.make_plugin_processor("my_synth", SYNTH_PLUGIN)
assert synth.get_name() == "my_synth"

# Plugins can show their UI.
synth.open_editor()  # Open the editor, make changes, and close
synth.save_state('C:/path/to/state1')
# Next time, we can load_state without using open_editor.
synth.load_state('C:/path/to/state1')

# For some plugins, it's possible to load presets:
synth.load_preset('C:/path/to/preset.fxp')
synth.load_vst3_preset('C:/path/to/preset.vstpreset')

synth.open_editor()は謎、私の環境では永遠に動作せず止まっているので、コメントアウトした。(jupyterでやったからだった、pyファイルで動作させると動いた)
synth.save_stateで設定したパラメータを保存できる
プリセット関連は、fxpとvstpreset形式が使用できるが、私が試しに使ったTAL-NoiseMakerもRoughriderも両形式に対応しておらず検証できなかった。

オートメーションの設定

# We'll set automation for our synth. Later we'll want to bake this automation into
# audio-rate data, so we must enable `record_automation`. If you don't intend to call
# `get_automation()` later, there's no need to do this:
synth.record_automation = True

# Get a list of dictionaries where each dictionary describes a controllable parameter.
print(synth.get_parameters_description()) 
print(synth.get_parameter_name(1)) # For Serum, returns "A Pan" (oscillator A's panning)

# Note that Plugin Processor parameters are between [0, 1], even "discrete" parameters.
# We can simply set a constant value.
synth.set_parameter(1, 0.1234)
# The Plugin Processor can set automation with data at audio rate.
num_seconds = 10
synth.set_automation(1, 0.5+.5*make_sine(.5, num_seconds)) # 0.5 Hz sine wave remapped to [0, 1]

絶対時間に沿って任意のパラメタのオートメーションを設定します。set_automation()では第一引数でパラメータを設定し、第二引数でパラメータの値を設定します。この時numpyの配列で設定できるので大概の事はできるはず。
上記の例では、
正弦波(A = [-1,1])*0.5+0.5で[0,1]のLFOを設定しています。

# It's also possible to set automation in alignment with the tempo.
# Let's make a numpy array whose "sample rate" is PPQN. Suppose PPQN is 960.
# Each 960 values in the array correspond to a quarter note of time progressing.
# Let's make a parameter alternate between 0.25 and 0.75 four times per beat.
# Here, the second argument to `make_sine` actually represents a number of beats.
num_beats = 20
automation = make_sine(4, num_beats, sr=PPQN)
automation = 0.25+.5*(automation > 0).astype(np.float32)
synth.set_automation(1, automation, ppqn=PPQN)

BPMに沿ってautomationを設定します。PPQNの概念がうまく掴めない。
ここでのautomationの設定の仕方が面白くて、正弦波>0でboolが返ってくるのですが、それによってゼロイチの矩形波を表現しているのがなるほどと思いました。

MIDI読込と追加

# Load a MIDI file and convert the timing to absolute seconds (beats=False).
# Changes to the Render Engine's BPM won't affect the timing. The kwargs below are defaults.
synth.load_midi(MIDI_PATH, clear_previous=True, beats=False, all_events=True)

# Load a MIDI file and keep the timing in units of beats. Changes to the Render Engine's BPM
# will affect the timing.
synth.load_midi(MIDI_PATH, beats=True)

# We can also add one note at a time, specifying a start time and duration, both in seconds
synth.add_midi_note(60, 127, 0.5, .25) # (MIDI note, velocity, start, duration)

# With `beats=True`, we can use beats as the unit for the start time and duration.
# Rest for a beat and then play a note for a half beat.
synth.add_midi_note(67, 127, 1, .5, beats=True)

MIDIを絶対時間とBPMに沿って追加している例

FX設定〜レンダリング等

# For any processor type, we can get the number of inputs and outputs
print("synth num inputs: ", synth.get_num_input_channels())
print("synth num outputs: ", synth.get_num_output_channels())

reverb_processor = engine.make_plugin_processor("reverb", REVERB_PLUGIN)

graph = [
  (synth, []),  # synth takes no inputs, so we give an empty list.
  (reverb_processor, [synth.get_name()])  # hard-coding "my_synth" also works instead of get_name()
]

engine.load_graph(graph)
engine.render(5)  # Render 5 seconds of audio.
# engine.render(5, beats=True)  # Render 5 beats of audio.

# You can modify processors without recreating the graph.
synth.load("C:/path/to/other_preset.fxp")

# After rendering, you can fetch PPQN-rate automation that has been baked into audio-rate data.
# Use a smaller buffer size to get more granularity in this data, otherwise there will be
# some "steppiness"/"nearest-lookup".
all_automation = synth.get_automation()  # a dictionary of all parameters at audio-rate.

# After rendering, you can save to MIDI with absolute times, in case the BPM affected it.
synth.save_midi("my_midi_output.mid")

# Even after a render, we can still modify our processors and re-render the graph.
# All of our MIDI is still loaded.
synth.load_preset("C:/path/to/other_preset.fxp")
engine.render(DURATION)

synth.clear_midi()
synth.add_midi_note(65, 100, 1, .5, beats=True)

# continue rendering...

MIDIはsaveできるが読み込みできない形になっており、調査が必要。バイナリを見る限りではそんなに変な事にはなってなかったけれど、きちんとは見れてないです。
ipynbで試している場合、IPython.displayのAudioなどで確認する事ができます。また、scipyを用いてwavに変換も可能です。(後述のリポジトリにソースを置いておきます。)

まとめ

サンプルの概要とつまづいた部分を中心に書きました。オーディオを対象とした深層学習のデータセット構築やシンセのパラメタ推定タスク等に使用できると思います。
Macで使用する場合は対応プラグインが少なく使い所が限られてくるのかなと思います。本格的に使用する際はWindows環境を用意した方が良いかも
自分の環境で試したのみですが、以下にソースを置いておきます。では



この記事が気に入ったらサポートをしてみませんか?