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

585 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DSP/LateReflectionsFast.h"
#include "DSP/BufferOnePoleLPF.h"
#include "DSP/FloatArrayMath.h"
namespace Audio
{
// The presets for the late refelections reverb were based on a strange sample rate. This
// function helps translate delay values in the paper to delay values for this reverb.
// https://ccrma.stanford.edu/~dattorro/EffectDesignPart1.pdf
static int32 LateReflectionsGetDelaySamples(float InSampleRate, float InPresetValue)
{
return (int32)((float)InSampleRate / 29761.f * InPresetValue);
}
FLateReflectionsFastSettings::FLateReflectionsFastSettings()
: LateDelayMsec(0.0f)
, LateGainDB(0.0f)
, Bandwidth(0.5f)
, Diffusion(0.5f)
, Dampening(0.5f)
, Decay(0.5f)
, Density(0.5f)
{}
bool FLateReflectionsFastSettings::operator==(const FLateReflectionsFastSettings& Other) const
{
bool bIsEqual = (
(Other.LateDelayMsec == LateDelayMsec) &&
(Other.LateGainDB == LateGainDB) &&
(Other.Bandwidth == Bandwidth) &&
(Other.Diffusion == Diffusion) &&
(Other.Dampening == Dampening) &&
(Other.Decay == Decay) &&
(Other.Density == Density));
return bIsEqual;
}
bool FLateReflectionsFastSettings::operator!=(const FLateReflectionsFastSettings& Other) const
{
return !(*this == Other);
}
void FLateReflectionsPlateOutputs::ResizeAndZero(int32 InNumSamples)
{
for (int32 i = 0; i < NumTaps; i++)
{
Taps[i].Reset(InNumSamples);
Taps[i].AddUninitialized(InNumSamples);
FMemory::Memset(Taps[i].GetData(), 0, sizeof(float) * InNumSamples);
}
Output.Reset(InNumSamples);
Output.AddUninitialized(InNumSamples);
FMemory::Memset(Output.GetData(), 0, sizeof(float) * InNumSamples);
}
FLateReflectionsPlateDelays FLateReflectionsPlateDelays::DefaultLeftDelays(float InSampleRate)
{
FLateReflectionsPlateDelays Delays;
Delays.NumSamplesModulatedBase = LateReflectionsGetDelaySamples(InSampleRate, 908);
Delays.NumSamplesModulatedDelta = LateReflectionsGetDelaySamples(InSampleRate, 16);
// Left Delay 1 should add up to 4217 and have tap outs at 353 (R0), 1190 (L4), and 3627 (R1)
Delays.NumSamplesDelayA = LateReflectionsGetDelaySamples(InSampleRate, 353);
Delays.NumSamplesDelayB = LateReflectionsGetDelaySamples(InSampleRate, 837);
Delays.NumSamplesDelayC = LateReflectionsGetDelaySamples(InSampleRate, 2437);
Delays.NumSamplesDelayD = LateReflectionsGetDelaySamples(InSampleRate, 590);
Delays.NumSamplesAPF = LateReflectionsGetDelaySamples(InSampleRate, 2656);
// APF Delay should have tapouts at 187 (L5) and 1228 (R2)
Delays.NumSamplesDelayE = LateReflectionsGetDelaySamples(InSampleRate, 187);
Delays.NumSamplesDelayF = LateReflectionsGetDelaySamples(InSampleRate, 1041);
// Left Delay 2 should add up to 3136 and have tap outs at 1066 (L6) and 2673 (R3)
Delays.NumSamplesDelayG = LateReflectionsGetDelaySamples(InSampleRate, 1066);
Delays.NumSamplesDelayH = LateReflectionsGetDelaySamples(InSampleRate, 1607);
Delays.NumSamplesDelayI = LateReflectionsGetDelaySamples(InSampleRate, 463);
return Delays;
}
FLateReflectionsPlateDelays FLateReflectionsPlateDelays::DefaultRightDelays(float InSampleRate)
{
FLateReflectionsPlateDelays Delays;
Delays.NumSamplesModulatedBase = LateReflectionsGetDelaySamples(InSampleRate, 672);
Delays.NumSamplesModulatedDelta = LateReflectionsGetDelaySamples(InSampleRate, 16);
// Right Delay 1 should add up to 4453 and have tap outs at 266 (L0), 2111 (R4), 2974 (L1)
Delays.NumSamplesDelayA = LateReflectionsGetDelaySamples(InSampleRate, 266);
Delays.NumSamplesDelayB = LateReflectionsGetDelaySamples(InSampleRate, 1845);
Delays.NumSamplesDelayC = LateReflectionsGetDelaySamples(InSampleRate, 863);
Delays.NumSamplesDelayD = LateReflectionsGetDelaySamples(InSampleRate, 1479);
Delays.NumSamplesAPF = LateReflectionsGetDelaySamples(InSampleRate, 1800);
// APF Delay should have tapouts at 335 (R5) and 1913 (L2)
Delays.NumSamplesDelayE = LateReflectionsGetDelaySamples(InSampleRate, 335);
Delays.NumSamplesDelayF = LateReflectionsGetDelaySamples(InSampleRate, 1578);
// Right Delay 2 should add up to 3720 and have tap outs at 121 (R6) and 1996 (L3)
Delays.NumSamplesDelayG = LateReflectionsGetDelaySamples(InSampleRate, 121);
Delays.NumSamplesDelayH = LateReflectionsGetDelaySamples(InSampleRate, 1875);
Delays.NumSamplesDelayI = LateReflectionsGetDelaySamples(InSampleRate, 1724);
return Delays;
}
FLateReflectionsPlate::FLateReflectionsPlate(
float InSampleRate,
int32 InMaxNumInternalBufferSamples,
const FLateReflectionsPlateDelays& InDelays)
: SampleRate(InSampleRate)
, NumInternalBufferSamples(InMaxNumInternalBufferSamples)
, Dampening(0.0005f)
, Decay(0.50f)
, Density(0.0f)
, PlateDelays(InDelays)
{
// NumInternalBufferSamples must be less than last delay and should be aligned to SIMD boundaries
if (NumInternalBufferSamples > PlateDelays.NumSamplesDelayI) {
NumInternalBufferSamples = PlateDelays.NumSamplesDelayI;
}
NumInternalBufferSamples -= (NumInternalBufferSamples % AUDIO_NUM_FLOATS_PER_VECTOR_REGISTER);
checkf(NumInternalBufferSamples > 0, TEXT("InDelays.DelaySample2C too small"));
// Create the delay line for a plate
// ModulatedAPF -> Delay1[A-D] -> LPF -> APF -> Delay2[A-C] -> Output
// |-> APFTapDelay[1-2]
ModulatedAPF = MakeUnique<FDynamicDelayAPF>(
-Density,
PlateDelays.NumSamplesModulatedBase - PlateDelays.NumSamplesModulatedDelta,
PlateDelays.NumSamplesModulatedBase + PlateDelays.NumSamplesModulatedDelta,
NumInternalBufferSamples,
SampleRate);
DelayA = MakeUnique<FIntegerDelay>(PlateDelays.NumSamplesDelayA, PlateDelays.NumSamplesDelayA);
DelayB = MakeUnique<FIntegerDelay>(PlateDelays.NumSamplesDelayB, PlateDelays.NumSamplesDelayB);
DelayC = MakeUnique<FIntegerDelay>(PlateDelays.NumSamplesDelayC, PlateDelays.NumSamplesDelayC);
DelayD = MakeUnique<FIntegerDelay>(PlateDelays.NumSamplesDelayD, PlateDelays.NumSamplesDelayD);
LPF = MakeUnique<FBufferOnePoleLPF>(Dampening);
APF = MakeUnique<FLongDelayAPF>(
Density - 0.15f,
PlateDelays.NumSamplesAPF,
NumInternalBufferSamples);
DelayE = MakeUnique<FIntegerDelay>(PlateDelays.NumSamplesDelayE, PlateDelays.NumSamplesDelayE);
DelayF = MakeUnique<FIntegerDelay>(PlateDelays.NumSamplesDelayF, PlateDelays.NumSamplesDelayF);
DelayG = MakeUnique<FIntegerDelay>(PlateDelays.NumSamplesDelayG, PlateDelays.NumSamplesDelayG);
DelayH = MakeUnique<FIntegerDelay>(PlateDelays.NumSamplesDelayH, PlateDelays.NumSamplesDelayH);
DelayI = MakeUnique<FIntegerDelay>(PlateDelays.NumSamplesDelayI, PlateDelays.NumSamplesDelayI);
}
FLateReflectionsPlate::~FLateReflectionsPlate()
{
}
void FLateReflectionsPlate::ProcessAudioFrames(
const FAlignedFloatBuffer& InSamples,
const FAlignedFloatBuffer& InFeedbackSamples,
const FAlignedFloatBuffer& InDelayModulations,
FLateReflectionsPlateOutputs& OutPlateSamples)
{
// Prepare output
const int32 InNum = InSamples.Num();
OutPlateSamples.ResizeAndZero(InNum);
checkf(InNum == InFeedbackSamples.Num(), TEXT("InSamples and InFeedbackSamples must have equal length"));
checkf(InNum == InDelayModulations.Num(), TEXT("InSamples and InDelayModulations must have equal length"));
// Check buffer sizes and quit early if there is an error.
if (InNum != InFeedbackSamples.Num())
{
return;
}
if (InNum != InDelayModulations.Num())
{
return;
}
float* DataPtr = nullptr;
// Prepare internal buffers to handle input data.
WorkBufferA.Reset(InNum);
WorkBufferA.AddUninitialized(InNum);
WorkBufferB.Reset(InNum);
WorkBufferB.AddUninitialized(InNum);
WorkBufferC.Reset(InNum);
WorkBufferC.AddUninitialized(InNum);
// Copy Feedback Samples to aligned internal buffer.
ArrayWeightedSum(InFeedbackSamples, 1.0 - Decay, InSamples, WorkBufferA);
// Input -> ModulatedAPF
ModulatedAPF->ProcessAudio(WorkBufferA, InDelayModulations, WorkBufferB);
// ModulatedAPF -> Delay1
DelayA->ProcessAudio(WorkBufferB, OutPlateSamples.Taps[0]);
DelayB->ProcessAudio(OutPlateSamples.Taps[0], OutPlateSamples.Taps[1]);
DelayC->ProcessAudio(OutPlateSamples.Taps[1], OutPlateSamples.Taps[2]);
DelayD->ProcessAudio(OutPlateSamples.Taps[2], WorkBufferA);
// Apply dampening
ArrayMultiplyByConstantInPlace(WorkBufferA, 1.0f - Dampening);
// Delay1 -> LPF
LPF->ProcessAudio(WorkBufferA, WorkBufferB);
// Apply decay
ArrayMultiplyByConstantInPlace(WorkBufferB, 1.0f - Decay);
// LPF -> APF
APF->ProcessAudio(WorkBufferB, WorkBufferA, WorkBufferC);
DelayE->ProcessAudio(WorkBufferC, OutPlateSamples.Taps[3]);
DelayF->ProcessAudio(OutPlateSamples.Taps[3], OutPlateSamples.Taps[4]);
// APF-> Delay2
DelayG->ProcessAudio(WorkBufferA, OutPlateSamples.Taps[5]);
DelayH->ProcessAudio(OutPlateSamples.Taps[5], OutPlateSamples.Taps[6]);
DelayI->ProcessAudio(OutPlateSamples.Taps[6], OutPlateSamples.Output);
}
void FLateReflectionsPlate::FlushAudio()
{
DelayA->Reset();
DelayB->Reset();
DelayC->Reset();
DelayD->Reset();
DelayE->Reset();
DelayF->Reset();
DelayG->Reset();
DelayH->Reset();
DelayI->Reset();
LPF->FlushAudio();
ModulatedAPF->Reset();
APF->Reset();
}
void FLateReflectionsPlate::SetDampening(float InDampening)
{
Dampening = InDampening;
LPF->SetG(Dampening);
}
void FLateReflectionsPlate::SetDecay(float InDecay)
{
Decay = InDecay;
}
void FLateReflectionsPlate::SetDensity(float InDensity)
{
Density = InDensity;
ModulatedAPF->SetG(FMath::Clamp(-Density, -0.9f, 0.9f));
APF->SetG(Density - 0.15f);
}
int32 FLateReflectionsPlate::GetNumInternalBufferSamples() const
{
return NumInternalBufferSamples;
}
void FLateReflectionsPlate::PeekDelayLine(int32 InNum, FAlignedFloatBuffer& OutSamples)
{
DelayI->PeekDelayLine(InNum, OutSamples);
}
// Limits on late reflections settings.
const float FLateReflectionsFast::MaxLateDelayMsec = 2000.0f;
const float FLateReflectionsFast::MinLateDelayMsec = 0.0f;
const float FLateReflectionsFast::MaxLateGainDB = 0.0f;
const float FLateReflectionsFast::MaxBandwidth = 0.99999f;
const float FLateReflectionsFast::MinBandwidth = 0.0f;
const float FLateReflectionsFast::MaxDampening = 0.99999f;
const float FLateReflectionsFast::MinDampening = 0.0f;
const float FLateReflectionsFast::MaxDiffusion = 0.99999f;
const float FLateReflectionsFast::MinDiffusion = 0.0f;
const float FLateReflectionsFast::MaxDecay = 1.0f;
const float FLateReflectionsFast::MinDecay = 0.0001f;
const float FLateReflectionsFast::MaxDensity = 1.0f;
const float FLateReflectionsFast::MinDensity = 0.0f;
const FLateReflectionsFastSettings FLateReflectionsFast::DefaultSettings;
FLateReflectionsFast::FLateReflectionsFast(float InSampleRate, int32 InMaxNumInternalBufferSamples, const FLateReflectionsFastSettings& InSettings)
: SampleRate(InSampleRate)
, Gain(1.0f)
, ModulationPhase(0.0f)
, ModulationQuadPhase(PI / 2.0f)
, ModulationPhaseIncrement(0.0f)
, NumInternalBufferSamples(0)
, Settings(InSettings)
, LeftPlateDelays(FLateReflectionsPlateDelays::DefaultLeftDelays(InSampleRate))
, RightPlateDelays(FLateReflectionsPlateDelays::DefaultRightDelays(InSampleRate))
{
// Copy and clamp settings.
Settings = InSettings;
ClampSettings(Settings);
// Create plates.
LeftPlate = MakeUnique<FLateReflectionsPlate>(SampleRate, InMaxNumInternalBufferSamples, LeftPlateDelays);
RightPlate = MakeUnique<FLateReflectionsPlate>(SampleRate, InMaxNumInternalBufferSamples, RightPlateDelays);
// The block size is limited by the delay lengths in the left & right plates.
// Internal block size will be set to minimum of allowable sizes from plates.
NumInternalBufferSamples = FMath::Min(LeftPlate->GetNumInternalBufferSamples(), RightPlate->GetNumInternalBufferSamples());
// Set maximum predelay. Add 8 samples to give a little extra buffer for floating point rounding
// differences.
int32 MaxDelay = (int32)(SampleRate * FLateReflectionsFast::MaxLateDelayMsec / 1000.0f) + 8;
PreDelay = MakeUnique<FIntegerDelay>(MaxDelay, 0, NumInternalBufferSamples);
InputLPF = MakeUnique<FBufferOnePoleLPF>(1.0f - Settings.Bandwidth);
// make the signal decorrelators
APF1 = MakeUnique<FLongDelayAPF>(Settings.Diffusion, LateReflectionsGetDelaySamples(SampleRate, 142), NumInternalBufferSamples);
APF2 = MakeUnique<FLongDelayAPF>(Settings.Diffusion, LateReflectionsGetDelaySamples(SampleRate, 107), NumInternalBufferSamples);
APF3 = MakeUnique<FLongDelayAPF>(Settings.Diffusion - 0.125f, LateReflectionsGetDelaySamples(SampleRate, 379), NumInternalBufferSamples);
APF4 = MakeUnique<FLongDelayAPF>(Settings.Diffusion - 0.125f, LateReflectionsGetDelaySamples(SampleRate, 277), NumInternalBufferSamples);
// Set modulation rate to 1 Hz.
ModulationPhaseIncrement = 2.0 * PI / SampleRate;
// Resize internal buffers
WorkBufferA.Reset(NumInternalBufferSamples);
WorkBufferB.Reset(NumInternalBufferSamples);
WorkBufferC.Reset(NumInternalBufferSamples);
LeftDelayModSamples.Reset(NumInternalBufferSamples);
RightDelayModSamples.Reset(NumInternalBufferSamples);
WorkBufferA.AddUninitialized(NumInternalBufferSamples);
WorkBufferB.AddUninitialized(NumInternalBufferSamples);
WorkBufferC.AddUninitialized(NumInternalBufferSamples);
LeftDelayModSamples.AddUninitialized(NumInternalBufferSamples);
RightDelayModSamples.AddUninitialized(NumInternalBufferSamples);
LeftPlateOutputs.ResizeAndZero(NumInternalBufferSamples);
RightPlateOutputs.ResizeAndZero(NumInternalBufferSamples);
ApplySettings();
}
FLateReflectionsFast::~FLateReflectionsFast()
{}
void FLateReflectionsFast::ClampSettings(FLateReflectionsFastSettings& InOutSettings)
{
// Enforce settings to be within max/min
InOutSettings.LateDelayMsec = FMath::Clamp(InOutSettings.LateDelayMsec, MinLateDelayMsec, MaxLateDelayMsec);
InOutSettings.LateGainDB = FMath::Min(InOutSettings.LateGainDB, FLateReflectionsFast::MaxLateGainDB);
InOutSettings.Bandwidth = FMath::Clamp(InOutSettings.Bandwidth, MinBandwidth, MaxBandwidth);
InOutSettings.Dampening = FMath::Clamp(InOutSettings.Dampening, MinDampening, MaxDampening);
InOutSettings.Diffusion = FMath::Clamp(InOutSettings.Diffusion, MinDiffusion, MaxDiffusion);
InOutSettings.Decay = FMath::Clamp(InOutSettings.Decay, MinDecay, MaxDecay);
InOutSettings.Density = FMath::Clamp(InOutSettings.Density, MinDensity, MaxDensity);
}
// Sets the reverb settings, applies, and updates
void FLateReflectionsFast::SetSettings(const FLateReflectionsFastSettings& InSettings)
{
Settings = InSettings;
ClampSettings(Settings);
ApplySettings();
}
void FLateReflectionsFast::ProcessAudio(const FAlignedFloatBuffer& InSamples, const int32 InNumChannels, FAlignedFloatBuffer& OutLeftSamples, FAlignedFloatBuffer& OutRightSamples)
{
checkf((InNumChannels == 1) || (InNumChannels == 2), TEXT("FLateReflections only supports 1 or 2 channel input audio"));
const int32 InNum = InSamples.Num();
const int32 InNumFrames = InNum / InNumChannels;
OutLeftSamples.Reset(InNumFrames);
OutRightSamples.Reset(InNumFrames);
OutLeftSamples.AddUninitialized(InNumFrames);
OutRightSamples.AddUninitialized(InNumFrames);
const float* InSampleData = InSamples.GetData();
float* OutLeftSampleData = OutLeftSamples.GetData();
float* OutRightSampleData = OutRightSamples.GetData();
int32 FramesLeftOver = InNumFrames;
int32 InPos = 0;
int32 OutPos = 0;
while (FramesLeftOver > 0)
{
int32 FramesToProcess = FMath::Min(NumInternalBufferSamples, FramesLeftOver);
ProcessAudioBuffer(&InSampleData[InPos], FramesToProcess, InNumChannels, &OutLeftSampleData[OutPos], &OutRightSampleData[OutPos]);
FramesLeftOver -= FramesToProcess;
InPos += FramesToProcess * InNumChannels;
OutPos += FramesToProcess;
}
}
void FLateReflectionsFast::FlushAudio()
{
PreDelay->Reset();
InputLPF->FlushAudio();
APF1->Reset();
APF2->Reset();
APF3->Reset();
APF4->Reset();
LeftPlate->FlushAudio();
RightPlate->FlushAudio();
}
void FLateReflectionsFast::ProcessAudioBuffer(const float* InSampleData, const int32 InNumFrames, const int32 InNumChannels, float* OutLeftSampleData, float* OutRightSampleData)
{
checkf((1 == InNumChannels) || (2 == InNumChannels), TEXT("FLateReflections only supports 1 and 2 channel input audio"));
// Resize internal buffers
WorkBufferA.Reset(InNumFrames);
WorkBufferB.Reset(InNumFrames);
LeftDelayModSamples.Reset(InNumFrames);
RightDelayModSamples.Reset(InNumFrames);
WorkBufferA.AddUninitialized(InNumFrames);
WorkBufferB.AddUninitialized(InNumFrames);
LeftDelayModSamples.AddUninitialized(InNumFrames);
RightDelayModSamples.AddUninitialized(InNumFrames);
// Downmix to mono
if (InNumChannels == 1)
{
FMemory::Memcpy(WorkBufferA.GetData(), InSampleData, sizeof(float) * InNumFrames);
}
else if (InNumChannels == 2)
{
BufferSum2ChannelToMonoFast(InSampleData, WorkBufferA.GetData(), InNumFrames);
}
else
{
// Shouldn't reach this line if checks are respected.
FMemory::Memset(OutLeftSampleData, 0, sizeof(float) * InNumFrames);
FMemory::Memset(OutRightSampleData, 0, sizeof(float) * InNumFrames);
return;
}
// Apply bandwidth, gain and channel averaging multipliers
ArrayMultiplyByConstantInPlace(WorkBufferA, Settings.Bandwidth * Gain / InNumChannels);
// Predelay
PreDelay->ProcessAudio(WorkBufferA, WorkBufferB);
// Input Diffusion
InputLPF->ProcessAudio(WorkBufferB, WorkBufferA);
APF1->ProcessAudio(WorkBufferA, WorkBufferB);
APF2->ProcessAudio(WorkBufferB, WorkBufferA);
APF3->ProcessAudio(WorkBufferA, WorkBufferB);
APF4->ProcessAudio(WorkBufferB, WorkBufferA);
// The plates are connected to eachother so that the output of the left plate is fed into the input of the
// right plate and vice versa. The delay lines of the plates are stored before processing new data through
// the plate.
LeftPlate->PeekDelayLine(InNumFrames, WorkBufferB);
RightPlate->PeekDelayLine(InNumFrames, WorkBufferC);
// Create the delay line modulations.
GeneraterPlateModulations(InNumFrames, LeftDelayModSamples, RightDelayModSamples);
// Run audio through plates
LeftPlate->ProcessAudioFrames(WorkBufferA, WorkBufferC, LeftDelayModSamples, LeftPlateOutputs);
RightPlate->ProcessAudioFrames(WorkBufferA, WorkBufferB, RightDelayModSamples, RightPlateOutputs);
TArrayView<float> OutLeftSampleDataView(OutLeftSampleData, InNumFrames);
TArrayView<float> OutRightSampleDataView(OutRightSampleData, InNumFrames);
TArrayView<const float> LeftTaps1View(LeftPlateOutputs.Taps[1].GetData(), InNumFrames);
TArrayView<const float> LeftTaps2View(LeftPlateOutputs.Taps[2].GetData(), InNumFrames);
TArrayView<const float> LeftTaps3View(LeftPlateOutputs.Taps[3].GetData(), InNumFrames);
TArrayView<const float> LeftTaps4View(LeftPlateOutputs.Taps[4].GetData(), InNumFrames);
TArrayView<const float> LeftTaps5View(LeftPlateOutputs.Taps[5].GetData(), InNumFrames);
TArrayView<const float> LeftTaps6View(LeftPlateOutputs.Taps[6].GetData(), InNumFrames);
TArrayView<const float> RightTaps1View(RightPlateOutputs.Taps[1].GetData(), InNumFrames);
TArrayView<const float> RightTaps2View(RightPlateOutputs.Taps[2].GetData(), InNumFrames);
TArrayView<const float> RightTaps3View(RightPlateOutputs.Taps[3].GetData(), InNumFrames);
TArrayView<const float> RightTaps4View(RightPlateOutputs.Taps[4].GetData(), InNumFrames);
TArrayView<const float> RightTaps5View(RightPlateOutputs.Taps[5].GetData(), InNumFrames);
TArrayView<const float> RightTaps6View(RightPlateOutputs.Taps[6].GetData(), InNumFrames);
// Left Output
// Channel -> [Plate and Tap#]
// L -> +R0
// L -> +R2
// L -> -R4
// L -> +R6
// L -> -L1
// L -> -L3
// L -> -L5
FMemory::Memcpy(OutLeftSampleData, RightPlateOutputs.Taps[0].GetData(), InNumFrames * sizeof(float));
ArrayMixIn(RightTaps2View, OutLeftSampleDataView);
ArraySubtractInPlace2(OutLeftSampleDataView, RightTaps4View);
ArrayMixIn(RightTaps6View, OutLeftSampleDataView);
ArraySubtractInPlace2(OutLeftSampleDataView, LeftTaps1View);
ArraySubtractInPlace2(OutLeftSampleDataView, LeftTaps3View);
ArraySubtractInPlace2(OutLeftSampleDataView, LeftTaps5View);
// Right Output
// Channel -> [Plate and Tap#]
// R -> +L0
// R -> +L2
// R -> -L4
// R -> +L6
// R -> -R1
// R -> -R3
// R -> -R5
FMemory::Memcpy(OutRightSampleData, LeftPlateOutputs.Taps[0].GetData(), InNumFrames * sizeof(float));
ArrayMixIn(LeftTaps2View, OutRightSampleDataView);
ArraySubtractInPlace2(OutRightSampleDataView, LeftTaps4View);
ArrayMixIn(LeftTaps6View, OutRightSampleDataView);
ArraySubtractInPlace2(OutRightSampleDataView, RightTaps1View);
ArraySubtractInPlace2(OutRightSampleDataView, RightTaps3View);
ArraySubtractInPlace2(OutRightSampleDataView, RightTaps5View);
}
void FLateReflectionsFast::ApplySettings()
{
// Convert gain to linear.
Gain = FMath::Pow(10.0f, Settings.LateGainDB/ 20.0f);
// Convert delay to samples
PreDelay->SetDelayLengthSamples((int32)(Settings.LateDelayMsec * SampleRate / 1000.0f));
InputLPF->SetG(1.0f - Settings.Bandwidth);
APF1->SetG(Settings.Diffusion);
APF2->SetG(Settings.Diffusion);
APF3->SetG(Settings.Diffusion - 0.125f);
APF4->SetG(Settings.Diffusion - 0.125f);
LeftPlate->SetDensity(Settings.Density);
LeftPlate->SetDampening(Settings.Dampening);
LeftPlate->SetDecay(Settings.Decay);
RightPlate->SetDensity(Settings.Density);
RightPlate->SetDampening(Settings.Dampening);
RightPlate->SetDecay(Settings.Decay);
}
void FLateReflectionsFast::GeneraterPlateModulations(const int32 InNum, FAlignedFloatBuffer& OutLeftDelays, FAlignedFloatBuffer& OutRightDelays)
{
OutLeftDelays.Reset(InNum);
OutRightDelays.Reset(InNum);
OutLeftDelays.AddUninitialized(InNum);
OutRightDelays.AddUninitialized(InNum);
// Generate quadrature plate modulations to help break up hi-freq modes in plates.
float* LeftModData = OutLeftDelays.GetData();
float* RightModData = OutRightDelays.GetData();
for (int32 i = 0; i < InNum; i++)
{
float NormalPhaseOut = 0.5f * FastSin(ModulationPhase) + 0.5f;
float QuadPhaseOut = 0.5f * FastSin(ModulationQuadPhase) + 0.5f;
ModulationPhase += ModulationPhaseIncrement;
if (ModulationPhase > PI)
{
ModulationPhase -= 2 * PI;
}
ModulationQuadPhase += ModulationPhaseIncrement;
if (ModulationQuadPhase > PI)
{
ModulationQuadPhase -= 2 * PI;
}
LeftModData[i] = LeftPlateDelays.NumSamplesModulatedBase + (NormalPhaseOut * LeftPlateDelays.NumSamplesModulatedDelta);
RightModData[i] = RightPlateDelays.NumSamplesModulatedBase + (QuadPhaseOut * RightPlateDelays.NumSamplesModulatedDelta);
}
}
}