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

251 lines
9.3 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DSP/ReverbFast.h"
#include "DSP/FloatArrayMath.h"
#include "AudioMixer.h"
namespace Audio {
FPlateReverbFastSettings::FPlateReverbFastSettings()
: bEnableEarlyReflections(true)
, bEnableLateReflections(true)
, QuadBehavior(EQuadBehavior::StereoOnly)
{}
bool FPlateReverbFastSettings::operator==(const FPlateReverbFastSettings& Other) const
{
bool bIsEqual = (
(Other.EarlyReflections == EarlyReflections) &&
(Other.LateReflections == LateReflections) &&
(Other.bEnableEarlyReflections == bEnableEarlyReflections) &&
(Other.bEnableLateReflections == bEnableLateReflections) &&
(Other.QuadBehavior == QuadBehavior));
return bIsEqual;
}
bool FPlateReverbFastSettings::operator!=(const FPlateReverbFastSettings& Other) const
{
return !(*this == Other);
}
const FPlateReverbFastSettings FPlateReverbFast::DefaultSettings;
FPlateReverbFast::FPlateReverbFast(float InSampleRate, int32 InMaxInternalBufferSamples, const FPlateReverbFastSettings& InSettings)
: SampleRate(InSampleRate)
, EarlyReflections(InSampleRate, InMaxInternalBufferSamples)
, LateReflections(InSampleRate, InMaxInternalBufferSamples, InSettings.LateReflections)
{
SetSettings(InSettings);
}
FPlateReverbFast::~FPlateReverbFast()
{}
void FPlateReverbFast::SetSettings(const FPlateReverbFastSettings& InSettings)
{
// If the early reflections are getting disabled, need to flush the audio.
// So that the audio history is empty when they are re-enabled.
if (Settings.bEnableEarlyReflections && !InSettings.bEnableEarlyReflections)
{
EarlyReflections.FlushAudio();
}
// If the late reflections are getting disabled, need to flush the audio.
// So that the audio history is empty when they are re-enabled.
if (Settings.bEnableLateReflections && !InSettings.bEnableLateReflections)
{
LateReflections.FlushAudio();
}
Settings = InSettings;
ApplySettings();
}
const FPlateReverbFastSettings& FPlateReverbFast::GetSettings() const
{
return Settings;
}
// Process a buffer of input audio samples.
void FPlateReverbFast::ProcessAudio(const FAlignedFloatBuffer& InSamples, const int32 InNumChannels, FAlignedFloatBuffer& OutSamples, const int32 OutNumChannels)
{
if(InSamples.Num() == 0)
{
OutSamples.Reset(0);
return;
}
ScaledInputBuffer.Reset(InSamples.Num());
ScaledInputBuffer.AddUninitialized(InSamples.Num());
check(ScaledInputBuffer.Num() == InSamples.Num());
FMemory::Memcpy(ScaledInputBuffer.GetData(), InSamples.GetData(), InSamples.Num() * sizeof(float));
checkf((1 == InNumChannels) || (2 == InNumChannels), TEXT("FPlateReverbFast only supports 1 or 2 channel inputs."))
checkf(OutNumChannels >= 2, TEXT("FPlateReverbFast requires at least 2 output channels."))
// Determine number of frames and output size.
const int32 InNum = InSamples.Num();
const int32 InNumFrames = InNum / InNumChannels;
if (0 == InNum)
{
// If not input samples, then reset output samples and return
OutSamples.Reset(0);
return;
}
if (!Settings.bEnableEarlyReflections && !Settings.bEnableLateReflections)
{
// Zero output buffers if all reverb is disabled.
const int32 OutNum = InNumFrames * OutNumChannels;
OutSamples.Reset(OutNum);
OutSamples.AddZeroed(OutNum);
return;
}
// Resize internal buffers
FrontLeftReverbSamples.Reset(InNumFrames);
FrontRightReverbSamples.Reset(InNumFrames);
FrontLeftReverbSamples.AddUninitialized(InNumFrames);
FrontRightReverbSamples.AddUninitialized(InNumFrames);
if (Settings.bEnableEarlyReflections && !Settings.bEnableLateReflections)
{
// Only generate early reflections.
EarlyReflections.ProcessAudio(ScaledInputBuffer, InNumChannels, FrontLeftReverbSamples, FrontRightReverbSamples);
}
else if (!Settings.bEnableEarlyReflections && Settings.bEnableLateReflections)
{
// Only generate late reflections.
LateReflections.ProcessAudio(ScaledInputBuffer, InNumChannels, FrontLeftReverbSamples, FrontRightReverbSamples);
}
else if (Settings.bEnableEarlyReflections && Settings.bEnableLateReflections)
{
// Resize internal buffers
FrontLeftLateReflectionsSamples.Reset(InNumFrames);
FrontRightLateReflectionsSamples.Reset(InNumFrames);
FrontLeftEarlyReflectionsSamples.Reset(InNumFrames);
FrontRightEarlyReflectionsSamples.Reset(InNumFrames);
FrontLeftLateReflectionsSamples.AddUninitialized(InNumFrames);
FrontRightLateReflectionsSamples.AddUninitialized(InNumFrames);
FrontLeftEarlyReflectionsSamples.AddUninitialized(InNumFrames);
FrontRightEarlyReflectionsSamples.AddUninitialized(InNumFrames);
// Generate both early reflections and late reflections
EarlyReflections.ProcessAudio(ScaledInputBuffer, InNumChannels, FrontLeftEarlyReflectionsSamples, FrontRightEarlyReflectionsSamples);
LateReflections.ProcessAudio(ScaledInputBuffer, InNumChannels, FrontLeftLateReflectionsSamples, FrontRightLateReflectionsSamples);
// Add early and late reflections together.
ArraySum(FrontLeftEarlyReflectionsSamples, FrontLeftLateReflectionsSamples, FrontLeftReverbSamples);
ArraySum(FrontRightEarlyReflectionsSamples, FrontRightLateReflectionsSamples, FrontRightReverbSamples);
}
// Interleave and upmix
InterleaveAndMixOutput(FrontLeftReverbSamples, FrontRightReverbSamples, OutSamples, OutNumChannels);
}
void FPlateReverbFast::ClampSettings(FPlateReverbFastSettings& InOutSettings)
{
// Clamp settings for this object and member objects.
FLateReflectionsFast::ClampSettings(InOutSettings.LateReflections);
FEarlyReflectionsFast::ClampSettings(InOutSettings.EarlyReflections);
}
// Copy reverberated samples to interleaved output samples. Map channels according to internal settings.
// InFrontLeftSamples and InFrontRightSamples may be modified in-place.
void FPlateReverbFast::InterleaveAndMixOutput(const FAlignedFloatBuffer& InFrontLeftSamples, const FAlignedFloatBuffer& InFrontRightSamples, FAlignedFloatBuffer& OutSamples, const int32 OutNumChannels)
{
check(InFrontLeftSamples.Num() == InFrontRightSamples.Num())
const int32 InNumFrames = InFrontLeftSamples.Num();
const int32 OutNum = OutNumChannels * InNumFrames;
// Resize output buffer
OutSamples.Reset(OutNum);
OutSamples.AddUninitialized(OutNum);
FMemory::Memset(OutSamples.GetData(), 0, sizeof(float) * OutNum);
// Interleave / mix reverb audio into output buffer
if (2 == OutNumChannels)
{
// Stereo interleaved output
BufferInterleave2ChannelFast(FrontLeftReverbSamples, FrontRightReverbSamples, OutSamples);
}
else
{
if ((OutNumChannels < 5) || (FPlateReverbFastSettings::EQuadBehavior::StereoOnly == Settings.QuadBehavior))
{
// We do not handle any quad reverb mapping when OutNumChannels is less than 5
float* LeftSampleData = FrontLeftReverbSamples.GetData();
float* RightSampleData = FrontRightReverbSamples.GetData();
float* OutSampleData = OutSamples.GetData();
int32 OutPos = 0;
for (int32 i = 0; i < InNumFrames; i++, OutPos += OutNumChannels)
{
OutSampleData[OutPos + EAudioMixerChannel::FrontLeft] = LeftSampleData[i];
OutSampleData[OutPos + EAudioMixerChannel::FrontRight] = RightSampleData[i];
}
}
else
{
// There are 5 or more output channels and quad mapping is enabled.
LeftAttenuatedSamples.Reset(InNumFrames);
RightAttenuatedSamples.Reset(InNumFrames);
LeftAttenuatedSamples.AddUninitialized(InNumFrames);
RightAttenuatedSamples.AddUninitialized(InNumFrames);
// Reduce volume of output reverbs.
ArrayMultiplyByConstant(FrontLeftReverbSamples, 0.5f, LeftAttenuatedSamples);
ArrayMultiplyByConstant(FrontRightReverbSamples, 0.5f, RightAttenuatedSamples);
const float* FrontLeftSampleData = LeftAttenuatedSamples.GetData();
const float* FrontRightSampleData = RightAttenuatedSamples.GetData();
// WARNING: this pointer will alias other pointers in this scope. Be conscious of RESTRICT keyword in any called functions.
const float* BackLeftSampleData = nullptr;
// WARNING: this pointer will alias other pointers in this scope. Be conscious of RESTRICT keyword in any called functions.
const float* BackRightSampleData = nullptr;
// Map quads by asigning pointers.
switch (Settings.QuadBehavior)
{
case FPlateReverbFastSettings::EQuadBehavior::QuadFlipped:
// Left and right are flipped.
BackLeftSampleData = FrontRightSampleData;
BackRightSampleData = FrontLeftSampleData;
break;
case FPlateReverbFastSettings::EQuadBehavior::QuadMatched:
default:
// Left and right are matched.
BackLeftSampleData = FrontLeftSampleData;
BackRightSampleData = FrontRightSampleData;
}
// Interleave to output
float* OutSampleData = OutSamples.GetData();
int32 OutPos = 0;
for (int32 i = 0; i < InNumFrames; i++, OutPos += OutNumChannels)
{
OutSampleData[OutPos + EAudioMixerChannel::FrontLeft] = FrontLeftSampleData[i];
OutSampleData[OutPos + EAudioMixerChannel::FrontRight] = FrontRightSampleData[i];
OutSampleData[OutPos + EAudioMixerChannel::BackLeft] = BackLeftSampleData[i];
OutSampleData[OutPos + EAudioMixerChannel::BackRight] = BackRightSampleData[i];
}
}
}
}
void FPlateReverbFast::ApplySettings()
{
EarlyReflections.SetSettings(Settings.EarlyReflections);
LateReflections.SetSettings(Settings.LateReflections);
}
}