はじめに
夏休みの自由研究で音楽制作用DAWのプラグイン仕様VST3についてインターネットのヤホーで調べました。今回は32bit/64bit処理対応について。この記事はVST3プラグインを書く人向けの内容です。
32bit/64bit処理対応とは
- VSTホスト/プラグインにはオーディオ処理の解像度が32bit浮動小数点、64bit浮動小数点の2種がある
- とても紛らわしいが、ビルドターゲットのx64のようなOSの処理単位とは関係ない(ちなみにビルドターゲットが64bitかどうかはSMTG_PLATFORM_64で判別可能)
- 音声信号の配列をホスト/プラグイン内部でfloat(32bit)で扱うかdouble(64bit)で扱うかの解像度の違い。当然64bitの方が音が良い
- DAWの仕様では64bit浮動小数点演算とか64bit-floatと書かれていることが多い。単に64bitと書かれている場合は64bit OS用のビルドという意味かも
- 64bit対応のDAWはまだそれほど多くない(Cubase、StudioOneなど。Logic、ProToolsも64bitだがVSTプラグインに対応していない)
- 内部処理は64bitなどで扱ったとして、wavファイルに出力するときに16bitなどにダウンサンプリングすることもよくあり、これも混同に注意
Cubase Elementsで設定を確認してみた。スタジオ設定→VSTオーディオシステム→プロセシング精度で選択できる。デフォルトでは32 bit floatになっていたのでこれを意図的に変更しないとプラグインが対応していても64bit処理はされないらしい。
以下に示すように十分考慮して実装すれば、おなじバイナリファイルで実行時に適切に32/64bitを切り替えて処理することができる。
プラグインが32bit/64bit対応か判別する方法
VSTホストは、読み込んだプラグインが32bit/64bitに対応しているかを以下の関数を呼んでチェックする。
tresult PLUGIN_API IAudioProcessor::canProcessSampleSize (int32 symbolicSampleSize)
つまり、プラグインはこれに正しく答えられるように実装する必要がある。
以下は32/64bit両対応する例。
tresult PLUGIN_API MyAudioProcessor::canProcessSampleSize (int32 symbolicSampleSize)
{
// 32bit処理可能な場合、kResultTrueを返す。もし処理不可ならkResultFalseを返すように書き換える
if (symbolicSampleSize == kSample32)
return kResultTrue;
// 64bit処理可能な場合、kResultTrueを返す。もし処理不可ならkResultFalseを返すように書き換える
if (symbolicSampleSize == kSample64)
return kResultTrue;
return kResultFalse;
}
明示的にcanProcessSampleSize関数をオーバーライドしない場合は、継承元のAudioEffectで32bitのみ対応と返答するように実装されている。
tresult PLUGIN_API AudioEffect::canProcessSampleSize (int32 symbolicSampleSize)
{
return symbolicSampleSize == kSample32 ? kResultTrue : kResultFalse;
}
オーディオ処理を32bit/64bit両対応にする方法
32bit/64bit両方に対応するにはAudioProcessorのprocess()関数内でdata.symbolicSampleSizeの値を見て処理を切り替える。
型に関する処理なので、処理本体を外出しの関数にしてテンプレート引数で型を渡す書き方をするとコードを共通化してシンプルに書ける。Sample32はfloatの、Sample64はdoubleの別名定義。
if (data.symbolicSampleSize == kSample32)
processImpl<Sample32> ((Sample32**)in, (Sample32**)out, numChannels, data.numSamples);
else
processImpl<Sample64> ((Sample64**)in, (Sample64**)out, numChannels, data.numSamples);
呼ばれる側の処理本体はこんな感じ。テンプレートなのでヘッダに書くのがミソ。
template <typename SampleType>
void MyVSTProcessor::processImpl(SampleType** in, SampleType**out, int32 numChannels, int32 sampleFrames)
{
for (int32 i = 0; i < numChannels; i++)
{
int32 samples = sampleFrames;
SampleType* pIn = (SampleType*)in[i];
SampleType* pOut = (SampleType*)out[i];
while (--samples >= 0)
{
*pOut = *pIn * 0.5; // 入力を0.5倍するオーディオ処理の例
pIn++;
pOut++;
}
}
}
32bit/64bit両対応にする方法その2
一律ゼロクリアする場合や入力を加工せず出力する場合、公式サンプルプラグインAGain等ではシンプル(若干トリッキー?)な方法で32bit/64bitの処理を切り替えずに実装している。
// バッファの要素数ではなく、バッファのバイト数をユーティリティ関数から取得
uint32 sampleFramesSize = getSampleFramesSizeInBytes (processSetup, data.numSamples);
// バッファへのポインタはvoid型として型を明記しない
void** in = getChannelBuffersPointer (processSetup, data.inputs[bus]);
void** out = getChannelBuffersPointer (processSetup, data.outputs[bus]);
//(中略)
// バッファを指定バイト数だけゼロクリア
memset (out[ch], 0, sampleFramesSize);
// バッファinからバッファoutへ指定バイト数だけコピー
memcpy (out[ch], in[i], sampleFramesSize);
getSampleFramesSizeInBytesは、public.sdk/source/vst/vstaudioprocessoralgo.hに定義されています。
そんなわけで、今からVSTプラグインを作るなら32bitだけでなく64bitにも対応しておいた方が利用者のためになりそうなのでやっておこうという話でした。
参考
VST3 Documentation IAudioProcessor
https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IAudioProcessor.html
VST3 Documentation AudioEffect
https://steinbergmedia.github.io/vst3_doc/vstsdk/classSteinberg_1_1Vst_1_1AudioEffect.html