251 lines
9.3 KiB
C++
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);
|
|
}
|
|
}
|