見出し画像

externalAudioPlugin class ~ MATLABで外部VSTプラグインをホスト ~ 動的変数名を使ってVSTパラメータをセットする

※ R2022aではパラメータの並び順が、
 loadAudioPlugin() : audioPluginParameter の設定順 で変更なし
 パラメータファイル : アルファベット順 に変更
となりインデックスによる突き合わせができなくなったため、パラメータファイルをMATLAB上で適用する手段がなくなりました・・。

 → 解決法が分かりましたので、具体的な方法はこちらをご参照ください。(2022/05/14)
MATLABでVST開発 ~ Map オブジェクト(文字ベクトル Key データ構造体)を使ってVSTパラメータをセットする(R2022a対応版)

・前書き

MATLABでは、普通の VSTプラグインを外部プラグインとして読み込んで利用することができます。(要 Audio Toolbox)

VST3 プラグインをホスト
parameterTuner() で GUI 表示・操作ができる

今回はそれを利用し、MATLABで開発した VSTプラグインで保存したパラメータファイルを、MATLABでホストしたプラグインに適用する方法についてです。

「MATLABで開発したVSTで保存したパラメータファイルならMATLABでホストしたプラグインでそのまま読めるでしょ?」と思われるかもしれませんが、そもそもVSTはMATLAB外の規格であるためか、そのような手段は用意されていません!

まあ、必要な方はほとんどいないかとは思いますが、備忘録的に・・。

ストリング処理動的変数名 辺りは他の用途にも参考になるかもしれません。

ストリング処理に関しては無駄なことをやっている感じもするので、もっと良い方法があれば教えてください。

・VST開発プロセス

私はオーディオ信号処理アルゴリズム開発を行う場合、以下のようなプロセスを踏みます。

1.Simulink、MATLABを用いて基本アルゴリズム開発

2.MATLABでフルパラメータのVST化

3.主観評価等を行いフィードバック、基本パラメータ決定

4.ハンドコーディングでC++化

5.ユーザー調整パラメータのみの VST を JUCE / Wwise 等で開発

6.DSPに最適化インプリ

5.で必要になるのが、MATLAB版と同等の動作をするか(バグがないか、演算精度等の問題はないか)の確認です。

最終的な動作確認には、それぞれのファイル出力を比較するのが一番確実なのですが、ファイル入出力できるVSTホストを私は知りません。(あったら教えてください!)

もちろんDAWでやればできますが、いちいち面倒なのと、DAWだとうっかり他のマスタリング処理等が入ってしまったりします。

それにMATLABであれば各段に自由度が高いので、MATLABでやりたいところです。(そう、とにかくMATLABでやるのが目的です (u_u) )

MATLABのVSTでは100以上のパラメータを使うことがあり(なぜか最大122個までしか使えません -> 上限は確認していませんが R2022a では123以上でもエラーが出なくなりました)、当然、比較には全パラメータを合わせる必要があるため、手動ではなく保存したパラメータを読み込みたいところです。

MATLAB で開発した VST の例
JUCE の例
Wwise の例
Unity の例
Blue Cat's の例

・VSTパラメータファイル

VSTは、GUIパラメータの状態をファイルに保存できます。
JUCEではその機能の実装は結構面倒ですが、MATLABであればそのためには1行もコードを書く必要はありません

通常、私はスタンドアローン版で検証を行うので、Options から保存/読み込みができます。

スタンドアローン版 VST

Virtual I/Oを使ったスタンドアローン版の使用方法については、「夏休みはVST! MATLABで楽々(?)VSTプラグイン開発」の「VSTの使用方法 -> 単体exeの使用方法」をご覧ください)

VST HOST であれば Edit Parameters 画面の Save Bank As... / Load Bank から、パラメータファイルの保存と読み込みができます。

VST HOST
VST HOST


VSTパラメータファイルは、バイナリだけどヘッダの一部以外はプレーンテキストという不思議なフォーマットです。

バイナリ部分を無視すれば、必要なパラメータ部分はテキストエディタでも読み込み可能です。

<PARAM id="Mode" value="1.0"/>
<PARAM id="Bias" value="180.0"/>
<PARAM id="Gain" value="0.5"/>

読み込んだパラメータの例

id が変数名、value が GUI に表示されている数値(またはセレクト値)です。

ということは、loadAudioPlugin() で MATLAB版とJUCE版のVSTをホストして、読み込んだパラメータをMATLAB版に適用して出力を比較すれば良さそうです。

MATLABでプラグインを扱うには、audioTestBench() (MATLABで作ったVST用)と loadAudioPlugin() (MATLABで作ったVSTを含む、外部プラグイン用)を使う方法があります。

しかしどちらにも、外部パラメータ読み込み機能は実装されていません

立ち上げる度に手動で設定することになります。

これは、VSTパラメータファイルを読み込んで変換するしかありません。

でも単なるテキストだし、簡単そうに思えますよね?

-パラメータファイル table 化

とりあえず、バイナリで読み込んでcharに変換します。

fileID = fopen('VST_para.set');
paraset = fread(fileID);
parasetStr = char(paraset');

VC2!X <VST_APST><PARAM id="channelMode" value="0.00000000000000000000"/><PARAM id="D_NS"

"PARAM id" と "value" 部分だけ取り出したいので分割し、ヘッダー、フッター、空白行を削除します。

paras = split(parasetStr,["<PARAM id=","value=", "/>"]);
paras([1 end],:) = [];
paras = paras(strlength(paras) > 0);

"channelMode"
"0.00000000000000000000"
"D_NS"
"0.00000000000000000000"

ダブルクォーテーション、行末の余分なスペースを削除します。

paras = erase(paras,'"');
paras = strtrim(paras);

channelMode
0.00000000000000000000
D_NS
0.00000000000000000000

使いやすいようにもうちょっと整理しましょう。

table(異なる型を含むことができる名前付き変数をもつ配列)型にすると便利なのでそれを使います。

parasTable = cell2table(reshape(paras,2,[]));

特に必要はありませんが、"PARAM id" を table の変数名にも入れておきます。

parasTable.Properties.VariableNames = table2cell(parasTable(1,:));

変数名の取り扱いがしやすしよう、string型に変換しておきます。"value" は double 型に変換します。

parasNameT = table2cell(parasTable(1,:));
parasName = string(parasNameT);
parasVal = str2double(parasTable{2,:});

必要であれば、構造体メンバーとしてアクセス可能な一覧をファイルにしておきます。

fid = fopen('VSThostPara.txt','W');
fprintf(fid, 'hostedPlugin.%s =  %g\n',[parasName;parasVal]);
fclose(fid);

hostedPlugin.channelMode = 0
hostedPlugin.D_NS = 0
hostedPlugin.BYPASS = 0

さあ、もうなんでもできそうですよね?

ところが・・。

-正規化パラメータ

VSTで保存できるパラメータファイルは正規化されていない数値です。(display値と値域は保存されない

hostedPlugin = loadAudioPlugin(); でホストしたプラグインにパラメータを適用する方法は2つ。setParameter() を使うか、hostedPlugin.Mode = のように構造体メンバーで指定するかです。

-関数による指定

setParameter(hostedPlugin,'Mode',0.5)

setParameter(hostedPlugin,1,0.5)

setParameter() による指定

二つ目の引数は 「display値」またはパラメータインデックス、三つ目の引数は値域が [0, 1]で 正規化された数値、で互換性がありません!

パラメータファイルには正規化に必要な値域情報がないので変換もできません。

-構造体メンバーアクスセス

構造体メンバーアクスセスの場合、数値変数の場合は正規化なしの値、それ以外では正規化数値かプロパティ文字です。

数値変数の場合はパラメータファイルから読み込んだ値をそのまま代入できるので問題ありませんが、Toggle(ON/OFF等の2状態選択) や ComboBox(メニュー等の複数状態選択) のセレクト値の場合は、

hostedPlugin.Mode = 'LR';

のように、VSTのGUIに表示される文字列を代入する必要があります。しかしパラメータファイルにはそれは保存されていません

う~ん、なんて複雑!

そこで苦肉の策として 'Label' を使います。

本来、数値プロパティに対して単位名等を表示(dB等)するのに使うのですが、数値以外では無視され表示されず、loadAudioPlugin ではアクセスすることができます

MATLAB版 VST作成時に 'Label' に選択肢の数を入れておき、これを使って正規化パラメータに変換を行います。

audioPluginParameter('Mode','Mapping',{'enum','LR','L','R'}, 'Label','3', ...

VSTスクリプト
選択肢が 3つある場合、それを 'Label' に記述しておく

>> dispParameter(hostedPlugin,1)
        Parameter  Value   Display
        ________________________________________
     1    Mode:   0.0000   LR 3

ホストしたプラグイン情報

-有効なMATLAB識別子

それと、構造体メンバーアクスセスの場合の注意点としては、(当然ながら)メンバーとして使用できる文字に制限があるということです。display値としてはスペースやスラッシュ等も使えますが、メンバー名には使えないため自動的に変換され、元の display値では参照できません。ただこれに関しては変換する関数が用意されているので、これを使えば問題ありません。

>> matlab.lang.makeValidName('a/b')

ans =

    'a_b'

>> matlab.lang.makeValidName('a b')

ans =

    'aB'

>> matlab.lang.makeValidName('1a b')

ans =

    'x1aB'

matlab.lang.makeValidName() による変換例


-DisplayName

さらにもう一点注意点があります。

VSTでは例えば、

audioPluginParameter('PEQ1F','DisplayName','F',~

audioPluginParameter('PEQ2F','DisplayName','F',~

と違う変数でも同じ表示名にできます

VSTパラメータファイルは、変数名なので問題ありません

<PARAM id="PEQ1F" value="1000.0"/>

しかし loadAudioPlugin() で扱えるのは display値なので、

>> hostedPlugin.F

クラス 'externalAudioPlugin' のメソッド、プロパティまたはフィールド 'F' が認識されません。

となってしまいます。

同じ display値の場合は、suffix が自動的に付加されているのです。

これは dispParameter() で確認ができます。

>> dispParameter(hostedP, 'F')

Parameter Value Display
_______________________________
45 F: 0.5663 1000.000
50 F: 0.7670 4000.000

>> hostedP.F_45

ans = 1000

しかし、表示することはできますが dispParameter() には返り値がないので参照できません!

これはどうしようもないので VST の DisplayName を変えるしかありません。

VSTでは、同じ DisplayName を使わないようにします。

・動的変数名

パラメータのセット方法は

setParameter(hostedPlugin, (パラメータインデックス), (正規化値))

setParameter(hostedPlugin, ('パラメータプロパティ文字'), (正規化値))

または、

hostedPlugin.(メンバー名) =

です。

ComboBox等は Label を使って正規化値に変換するので、setParameter() を使います。

それ以外の数値変数は、構造体メンバーアクスセスを使えば正規化なしの値が入れられるのでそちらを使います。

(メンバー名)部分を動的に変えたい場合、hostedPlugin.('メンバー名') = と、文字変数を () で囲えばOKです。

N = sprintf("%s",displayName);
hostedPlugin.(N) = displayName,parasVal(k);

他に eval() を使う方法もあります。

N = sprintf("hostedPlugin.%s = %f",displayName,parasVal(k));
eval(N);

これで、N の文字列をそのままMATLABで実行するのと同じことができます。

例えば

N = 

    "hostedPlugin.Bias=  128.000000;"

だった場合、

hostedPlugin.Bias=  128.000000;

が実行されます。

但し eval() を使った場合は実行前に定義されないため、パフォーマンス低下等のデメリットもあります。また、"hostedPlugin" 部分が変数ではなく文字列なので、エディター上で変数を選択してもハイライト表示されず編集時に見逃すこともあり得ますのでご注意ください(実際にやらかした!)。


これでやっと、今までMATLAB版VSTとJUCE版VSTの動作確認を「耳」に頼っていたのが、数値での確認が全自動でできるようになりました。捗る!


・MATLABスクリプト例

clear

% read VST parameter file
fileID = fopen('test.set');
paraset = fread(fileID);  % read as binary
fclose(fileID);

parasetStr = char(paraset');  % convert to 1 line string
paras = split(parasetStr,["<PARAM id=","value=", "/>"]);  % split at "id", "value" and "/"
paras([1 end],:) = [];  % delete other than "id" and "value"
paras = paras(strlength(paras) > 0);  % delete blank lines
paras = erase(paras,'"');  % delete "
paras = strtrim(paras);  % delete unnecessary spaces

parasTable = cell2table(reshape(paras,2,[]));  % convert to table
% parasTable.Properties.VariableNames = table2cell(parasTable(1,:));  % set properties name
% parasNameT = table2cell(parasTable(1,:));  % take properties
% parasName = string(parasNameT);  % convert to strings

parasVal = str2double(parasTable{2,:});  % take values


% write parameter file for reference
% fid = fopen('VSThostPara.txt','W');
% fprintf(fid, 'hostedPlugin.%s =  %g\n',[parasName;parasVal]);
% fclose(fid);


% VST hosting
hostedPlugin = loadAudioPlugin('VST_test.dll');
parameterTuner(hostedPlugin)

% set parameter file values to hosted VST
for k = 1:length(parasVal)
    [pvalN, pInf] = getParameter(hostedPlugin,k);
    displayName = matlab.lang.makeValidName(pInf.DisplayName);

    tog_comboFlag = isnan(str2double(pInf.DisplayValue));
    
    if tog_comboFlag
        tog_comboVal = parasVal(k) / (str2double(pInf.Label)-1);  % normarize
        setParameter(hostedPlugin,k,tog_comboVal);
    else
        N = sprintf("%s",displayName);
        hostedPlugin.(N) = parasVal(k);
%         N = sprintf("hostedPlugin.%s =  %f;",displayName,parasVal(k));
%         eval(N);
    end
end

% You can change parameters manually also
% hostedPlugin.Mode = 'LR'


% Read source audio file
fname = 'src\autopan3.wav';
fileReader = dsp.AudioFileReader(fname, 'SamplesPerFrame', 256);
sampleRate = fileReader.SampleRate;
setSampleRate(hostedPlugin,sampleRate);

% Output processed file using VST
outputFilename = 'test_MATLAB.wav';
hAudioFile = dsp.AudioFileWriter(outputFilename,'SampleRate',sampleRate,'DataType','int24');


while ~isDone(fileReader)
    audioIn = fileReader();
    hAudioFile(process(hostedPlugin,audioIn));
end
release(fileReader)
release(hAudioFile)


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