This topic provides an example of Community plug-in development from start to finish. In this case, we will develop a lowpass filter plug-in.
LowpassFX::LowpassFX()
:
, m_coeff(0.99f)
AKRESULT LowpassFX::
Init(
AK::IAkPluginMemAlloc* in_pAllocator,
AK::IAkEffectPluginContext* in_pContext,
AK::IAkPluginParam* in_pParams,
AkAudioFormat& in_rFormat)
{
m_previousOutput.resize(in_rFormat.GetNumChannels(), 0.0f);
}
{
for (
AkUInt32 i = 0; i
{
uFramesProcessed = 0;
while (uFramesProcessed uValidFrames)
{
m_previousOutput[i] = pBuf[uFramesProcessed] =
pBuf[uFramesProcessed] + (m_previousOutput[i] - pBuf[uFramesProcessed]) * m_coeff;
++uFramesProcessed;
}
}
}
フィルタの周波数を制御するために、RTPCパラメータを使う
今は、このフィルタと何のやり取りもできないので、面白みのないフィルタです。それでは、フィルタの周波数にRTPCパラメータをバインドし、リアルタイムでその値を変更できるようにします。プラグインがRTPCパラメータを使えるように、4つの変更を行います。
最初に、その定義を WwisePlugin/Lowpass.xml に追加します。プラグインテンプレートに、既に"PlaceHolder"という名のパラメータスケルトンがあります。 これを使って"Frequency"パラメータを定義します。 WwisePlugin/Lowpass.xml で、プレースホルダとしてのプロパティを、以下に置き換えます:
Property Name="Frequency" Type="Real32" SupportRTPCType="Exclusive" DisplayName="Cutoff Frequency">
UserInterface Step="0.1" Fine="0.001" Decimals="3" UIMax="10000" />
DefaultValue>1000.0DefaultValue>
AudioEnginePropertyID>0AudioEnginePropertyID>
Restrictions>
ValueRestriction>
Range Type="Real32">
Min>20.0Min>
Max>10000.0Max>
Range>
ValueRestriction>
Restrictions>
Property>
2つ目に、SoundEnginePluginフォルダの中で、 LowpassFXParams.h と LowpassFXParams.cpp を更新し、プロパティの変更を反映させます。
LowpassFXParams.h で、 LowpassRTPCParams ストラクチャの中のパラメータIDとパラメータ名を更新します。
struct LowpassRTPCParams
{
};
LowpassFXParams.cpp も更新します:
{
if (in_ulBlockSize == 0)
{
RTPC.fFrequency = 1000.0f;
}
}
AKRESULT LowpassFXParams::SetParamsBlock(
const void* in_pParamsBlock,
AkUInt32 in_ulBlockSize)
{
}
{
case PARAM_FREQUENCY_ID:
RTPC.fFrequency = *((
AkReal32*)in_pValue);
}
3つ目に、 WwisePlugin フォルダで、バンクに"Frequency"パラメータを書くために Lowpass::GetBankParameters関数を更新します。
{
in_pDataWriter->
WriteReal32(m_propertySet.GetReal32(in_guidPlatform,
"Frequency"));
return true;
}
最後に、処理ループでこの公式(formula)を使い、現在の周波数を使ってフィルタ係数を計算します。
coeff = exp(-2 * pi * f / sr)
現在のサンプリングレートを取得する必要があります。
数学記号を含め、
#include
#ifndef M_PI
#define M_PI 3.14159265359
#endif
フィルタ係数を計算します:
{
m_coeff =
static_castAkReal32>(exp(-2.0 * M_PI * m_pParams->RTPC.fFrequency / m_sampleRate));
}
パラメータ値を補間する
処理パラメータをバッファサイズ(1つのオーディオバッファチャンネルにおけるサンプル数のことで、一般的に64から2048の間)ごとに一回だけ更新するのでは、不十分であることが多いです。特に、処理周波数やゲインにこのパラメータが影響するのであれば、値をあまりゆっくり更新したのでは、アウトプットサウンドでジッパーノイズやクリックが発生してしまいます。
この問題のシンプルな対策として、バッファ全体に渡り、値を線形補間することができます。今回の周波数パラメータではどのように行うのかを、以下に示します。
オーディオサンプルの新しいフレームを計算する直前、つまり LowpassFX.cpp の、 Execute 関数の一番上で、周波数パラメータが変化したかどうかをチェックします。 チェックするには、 LowpassFXParams クラスの AkFXParameterChangeHandler オブジェクトに聞けばいいのです。周波数に変化があれば、ランプ(ramp)の変数を計算します:
 | 注釈: AkAudioBuffer オブジェクトのメンバ変数 uValidFrames は、バッファに含まれるチャンネルごとの有効サンプル数を表します。 |
{
AkReal32 coeffBegin = m_coeff, coeffEnd = 0.0f, coeffStep = 0.0f;
if (m_pParams->m_paramChangeHandler.HasChanged(PARAM_FREQUENCY_ID))
{
coeffEnd =
static_castAkReal32>(exp(-2.0 * M_PI * m_pParams->RTPC.fFrequency / m_sampleRate));
coeffStep = (coeffEnd - coeffBegin) / io_pBuffer->
uValidFrames;
}
}
このデータがあれば、フレームの各サンプルで、 coeffBegin を coeffStep だけ増やせばいいのです。これを、イン・アウトバッファの、各チャンネルに対して行う必要があります。
{
for (
AkUInt32 i = 0; i
{
coeffBegin = m_coeff;
uFramesProcessed = 0;
while (uFramesProcessed uValidFrames)
{
m_previousOutput[i] = pBuf[uFramesProcessed] =
pBuf[uFramesProcessed] + (m_previousOutput[i] - pBuf[uFramesProcessed]) * coeffBegin;
coeffBegin += coeffStep;
++uFramesProcessed;
}
}
m_coeff = coeffBegin;
}
さて、カットオフ周波数をリアルタイムで制御できる単純なローパスフィルタを実装した、基本的で機能的なプラグインができたところで、デザイン上の懸念点について考えます。
処理のカプセル化
今、全ての信号処理ロジックがプラグインのメインクラスに記述されています。これは悪いデザインパターンで、その理由は以下のように多数あります:
- メインプラグインクラスが膨張していて、今後、複雑なエフェクトをビルドするために新しい処理過程を追加していった場合は、さらにひどくなるはずです。
- このフィルタをほかのプラグインで使いたくても、再利用しにくいのですが、このような基本的な処理ユニットは、絶対に再利用したくなります!
- 単一責任の原則(single responsibility principle)に準拠していません。
そこで、コードのリファクタリングを行い、フィルタ処理を、自分自身のクラスにカプセル化します。ファイル FirstOrderLowpass.h を SoundEnginePlugin フォルダに作成し、このように定義します:
#pragma once
#include AK/SoundEngine/Common/AkCommonDefs.h>
class FirstOrderLowpass
{
public:
FirstOrderLowpass();
~FirstOrderLowpass();
void SetFrequency(
AkReal32 in_newFrequency);
private:
bool m_frequencyChanged;
};
そして、この実装を FirstOrderLowpass.cpp と呼ばれるファイルに追加します:
#include "FirstOrderLowpass.h"
#include
#ifndef M_PI
#define M_PI 3.14159265359
#endif
FirstOrderLowpass::FirstOrderLowpass()
: m_sampleRate(0)
, m_frequency(0.0f)
, m_coeff(0.0f)
, m_previousOutput(0.0f)
, m_frequencyChanged(false)
{
}
FirstOrderLowpass::~FirstOrderLowpass() {}
void FirstOrderLowpass::Setup(
AkUInt32 in_sampleRate)
{
m_sampleRate = in_sampleRate;
}
void FirstOrderLowpass::SetFrequency(
AkReal32 in_newFrequency)
{
if (m_sampleRate > 0)
{
m_frequency = in_newFrequency;
m_frequencyChanged = true;
}
}
{
AkReal32 coeffBegin = m_coeff, coeffEnd = 0.0f, coeffStep = 0.0f;
if (m_frequencyChanged)
{
coeffEnd =
static_castAkReal32>(exp(-2.0 * M_PI * m_frequency / m_sampleRate));
coeffStep = (coeffEnd - coeffBegin) / in_uValidFrames;
m_frequencyChanged = false;
}
while (uFramesProcessed
{
m_previousOutput = io_pBuffer[uFramesProcessed] =
io_pBuffer[uFramesProcessed] + (m_previousOutput - io_pBuffer[uFramesProcessed]) * coeffBegin;
coeffBegin += coeffStep;
++uFramesProcessed;
}
m_coeff = coeffBegin;
}
これで、メインプラグインクラスでは、 FirstOrderLowpass オブジェクト(オーディオチャンネル1つに対し、1つ)のベクトルを作成し、その Setup 関数をコールし、それらを使い始めるだけでいいのです。
#include "FirstOrderLowpass.h"
#include
private:
std::vector m_filter;
{
for (
auto & filterChannel : m_filter) { filterChannel.Setup(in_rFormat.
uSampleRate); }
}
{
if (m_pParams->m_paramChangeHandler.HasChanged(PARAM_FREQUENCY_ID))
{
for (auto & filterChannel : m_filter) { filterChannel.SetFrequency(m_pParams->RTPC.fFrequency); }
}
AkSampleType * GetChannel(AkUInt32 in_uIndex)
uint16_t AkUInt16
Unsigned 16-bit integer
Definition of data structures for AkAudioObject
Interface used to write data during sound bank generation.
AkForceInline AkUInt32 NumChannels() const
Get the number of channels.
AKRESULT
Standard function call result.
AKSOUNDENGINE_API AKRESULT Init(const AkCommSettings &in_settings)
bool WriteReal32(float in_value)
Writes a 32-bit, single-precision floating point value.
float AkReal32
32-bit floating point
AkUInt16 uValidFrames
Number of valid sample frames in the audio buffer
#define READBANKDATA(_Type, _Ptr, _Size)
Read and return bank data of a given type, incrementing running pointer and decrementing block size f...
uint32_t AkUInt32
Unsigned 32-bit integer
AkInt16 AkPluginParamID
Source or effect plug-in parameter ID