239 lines
8.1 KiB
C++
239 lines
8.1 KiB
C++
// 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();
|
|
}
|
|
}
|