Files
UnrealEngineUWP/Engine/Source/Runtime/SignalProcessing/Private/ReverbFast.cpp
maxwell hayes b22545950d Porting parameter smoothing for reverb from 4.23-Release
#rb Phillip.Popp
[FYI] Randolph.Kestner, Ryan.Mangin


#ROBOMERGE-SOURCE: CL 8346262 via CL 8346350 via CL 8358291
#ROBOMERGE-BOT: (v401-8057353)

[CL 8360062 by maxwell hayes in Main branch]
2019-08-27 21:54:21 -04:00

334 lines
12 KiB
C++

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "DSP/ReverbFast.h"
#include "DSP/BufferVectorOperations.h"
#include "AudioMixer.h"
namespace Audio {
FPlateReverbFastSettings::FPlateReverbFastSettings()
: Wetness(0.5f)
, QuadBehavior(EQuadBehavior::StereoOnly)
{}
bool FPlateReverbFastSettings::operator==(const FPlateReverbFastSettings& Other) const
{
bool bIsEqual = (
(Other.EarlyReflections == EarlyReflections) &&
(Other.LateReflections == LateReflections) &&
(Other.Wetness == Wetness) &&
(Other.QuadBehavior == QuadBehavior));
return bIsEqual;
}
bool FPlateReverbFastSettings::operator!=(const FPlateReverbFastSettings& Other) const
{
return !(*this == Other);
}
const float FPlateReverbFast::MaxWetness = 10.f;
const float FPlateReverbFast::MinWetness = 0.0f;
const FPlateReverbFastSettings FPlateReverbFast::DefaultSettings;
FPlateReverbFast::FPlateReverbFast(float InSampleRate, int32 InMaxInternalBufferSamples, const FPlateReverbFastSettings& InSettings)
: SampleRate(InSampleRate)
, LastWetness(0.0f)
, bProcessCallSinceWetnessChanged(false)
, EarlyReflections(InSampleRate, InMaxInternalBufferSamples)
, LateReflections(InSampleRate, InMaxInternalBufferSamples, InSettings.LateReflections)
, bEnableEarlyReflections(true)
, bEnableLateReflections(true)
{
SetSettings(InSettings);
}
FPlateReverbFast::~FPlateReverbFast()
{}
void FPlateReverbFast::SetSettings(const FPlateReverbFastSettings& InSettings)
{
// Copy, clamp and apply settings
if (bProcessCallSinceWetnessChanged)
{
LastWetness = Settings.Wetness;
bProcessCallSinceWetnessChanged = false;
}
Settings = InSettings;
ClampSettings(Settings);
ApplySettings();
}
const FPlateReverbFastSettings& FPlateReverbFast::GetSettings() const
{
return Settings;
}
// Whether or not to enable late reflections
void FPlateReverbFast::EnableLateReflections(const bool bInEnableLateReflections)
{
bEnableLateReflections = bInEnableLateReflections;
}
// Whether or not to enable late reflections
void FPlateReverbFast::EnableEarlyReflections(const bool bInEnableEarlyReflections)
{
bEnableEarlyReflections = bInEnableEarlyReflections;
}
// Process a buffer of input audio samples.
void FPlateReverbFast::ProcessAudio(const AlignedFloatBuffer& InSamples, const int32 InNumChannels, AlignedFloatBuffer& 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));
// Scale input by wetness (or fade to new wetness)
if (FMath::IsNearlyEqual(LastWetness, Settings.Wetness))
{
MultiplyBufferByConstantInPlace(ScaledInputBuffer.GetData(), InSamples.Num(), Settings.Wetness);
}
else
{
FadeBufferFast(ScaledInputBuffer, LastWetness, Settings.Wetness);
LastWetness = Settings.Wetness;
}
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 (!bEnableEarlyReflections && !bEnableLateReflections)
{
// Zero output buffers if all reverb is disabled.
const int32 OutNum = InNumFrames * OutNumChannels;
OutSamples.Reset(OutNum);
OutSamples.AddUninitialized(OutNum);
FMemory::Memset(OutSamples.GetData(), 0, sizeof(float) * OutNum);
return;
}
// Resize internal buffers
FrontLeftReverbSamples.Reset(InNumFrames);
FrontRightReverbSamples.Reset(InNumFrames);
FrontLeftReverbSamples.AddUninitialized(InNumFrames);
FrontRightReverbSamples.AddUninitialized(InNumFrames);
if (bEnableEarlyReflections && !bEnableLateReflections)
{
// Only generate early reflections.
EarlyReflections.ProcessAudio(ScaledInputBuffer, InNumChannels, FrontLeftReverbSamples, FrontRightReverbSamples);
}
else if (!bEnableEarlyReflections && bEnableLateReflections)
{
// Only generate late reflections.
LateReflections.ProcessAudio(ScaledInputBuffer, InNumChannels, FrontLeftReverbSamples, FrontRightReverbSamples);
}
else if (bEnableEarlyReflections && 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.
SumBuffers(FrontLeftEarlyReflectionsSamples, FrontLeftLateReflectionsSamples, FrontLeftReverbSamples);
SumBuffers(FrontRightEarlyReflectionsSamples, FrontRightLateReflectionsSamples, FrontRightReverbSamples);
}
// Interleave and upmix
InterleaveAndMixOutput(FrontLeftReverbSamples, FrontRightReverbSamples, OutSamples, OutNumChannels);
bProcessCallSinceWetnessChanged = true;
}
void FPlateReverbFast::ClampSettings(FPlateReverbFastSettings& InOutSettings)
{
// Clamp settings for this object and member objects.
InOutSettings.Wetness = FMath::Clamp(InOutSettings.Wetness, MinWetness, MaxWetness);
FLateReflectionsFast::ClampSettings(InOutSettings.LateReflections);
FEarlyReflectionsFast::ClampSettings(InOutSettings.EarlyReflections);
}
// Copy input samples to output samples. Remap channels if necessary.
void FPlateReverbFast::PassThroughAudio(const AlignedFloatBuffer& InSamples, const int32 InNumChannels, AlignedFloatBuffer& OutSamples, const int32 OutNumChannels)
{
const int32 InNum = InSamples.Num();
const int32 InNumFrames = InNum / InNumChannels;
const int32 OutNum = OutNumChannels * InNumFrames;
// Resize output buffer
OutSamples.Reset(OutNum);
OutSamples.AddUninitialized(OutNum);
FMemory::Memset(OutSamples.GetData(), 0, sizeof(float) * OutNum);
if (InNum > 0)
{
if (InNumChannels == OutNumChannels)
{
FMemory::Memcpy(OutSamples.GetData(), InSamples.GetData(), sizeof(float) * InNum);
}
else
{
// InNumChannels can only be 1 or 2 channels so we have a limited number of
// upmix situations.
FMemory::Memset(OutSamples.GetData(), 0, sizeof(float) * OutNum);
if (1 == InNumChannels)
{
// Upmix a mono signal
float* OutSampleData = OutSamples.GetData();
const float* InSampleData = InSamples.GetData();
int32 OutPos = 0;
for (int32 i = 0; i < InNumFrames; i++, OutPos += OutNumChannels)
{
// Scale to keep loudnes consistent.
float value = InSampleData[i] * 0.5f;
OutSampleData[OutPos] = value;
OutSampleData[OutPos + 1] = value;
}
}
else if (2 == InNumChannels)
{
// Upmix a stereo signal by copying:
// FrontLeft -> FrontLeft
// FrontRight -> FrontRight
float* OutSampleData = OutSamples.GetData();
const float* InSampleData = InSamples.GetData();
int32 OutPos = 0;
for (int32 i = 0; i < InNum; i += InNumChannels, OutPos += OutNumChannels)
{
OutSampleData[OutPos] = InSampleData[i];
OutSampleData[OutPos + 1] = InSampleData[i + 1];
}
}
}
}
}
// 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 AlignedFloatBuffer& InFrontLeftSamples, const AlignedFloatBuffer& InFrontRightSamples, AlignedFloatBuffer& 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.
BufferMultiplyByConstant(FrontLeftReverbSamples, 0.5f, LeftAttenuatedSamples);
BufferMultiplyByConstant(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;
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);
}
}