夏休みはVST! MATLABで楽々(?)VSTプラグイン開発
電子書籍発売しました!(2023/01/22)
動画・音声リンク付き紹介記事はこちら。
Amazon Kindle 電子書籍「MATLAB で簡単オーディオ プラグイン開発」
スクリプトはどなたでも無料でダウンロードできます。
入門編の記事を追加しました(2021/10/9)。
MATLABでVSTプラグイン開発 [初級編]~数分?で作れるVST~LMSで無相関プラグイン
前書き
VSTプラグインの開発方法はMATLABの公式ドキュメントに色々と詳しく載ってはいるのですが、例によって分散してかなり分量があり取っつきにくいかと思います。
特に日本語の資料は少ないようなので解説をしてみます。
本来VSTプラグイン開発はオブジェクト指向を理解していること前提のようですが、私自身、オブジェクト指向は20年以上前にC++を使ってみて、
「//でコメント書けるのちょー便利!」
「特に打合せもせず、複数人で書いたソースまとめると動いちゃうのすげー!!」
くらいの感じで止まっているので、オブジェクト指向とか知らなくても、ここに書いてあることをだいたい理解すれば、自由にVSTプラグインを作って活用できるようになることを目指します!
余裕のある方はあらかじめ、"audio plugin" で検索して公式ドキュメントを眺めてみてください。全て理解しなくても、「あそこになんか書かれてたような・・」と思い出せると理解が早まると思います。
ここでは信号処理自体には触れませんが、VSTプラグイン開発自体だけではなく、それをどう活用するかについても解説しているため、文字数はかなり多めです。
基本的に、Windows版での説明になります。
VSTプラグインとは
VST(Virtual Studio Technology)とは、スタインバーグ社(現ヤマハ傘下)がDAW用に開発したプラグイン規格です。
オーディオ用は VSTインストゥルメント (VSTi) と VSTエフェクトがありますが、MATLABで開発できるのはエフェクト(狭義のVST)の方です。
VSTって何に使えるの?
以下の3種類の使い方があります。
1.DAWに組み込む
-複数トラックへの適用が可能
-(DAWによっては)パラメータ操作のオートメーション(リアルタイム記録・編集等)が可能
この辺りは、DAWをお使いの方はよくご存じかと思いますので説明は省きます。(GUIの "Rotary knob" 等を表示させるには、REAPER等、カスタムGUI対応のDAWが必要です)
2.foobar2000、MusicBee等の音楽プレーヤーに組み込む
後述しますが、一部の音楽プレイヤーなどは拡張機能としてVSTプラグインの適用が可能です。
3.単体動作(exe)、VSTホストアプリの利用
外部入力に対して適用することができます。
後述するバーチャルI/O系を併用すれば、VST非対応のプレイヤーやWebブラウザー出力に(YouTube等にも)適用が可能です。
ただ私の環境ではexe版は、原因不明ですがI/Fによってプチプチノイズが発生したりします・・。
VSTホストを使った方が安定度が高い印象です。
----> 2021/1/19追記
今はノイズも出なくなりましたので追記します。
・PCの標準出力
Realtek製である場合が多いと思いますが、サウンドコントロールパネルのプロパティ->詳細で、サンプリングレートを48kHz(Virtual In・Virtual Outも)にすることで解決しました。
もしかするとRealtekの内部処理が48kHz固定(?)で、それ以外だとSRC(サンプリングレートコンバーター)が入って処理タイミングが合わなくなるのかもしれません。
SRCは、原理的に44.1kHz <-> 48kHzの高精度変換処理量はかなり多く、SRCによっては音質劣化の原因ともなります。それぞれの環境に合わせて確認してみてください。
・USBオーディオI/F(Behringer UCA202/222)
USBハブを使った場合に出やすいようです。PC直差しにしたらノイズは出なくなりました。
それと、PCでオーディオ関連の開発を行う場合は、サウンドコントロールパネルのプロパティに「立体音響」等のタブがあれば全て無効にしておきましょう。OFFにしないと、想定外のエフェクトが掛かってしまいます。
また、PCによってはそれと別の音響効果がデフォルトで入っている場合があるので、そちらもOFFにしておきましょう。例えばDELLであれば、全てのPCにWaves MaxxAudioというのが入っていますので、こちらの設定も確認しておきましょう。
<---- 2021/1/19追記
VSTプラグイン化のメリット
仕様にしたがってGUIパラメータの宣言さえすれば、そこそこ手間のかかるGUI自体を作る必要がありません。
基本的にメインの信号処理部分だけを書けば、GUIでの操作が可能となります。
生成されたVSTはネイティブ動作なので処理が高速です。
VST開発をMATLABで行うメリットは
-VST SDKを使わなくて良い
-信号処理部にMATLABの関数が使える
-MATLAB上でデバッグができる(通常のグラフィカルなデバッグも可能)
といったところでしょうか。
但し、VST動作では制限事項もあります。
-グラフ出力不可
-タイム関数やWebアクセス関数等使用不可(これらを使った認証等が行えません)
-ファイル読み込み不可
-GUIのカスタマイズ制限(背景画像等、一部画像貼り付けは可能)
-MIDIによるパラメータ操作不可
audioTestBench 上では可能ですがVSTでは未対応です
売り物のVSTプラグインのような見た目もカッコイイのを作るには、通常通り VST SDKとC++を使う必要があります。
また、スタインバーグ社自体はVST2と互換性のないVST3に移行し、VST2の正式サポートをすでにやめてしまっていますが、MATLABがサポートしているのは今も広く使われているVST2のみです。(R2022aで、VST3も生成可能となりました)
32bit版や、単体exe(R2020a以降)、MacであればAU v2も生成可能です。
MATLAB Coder があれば、JUCEプロジェクトも生成できます。
プラットフォーム依存ですので、Linux版MATLABでは生成できません。
プラグイン形式としては継承元によって以下の4種類があるようですが、今のところ "BasicPlugin" だけで事足りていて他は使ったことがないので、今回は "BasicPlugin" のみの説明とさせて頂きます。
BasicPlugin < audioPlugin
BasicSourcePlugin < audioPluginSource
SystemObjectPlugin < audioPlugin & matlab.System
SystemObjectSourcePlugin < audioPluginSource & matlab.System
開発環境
CPU:なるべく速いヤツ
("Parallel Computing Toolbox" がなくても、ある程度自動的にマルチコア上で演算されます)
メモリ:なるべくいっぱい
モニター:なるべく高解像度で大きいヤツ
下のような私の環境でもほとんど問題はありませんが。
VAIO S11
Core i5-8250U(1.60GHz)、RAM 8GB、SSD 256GB(NVMe対応)
モニター DELL 2560×1440 27型
外部HDD
MATLAB 本体以外に必要な Toolbox
DSP System Toolbox
Signal Processing Toolbox
Audio Toolbox
(MATLAB Coder) ← JUCEプロジェクト生成には必要
これらがないと作れません。
最初の2つは "Audio Toolbox" の動作に必要なので、"Audio Toolbox" が動く状態ならVSTプラグインが作れると言うことになります。
MATLABでは、MATLABアプリは "MATLAB Compiler" 、コード生成及びネイティブアプリは "MATLAB Coder" がないと作れませんが、VSTは "Audio Toolbox" があれば作れます。ですので、"MATLAB Compiler/Coder" 製品自体がない MATLAB HOME ライセンスでも作ることができます。
あともう一つ、Cコンパイラが必要です。PCに入っていない場合はインストールします。対応しているコンパイラについてはこちらをご参照ください。
私は Visual C++ 2017 を使っています。
Cコンパイラインストール後、MATLABコマンドラインで
> mex -setup
と打って、使用するコンパイラを指定します。
開発プロセス
VSTプラグイン開発のプロセスとしては以下のようになります。
(1)通常通りMATLABで信号処理部を開発
(2)プラグイン仕様にしたがってコード作成
(3)audioTestBench でテスト実行 & VST生成
"run as MATLAB" でデバッグ
"run as VST"(コード生成)でデバッグ
VST生成
"run as MATLAB" と "run as VST" では一部動作が異なりますので完全にデバッグできるわけではありません。メインの信号処理部は、通常のMATLAB開発プロセスでしっかり動作を確認しておきましょう。
また、VST時固有のエラーがあると、 "run as VST" で長時間返ってこない場合もあるので、個人的にはコマンドラインでの
validateAudioPlugin / generateAudioPlugin
の使用をお勧めします。
validateAudioPlugin も規模が大きくなると(?)かなり長時間戻ってこないことがあるので、推奨はしませんがほぼデバッグができた後はこれはスキップして、直接 generateAudioPlugin することも可能です。
VSTを作ってみよう!
公式サンプルもありますので、ご自分で色々試してみたい方はどうぞ。抽象クラスを使ったより高度な例(Bandpass/Highpass/Lowpass IIR Filter)等も載っています。
ここでは、公式ドキュメント/サンプルだけでは理解しにくいところ、引っかかりやすいと思われるところに重点を置きながら、以下の簡単なVSTを例に解説をします。サンプルについては全て実際のコードも添付しますので、ぜひ実際に動かしてみてください。
1.チューナブルパラメータを利用した3バンドイコライザー(デバッグ用特性表示付き)
2.係数設計部付きフィルター(デバッグ用特性表示付き)
3.ステレオローテーション
1.チューナブルパラメータを利用した3バンドイコライザー(デバッグ用特性表示付き)
VSTtest_EQU1.m というファイル名で、通常のMATLAB関数を作成するのと同じ手順でコードを作成します。
以下、全コードについて簡単に説明をします。
(1)audioPlugin クラスを継承してファイル名と同じ VSTクラスを宣言します
classdef VSTtest_EQU1 < audioPlugin % Inherit from audioPlugin
(2)GUIで変更したいパラメータを初期値と共に宣言します
properties
% GUI parameters
% EUQ1 : 3Band-EQU ON/OFF
EQU1 = true;
% EQU1 parameters
EQU1_F1 = 400; EQU1_F2 = 1000; EQU1_F3 = 5000;
EQU1_G1 = 0; EQU1_G2 = 0; EQU1_G3 = 0;
EQU1_Q1 = 1.6; EQU1_Q2 = 1.6; EQU1_Q3 = 1.6;
end
(3)audioPluginInterface を使ってVSTの概要を記述します
properties (Constant)
% set GUI properties and layout
PluginInterface = audioPluginInterface( ...
'PluginName','EUQ1',...
'VendorName', '', ...
'VendorVersion', '0.0.1', ...
'UniqueId', 'hiro',...
audioPluginInterface のパラメータ詳細はここを参照してください。
'VendorName' を省略した場合は "MathWorks" になります。
'UniqueId' はとりあえず使う分には適当な4文字でOKです。
この行は省略可能です。
(4)続いて audioPluginParameter を使って各パラメータの属性とレイアウトを指定します
audioPluginParameter('EQU1', 'Mapping', {'enum','OFF','ON'}, 'Layout', [1 1]), ...
audioPluginParameter('EQU1_F1','DisplayName','F1', 'Mapping',{'log',20, 22000},'Style','rotaryknob', 'Layout', [1 2]), ...
audioPluginParameter('EQU1_G1','DisplayName','G1', 'Mapping',{'lin',-12, 12},'Style','rotaryknob', 'Layout', [3 2]), ...
audioPluginParameter('EQU1_Q1','DisplayName','Q1', 'Mapping',{'lin',0.2, 50},'Style','rotaryknob', 'Layout', [5 2]), ...
audioPluginParameter('EQU1_F2','DisplayName','F2', 'Mapping',{'log',20, 22000},'Style','rotaryknob', 'Layout', [1 3]), ...
audioPluginParameter('EQU1_G2','DisplayName','G2', 'Mapping',{'lin',-12, 12},'Style','rotaryknob', 'Layout', [3 3]), ...
audioPluginParameter('EQU1_Q2','DisplayName','Q2', 'Mapping',{'lin',0.2, 50},'Style','rotaryknob', 'Layout', [5 3]), ...
audioPluginParameter('EQU1_F3','DisplayName','F3', 'Mapping',{'log',20, 22000},'Style','rotaryknob', 'Layout', [1 4]), ...
audioPluginParameter('EQU1_G3','DisplayName','G3', 'Mapping',{'lin',-12, 12},'Style','rotaryknob', 'Layout', [3 4]), ...
audioPluginParameter('EQU1_Q3','DisplayName','Q3', 'Mapping',{'lin',0.2, 50},'Style','rotaryknob', 'Layout', [5 4]), ...
audioPluginGridLayout('RowHeight', repmat([70 15],1,3), ...
'ColumnWidth', repmat([80 80],1,2), ...
'Padding', [20 20 20 20]) ... % [left, bottom, right, top]
);
end
パラメータの属性に関しても公式ドキュメントをご参照ください。
レイアウトの指定方法はここに詳しく書いてありますが、最初は分かりにくいと思いますので解説します。
audioPluginGridLayout('RowHeight', repmat([70 15],1,3), ...
'ColumnWidth', repmat([80 80],1,2), ...
'Padding', [20 20 20 20]) ... % [left, bottom, right, top]
と書くと、高さ:70 15 70 15 70 15 pixel、幅:80 80 80 80 pixel のグリッドが作成され、その間にパディング(デフォルト10)が入り、その外側に20のスペースが取られます。
上の図で、オレンジの大きい四角が 80x70pixel、小さい方が80x15pixelの枠になっています。
また、パラメータ名等を表示すると、それが一行分を占有します。
例えば 'EQU1_F1' は[1行目, 2列目]に配置していますが、 'DisplayName','F1' としているので、ロータリーノブ部分が1行目、'F1' が2行目に配置されます。したがって、 次の 'EQU1_G1' を置くべきは[3行目, 2列目]になります。
2行目に置くことはできません。
'DisplayNameLocation','above' とした場合は、1行目に置くことはできません。同様に、'DisplayNameLocation','left' とした場合は、1列目に置くことはできません。パラメータ名を消したい場合は 'DisplayNameLocation','none' とします。
タイトル部を除く内部サイズは、
横:80*4+10*3+20*2 = 390
縦:(70+15)*3+10*5+20*2 = 345
となります。背景画像を用いる場合はこのサイズにします。
(5)ユーザーが直接操作しないプライベートプロパティを宣言します
ここではフィルター処理を行うためのフィルターオブジェクトを宣言します。
properties(Access = private)
% EUQ1 : 3Band-EQU
objEQU1;
end
(6)コンストラクタで実際のフィルターオブジェクトを構築します
% Constructor
function plugin = VSTtest_EQU1
plugin@audioPlugin;
plugin.objEQU1 = multibandParametricEQ('NumEQBands',3,'EQOrder',4, ...
'HasLowShelfFilter',0,'HasHighShelfFilter',0,'HasLowpassFilter',0,'HasHighpassFilter',0, ...
'Frequencies',[400, 1000, 5000],'PeakGains',ones(1,3)*0,'QualityFactors',ones(1,3)*1.6,'SampleRate',getSampleRate(plugin));
end
(7)GUI操作をした場合の挙動をset/getに記述します
これ以降は、ユーザー操作によってアップデートされる内容になります。
%% EUQ1 : 3Band-EQU
%-------------------------------------------
% EUQ1 : 3Band-EQU
function set.EQU1_F1(plugin,val)
plugin.objEQU1.Frequencies(1) = val; %#ok
end
function val = get.EQU1_F1(plugin)
val = plugin.objEQU1.Frequencies(1);
end
function set.EQU1_G1(plugin,val)
plugin.objEQU1.PeakGains(1) = val; %#ok
end
function val = get.EQU1_G1(plugin)
val = plugin.objEQU1.PeakGains(1);
end
function set.EQU1_Q1(plugin,val)
plugin.objEQU1.QualityFactors(1) = val; %#ok
end
function val = get.EQU1_Q1(plugin)
val = plugin.objEQU1.QualityFactors(1);
end
function set.EQU1_F2(plugin,val)
plugin.objEQU1.Frequencies(2) = val; %#ok
end
function val = get.EQU1_F2(plugin)
val = plugin.objEQU1.Frequencies(2);
end
function set.EQU1_G2(plugin,val)
plugin.objEQU1.PeakGains(2) = val; %#ok
end
function val = get.EQU1_G2(plugin)
val = plugin.objEQU1.PeakGains(2);
end
function set.EQU1_Q2(plugin,val)
plugin.objEQU1.QualityFactors(2) = val; %#ok
end
function val = get.EQU1_Q2(plugin)
val = plugin.objEQU1.QualityFactors(2);
end
function set.EQU1_F3(plugin,val)
plugin.objEQU1.Frequencies(3) = val; %#ok
end
function val = get.EQU1_F3(plugin)
val = plugin.objEQU1.Frequencies(3);
end
function set.EQU1_G3(plugin,val)
plugin.objEQU1.PeakGains(3) = val; %#ok
end
function val = get.EQU1_G3(plugin)
val = plugin.objEQU1.PeakGains(3);
end
function set.EQU1_Q3(plugin,val)
plugin.objEQU1.QualityFactors(3) = val; %#ok
end
function val = get.EQU1_Q3(plugin)
val = plugin.objEQU1.QualityFactors(3);
end
通常のMATLABのGUIでは
set:GUI表示へのset
get:GUIの値のget
なので、MATLABでGUIを作った経験がある方はかえって戸惑うかもしれませんが、
set:ユーザーインタラクション → プロパティの値をset
get:プロパティの値をget
です。
GUIパラメータ変更時にインタラクティブに何かしたい場合は、ここに書きます。メインルーチンで値を参照するだけであれば、これらは書く必要はありません。
基本的に、GUIパラメータの値をプログラム内から変えることはできません。ユーザーのGUI操作(またはDAW等からの操作)によってのみ変更できます。値自体を変えることは可能ですが、表示には反映されません。
"Run As MATLAB" では表示にも反映されますが、VSTにした場合には反映されませんので注意が必要です。他にも audiotesubench 上と実際のVSTでは見た目や動作が異なる場合もあるので、途中でVSTを生成して確認すると良いでしょう(単体exe版が、そのまま立ち上がるので便利)。
ここで、multibandParametricEQ について簡単に説明します。
最大10バンド(LPF/HPF/shelf使用時は最大12)のパラメトリックイコライザーを一度に生成できる便利な関数です。
詳しくはここを参照してください。
"Properties" で "Tunable: Yes" となっているパラメータは、オブジェクトを再定義することなく変更が可能です。
つまり、先ほどコンストラクタで構築したフィルターのチューナブルパラメータである各バンドのF/G/Qを、ここで代入してあげればフィルター特性が変わってくれます。
こうすることで、メインルーチン内での更新不要でユーザー操作があったときだけフィルター特性を変えられるので、実行速度を落とすことなく特性変更が可能となります。
そのまま書くと警告が出ますが問題ありません(MathWorks開発チームに確認済み)。気になる場合は行の最後に "%#ok" と書けば出なくなります。
(8)デバッグ用周波数特性ビジュアライズオブジェクトを定義します
multibandParametricEQ は visualize() で簡単に周波数特性表示ができますので、function visualize() 内にこう書いておけば、audiotesubench 上で表示できるようになります。
function visualize(plugin,NFFT)
if nargin < 2
NFFT = 2048;
end
visualize(plugin.objEQU1, NFFT);
end
(9)メイン処理部を書きます
入力データはオーディオI/Fの設定で決まるフレームサイズ×チャネル数で入ってきますが、MATLABの関数へはそのまま渡せばOKです。内部ディレイバッファの内容も保持されますのでケアする必要はありません。「3.ステレオローテーション」に、サンプル単位で処理する例を載せます。
%------ main process -------
function out = process(plugin,in)
if plugin.EQU1
out = step(plugin.objEQU1,in);
else
out = in;
end
end
%---------------------------
(10)リセット関数を書きます
function reset(plugin)
plugin.objEQU1.SampleRate = getSampleRate(plugin);
reset(plugin.objEQU1);
end
サンプリング周波数が変わったときに呼ばれます。
1-2.デバッグ
MATLAB のコマンドラインで audioTestBench を起動します。
> audioTestBench(VSTtest_EQU1)
audioTestBenchの詳細はここを参照してください。
まずは "Input" を設定します。
ファイルから読みたい場合は "Audio File Reader"、オーディオI/F等にしたい場合は "Audio Device Reader" にしてから、横の歯車アイコンでさらに詳細を決定します。
同様に "Output" を設定します。
"Run"で音が出ます。
”View Source Code” でエディターにソースコードが表示され、ブレイクポイントの設定等もできます。
"Visualize Plugin" アイコンをクリックすると、周波数特性が表示されます。
同様に、"Time Scope" と "Spectrum Analyzer" も表示できます。
通常のMATLABと同じですので、凡例をクリックして各データ表示のON/OFF、右クリックでグラフプロパティ等も設定できます。
但し、マルチチャネルの場合は1ch目しか表示されないようです(2chステレオの場合はLchのみ)。
GENERATION -> Generate Script でスクリプトに出力し、それを修正してmファイルを実行すれば、複数ch表示も可能です。スペアナの場合、
'ChannelNames',{'Input channel 1','Output channel 1'});
'ChannelNames',{'Input channel L','Input channel R','Output channel L','Output channel R'});
% Visualize input and output data in scope
specScope([in(:,1),out(:,1)]);
% Visualize input and output data in scope
specScope([in(:,:),out(:,:)]);
にそれぞれ変更して実行すると、以下のように表示されます。
(右クリック -> スタイル でラインプロパティも変えています)
スクリプト実行させた場合の停止は、"Input" で指定した回数ソースの再生が終わるのを待つか、コマンドウィンドウで Ctrl+c です。
MIDIコントロール
audioTestBench 上ではMIDIコントロールチェンジも受けられるので、PCにrtpMIDI、スマホにMIDIコントローラーアプリ(YAMAHA Faders 等)を入れれば、LAN内でスマホからリアルタイムに複数のパラメータを変更したりもできます。(もちろん通常のMATLAB/Simulinkでもできます)
rtpMIDI の設定方法
藤本さんの記事に詳しく書いてあるのでそちらを参照してください。
YAMAHA Faders設定方法
右上の歯車アイコンをタッチして設定画面で "wireless LAN"を選択
"Wireless LAN MIDI Port" をタッチして、自分のPC名が出てくればそれを選択して終了です。
audioTestBench の設定
MIDIコネクタアイコンをクリック
同期したいパラメータをセレクト
下の "Unset" 部分をクリック
YAMAHA Faders の割り当てたいフェーダーを操作
すると "Unset" の部分に検出されたcontrolナンバーが入るはずです。
もう一つのフェーダーに別のパラメータを割り当てたい場合はこれを繰り返します。
これで "OK" で閉じれば、スマホから操作可能となります。
実際に iPad からフィルターの周波数を操作するデモ動画をご覧ください。
また、"Miditure" というアプリを使うと、X/Y/Z(Zは押し込み)の3軸で別々のパラメータを同時にコントロールすることもできます。
Miditure でフィルターの周波数/ゲイン/Q を操作するデモ動画をご覧ください。
"Miditure" の設定歯車アイコンをタッチ -> XYZ axis assignment で、
X/Y/Z それぞれ、ONE/TWO/THREE の内 各一つだけをON にし、別々の CONTROL CHANGE NUMBER を割り当てます。
audioTestBench のMIDI設定に割り当てる際は面倒ですが、複数のコントロールチェンジ信号が出て行かないよう、X/ONE、Y/TWO、Z/THREE の内 一つだけをON/他の二つはOFF にして、3回に分けて行い、設定終了後、全てをONにします。
KORG nanoKONTROL2 などの有線コントローラーであれば rtpMIDI を用いなくても、同様の設定方法で使用できます。
1-3.プラグイン生成
"GENERATION" -> "Generate VST Plugin" でVST2プラグインが生成されます。
またはコマンドウィンドウで
generateAudioPlugin VSTtest_EQU1
32bit版が欲しい場合は
generateAudioPlugin -win32 VSTtest_EQU1
単体exeは
generateAudioPlugin -exe VSTtest_EQU1
(R2020a以降)
JUCEプロジェクト
generateAudioPlugin -juceproject VSTtest_EQU1
(要 MATLAB Coder)
とします。
2.係数設計部付きフィルター(デバッグ用特性表示付き)
dsp.BiquadFilter オブジェクトを使ったフィルターの例です。
VSTtest_EQU2.m として作成します。
先ほどの VSTtest_EQU1 に加える形で作ってみます。
構成としては VSTtest_EQU1 と同じですが、フィルター係数計算/周波数特性表示ルーチンを自前で用意する必要がある分複雑になっています。
(1)audioPlugin クラスを継承してファイル名と同じ VSTクラスを宣言します
classdef VSTtest_EQU2 < audioPlugin % Inherit from audioPlugin
(2)GUIで変更したいパラメータを初期値と共に宣言します
両方同時にON/OFFもしたいので、バイパスSWも加えましょう。
properties
% GUI parameters
% EUQ1 : 3Band-EQU ON/OFF
EQU1 = true;
% EQU1 parameters
EQU1_F1 = 400; EQU1_F2 = 1000; EQU1_F3 = 5000;
EQU1_G1 = 0; EQU1_G2 = 0; EQU1_G3 = 0;
EQU1_Q1 = 1.6; EQU1_Q2 = 1.6; EQU1_Q3 = 1.6;
% EQU2 ON/OFF
EQU2 = true;
% EQU2 parameters
EQU2_F = 400; EQU2_G = 0; EQU2_Q = 1.6;
% both of EQUs ON/OFF
BYPASS= false;
end
(3)audioPluginInterface を使ってVSTの概要を記述します
(4)audioPluginParameter を使って各パラメータの属性とレイアウトを指定します
properties (Constant)
% set GUI properties and layout
PluginInterface = audioPluginInterface( ...
'PluginName','EUQ2',...
'VendorName', '', ...
'VendorVersion', '0.0.1', ...
'UniqueId', 'hiro',...
audioPluginParameter('EQU1', 'Mapping', {'enum','OFF','ON'}, 'Layout', [1 1]), ...
audioPluginParameter('EQU1_F1','DisplayName','F1', 'Mapping',{'log',20, 22000},'Style','rotaryknob', 'Layout', [1 2]), ...
audioPluginParameter('EQU1_G1','DisplayName','G1', 'Mapping',{'lin',-12, 12},'Style','rotaryknob', 'Layout', [3 2]), ...
audioPluginParameter('EQU1_Q1','DisplayName','Q1', 'Mapping',{'lin',0.2, 50},'Style','rotaryknob', 'Layout', [5 2]), ...
audioPluginParameter('EQU1_F2','DisplayName','F2', 'Mapping',{'log',20, 22000},'Style','rotaryknob', 'Layout', [1 3]), ...
audioPluginParameter('EQU1_G2','DisplayName','G2', 'Mapping',{'lin',-12, 12},'Style','rotaryknob', 'Layout', [3 3]), ...
audioPluginParameter('EQU1_Q2','DisplayName','Q2', 'Mapping',{'lin',0.2, 50},'Style','rotaryknob', 'Layout', [5 3]), ...
audioPluginParameter('EQU1_F3','DisplayName','F3', 'Mapping',{'log',20, 22000},'Style','rotaryknob', 'Layout', [1 4]), ...
audioPluginParameter('EQU1_G3','DisplayName','G3', 'Mapping',{'lin',-12, 12},'Style','rotaryknob', 'Layout', [3 4]), ...
audioPluginParameter('EQU1_Q3','DisplayName','Q3', 'Mapping',{'lin',0.2, 50},'Style','rotaryknob', 'Layout', [5 4]), ...
audioPluginParameter('EQU2', 'Mapping', {'enum','OFF','ON'}, 'Layout', [7 1]), ...
audioPluginParameter('EQU2_F','DisplayName','F', 'Mapping',{'log',20, 22000},'Style','rotaryknob', 'Layout', [7 2]), ...
audioPluginParameter('EQU2_G','DisplayName','G', 'Mapping',{'lin',-12, 12},'Style','rotaryknob', 'Layout', [7 3]), ...
audioPluginParameter('EQU2_Q','DisplayName','Q', 'Mapping',{'lin',0.2, 50},'Style','rotaryknob', 'Layout', [7 4]), ...
audioPluginParameter('BYPASS', 'Mapping', {'enum','OFF','ON'}, 'Layout', [8 1]), ...
audioPluginGridLayout('RowHeight', repmat([70 15],1,4), ...
'ColumnWidth', repmat([80 80],1,2), ...
'Padding', [20 20 20 20]) ... % [left, bottom, right, top]
);
end
ここも同じですね
(5)ユーザーが直接操作しないプライベートプロパティを宣言します
ここではフィルター処理を行うためのフィルターオブジェクトと周波数特性表示用のオブジェクトを宣言します。
properties(Access = private)
% EUQ1 : 3Band-EQU
objEQU1;
% EUQ2
Num, Den, objEQU2;
% Visual object
visualObj
AreFiltersDesigned = false;
end
(6)コンストラクターで、実際のフィルターオブジェクトを構築します
% Constructor
function plugin = VSTtest_EQU2
plugin@audioPlugin;
plugin.objEQU1 = multibandParametricEQ('NumEQBands',3,'EQOrder',4, ...
'HasLowShelfFilter',0,'HasHighShelfFilter',0,'HasLowpassFilter',0,'HasHighpassFilter',0, ...
'Frequencies',[400, 1000, 5000],'PeakGains',ones(1,3)*0,'QualityFactors',ones(1,3)*1.6,'SampleRate',getSampleRate(plugin));
plugin.objEQU2 = dsp.BiquadFilter('SOSMatrixSource','Input port', 'ScaleValuesInputPort',false);
calculateCoefficients(plugin);
end
dsp.BiquadFilter は Tunable パラメータがありませんので、'SOSMatrixSource', 'Input port' と設定して、フィルター係数を自分で計算して外部から更新できるようにします。
visualize() も適用できないので、
plugin.visualObj = dsp.DynamicFilterVisualizer ~
とし、それらに使用する変数 "Num", "Den" も、(5)で定義しておきます。
(7)GUI操作をした場合の挙動をsetに記述します
ここは長くなるのでEQU1の部分は省略します。
自分で宣言した変数に入れるだけなので、 get なしでこう書いてもOKです。
% EQU2
function set.EQU2_F(plugin,val)
plugin.EQU2_F = val;
needToDesignFilters(plugin);
end
function set.EQU2_G(plugin,val)
plugin.EQU2_G = val;
needToDesignFilters(plugin);
end
function set.EQU2_Q(plugin,val)
plugin.EQU2_Q = val;
needToDesignFilters(plugin);
end
(8)デバッグ用周波数特性ビジュアライズオブジェクトを定義します
周波数特性表示用に dsp.DynamicFilterVisualizer オブジェクトを構築しておきます。
function visualize(plugin,NFFT)
if nargin < 2
NFFT = 2048;
end
visualize(plugin.objEQU1, NFFT);
if isempty(plugin.visualObj) || nargin > 1
Fs = getSampleRate(plugin);
plugin.visualObj = dsp.DynamicFilterVisualizer(...
NFFT,Fs,[20 20e3], ...
'XScale', 'Log', ...
'YLimits', [-12 12], ...
'Title', 'Equalizer');
else
if ~isVisible(plugin.visualObj)
show(plugin.visualObj);
end
end
% Step the visual object with the filter
step(plugin.visualObj, plugin);
end
フィルター設定が変更されたときに飛ぶ needToDesignFilters
そのサブルーチンである calculateCoefficients
dsp.DynamicFilterVisualizer が hidden method として呼び出す freqz
そのサブルーチンの getFilterCoefficients
をそれぞれ用意します。
function [B, A] = getFilterCoefficients(plugin)
calculateCoefficients(plugin);
B = plugin.Num.';
A = plugin.Den;
A = [ones(1,size(A,2));A].';
end
methods (Access = private)
function calculateCoefficients(plugin)
% Function to compute filter coefficients
Fs = getSampleRate(plugin);
G = 10^(plugin.EQU2_G/20); % convert dB to VGain
if G < 1
PEQ_q = plugin.EQU2_Q*G;
else
PEQ_q = plugin.EQU2_Q;
end
A = G/PEQ_q;
W = tan (pi*plugin.EQU2_F/Fs);
N = 1/(W^2 + W/PEQ_q + 1);
B0 = N*(W^2 + W*A + 1);
B1 = 2* N*(W^2 - 1);
B2 = N*(W^2 - W*A + 1);
A1 = B1;
A2 = N*(W^2 - W/PEQ_q + 1);
plugin.Num = [B0 B1 B2; 1 0 0]';
plugin.Den = [A1 A2; 0 0]';
plugin.AreFiltersDesigned = true;
end
function needToDesignFilters(plugin)
plugin.AreFiltersDesigned = false;
calculateCoefficients(plugin);
% Update visual if visualize has been called
if isempty(coder.target) && ~isempty(plugin.visualObj)
num = plugin.Num.';
den = plugin.Den;
den = [ones(1,size(den,2));den].';
step(plugin.visualObj, num, den);
plugin.visualObj.SampleRate = plugin.getSampleRate;
end
end
end
methods (Hidden)
function h = freqz(plugin, f, Fs)
[B, A] = getFilterCoefficients(plugin);
h = zeros(length(f),1);
h(:,1) = freqz(B(1,:), A(1,:), f, Fs);
for k = 2:size(B,1)
h(:,1) = h(:,1).*freqz(B(k, :), A(k, :) , f, Fs).';
end
end
end
(9)メイン処理部を書きます
%------ main process -------
function out = process(plugin,in)
if plugin.BYPASS
out = in;
else
if plugin.EQU1
out = step(plugin.objEQU1,in);
else
out = in;
end
if plugin.EQU2
out = plugin.objEQU2(out, plugin.Num, plugin.Den);
end
end
end
%---------------------------
(10)リセット関数を書きます
function reset(plugin)
plugin.objEQU1.SampleRate = getSampleRate(plugin);
reset(plugin.objEQU1);
reset(plugin.objEQU2);
end
3.ステレオローテーション
回転行列を使って、ステレオのままLRの定位を回転させます。
classdef VSTtest_Rot < audioPlugin % Inherit from audioPlugin
properties
% GUI parameters
XT=1, YT=1, Speed=1, Gain=1;
BYPASS = false;
MaxAngle = '360'; MaxAngleN = 360;
Pos_angle, pos_u, pos_d, ang
Rate = 1/4000;
end
properties (Constant)
% set GUI properties and layout
PluginInterface = audioPluginInterface( ...
'PluginName','Stereo Rotation',...
'VendorName', '', ...
'VendorVersion', '0.0.1', ...
'UniqueId', 'hiro',...
audioPluginParameter('BYPASS', 'Mapping', {'enum','OFF','ON'}, 'Layout', [1 1]), ...
audioPluginParameter('Gain', 'Mapping',{'lin', 0, 1},'Style','rotaryknob', 'Layout', [1 2]), ...
audioPluginParameter('Speed', 'Mapping',{'lin', 0, 10},'Style','rotaryknob', 'Layout', [1 3]), ...
audioPluginParameter('MaxAngle', 'DisplayNameLocation','above', 'Mapping',{'enum','15','30','45','90','180','270','360'},'Style','Dropdown', 'Layout', [4 1]), ...
audioPluginParameter('XT', 'Mapping',{'int', 1, 10},'Style','rotaryknob', 'Layout', [3 2]), ...
audioPluginParameter('YT', 'Mapping',{'int', 1, 10},'Style','rotaryknob', 'Layout', [3 3]), ...
audioPluginGridLayout('RowHeight', [70 15 70 15], ...
'ColumnWidth', [80 80 80], ...
'Padding', [20 20 20 20]) ... % [left, bottom, right, top]
);
end
methods
% Constructor
function plugin = VSTtest_Rot
plugin@audioPlugin;
plugin.Pos_angle = 0;
plugin.pos_u = plugin.Rate;
plugin.pos_d = plugin.pos_u;
plugin.ang = plugin.Pos_angle;
end
function set.Speed(plugin,val)
plugin.Speed = val;
plugin.pos_d = val * plugin.Rate; %#ok
plugin.pos_u = val * plugin.Rate; %#ok
end
function set.MaxAngle(plugin,val)
plugin.MaxAngle = val;
plugin.MaxAngleN = abs(str2double(val)); %#ok
end
function val = get.MaxAngle(plugin)
val = plugin.MaxAngle;
end
%% main
%------ main process -------
function out = process(plugin,in)
out = in * plugin.Gain;
if ~plugin.BYPASS
for n=1:length(in)
plugin.ang = plugin.Pos_angle;
plugin.Pos_angle = plugin.Pos_angle + plugin.pos_d;
if plugin.Pos_angle >= plugin.MaxAngleN
plugin.Pos_angle = plugin.MaxAngleN;
plugin.pos_d = -plugin.pos_u;
elseif plugin.Pos_angle <= -plugin.MaxAngleN
plugin.Pos_angle = -plugin.MaxAngleN;
plugin.pos_d = plugin.pos_u;
end
cos_coef = cos(plugin.XT*plugin.ang/180*pi);
sin_coef = sin(plugin.YT*plugin.ang/180*pi);
mag = abs(cos_coef) + abs(sin_coef);
if mag <= 1; mag = 1; end
Lin = in(n,1) * plugin.Gain; Rin = in(n,2) * plugin.Gain;
out(n,1) = (Lin * cos_coef - Rin * sin_coef)/mag;
out(n,2) = (Lin * sin_coef + Rin * cos_coef)/mag;
end
end
end
%---------------------------
end
end
「MATLAB/Simulinkで動画Picture In Picture」でも用いたリサージュ関数を使っており、XT:YTの比率で回転パターンが変わります。
Speedが回転速度で、0 にするとそこで回転が止まります。
"Angle" はいわゆる立体音響でいう音源の位置ではなく、あくまでも「回転行列の回転角」です。
XT = YT = 1 の場合、以下のようになります。
Angle L_out R_out
0 L R
45 S M
90 -R L
135 -M S
180 -L -R
225 -S -M
270 R -L
315 M -S
"M/S" は "Mid/Side" のことで
M = (L + R)/2; S = (L - R)/2;
です。
前の2例が分かれば特に説明の必要はないかと思います。コードも短いのでソースを見てください。
一点だけ
function set.MaxAngle(plugin,val) で
plugin.MaxAngleN = abs(str2double(val));
としていますが、コード生成時の str2double の戻り値は常に複素数となるため、absを取っています。
メイン関数は、実用上はサンプル単位で処理をする必要はないと思いますが、サンプル単位で処理する場合の例も兼ねてそうしてみました。ここで自前ルーチンでフィルター処理をする場合なども、ディレイバッファ等、変数の内容は保持されます。
サンプル単位の場合は特に、メインの処理が重くなるとMATLAB上ではリアルタイム動作しなくなりますので、アルゴリズム的に処理を軽くするか、その部分をmexファイル化して高速化する等の工夫が必要になります。もちろんそのままでも、VST化すれば速くなります。
たまに、プロパティ関連を変更した後に動作が重くなる場合があるようですが、その場合はMATLABを再立ち上げしてみましょう!
VSTの使用方法
ここからは、VSTプラグインを他のアプリと組み合わせて使用する方法を解説します。
foobar2000への組込み方法
32bit版のみ対応なので、32bit版VSTを生成します。
generateAudioPlugin -win32 ~
Foobar2000 で VST を使うためにはまず、VST Wrapper をインストールします。いくつかあるようですが、例えば Foobar2000 VST Wrapper があります。
-インストール
Foovarvst.zip を解凍後、"foo_dsp_vstwrap.dll" を以下のフォルダーに置いて下さい。
C:\Program Files (x86)\foobar2000\components\
VST も同じフォルダーに置きます。
-設定
foobar2000 を起動し、File -> Preferences
"Components" に "George Yohng's VST Wrapper" が追加されているのを確認
"Playback" -> DSP Manager で、"Available DSPs" から "George Yohng's VST Wrapper" 右の "+"で "Active DSPs" に追加
(ここで "Resampler”も入れておくと、foobar2000出力のサンプリングレートを、ソースに寄らず固定にもできます)
"Oputput" で出力デバイスやバッファサイズを設定できます
あとは、ツールバーアイコンで切替等を行います
ツールバーアイコンをクリック -> "Use VST Effect" で使用するプラグインを選択します
"Show/Hide Plugin Editor" でGUIの表示ON/OFFができます
アイコンが見当たらない場合は左の ^の中にあるはずですので探してみてください
VSTが一覧に表示されなかった場合は foobar2000 を立ち上げ直してみてください
単体exeの使用方法
単体で起動でき、外部入力は選べますが、このままでは音楽プレイヤーの出力等に処理を掛けることができません。
VB-CABLE(Donationware)等を使うと、Virtual In → Virtual Out へ信号を流せるので、プレイヤー出力を Virtual In、単体exeの入力を Virtual Out、出力を実際のスピーカー等とすると、
プレイヤー → Virtual In → Virtual Out → 単体exe → スピーカー
のように信号が流れて、プレイヤーで再生した音楽に処理を掛けてスピーカーから出すことができます。
Windowsタスクバーのスピーカーアイコンをクリックして、プライマリ出力を Virtual In にしておけば、Webブラウザー等の出力にも効果を掛けることができます。
-インストール
上記サイトから "VB-CABLE Driver" をDL/解凍後、右クリック→管理者インストール してからPCを再起動してください。
"VB-Audio Virtual Cable" がバーチャル再生/録音デバイスとしてそれぞれ登録されます。
-設定
(1)Windowsツールバーのスピーカーアイコンをクリックして "VB-Audio Virtual Cable" を選択します。
これでWindowsのデフォルト音声出力が Virtual Cable になります。
(使い終わったら戻しておきましょう)
VLC/foobar2000等、プレイヤー側で出力デバイスを選択できる場合はそちらで選択しても構いません。
(2)VSTの Options -> Audio/MIDI Settings で、"Input" を "Cable Output"に、"Output" を実際に音を出したいデバイスに設定します。
"Mute audio input" にチェックが付いている場合は外してください。
同じくVB-AUDIOの Voicemeeter/Banana/Potato 辺りを入れても
"VoiceMeeter VAIO”、"VoiceMeeter VAIO3" がサウンドドライバーとして登録されるので、同様に使うことができます。
VSTホスト
exe版でバッファサイズを大きくしても音切れが発生するようであれば、VSTホストの利用をお勧めします。
-設定方法
(1)DL/解凍後、vsthost.exe を起動(インストール不要)
(2)キャンバス上で右クリック File -> New Plungin でVSTを選択
(3)Devices -> Wave で Input:CABLE Output、Output:任意のデバイスに
(4)Buffer を、ノイズの出ない範囲で小さな値に設定
(5)Windowsのデフォルト音声出力を Virtual Cable に設定
これでPC上で何か音を出せば、
Virtual Cable -> VSTHost -> VST -> Virtual Cable
経由で音が流れます。
追補
最近は、App Designer アプリで検証して仕様がだいたい決まってくると、それ以降はVSTも同時に作るようにしています。しかし、VSTの様なコード生成では、GUIパラメータ以外は定数としてプログラム内で設定する必要があります。まだ検討段階でGUIパラメータ以外も色々変えたいときは、VSTのソースもいちいち書き直すのは面倒ですし、書き間違いでバグが発生するかもしれません。
そこでその場合、アプリで設定ファイルの LOAD/SAVE ができるようにしておいて、その設定ファイルを、VSTのコンストラクタ内で変数に読み込んでコード生成するようにしています。
(表示への反映は不可なので、GUIパラメータ以外)
ただし、MathWorks 非推奨です!
開発チームとしては「コード上で直接指定することを "強く" 推奨する」とのことですので、自己責任で・・。
その例を示します。
先ほどのステレオローテーションで、元々内部定数である "Rate" に加え、MaxAngle もGUIから削除して、設定ファイルから読み込んだ初期値で固定することにします。
1.App Designer アプリ(またはMATLAB上で)
変数を設定ファイル(.mat)ファイルにSAVE
MaxAngleN = 360; MaxAngle = num2str(MaxAngleN); Rate = 1/4000;
save('VSTtest_Rot_para.mat', 'MaxAngleN', 'MaxAngle', 'Rate')
もしくは構造体でSAVEします。
s.MaxAngleN = 360; s.MaxAngle = num2str(s.MaxAngleN); s.Rate = 1/4000;
save('VSTtest_Rot_para.mat', '-struct', 's')
2.VST側
以下をコンストラクタ内に追加します。
paraFilename = 'VSTtest_Rot_para.mat';
s = coder.load(paraFilename);
plugin.MaxAngleN = s.MaxAngleN; plugin.MaxAngle = s.MaxAngle; plugin.Rate = s.Rate;
こうしておけば、パラメータファイルを変更あるいはVST内の paraFilename を変えてコンパイルすれば、ソースの他の部分を修正する必要がなくなります。
おまけ
ステレオローテーションのメイン処理をフレーム単位にして、回転角のリセットとマニュアル調整を付加したバージョンを、32bit版VST(ID登録済み)と一緒に載せておきます。
ご興味のある方は遊んでみてください!
"Reset" をチェックすると元の状態に戻り、チェックを外すと回転が始まります。
"Speed" を "0" にするとそこで回転が止まります。
"XT" : "YT" の比率で回転パターンが変わります。
"MaxAngle" を "Manual" にすると、 "Manual Angle" スライダーが有効になります。
"Manual Angle" は早く動かすと波形が不連続になりノイズが出ますので、ゆっくり動かすようにしてください。
Try it out!
VSTプラグインはDAWをやっている人以外にはあまり馴染みがないかもしれませんが、オーディオ信号処理の検証にもとても便利です。MATLAB使いであれば、ぜひ活用してみてはいかがでしょうか!?
逆に、VSTプラグインには興味あるけどCとかはちょっと・・、という方も、MATLABで始めてみませんか!?
それでは、充実した夏休みを!
その他公式参考リンク
Design an Audio Plugin
Convert MATLAB Code to an Audio Plugin
Export a MATLAB Plugin to a DAW
Design an Audio Plugin
Tips and Tricks for Plugin Authoring
この記事が気に入ったらサポートをしてみませんか?