291 lines
8.9 KiB
C++
291 lines
8.9 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "SubmixEffects/AudioMixerSubmixEffectEQ.h"
|
|
#include "Misc/ScopeLock.h"
|
|
#include "AudioMixer.h"
|
|
#include "ProfilingDebugging/CsvProfiler.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(AudioMixerSubmixEffectEQ)
|
|
|
|
// Link to "Audio" profiling category
|
|
CSV_DECLARE_CATEGORY_MODULE_EXTERN(AUDIOMIXERCORE_API, Audio);
|
|
|
|
DEFINE_STAT(STAT_AudioMixerSubmixEQ);
|
|
|
|
static bool IsEqual(const FSubmixEffectSubmixEQSettings& Left, const FSubmixEffectSubmixEQSettings& Right)
|
|
{
|
|
// return false if the number of bands changed
|
|
if (Left.EQBands.Num() != Right.EQBands.Num())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
for (int32 i = 0; i < Right.EQBands.Num(); ++i)
|
|
{
|
|
const FSubmixEffectEQBand& OtherBand = Right.EQBands[i];
|
|
const FSubmixEffectEQBand& ThisBand = Left.EQBands[i];
|
|
|
|
if (OtherBand.bEnabled != ThisBand.bEnabled)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!FMath::IsNearlyEqual(OtherBand.Bandwidth, ThisBand.Bandwidth))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!FMath::IsNearlyEqual(OtherBand.Frequency, ThisBand.Frequency))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!FMath::IsNearlyEqual(OtherBand.GainDb, ThisBand.GainDb))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If we made it this far these are equal
|
|
return true;
|
|
}
|
|
|
|
|
|
FSubmixEffectSubmixEQ::FSubmixEffectSubmixEQ()
|
|
: SampleRate(0)
|
|
, NumOutputChannels(2)
|
|
{
|
|
FMemory::Memzero((void*)ScratchInBuffer, sizeof(float) * 2);
|
|
FMemory::Memzero((void*)ScratchOutBuffer, sizeof(float) * 2);
|
|
}
|
|
|
|
void FSubmixEffectSubmixEQ::Init(const FSoundEffectSubmixInitData& InitData)
|
|
{
|
|
SampleRate = InitData.SampleRate;
|
|
|
|
// Assume 8 channels (max supported channels)
|
|
NumOutputChannels = 8;
|
|
|
|
const int32 NumFilters = NumOutputChannels / 2;
|
|
for (int32 i = 0; i < NumFilters; ++i)
|
|
{
|
|
int32 Index = FiltersPerChannel.Add(FEQ());
|
|
}
|
|
|
|
bEQSettingsSet = false;
|
|
}
|
|
|
|
// Called when an audio effect preset is changed
|
|
void FSubmixEffectSubmixEQ::OnPresetChanged()
|
|
{
|
|
LLM_SCOPE(ELLMTag::AudioMixer);
|
|
|
|
GET_EFFECT_SETTINGS(SubmixEffectSubmixEQ);
|
|
|
|
// Don't make any changes if this is the exact same parameters
|
|
if (!IsEqual(GameThreadEQSettings, Settings))
|
|
{
|
|
GameThreadEQSettings = Settings;
|
|
PendingSettings.SetParams(GameThreadEQSettings);
|
|
}
|
|
}
|
|
|
|
void FSubmixEffectSubmixEQ::OnProcessAudio(const FSoundEffectSubmixInputData& InData, FSoundEffectSubmixOutputData& OutData)
|
|
{
|
|
LLM_SCOPE(ELLMTag::AudioMixer);
|
|
|
|
CSV_SCOPED_TIMING_STAT(Audio, SubmixEQ);
|
|
SCOPE_CYCLE_COUNTER(STAT_AudioMixerSubmixEQ);
|
|
|
|
// Update parameters that may have been set from game thread
|
|
UpdateParameters(InData.NumChannels);
|
|
|
|
Audio::FAlignedFloatBuffer& InAudioBuffer = *InData.AudioBuffer;
|
|
Audio::FAlignedFloatBuffer& OutAudioBuffer = *OutData.AudioBuffer;
|
|
|
|
if (bEQSettingsSet && RenderThreadEQSettings.EQBands.Num() > 0)
|
|
{
|
|
// Feed every other channel through the EQ filters
|
|
int32 NumFilters = InData.NumChannels / 2;
|
|
for (int32 FilterIndex = 0; FilterIndex < NumFilters; ++FilterIndex)
|
|
{
|
|
FEQ& EQFilter = FiltersPerChannel[FilterIndex];
|
|
const int32 ChannelOffset = FilterIndex * 2;
|
|
for (int32 FrameIndex = 0; FrameIndex < InData.NumFrames; ++FrameIndex)
|
|
{
|
|
// Get the sample index of this frame for this filter
|
|
const int32 SampleIndex = FrameIndex * InData.NumChannels + ChannelOffset;
|
|
|
|
// Copy the audio from the input buffer for this frame
|
|
ScratchInBuffer[0] = InAudioBuffer[SampleIndex];
|
|
ScratchInBuffer[1] = InAudioBuffer[SampleIndex + 1];
|
|
|
|
const int32 NumBands = EQFilter.Bands.Num();
|
|
for (int32 BandIndex = 0; BandIndex < NumBands; ++BandIndex)
|
|
{
|
|
EQFilter.Bands[BandIndex].ProcessAudioFrame(ScratchInBuffer, ScratchOutBuffer);
|
|
|
|
// Copy the output of this band into the input for sequential processing
|
|
for (int32 Channel = 0; Channel < 2; ++Channel)
|
|
{
|
|
ScratchInBuffer[Channel] = ScratchOutBuffer[Channel];
|
|
}
|
|
}
|
|
|
|
// Copy the results of this frame to the output buffer
|
|
OutAudioBuffer[SampleIndex] = ScratchOutBuffer[0];
|
|
OutAudioBuffer[SampleIndex + 1] = ScratchOutBuffer[1];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// pass through
|
|
for (int32 i = 0; i < InAudioBuffer.Num(); ++i)
|
|
{
|
|
OutAudioBuffer[i] = InAudioBuffer[i];
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
static float GetClampedGain(const float InGain)
|
|
{
|
|
// These are clamped to match XAudio2 FXEQ_MIN_GAIN and FXEQ_MAX_GAIN
|
|
return FMath::Clamp(InGain, 0.001f, 7.94f);
|
|
}
|
|
|
|
static float GetClampedBandwidth(const float InBandwidth)
|
|
{
|
|
// These are clamped to match XAudio2 FXEQ_MIN_BANDWIDTH and FXEQ_MAX_BANDWIDTH
|
|
return FMath::Clamp(InBandwidth, 0.1f, 2.0f);
|
|
}
|
|
|
|
static float GetClampedFrequency(const float InFrequency)
|
|
{
|
|
// These are clamped to match XAudio2 FXEQ_MIN_FREQUENCY_CENTER and FXEQ_MAX_FREQUENCY_CENTER
|
|
return FMath::Clamp(InFrequency, 20.0f, 20000.0f);
|
|
}
|
|
|
|
bool FSubmixEffectSubmixEQ::SetParameters(const FAudioEffectParameters& InParameters)
|
|
{
|
|
const FAudioEQEffect& EQEffectParameters = static_cast<const FAudioEQEffect&>(InParameters);
|
|
|
|
// This function maps the old audio engine eq effect params to the new eq effect.
|
|
// Note that this is always a 4-band EQ and not flexible w/ respect.
|
|
FSubmixEffectSubmixEQSettings NewSettings;
|
|
FSubmixEffectEQBand Band;
|
|
|
|
Band.bEnabled = true;
|
|
Band.Frequency = GetClampedFrequency(EQEffectParameters.FrequencyCenter0);
|
|
Band.Bandwidth = GetClampedBandwidth(EQEffectParameters.Bandwidth0);
|
|
Band.GainDb = Audio::ConvertToDecibels(GetClampedGain(EQEffectParameters.Gain0));
|
|
NewSettings.EQBands.Add(Band);
|
|
|
|
Band.bEnabled = true;
|
|
Band.Frequency = GetClampedFrequency(EQEffectParameters.FrequencyCenter1);
|
|
Band.Bandwidth = GetClampedBandwidth(EQEffectParameters.Bandwidth1);
|
|
Band.GainDb = Audio::ConvertToDecibels(GetClampedGain(EQEffectParameters.Gain1));
|
|
NewSettings.EQBands.Add(Band);
|
|
|
|
Band.bEnabled = true;
|
|
Band.Frequency = GetClampedFrequency(EQEffectParameters.FrequencyCenter2);
|
|
Band.Bandwidth = GetClampedBandwidth(EQEffectParameters.Bandwidth2);
|
|
Band.GainDb = Audio::ConvertToDecibels(GetClampedGain(EQEffectParameters.Gain2));
|
|
NewSettings.EQBands.Add(Band);
|
|
|
|
Band.bEnabled = true;
|
|
Band.Frequency = GetClampedFrequency(EQEffectParameters.FrequencyCenter3);
|
|
Band.Bandwidth = GetClampedBandwidth(EQEffectParameters.Bandwidth3);
|
|
Band.GainDb = Audio::ConvertToDecibels(GetClampedGain(EQEffectParameters.Gain3));
|
|
NewSettings.EQBands.Add(Band);
|
|
|
|
// Don't make any changes if this is the exact same parameters
|
|
if (!IsEqual(GameThreadEQSettings, NewSettings))
|
|
{
|
|
GameThreadEQSettings = NewSettings;
|
|
PendingSettings.SetParams(GameThreadEQSettings);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FSubmixEffectSubmixEQ::UpdateParameters(const int32 InNumOutputChannels)
|
|
{
|
|
// We need to update parameters if the output channel count changed
|
|
bool bParamsChanged = false;
|
|
|
|
// Also need to update if new settings have been applied
|
|
FSubmixEffectSubmixEQSettings NewSettings;
|
|
if (PendingSettings.GetParams(&NewSettings))
|
|
{
|
|
bParamsChanged = true;
|
|
|
|
// Make sure we clamp our freq and bandwidth to reasonable values here
|
|
for (FSubmixEffectEQBand& Band : NewSettings.EQBands)
|
|
{
|
|
Band.Frequency = GetClampedFrequency(Band.Frequency);
|
|
Band.Bandwidth = GetClampedBandwidth(Band.Bandwidth);
|
|
}
|
|
|
|
RenderThreadEQSettings = NewSettings;
|
|
}
|
|
|
|
if (bParamsChanged || !bEQSettingsSet)
|
|
{
|
|
bEQSettingsSet = true;
|
|
|
|
const int32 NumBandsInSetting = RenderThreadEQSettings.EQBands.Num();
|
|
const int32 CurrentFilterCount = FiltersPerChannel.Num();
|
|
|
|
// Now loop through all the bands and set them up on all the filters.
|
|
for (int32 FilterIndex = 0; FilterIndex < CurrentFilterCount; ++FilterIndex)
|
|
{
|
|
FEQ& EqFilter = FiltersPerChannel[FilterIndex];
|
|
EqFilter.bEnabled = true;
|
|
|
|
// Create more bands as needed
|
|
const int32 NumCurrentBands = EqFilter.Bands.Num();
|
|
if (NumCurrentBands < NumBandsInSetting)
|
|
{
|
|
// Create and initialize the biquad filters per band
|
|
for (int32 BandIndex = NumCurrentBands; BandIndex < NumBandsInSetting; ++BandIndex)
|
|
{
|
|
// Create new filter instance
|
|
int32 BiquadIndex = EqFilter.Bands.Add(Audio::FBiquadFilter());
|
|
|
|
// Initialize it
|
|
EqFilter.Bands[BiquadIndex].Init(SampleRate, 2, Audio::EBiquadFilter::ParametricEQ);
|
|
}
|
|
}
|
|
// Disable bands as needed
|
|
else if (NumCurrentBands > NumBandsInSetting)
|
|
{
|
|
// Disable all filters that are greater than the number of bands in the new setting
|
|
for (int32 BandIndex = NumBandsInSetting; BandIndex < NumCurrentBands; ++BandIndex)
|
|
{
|
|
EqFilter.Bands[BandIndex].SetEnabled(false);
|
|
}
|
|
}
|
|
|
|
// Now copy the settings over to the specific EQ filter
|
|
check(NumBandsInSetting <= EqFilter.Bands.Num());
|
|
for (int32 BandIndex = 0; BandIndex < NumBandsInSetting; ++BandIndex)
|
|
{
|
|
const FSubmixEffectEQBand& EQBandSetting = RenderThreadEQSettings.EQBands[BandIndex];
|
|
EqFilter.Bands[BandIndex].SetEnabled(EQBandSetting.bEnabled);
|
|
EqFilter.Bands[BandIndex].SetParams(Audio::EBiquadFilter::ParametricEQ, EQBandSetting.Frequency, EQBandSetting.Bandwidth, EQBandSetting.GainDb);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void USubmixEffectSubmixEQPreset::SetSettings(const FSubmixEffectSubmixEQSettings& InSettings)
|
|
{
|
|
UpdateSettings(InSettings);
|
|
}
|
|
|