menu
 
バージョン
2024.1.3.8749

2024.1.3.8749

2023.1.11.8682

2022.1.18.8567

2021.1.14.8108

2019.2.15.7667

2019.1.11.7296

2018.1.11.6987

2017.2.10.6745

2017.1.9.6501

2016.2.6.6153

2015.1.9.5624


menu_open
Wwise SDK 2024.1.3
Example: Developing a Lowpass Filter Plug-in

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.

新しいプロジェクトを作成する

まず最初に、 wp.py ツールで新しいプラグインプロジェクトを作成します。 Creating Audio Plug-ins に、 new 引数の詳しい内容があります。

python "%WWISEROOT%/Scripts/Build/Plugins/wp.py" new --effect -a author_name -n Lowpass -t "First Order Lowpass" -d "Simple First Order Lowpass Filter"
cd Lowpass

WindowsでAuthoringプラットフォームをターゲットとするので、 premake をコールします:

python "%WWISEROOT%/Scripts/Build/Plugins/wp.py" premake Authoring

SoundEngine部分とAuthoring (WwisePlugin) 部分をビルドするためのソリューションが作成されています。

これで、プラグインをビルドしてから、Wwiseでロードされることを確認します。

python "%WWISEROOT%/Scripts/Build/Plugins/wp.py" build -c Release -x x64 -t vc160 Authoring

フィルタプロセスを実装する

次に、このプラグインがもう少し役に立つように、処理能力を少し追加します。この式を使って簡単な1次ローパスフィルタを実装します:

y[n] = x[n] + (y[n-1] - x[n]) * coeff

このとき coeff は0から1の間の浮動小数点とします。

最初に、このフィルタのデータを保持できるように、 SoundEnginePlugin/LowpassFX.h にいくつかの変数を作成します。

#include <vector>
//...
private:
//...
AkReal32 m_coeff;
std::vector<AkReal32> m_previousOutput;

変数値 m_coeff は浮動小数点としてのフィルタ係数で、全サウンドチャンネルに使います。ベクトル m_previousOutput が全チャンネルの最後のアウトプット値を保持しますが、これはフィルタの次の値を計算するために必須です。

フィルタエフェクトを適用するには、係数変数を初期化し、チャンネル数に従いベクトルサイズを調整し、各サンプルを前の公式で処理するだけです。

SoundEnginePlugin/LowpassFX.cpp で:

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);
//...
}
void LowpassFX::Execute(AkAudioBuffer* io_pBuffer)
{
const AkUInt32 uNumChannels = io_pBuffer->NumChannels();
AkUInt16 uFramesProcessed;
for (AkUInt32 i = 0; i < uNumChannels; ++i)
{
uFramesProcessed = 0;
while (uFramesProcessed < io_pBuffer->uValidFrames)
{
// Apply lowpass filtering per channel.
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.0</DefaultValue>
<AudioEnginePropertyID>0</AudioEnginePropertyID>
<Restrictions>
<ValueRestriction>
<Range Type="Real32">
<Min>20.0</Min>
<Max>10000.0</Max>
</Range>
</ValueRestriction>
</Restrictions>
</Property>

2つ目に、SoundEnginePluginフォルダの中で、 LowpassFXParams.hLowpassFXParams.cpp を更新し、プロパティの変更を反映させます。

LowpassFXParams.h で、 LowpassRTPCParams ストラクチャの中のパラメータIDとパラメータ名を更新します。

static const AkPluginParamID PARAM_FREQUENCY_ID = 0;
struct LowpassRTPCParams
{
AkReal32 fFrequency;
};

LowpassFXParams.cpp も更新します:

AKRESULT LowpassFXParams::Init(AK::IAkPluginMemAlloc* in_pAllocator, const void* in_pParamsBlock, AkUInt32 in_ulBlockSize)
{
if (in_ulBlockSize == 0)
{
// Initialize default parameters here
RTPC.fFrequency = 1000.0f;
//...
}
//...
}
AKRESULT LowpassFXParams::SetParamsBlock(const void* in_pParamsBlock, AkUInt32 in_ulBlockSize)
{
//...
// Read bank data here
RTPC.fFrequency = READBANKDATA(AkReal32, pParamsBlock, in_ulBlockSize);
//...
}
AKRESULT LowpassFXParams::SetParam(AkPluginParamID in_paramID, const void* in_pValue, AkUInt32 in_ulParamSize)
{
//...
// Handle parameter change here
case PARAM_FREQUENCY_ID:
RTPC.fFrequency = *((AkReal32*)in_pValue);
//...
}

3つ目に、 WwisePlugin フォルダで、バンクに"Frequency"パラメータを書くために Lowpass::GetBankParameters関数を更新します。

bool LowpassPlugin::GetBankParameters(const GUID & in_guidPlatform, AK::Wwise::Plugin::DataWriter* in_pDataWriter) const
{
// Write bank data here
in_pDataWriter->WriteReal32(m_propertySet.GetReal32(in_guidPlatform, "Frequency"));
return true;
}

最後に、処理ループでこの公式(formula)を使い、現在の周波数を使ってフィルタ係数を計算します。

coeff = exp(-2 * pi * f / sr)

現在のサンプリングレートを取得する必要があります。

// LowpassFX.h
private:
//...
AkUInt32 m_sampleRate;
// LowpassFX.cpp
{
//...
m_sampleRate = in_rFormat.uSampleRate;
//...
}

数学記号を含め、

// LowpassFX.cpp
#include <cmath>
#ifndef M_PI
#define M_PI 3.14159265359
#endif
//...

フィルタ係数を計算します:

// LowpassFX.cpp
void LowpassFX::Execute(AkAudioBuffer* io_pBuffer)
{
m_coeff = static_cast<AkReal32>(exp(-2.0 * M_PI * m_pParams->RTPC.fFrequency / m_sampleRate));
//...
}

パラメータ値を補間する

処理パラメータをバッファサイズ(1つのオーディオバッファチャンネルにおけるサンプル数のことで、一般的に64から2048の間)ごとに一回だけ更新するのでは、不十分であることが多いです。特に、処理周波数やゲインにこのパラメータが影響するのであれば、値をあまりゆっくり更新したのでは、アウトプットサウンドでジッパーノイズやクリックが発生してしまいます。

この問題のシンプルな対策として、バッファ全体に渡り、値を線形補間することができます。今回の周波数パラメータではどのように行うのかを、以下に示します。

オーディオサンプルの新しいフレームを計算する直前、つまり LowpassFX.cpp の、 Execute 関数の一番上で、周波数パラメータが変化したかどうかをチェックします。 チェックするには、 LowpassFXParams クラスの AkFXParameterChangeHandler オブジェクトに聞けばいいのです。周波数に変化があれば、ランプ(ramp)の変数を計算します:

info注釈: AkAudioBuffer オブジェクトのメンバ変数 uValidFrames は、バッファに含まれるチャンネルごとの有効サンプル数を表します。
void LowpassFX::Execute(AkAudioBuffer* io_pBuffer)
{
AkReal32 coeffBegin = m_coeff, coeffEnd = 0.0f, coeffStep = 0.0f;
if (m_pParams->m_paramChangeHandler.HasChanged(PARAM_FREQUENCY_ID))
{
coeffEnd = static_cast<AkReal32>(exp(-2.0 * M_PI * m_pParams->RTPC.fFrequency / m_sampleRate));
coeffStep = (coeffEnd - coeffBegin) / io_pBuffer->uValidFrames;
}
//...
}

このデータがあれば、フレームの各サンプルで、 coeffBegincoeffStep だけ増やせばいいのです。これを、イン・アウトバッファの、各チャンネルに対して行う必要があります。

void LowpassFX::Execute(AkAudioBuffer* io_pBuffer)
{
//...
for (AkUInt32 i = 0; i < uNumChannels; ++i)
{
coeffBegin = m_coeff; // restart the ramp.
uFramesProcessed = 0;
while (uFramesProcessed < io_pBuffer->uValidFrames)
{
m_previousOutput[i] = pBuf[uFramesProcessed] =
pBuf[uFramesProcessed] + (m_previousOutput[i] - pBuf[uFramesProcessed]) * coeffBegin;
coeffBegin += coeffStep; // increase by coeffStep every sample.
++uFramesProcessed;
}
}
m_coeff = coeffBegin; // save the current value for the next frame.
}

さて、カットオフ周波数をリアルタイムで制御できる単純なローパスフィルタを実装した、基本的で機能的なプラグインができたところで、デザイン上の懸念点について考えます。

処理のカプセル化

今、全ての信号処理ロジックがプラグインのメインクラスに記述されています。これは悪いデザインパターンで、その理由は以下のように多数あります:

  • メインプラグインクラスが膨張していて、今後、複雑なエフェクトをビルドするために新しい処理過程を追加していった場合は、さらにひどくなるはずです。
  • このフィルタをほかのプラグインで使いたくても、再利用しにくいのですが、このような基本的な処理ユニットは、絶対に再利用したくなります!
  • 単一責任の原則(single responsibility principle)に準拠していません。

そこで、コードのリファクタリングを行い、フィルタ処理を、自分自身のクラスにカプセル化します。ファイル FirstOrderLowpass.hSoundEnginePlugin フォルダに作成し、このように定義します:

// FirstOrderLowpass.h
#pragma once
class FirstOrderLowpass
{
public:
FirstOrderLowpass();
~FirstOrderLowpass();
void Setup(AkUInt32 in_sampleRate);
void SetFrequency(AkReal32 in_newFrequency);
void Execute(AkReal32* io_pBuffer, AkUInt16 in_uValidFrames);
private:
AkUInt32 m_sampleRate;
AkReal32 m_frequency;
AkReal32 m_coeff;
AkReal32 m_previousOutput;
bool m_frequencyChanged;
};

そして、この実装を FirstOrderLowpass.cpp と呼ばれるファイルに追加します:

// FirstOrderLowpass.cpp
#include "FirstOrderLowpass.h"
#include <cmath>
#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;
}
}
void FirstOrderLowpass::Execute(AkReal32* io_pBuffer, AkUInt16 in_uValidFrames)
{
AkReal32 coeffBegin = m_coeff, coeffEnd = 0.0f, coeffStep = 0.0f;
if (m_frequencyChanged)
{
coeffEnd = static_cast<AkReal32>(exp(-2.0 * M_PI * m_frequency / m_sampleRate));
coeffStep = (coeffEnd - coeffBegin) / in_uValidFrames;
m_frequencyChanged = false;
}
AkUInt16 uFramesProcessed = 0;
while (uFramesProcessed < in_uValidFrames)
{
m_previousOutput = io_pBuffer[uFramesProcessed] =
io_pBuffer[uFramesProcessed] + (m_previousOutput - io_pBuffer[uFramesProcessed]) * coeffBegin;
coeffBegin += coeffStep;
++uFramesProcessed;
}
m_coeff = coeffBegin;
}

これで、メインプラグインクラスでは、 FirstOrderLowpass オブジェクト(オーディオチャンネル1つに対し、1つ)のベクトルを作成し、その Setup 関数をコールし、それらを使い始めるだけでいいのです。

// LowpassFX.h
#include "FirstOrderLowpass.h"
#include <vector>
//...
private:
std::vector<FirstOrderLowpass> m_filter;
// LowpassFX.cpp
{
//...
m_filter.resize(in_rFormat.GetNumChannels());
for (auto & filterChannel : m_filter) { filterChannel.Setup(in_rFormat.uSampleRate); }
//...
}
void LowpassFX::Execute(AkAudioBuffer* io_pBuffer)
{
if (m_pParams->m_paramChangeHandler.HasChanged(PARAM_FREQUENCY_ID))
{
for (auto & filterChannel : m_filter) { filterChannel.SetFrequency(m_pParams->RTPC.fFrequency); }
}
const AkUInt32 uNumChannels = io_pBuffer->NumChannels();
for (AkUInt32 i = 0; i < uNumChannels; ++i)
{
m_filter[i].Execute(pBuf, io_pBuffer->uValidFrames);
}
}
AkSampleType * GetChannel(AkUInt32 in_uIndex)
Definition: AkCommonDefs.h:432
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.
Definition: AkCommonDefs.h:348
AKRESULT
Standard function call result.
Definition: AkTypes.h:134
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
Definition: AkCommonDefs.h:513
#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
Definition: AkTypes.h:66
AkForceInline AkUInt32 GetNumChannels() const
Definition: AkCommonDefs.h:72
Defines the parameters of an audio buffer format.
Definition: AkCommonDefs.h:60
AkUInt32 uSampleRate
Number of samples per second
Definition: AkCommonDefs.h:61
#define AK_RESTRICT
Refers to the __restrict compilation flag available on some platforms
Definition: AkTypes.h:45

このページはお役に立ちましたか?

サポートは必要ですか?

ご質問や問題、ご不明点はございますか?お気軽にお問い合わせください。

サポートページをご確認ください

あなたのプロジェクトについて教えてください。ご不明な点はありませんか。

プロジェクトを登録していただくことで、ご利用開始のサポートをいたします。

Wwiseからはじめよう