Files
UnrealEngine/Engine/Source/Runtime/AudioMixer/Private/Effects/AudioMixerSubmixEffectReverb.cpp
2025-05-18 13:04:45 +08:00

304 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SubmixEffects/AudioMixerSubmixEffectReverb.h"
#include "AudioMixerEffectsManager.h"
#include "HAL/IConsoleManager.h"
#include "Sound/ReverbEffect.h"
#include "Audio.h"
#include "AudioMixer.h"
#include "DSP/BufferVectorOperations.h"
#include "DSP/FloatArrayMath.h"
#include "DSP/ReverbFast.h"
#include "DSP/Amp.h"
#include "ProfilingDebugging/CsvProfiler.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AudioMixerSubmixEffectReverb)
// Link to "Audio" profiling category
CSV_DECLARE_CATEGORY_MODULE_EXTERN(AUDIOMIXERCORE_API, Audio);
DEFINE_STAT(STAT_AudioMixerSubmixReverb);
static int32 DisableSubmixReverbCVarFast = 0;
static FAutoConsoleVariableRef CVarDisableSubmixReverb(
TEXT("au.DisableReverbSubmix"),
DisableSubmixReverbCVarFast,
TEXT("Disables the reverb submix.\n")
TEXT("0: Not Disabled, 1: Disabled"),
ECVF_Default);
static int32 EnableReverbStereoFlipForQuadCVarFast = 0;
static FAutoConsoleVariableRef CVarReverbStereoFlipForQuadFast(
TEXT("au.EnableReverbStereoFlipForQuad"),
EnableReverbStereoFlipForQuadCVarFast,
TEXT("Enables doing a stereo flip for quad reverb when in surround.\n")
TEXT("0: Not Enabled, 1: Enabled"),
ECVF_Default);
static int32 DisableQuadReverbCVarFast = 0;
static FAutoConsoleVariableRef CVarDisableQuadReverbCVarFast(
TEXT("au.DisableQuadReverb"),
DisableQuadReverbCVarFast,
TEXT("Disables quad reverb in surround.\n")
TEXT("0: Not Disabled, 1: Disabled"),
ECVF_Default);
const float FSubmixEffectReverb::MinWetness = 0.0f;
const float FSubmixEffectReverb::MaxWetness = 10.f;
FSubmixEffectReverb::FSubmixEffectReverb()
: CurrentWetDry(-1.0f, -1.0f)
{
}
void FSubmixEffectReverb::Init(const FSoundEffectSubmixInitData& InitData)
{
LLM_SCOPE(ELLMTag::AudioMixer);
/* `FPlateReverb` produces slightly different quality effect than `FPlateReverb`. Comparing the Init
* settings between FSubmixEffectReverb and FSubmixEffectReverb slight differences will arise.
*
* The delay line implementations significantly differ between the `FPlateReverb` and `FPlateReverb` classes.
* Specifically, the `FPlateReverb` class utilizes linearly interpolated fractional delay line and fractional
* delays while the `FPlateReverb` class uses integer delay lines and integer delays whenever possible.
* Linearly interpolated fractional delay lines introduce a low pass filter dependent upon the fractional portion
* of the delay value. As a result, the `FPlateReverb` class produces a darker reverb.
*/
Audio::FPlateReverbFastSettings NewSettings;
SampleRate = InitData.SampleRate;
NewSettings.EarlyReflections.Decay = 0.9f;
NewSettings.EarlyReflections.Absorption = 0.7f;
NewSettings.EarlyReflections.Gain = 1.0f;
NewSettings.EarlyReflections.PreDelayMsec = 0.0f;
NewSettings.EarlyReflections.Bandwidth = 0.8f;
NewSettings.LateReflections.LateDelayMsec = 0.0f;
NewSettings.LateReflections.LateGainDB = 0.0f;
NewSettings.LateReflections.Bandwidth = 0.54f;
NewSettings.LateReflections.Diffusion = 0.60f;
NewSettings.LateReflections.Dampening = 0.35f;
NewSettings.LateReflections.Decay = 0.15f;
NewSettings.LateReflections.Density = 0.85f;
ReverbParams.SetParams(NewSettings);
DecayCurve.AddKey(0.0f, 0.99f);
DecayCurve.AddKey(2.0f, 0.45f);
DecayCurve.AddKey(5.0f, 0.15f);
DecayCurve.AddKey(10.0f, 0.1f);
DecayCurve.AddKey(18.0f, 0.01f);
DecayCurve.AddKey(19.0f, 0.002f);
DecayCurve.AddKey(20.0f, 0.0001f);
if (DisableSubmixReverbCVarFast == 0)
{
PlateReverb = MakeUnique<Audio::FPlateReverbFast>(SampleRate, 512, NewSettings);
}
}
void FSubmixEffectReverb::OnPresetChanged()
{
LLM_SCOPE(ELLMTag::AudioMixer);
GET_EFFECT_SETTINGS(SubmixEffectReverb);
FAudioReverbEffect ReverbEffect;
ReverbEffect.bBypassEarlyReflections = Settings.bBypassEarlyReflections;
ReverbEffect.bBypassLateReflections = Settings.bBypassLateReflections;
ReverbEffect.Density = Settings.Density;
ReverbEffect.Diffusion = Settings.Diffusion;
ReverbEffect.Gain = Settings.Gain;
ReverbEffect.GainHF = Settings.GainHF;
ReverbEffect.DecayTime = Settings.DecayTime;
ReverbEffect.DecayHFRatio = Settings.DecayHFRatio;
ReverbEffect.ReflectionsGain = Settings.ReflectionsGain;
ReverbEffect.ReflectionsDelay = Settings.ReflectionsDelay;
ReverbEffect.LateGain = Settings.LateGain;
ReverbEffect.LateDelay = Settings.LateDelay;
ReverbEffect.AirAbsorptionGainHF = Settings.AirAbsorptionGainHF;
ReverbEffect.Volume = Settings.bBypass ? 0.0f : FMath::Clamp(Settings.WetLevel, MinWetness, MaxWetness);
SetParameters(ReverbEffect);
// These wet dry parameters need to be set after the call to SetParameters(ReverbEffect) parameter.
// SetParameters sets the WetDryParams, but they need to be overriden here.
Audio::FWetDry NewWetDryParams;
NewWetDryParams.DryLevel = Settings.bBypass ? 1.0f : Settings.DryLevel;
NewWetDryParams.WetLevel = Settings.bBypass ? 0.0f : FMath::Clamp(Settings.WetLevel, MinWetness, MaxWetness);
WetDryParams.SetParams(NewWetDryParams);
}
void FSubmixEffectReverb::OnProcessAudio(const FSoundEffectSubmixInputData& InData, FSoundEffectSubmixOutputData& OutData)
{
LLM_SCOPE(ELLMTag::AudioMixer);
check(InData.NumChannels == 2);
if (OutData.NumChannels < 2 || DisableSubmixReverbCVarFast == 1)
{
// Not supported
return;
}
if (!PlateReverb.IsValid())
{
Audio::FPlateReverbFastSettings NewSettings;
ReverbParams.CopyParams(NewSettings);
PlateReverb = MakeUnique<Audio::FPlateReverbFast>(SampleRate, 512, NewSettings);
}
CSV_SCOPED_TIMING_STAT(Audio, SubmixReverb);
SCOPE_CYCLE_COUNTER(STAT_AudioMixerSubmixReverb);
UpdateParameters();
float LastWet = CurrentWetDry.WetLevel;
float LastDry = CurrentWetDry.DryLevel;
WetDryParams.GetParams(&CurrentWetDry);
// Set to most recent if uninitialized (less than 0.0f)
if (LastWet < 0.0f)
{
LastWet = CurrentWetDry.WetLevel;
}
WetInputBuffer.Reset();
if (InData.AudioBuffer->Num() > 0)
{
// Wet level is applied to input audio to preserve reverb tail when changing wet level
WetInputBuffer.AddZeroed(InData.AudioBuffer->Num());
Audio::ArrayMixIn(*InData.AudioBuffer, WetInputBuffer, LastWet, CurrentWetDry.WetLevel);
}
PlateReverb->ProcessAudio(WetInputBuffer, InData.NumChannels, *OutData.AudioBuffer, OutData.NumChannels);
}
bool FSubmixEffectReverb::SetParameters(const FAudioEffectParameters& InParams)
{
LLM_SCOPE(ELLMTag::AudioMixer);
const FAudioReverbEffect& ReverbEffectParams = static_cast<const FAudioReverbEffect&>(InParams);
/* `FPlateReverb` produces slightly different quality effect than `FPlateReverb`. Comparing the
* settings between FSubmixEffectReverb and FSubmixEffectReverb slight differences will arise.
*
* The delay line implementations significantly differ between the `FPlateReverb` and `FPlateReverb` classes.
* Specifically, the `FPlateReverb` class utilizes linearly interpolated fractional delay line and fractional
* delays while the `FPlateReverb` class uses integer delay lines and integer delays whenever possible.
* Linearly interpolated fractional delay lines introduce a low pass filter dependent upon the fractional portion
* of the delay value. As a result, the `FPlateReverb` class produces a darker reverb.
*/
Audio::FPlateReverbFastSettings NewSettings;
NewSettings.bEnableEarlyReflections = !ReverbEffectParams.bBypassEarlyReflections;
NewSettings.bEnableLateReflections = !ReverbEffectParams.bBypassLateReflections;
// Early Reflections
NewSettings.EarlyReflections.Gain = FMath::GetMappedRangeValueClamped(FVector2f{ 0.0f, 3.16f }, FVector2f{ 0.0f, 1.0f }, ReverbEffectParams.ReflectionsGain);
NewSettings.EarlyReflections.PreDelayMsec = FMath::GetMappedRangeValueClamped(FVector2f{ 0.0f, 0.3f }, FVector2f{ 0.0f, 300.0f }, ReverbEffectParams.ReflectionsDelay);
NewSettings.EarlyReflections.Bandwidth = FMath::GetMappedRangeValueClamped(FVector2f{ 0.0f, 1.0f }, FVector2f{ 0.0f, 1.0f }, 1.0f - ReverbEffectParams.GainHF);
// LateReflections
NewSettings.LateReflections.LateDelayMsec = FMath::GetMappedRangeValueClamped(FVector2f{ 0.0f, 0.1f }, FVector2f{ 0.0f, 100.0f }, ReverbEffectParams.LateDelay);
NewSettings.LateReflections.LateGainDB = FMath::GetMappedRangeValueClamped(FVector2f{ 0.0f, 1.0f }, FVector2f{ 0.0f, 1.0f }, ReverbEffectParams.Gain);
NewSettings.LateReflections.Bandwidth = FMath::GetMappedRangeValueClamped(FVector2f{ 0.0f, 1.0f }, FVector2f{ 0.1f, 0.6f }, ReverbEffectParams.AirAbsorptionGainHF);
NewSettings.LateReflections.Diffusion = FMath::GetMappedRangeValueClamped(FVector2f{ 0.05f, 1.0f }, FVector2f{ 0.0f, 0.95f }, ReverbEffectParams.Diffusion);
NewSettings.LateReflections.Dampening = FMath::GetMappedRangeValueClamped(FVector2f{ 0.05f, 1.95f }, FVector2f{ 0.0f, 0.999f }, ReverbEffectParams.DecayHFRatio);
NewSettings.LateReflections.Density = FMath::GetMappedRangeValueClamped(FVector2f{ 0.0f, 0.95f }, FVector2f{ 0.06f, 1.0f }, ReverbEffectParams.Density);
// Use mapping function to get decay time in seconds to internal linear decay scale value
const float DecayValue = DecayCurve.Eval(ReverbEffectParams.DecayTime);
NewSettings.LateReflections.Decay = DecayValue;
// Convert to db
NewSettings.LateReflections.LateGainDB = Audio::ConvertToDecibels(NewSettings.LateReflections.LateGainDB);
// Apply the settings the thread safe settings object
ReverbParams.SetParams(NewSettings);
// Apply wet/dry level
// When using a FAudioReverbEffect, the volume parameter controls wetness and the dry level remains 0.
Audio::FWetDry NewWetDry(ReverbEffectParams.Volume, 0.f);
WetDryParams.SetParams(NewWetDry);
return true;
}
void FSubmixEffectReverb::UpdateParameters()
{
Audio::FPlateReverbFastSettings NewSettings;
if (PlateReverb.IsValid() && ReverbParams.GetParams(&NewSettings))
{
PlateReverb->SetSettings(NewSettings);
}
// Check cVars for quad mapping
Audio::FPlateReverbFastSettings::EQuadBehavior TargetQuadBehavior;
if (DisableQuadReverbCVarFast)
{
// Disable quad mapping.
TargetQuadBehavior = Audio::FPlateReverbFastSettings::EQuadBehavior::StereoOnly;
}
else if (!DisableQuadReverbCVarFast && EnableReverbStereoFlipForQuadCVarFast)
{
// Enable quad flipped mapping
TargetQuadBehavior = Audio::FPlateReverbFastSettings::EQuadBehavior::QuadFlipped;
}
else
{
// Enable quad mapping
TargetQuadBehavior = Audio::FPlateReverbFastSettings::EQuadBehavior::QuadMatched;
}
if (!PlateReverb.IsValid())
{
return;
}
// Check if settings need to be updated
const Audio::FPlateReverbFastSettings& ReverbSettings = PlateReverb->GetSettings();
if (ReverbSettings.QuadBehavior != TargetQuadBehavior)
{
// Update quad settings
NewSettings = ReverbSettings;
NewSettings.QuadBehavior = TargetQuadBehavior;
PlateReverb->SetSettings(NewSettings);
}
}
void USubmixEffectReverbPreset::SetSettingsWithReverbEffect(const UReverbEffect* InReverbEffect, const float InWetLevel, const float InDryLevel)
{
if (InReverbEffect)
{
Settings.bBypassEarlyReflections = InReverbEffect->bBypassEarlyReflections;
Settings.bBypassLateReflections = InReverbEffect->bBypassLateReflections;
Settings.Density = InReverbEffect->Density;
Settings.Diffusion = InReverbEffect->Diffusion;
Settings.Gain = InReverbEffect->Gain;
Settings.GainHF = InReverbEffect->GainHF;
Settings.DecayTime = InReverbEffect->DecayTime;
Settings.DecayHFRatio = InReverbEffect->DecayHFRatio;
Settings.ReflectionsGain = InReverbEffect->ReflectionsGain;
Settings.ReflectionsDelay = InReverbEffect->ReflectionsDelay;
Settings.LateGain = InReverbEffect->LateGain;
Settings.LateDelay = InReverbEffect->LateDelay;
Settings.AirAbsorptionGainHF = InReverbEffect->AirAbsorptionGainHF;
Settings.WetLevel = InWetLevel;
Settings.DryLevel = InDryLevel;
Update();
}
}
void USubmixEffectReverbPreset::SetSettings(const FSubmixEffectReverbSettings& InSettings)
{
UpdateSettings(InSettings);
}