はじめに
夏休みの自由研究で音楽制作用DAWのプラグイン仕様VST3について調べました。今回はサイレンスフラグ(※)について。この記事はVST3プラグインを書く人向けの内容です。
※ サイレントフラグとも呼ばれます
サイレンスフラグとは
VST3では無音のときに処理をスキップして挙動を軽くするためのサイレンスフラグというものが用意されている。
サイレンスフラグ対応は、オプションであり対応しなくてもプラグインとしては機能する。
ただしサイレンスフラグを立てつつオーディオ信号を送ってくる行儀の悪いDAWもあるようなので、不要なノイズを出力しないために対応すべき。
サイレンスフラグを立てても出力バッファへのポインタは有効なメモリを示す必要がある。
サイレンスフラグはサンプルフレームのチャンネル単位で立つ。つまりAudioProcessorのprocess()関数で毎回チェックする必要がある。
サイレンスフラグはbool型ではないことに注意。 uint64のビットセットで各ビットがチャンネルに対応している(silenceFlagsと複数形になっていることに注目)。
つまり、以下のようにチェックする必要がある。
data.inputs[bus].silenceFlags & ((uint64)1 << ch) != 0
フラグチェック実装例
process()関数の実装イメージは以下のようになる。1
for (int32 ch = 0; ch < numChannels; ch++)
{
// サイレンスフラグはprocess関数の引数dataのinputsから読み取る
// ビット演算で着目するチャンネルのビット以外をゼロにする
uint64 silenceFlag = data.inputs[bus].silenceFlags & ((uint64)1 << ch);
if (silenceFlag != 0)
{
// 入力にサイレンスフラグが立っていた場合、プラグインの責任で出力側のフラグを立てる
data.outputs[ch].silenceFlags |= silenceFlag;
// 入力にサイレンスフラグが立っていた場合、プラグインの責任で出力バッファをクリアする
// 入力バッファと出力バッファが同じ場合はクリア不要
if (in[ch] != out[ch])
{
memset (out[ch], 0, sampleFramesSize);
}
// 入力にサイレンスフラグが立っていた場合、プラグインは信号処理をキャンセルして挙動を軽くする
continue;
}
// 意味のあるオーディオ出力をする場合、出力側のサイレンスフラグをクリアする
// 対象ビットの反転とAND演算することで対象ビットのみをゼロにしている
data.outputs[bus].silenceFlags &= ~(1 << ch);
// 実際の信号処理を以下に記述
}
簡易的なフラグチェック実装例
ステレオ信号の片方chだけサイレンスフラグが立つようなことはまれなので、ビットがすべて0か否か、つまりいずれかのチャンネルにフラグが立っているかを見て、全チャンネルを一様に処理する簡易的な実装もあり得る(VST3SDK付属の公式againサンプルプログラムでもそのように実装されている)。
仕様の細部に忠実な書き方ではないが、この書き方をすることで、ビット演算が不要かつフラグチェック部と信号処理部が明確に分かれるのでコードがシンプルになり、実害もほとんどない(はず)。
// いずれかのビットが立っているかをチェックする
if (data.inputs[bus].silenceFlags != 0)
{
// 入力にサイレンスフラグが立っていた場合、プラグインの責任で出力側のフラグを立てる
data.outputs[bus].silenceFlags = data.inputs[0].silenceFlags;
// 入力にサイレンスフラグが立っていた場合、プラグインの責任で出力バッファをクリアする
for (int32 ch = 0; ch < numChannels; ch++)
{
// 入力バッファと出力バッファが同じ場合はクリア不要
if (in[ch] != out[ch])
{
memset (out[ch], 0, sampleFramesSize);
}
}
// 入力にサイレンスフラグが立っていた場合、プラグインは信号処理をキャンセルして挙動を軽くする
return kResultOk;
}
// 意味のあるオーディオ出力をする場合、出力側のサイレンスフラグをクリアする
data.outputs[bus].silenceFlags = 0;
// 実際の信号処理を以下に記述
出力側のみサイレンスフラグを立てる例
自プラグインの出力レベルを絞るなど、入力信号はあるものの出力信号がほぼゼロになるときは、出力バッファをゼロクリアして出力側のサイレンスフラグを立てる。
if (gain < 0.0000001)
{
for (int32 ch = 0; ch < numChannels; ch++)
{
memset (out[ch], 0, sampleFramesSize);
}
// 全ビットを立てる処理
// 例:numChannelsが4の場合、((uint64)1 << numChannels)は0x10000、1を引くと0x1111
data.outputs[bus].silenceFlags = ((uint64)1 << numChannels) - (uint64)1;
}
逆に入力が無音のときでも、なんらかのオーディオ信号を自前で生成して出力するような特殊なプラグインの場合(ルーパーとかフィードバッカーとか)、出力側のサイレンスフラグのみをクリアすることもありえそう。
参考
VST3 Documentation Frequently Asked Questions
Q: How does it work with silence flags?
https://developer.steinberg.help/display/VST/Frequently+Asked+Questions#FrequentlyAskedQuestions-Q:Howdoesitworkwithsilenceflags?
VST3 Documentation AudioBusBuffers
https://steinbergmedia.github.io/vst3_doc/vstinterfaces/structSteinberg_1_1Vst_1_1AudioBusBuffers.html
VST3開発者必携バイブル「ZATSU NA MEMO」 - しらないと損するVST3開発の罠
https://qiita.com/engineer/items/bf637b588d5f492c7c5d
-
sampleFrameSizeは、
#include "public.sdk/source/vst/vstaudioprocessoralgo.h"
して、uint32 sampleFramesSize = Vst::getSampleFramesSizeInBytes(processSetup, data.numSamples);
で取得する。 ↩