// Copyright Epic Games, Inc. All Rights Reserved. #include "DSP/EarlyReflectionsFast.h" #include "DSP/FloatArrayMath.h" namespace Audio { namespace EarlyReflectionsPrivate { float NormalizedLinearToLog(float InLinear) { check(InLinear >= 0.f); check(InLinear <= 1.f); static const float InvLn2 = 1.f / FMath::Loge(2.f); return FMath::Loge(1.f + InLinear) * InvLn2; } } FEarlyReflectionsFastSettings::FEarlyReflectionsFastSettings() : Gain(1.0f) , PreDelayMsec(0.0f) , Bandwidth(0.8) , Decay(0.5) , Absorption(0.7) {} bool FEarlyReflectionsFastSettings::operator==(const FEarlyReflectionsFastSettings& Other) const { bool bIsEqual = ( (Other.Gain == Gain) && (Other.PreDelayMsec == PreDelayMsec) && (Other.Bandwidth == Bandwidth) && (Other.Decay == Decay) && (Other.Absorption == Absorption)); return bIsEqual; } bool FEarlyReflectionsFastSettings::operator!=(const FEarlyReflectionsFastSettings& Other) const { return !(*this == Other); } const float FEarlyReflectionsFast::MinGain = 0.0f; const float FEarlyReflectionsFast::MaxGain = 0.9999f; const float FEarlyReflectionsFast::MaxPreDelay = 1000.0f; const float FEarlyReflectionsFast::MinPreDelay = 0.0f; const float FEarlyReflectionsFast::MaxBandwidth = 0.99999f; const float FEarlyReflectionsFast::MinBandwidth = 0.0f; const float FEarlyReflectionsFast::MaxDecay = 1.0f; const float FEarlyReflectionsFast::MinDecay = 0.0001f; const float FEarlyReflectionsFast::MaxAbsorption = 0.99999f; const float FEarlyReflectionsFast::MinAbsorption = 0.0f; const FEarlyReflectionsFastSettings FEarlyReflectionsFast::DefaultSettings; FEarlyReflectionsFast::FEarlyReflectionsFast(float InSampleRate, int32 InMaxNumInternalBufferSamples, const FEarlyReflectionsFastSettings& InSettings) : Settings(InSettings), SampleRate(InSampleRate), LeftFDN(InMaxNumInternalBufferSamples, FFDNDelaySettings::DefaultLeftDelays(InSampleRate)), RightFDN(InMaxNumInternalBufferSamples, FFDNDelaySettings::DefaultRightDelays(InSampleRate)), LeftPreDelay((int32)InSampleRate * 2.0f, 0, InMaxNumInternalBufferSamples), RightPreDelay((int32)InSampleRate * 2.0f, 0, InMaxNumInternalBufferSamples) { LeftCoefficients.InputScale = 0.25f; RightCoefficients.InputScale = 0.25f; ClampSettings(Settings); ApplySettings(); } FEarlyReflectionsFast::~FEarlyReflectionsFast() { } void FEarlyReflectionsFast::ClampSettings(FEarlyReflectionsFastSettings& InOutSettings) { InOutSettings.Gain = FMath::Clamp( InOutSettings.Gain, FEarlyReflectionsFast::MinGain, FEarlyReflectionsFast::MaxGain); InOutSettings.PreDelayMsec = FMath::Clamp( InOutSettings.PreDelayMsec, FEarlyReflectionsFast::MinPreDelay, FEarlyReflectionsFast::MaxPreDelay); InOutSettings.Bandwidth = FMath::Clamp( InOutSettings.Bandwidth, FEarlyReflectionsFast::MinBandwidth, FEarlyReflectionsFast::MaxBandwidth); InOutSettings.Decay = FMath::Clamp( InOutSettings.Decay, FEarlyReflectionsFast::MinDecay, FEarlyReflectionsFast::MaxDecay); InOutSettings.Absorption = FMath::Clamp( InOutSettings.Absorption, FEarlyReflectionsFast::MinAbsorption, FEarlyReflectionsFast::MaxAbsorption); } void FEarlyReflectionsFast::SetSettings(const FEarlyReflectionsFastSettings& InSettings) { Settings = InSettings; ClampSettings(Settings); ApplySettings(); } void FEarlyReflectionsFast::ApplySettings() { using namespace EarlyReflectionsPrivate; int32 DelaySamples = (int32)SampleRate * Settings.PreDelayMsec / 1000.0f; LeftPreDelay.SetDelayLengthSamples(DelaySamples); RightPreDelay.SetDelayLengthSamples(DelaySamples); // Convert from linear 0-1 scale to logarithmic 0-1 scale. const float LPFG = NormalizedLinearToLog(Settings.Bandwidth); LeftInputLPF.SetG(LPFG); RightInputLPF.SetG(LPFG); LeftCoefficients.LPFB[0] = FMath::Min(Settings.Absorption + 0.1f, 0.9999f); LeftCoefficients.LPFB[1] = FMath::Min(Settings.Absorption - 0.12f, 0.9999f); LeftCoefficients.LPFB[2] = FMath::Min(Settings.Absorption + 0.08f, 0.9999f); LeftCoefficients.LPFB[3] = FMath::Min(Settings.Absorption - 0.07f, 0.9999f); LeftCoefficients.LPFA[0] = 1.0f - LeftCoefficients.LPFB[0]; LeftCoefficients.LPFA[1] = 1.0f - LeftCoefficients.LPFB[1]; LeftCoefficients.LPFA[2] = 1.0f - LeftCoefficients.LPFB[2]; LeftCoefficients.LPFA[3] = 1.0f - LeftCoefficients.LPFB[3]; LeftCoefficients.APFG[0] = 0.1f; LeftCoefficients.APFG[1] = 0.2f; LeftCoefficients.APFG[2] = 0.3f; LeftCoefficients.APFG[3] = 0.4f; RightCoefficients.LPFB[0] = FMath::Min(Settings.Absorption + 0.17f, 0.999f); RightCoefficients.LPFB[1] = FMath::Min(Settings.Absorption - 0.07f, 0.999f); RightCoefficients.LPFB[2] = FMath::Min(Settings.Absorption + 0.05f, 0.999f); RightCoefficients.LPFB[3] = FMath::Min(Settings.Absorption - 0.11f, 0.999f); RightCoefficients.LPFA[0] = 1.0f - RightCoefficients.LPFB[0]; RightCoefficients.LPFA[1] = 1.0f - RightCoefficients.LPFB[1]; RightCoefficients.LPFA[2] = 1.0f - RightCoefficients.LPFB[2]; RightCoefficients.LPFA[3] = 1.0f - RightCoefficients.LPFB[3]; RightCoefficients.APFG[0] = 0.1f; RightCoefficients.APFG[1] = 0.2f; RightCoefficients.APFG[2] = 0.3f; RightCoefficients.APFG[3] = 0.4f; // 1/sqrt(2) * Decay //float Feedback = (1.0f - Settings.Decay) * 0.707f; float Feedback = (1.0f - Settings.Decay) * 0.5f; LeftCoefficients.Feedback = Feedback; RightCoefficients.Feedback = Feedback; LeftFDN.SetCoefficients(LeftCoefficients); RightFDN.SetCoefficients(RightCoefficients); } void FEarlyReflectionsFast::ProcessAudio(const FAlignedFloatBuffer& InSamples, const int32 InNumChannels, FAlignedFloatBuffer& OutLeftSamples, FAlignedFloatBuffer& OutRightSamples) { checkf((InNumChannels == 1) || (InNumChannels == 2), TEXT("EarlyReflections only supports one or two channel input samples.")); const int32 InNum = InSamples.Num(); const int32 InNumFrames = InNum / InNumChannels; // Resize internal buffers LeftInputBuffer.Reset(InNumFrames); LeftWorkBufferA.Reset(InNumFrames); LeftWorkBufferB.Reset(InNumFrames); RightInputBuffer.Reset(InNumFrames); RightWorkBufferA.Reset(InNumFrames); RightWorkBufferB.Reset(InNumFrames); LeftInputBuffer.AddUninitialized(InNumFrames); LeftWorkBufferA.AddUninitialized(InNumFrames); LeftWorkBufferB.AddUninitialized(InNumFrames); RightInputBuffer.AddUninitialized(InNumFrames); RightWorkBufferA.AddUninitialized(InNumFrames); RightWorkBufferB.AddUninitialized(InNumFrames); // Resize output buffers OutLeftSamples.Reset(InNumFrames); OutRightSamples.Reset(InNumFrames); OutLeftSamples.AddUninitialized(InNumFrames); OutRightSamples.AddUninitialized(InNumFrames); if (1 == InNumChannels) { // Copy mono to both left and right FMemory::Memcpy(LeftInputBuffer.GetData(), InSamples.GetData(), sizeof(float) * InNumFrames); FMemory::Memcpy(RightInputBuffer.GetData(), InSamples.GetData(), sizeof(float) * InNumFrames); } else if (2 == InNumChannels) { BufferDeinterleave2ChannelFast(InSamples, LeftInputBuffer, RightInputBuffer); } else { // This class only supports 1 and 2 channel input audio. FMemory::Memset(OutLeftSamples.GetData(), 0, sizeof(float) * InNumFrames); FMemory::Memset(OutRightSamples.GetData(), 0, sizeof(float) * InNumFrames); return; } // predelay LeftPreDelay.ProcessAudio(LeftInputBuffer, LeftWorkBufferB); RightPreDelay.ProcessAudio(RightInputBuffer, RightWorkBufferB); // lpf LeftInputLPF.ProcessAudio(LeftWorkBufferB, LeftWorkBufferA); RightInputLPF.ProcessAudio(RightWorkBufferB, RightWorkBufferA); // feedback delay network LeftFDN.ProcessAudio(LeftWorkBufferA, OutLeftSamples); RightFDN.ProcessAudio(RightWorkBufferA, OutRightSamples); const float OneMinusGain = 1.0 - Settings.Gain; // Apply Gain ArrayMultiplyByConstantInPlace(OutLeftSamples, Settings.Gain); ArrayMultiplyByConstantInPlace(OutRightSamples, Settings.Gain); } void FEarlyReflectionsFast::FlushAudio() { // predelay LeftPreDelay.Reset(); RightPreDelay.Reset(); // lpf LeftInputLPF.FlushAudio(); RightInputLPF.FlushAudio(); // feedback delay network LeftFDN.FlushAudio(); RightFDN.FlushAudio(); } }