// 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( -Density, PlateDelays.NumSamplesModulatedBase - PlateDelays.NumSamplesModulatedDelta, PlateDelays.NumSamplesModulatedBase + PlateDelays.NumSamplesModulatedDelta, NumInternalBufferSamples, SampleRate); DelayA = MakeUnique(PlateDelays.NumSamplesDelayA, PlateDelays.NumSamplesDelayA); DelayB = MakeUnique(PlateDelays.NumSamplesDelayB, PlateDelays.NumSamplesDelayB); DelayC = MakeUnique(PlateDelays.NumSamplesDelayC, PlateDelays.NumSamplesDelayC); DelayD = MakeUnique(PlateDelays.NumSamplesDelayD, PlateDelays.NumSamplesDelayD); LPF = MakeUnique(Dampening); APF = MakeUnique( Density - 0.15f, PlateDelays.NumSamplesAPF, NumInternalBufferSamples); DelayE = MakeUnique(PlateDelays.NumSamplesDelayE, PlateDelays.NumSamplesDelayE); DelayF = MakeUnique(PlateDelays.NumSamplesDelayF, PlateDelays.NumSamplesDelayF); DelayG = MakeUnique(PlateDelays.NumSamplesDelayG, PlateDelays.NumSamplesDelayG); DelayH = MakeUnique(PlateDelays.NumSamplesDelayH, PlateDelays.NumSamplesDelayH); DelayI = MakeUnique(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(SampleRate, InMaxNumInternalBufferSamples, LeftPlateDelays); RightPlate = MakeUnique(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(MaxDelay, 0, NumInternalBufferSamples); InputLPF = MakeUnique(1.0f - Settings.Bandwidth); // make the signal decorrelators APF1 = MakeUnique(Settings.Diffusion, LateReflectionsGetDelaySamples(SampleRate, 142), NumInternalBufferSamples); APF2 = MakeUnique(Settings.Diffusion, LateReflectionsGetDelaySamples(SampleRate, 107), NumInternalBufferSamples); APF3 = MakeUnique(Settings.Diffusion - 0.125f, LateReflectionsGetDelaySamples(SampleRate, 379), NumInternalBufferSamples); APF4 = MakeUnique(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 OutLeftSampleDataView(OutLeftSampleData, InNumFrames); TArrayView OutRightSampleDataView(OutRightSampleData, InNumFrames); TArrayView LeftTaps1View(LeftPlateOutputs.Taps[1].GetData(), InNumFrames); TArrayView LeftTaps2View(LeftPlateOutputs.Taps[2].GetData(), InNumFrames); TArrayView LeftTaps3View(LeftPlateOutputs.Taps[3].GetData(), InNumFrames); TArrayView LeftTaps4View(LeftPlateOutputs.Taps[4].GetData(), InNumFrames); TArrayView LeftTaps5View(LeftPlateOutputs.Taps[5].GetData(), InNumFrames); TArrayView LeftTaps6View(LeftPlateOutputs.Taps[6].GetData(), InNumFrames); TArrayView RightTaps1View(RightPlateOutputs.Taps[1].GetData(), InNumFrames); TArrayView RightTaps2View(RightPlateOutputs.Taps[2].GetData(), InNumFrames); TArrayView RightTaps3View(RightPlateOutputs.Taps[3].GetData(), InNumFrames); TArrayView RightTaps4View(RightPlateOutputs.Taps[4].GetData(), InNumFrames); TArrayView RightTaps5View(RightPlateOutputs.Taps[5].GetData(), InNumFrames); TArrayView 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); } } }