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 参数,我们需要实施以下四项更改。
首先,必须在 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>
其次,需要更新 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);
}
再次,在 WwisePlugin 文件夹中,需要更新 Lowpass::GetBankParameters 函数,以将 Frequency 参数写入到 SoundBank 中:
{
in_pDataWriter->
WriteReal32(m_propertySet.GetReal32(in_guidPlatform,
"Frequency"));
return true;
}
最后,在处理循环中,还要使用以下公式来依据当前频率计算滤波器的系数:
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));
}
插入参数值
通常,每个缓冲区大小更新一次处理参数是不够的(缓冲区大小等于声道缓冲区中的采样数,一般介于 64 ~ 2048 之间)。尤其是在此参数会影响处理的频率或增益的情况下,数值更新太慢会导致输出声音中出现拉链噪声或噼啪噪声。
对此,只需在整个缓冲区当中以线性方式插入数值即可。下面我们来针对频率参数执行此操作。
在计算一帧新的音频样本之前(即在 LowpassFX.cpp 中的 Execute 函数最上面),我们要检查频率参数是否发生了更改。 为此,可直接查询 LowpassFXParams 类中的 AkFXParameterChangeHandler 对象。若频率发生了更改,则计算 ramp 的变量:
{
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;
}
现在我们构建了一个能够实现简单的低通滤波效果并实时控制截止频率的基础功能插件。接下来,我们说说设计方面的一些问题。
封装处理逻辑
现在,所有的信号处理逻辑都写在了插件主类内。这种设计模式存在很多弊端:
- 插件主类会显得很臃肿,等到添加新的处理功能来构建较为复杂的效果时,情况会更加糟糕。
- 很难在需要时将该滤波器用于其他插件。对这种基础处理组件来说更是如此。
- 不符合单一职责原则。
下面我们来重构代码以将滤波处理封装到其自身的类中。首先,依据以下定义在 SoundEnginePlugin 文件夹中创建 FirstOrderLowpass.h 文件:
#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 对象(每个声道一个),然后通过调用 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