見出し画像

MATLABでVST開発 ~ VSTプラグインをホストしてパラメータファイル適用 ~ dictionary を使う

まえがき

以前、MATLABで開発したVSTで保存した設定パラメータファイルをMATLAB上で適用して使う以下の記事を書きましたが、より簡単な方法が見つかったのでまとめておきます。

MATLABでVST開発 ~ Map オブジェクト(文字ベクトル Key データ構造体)を使ってVSTパラメータをセットする(R2022a対応版)

問題点

問題点をもう一度整理すると、以下のようになります。

  • VSTで保存できるパラメータファイルは変数名と正規化されていない数値( DisplayName 、値域、enumのプロパティ値は保存されない)

audioPluginParameter('inputGaindB', 'DisplayName','Input Gain', 'Label','dB','Mapping',{'pow', 1/3, -80, 6}), ...
audioPluginParameter('channelMode', 'DisplayName','Process Channel', 'Mapping',{'enum','LR','L','R'}), ...
audioPluginParameter('PEQ_ON', 'Mapping',{'enum','OFF','ON'}, 'DisplayName','PEQ'), ...
audioPluginParameter('centerFreq','Mapping', {'log',20,20000}, 'DisplayName','Frequency'), ...
audioPluginParameter('peakGain','Mapping', {'lin',-40,40}, 'DisplayName','Peak Gain'), ...
audioPluginParameter('Q','Mapping', {'lin',0.2,20}, 'DisplayName','Q') ...

VSTソースコードでの宣言例(第1引数が変数名

<PARAMM id="PEQ_ON" value="1.0"/>
<PARAMM id="Q" value="1.00000011920929"/>
<PARAMM id="centerFreq" value="1000.0"/>
<PARAMM id="channelMode" value="0.0"/>
<PARAMM id="inputGaindB" value="-6.0"/>
<PARAMM id="peakGain" value="-3.0"/>

パラメータファイル例
(R2022a以降で genarateした場合の例
 それ以前はソースコードでの宣言順)

  • hostedPlugin = loadAudioPlugin(); でホストしたプラグインにパラメータを適用する方法は2つだが、ComboBoxに対する「変数名と正規化されていない数値」による設定方法がない

2つの方法とは、
 1. hostedPlugin.InputGain = のように構造体メンバーで設定
 (構造体メンバー名は matlab.lang.makeValidName('DisplayName'))
 2.setParameter() で設定
です。

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

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

>> hostedPlugin = loadAudioPlugin('VST_simple3.dll')

hostedPlugin = 

  VST plugin 'VST_simple3'  2 in, 2 out

         InputGain: 0 dB
    ProcessChannel: 'LR'
               PEQ: 'OFF'
         Frequency: 4000
          PeakGain: 0
                 Q: 1.6
hostedPlugin.InputGain = -3;
hostedPlugin.ProcessChannel = 'L';

ComboBoxの場合数値ではなく、パラメータファイルには保存されていないenum で設定したプロパティ文字を使用する必要があります。

(構造体メンバー名に使用できる文字列には制限があるので、元の'DisplayName'そのものではなく、matlab.lang.makeValidName('DisplayName')を使用することにも注意)

- setParameter() による指定

setParameter(hostedPlugin, (パラメータインデックス), (正規化値))
setParameter(hostedPlugin, ('パラメータプロパティ文字'), (正規化値))

の2種類の指定方法があり、2つ目の引数は 「display値」またはパラメータインデックス、三つ目の引数は値域が [0, 1]で 正規化された数値です。

minX = -80;  maxX = 6;  val = -3.0;
Xs = (val - minX)/(maxX - minX);
Xp = nthroot(Xs,1/3);  % {'pow', 1/3, -80, 6}
setParameter(hostedPlugin,1,Xp)
minX = 0;  maxX = 2;  val = 1;
Xs = (val - minX)/(maxX - minX);  % 'LR','L','R' -> 0, 1, 2
setParameter(hostedPlugin,2,Xs)
% または
setParameter(hostedPlugin,'Process Channel',Xs)

正規化値で指定する必要がありますが、パラメータファイルには値域が保存されていないので正規化値を求めることができません。

(この場合は構造体ではないので、matlab.lang.makeValidName('DisplayName')ではなく元の'DisplayName' そのものを使います)

詳しくは
externalAudioPlugin class ~ MATLABで外部VSTプラグインをホスト ~ 動的変数名を使ってVSTパラメータをセットする
をご覧ください。

ソースファイルのプロパティ値を参照する

新たな解決方法としては、VSTのソースファイル(VST_simple3.m)から、enum の宣言内容を参照して使います

スクリプト例

clear
% read VST parameter file
fileID = fopen('R2022b.para');
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


% Create a map of DisplayName from ParameterName using the plugin class
pm = VST_simple3;  % get properties
params = pm.PluginInterface.Parameters;  % get GUI parameters as struct
fn = fieldnames(params);  % get property names

m = containers.Map('KeyType','char','ValueType','char');

for k = 1:numel(fn)
   m(fn{k}) = params.(fn{k}).DisplayName;  % set PropertyNames as keys and set DisplayNames as values
end


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

for k = 1:numel(fn)
    varName = matlab.lang.makeValidName(m(parasName(k)));  % properties
    valName = parasName(k);
    comboFlag = ~isnumeric(pm.(valName));  % ComboBox
    
    if comboFlag
        disp('ComboBox')
        hostedPlugin.(varName) = strtrim(pm.PluginInterface.Parameters.(valName).Enums(parasVal(k)+1,:));
    else
        hostedPlugin.(varName) = parasVal(k);
    end
end

前半のVSTパラメータファイル読み込み部分は、VSTをホストする場合のスクリプトと同じです。

違いは読み込んだパラメータをホスティングしたプラグインに適用する "% VST hosting" 以降の後半部分です。

audioTestbench で VST設定ファイルを適用 ~ MATLAB で VST 開発
で、enum(列挙型)の宣言内容も参照できることが分かったので、VSTソースの方で 'Label' を使う必要はなくなりました。

ただ、VSTソースで DisplayName を同じのを作ってしまうと suffix が自動的に付加され元がどちらか区別が付かなくなるため、同じ名前を使わないようにするのは変わりません。

実行結果

VST ( VST_simple3.exe ) で Options -> Save current state
parameterTuner(hostedPlugin) 初期画面
パラメータ適用スクリプト実行後


dictionary

また、R2022bからは、"containers.Map" よりも高速な "dictionary" が追加されました。
基本的な使い方は同じで

m = containers.Map('KeyType','char','ValueType','char');
for k = 1:numel(fn)
   m(fn{k}) = params.(fn{k}).DisplayName;
end
d = dictionary(string([]),string([]));
for k = 1:numel(fn)
   d(fn{k}) = params.(fn{k}).DisplayName;
end

とすれば、m を d でそのまま置き換えができます。

containers.Map と違って対応付けがすぐ確認できるので便利ですね。

d =

6 個のエントリをもつ dictionary (stringstring):

"inputGaindB""Input Gain"
"channelMode""Process Channel"
"PEQ_ON""PEQ"
"centerFreq""Frequency"
"peakGain""Peak Gain"
"Q""Q"


あるいは

c = struct2cell(params);
c2 = [c{:,1}];
c3 = {c2.DisplayName};
d = dictionary(string(fn)',string(c3));

とstring配列で指定すれば、forループも不要です。

また、Twitterで教えていただいた

c = cellfun(@(x) {getfield(params,x,'DisplayName')},fieldnames(params));
d = dictionary(string(fn)',string(c'));

という方法も使えます。

structfun() を使う例も教えていただいたのですが、今回の例では 'UniformOutput', false にする必要があり、その場合は返り値がまた struct になってしまうので簡単になりそうもないです。

a = structfun(@(x) x.DisplayName, params, 'UniformOutput', false)

a =

フィールドをもつ struct:

inputGaindB: 'Input Gain'
channelMode: 'Process Channel'
     PEQ_ON: 'PEQ'
 centerFreq: 'Frequency'
   peakGain: 'Peak Gain'
          Q: 'Q'

上記 cellfun の戻り値のように cell になってくれると良いのですが。

c =

6×1 の cell 配列

{'Input Gain'     }
{'Process Channel'}
{'PEQ'            }
{'Frequency'      }
{'Peak Gain'      }
{'Q'              }

structfun() でも簡単に変換できる方法があればぜひ教えてください。

まあとにかく色々とややこしいですが、一度できてしまえば使い回しが効くので。(u_u)


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