164 lines
6.2 KiB
C++
164 lines
6.2 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "DSP/AllPassFractionalDelay.h"
|
|
#include "DSP/BufferVectorOperations.h"
|
|
#include "DSP/Dsp.h"
|
|
#include "DSP/FloatArrayMath.h"
|
|
|
|
namespace Audio
|
|
{
|
|
FAllPassFractionalDelay::FAllPassFractionalDelay(int32 InMaxDelay, int32 InNumInternalBufferSamples)
|
|
: MaxDelay(InMaxDelay)
|
|
, NumInternalBufferSamples(InNumInternalBufferSamples)
|
|
, Z1(0.0f)
|
|
{
|
|
checkf(MaxDelay > 0, TEXT("Maximum delay must be greater than zero"));
|
|
checkf(InNumInternalBufferSamples > 0, TEXT("Internal buffer length must be greater than zero"));
|
|
|
|
// Clamp settings to avoid hard crashes during runtime.
|
|
if (MaxDelay < 1)
|
|
{
|
|
MaxDelay = 1;
|
|
}
|
|
if (InNumInternalBufferSamples < 1)
|
|
{
|
|
InNumInternalBufferSamples = 32;
|
|
}
|
|
|
|
// Allocate and prepare delay line for maximum delay.
|
|
DelayLine = MakeUnique<FAlignedBlockBuffer>((2 * MaxDelay) + NumInternalBufferSamples, MaxDelay + NumInternalBufferSamples);
|
|
DelayLine->AddZeros(MaxDelay);
|
|
|
|
Coefficients.Reset(NumInternalBufferSamples);
|
|
FractionalDelays.Reset(NumInternalBufferSamples);
|
|
IntegerDelays.Reset(NumInternalBufferSamples);
|
|
IntegerDelayOffsets.Reset(NumInternalBufferSamples);
|
|
|
|
Coefficients.AddUninitialized(NumInternalBufferSamples);
|
|
FractionalDelays.AddUninitialized(NumInternalBufferSamples);
|
|
IntegerDelays.AddUninitialized(NumInternalBufferSamples);
|
|
IntegerDelayOffsets.AddUninitialized(NumInternalBufferSamples);
|
|
|
|
// Integer delay offsets account for buffer position when doing block processing of data.
|
|
for (int32 i = 0; i < InNumInternalBufferSamples; i++)
|
|
{
|
|
IntegerDelayOffsets[i] = i + MaxDelay;
|
|
}
|
|
}
|
|
|
|
|
|
// Destructor
|
|
FAllPassFractionalDelay::~FAllPassFractionalDelay()
|
|
{}
|
|
|
|
// Resets the delay line state, flushes buffer and resets read/write pointers.
|
|
void FAllPassFractionalDelay::Reset()
|
|
{
|
|
DelayLine->ClearSamples();
|
|
DelayLine->AddZeros(MaxDelay);
|
|
Z1 = 0.0f;
|
|
}
|
|
|
|
|
|
void FAllPassFractionalDelay::ProcessAudio(const FAlignedFloatBuffer& InSamples, const FAlignedFloatBuffer& InDelays, FAlignedFloatBuffer& OutSamples)
|
|
{
|
|
const int32 InNum = InSamples.Num();
|
|
checkf(InNum == InDelays.Num(), TEXT("Input buffers must be equal length."));
|
|
|
|
// Prepare output buffer
|
|
OutSamples.Reset(InNum);
|
|
OutSamples.AddUninitialized(InNum);
|
|
|
|
float* OutSampleData = OutSamples.GetData();
|
|
const float* InSampleData = InSamples.GetData();
|
|
const float* InDelayData = InDelays.GetData();
|
|
|
|
// Process audio one block at a time.
|
|
int32 LeftOver = InNum;
|
|
int32 BufferPos = 0;
|
|
while (LeftOver > 0)
|
|
{
|
|
int32 NumToProcess = FMath::Min(LeftOver, NumInternalBufferSamples);
|
|
ProcessAudioBlock(&InSampleData[BufferPos], &InDelayData[BufferPos], NumToProcess, &OutSampleData[BufferPos]);
|
|
BufferPos += NumToProcess;
|
|
LeftOver -= NumToProcess;
|
|
}
|
|
}
|
|
|
|
void FAllPassFractionalDelay::ProcessAudioBlock(const float* InSamples, const float* InDelays, const int32 InNum, float* OutSamples)
|
|
{
|
|
checkf(0 == (InNum % 4), TEXT("Array length must be multiple of 4"));
|
|
|
|
// All these assume that InNum <= NumInternalBufferSamples
|
|
const int32* IntegerDelayOffsetData = IntegerDelayOffsets.GetData();
|
|
int32* IntegerDelayData = IntegerDelays.GetData();
|
|
float* FractionalDelayData = FractionalDelays.GetData();
|
|
float* CoefficientsData = Coefficients.GetData();
|
|
|
|
FMemory::Memcpy(FractionalDelayData, InDelays, InNum * sizeof(float));
|
|
|
|
// Determine integer delays and filter coefficients per a sample
|
|
const VectorRegister4Float VTwoAndAHalf = MakeVectorRegister(2.5f, 2.5f, 2.5f, 2.5f);
|
|
const VectorRegister4Float VMaxDelay = MakeVectorRegister((float)MaxDelay, (float)MaxDelay, (float)MaxDelay, (float)MaxDelay);
|
|
for (int32 i = 0; i < InNum; i += 4)
|
|
{
|
|
checkf(FractionalDelayData[i] <= MaxDelay, TEXT("Delay exceeds maximum"));
|
|
checkf(FractionalDelayData[i + 1] <= MaxDelay, TEXT("Delay exceeds maximum"));
|
|
checkf(FractionalDelayData[i + 2] <= MaxDelay, TEXT("Delay exceeds maximum"));
|
|
checkf(FractionalDelayData[i + 3] <= MaxDelay, TEXT("Delay exceeds maximum"));
|
|
|
|
// Ensure that delays are in [0.5, MaxDelay]
|
|
// Delay= Max(Delay, 0.5)
|
|
VectorRegister4Float VFractionalDelays = VectorLoad(&FractionalDelayData[i]);
|
|
VFractionalDelays = VectorMax(VFractionalDelays, GlobalVectorConstants::FloatOneHalf);
|
|
// Delay = Min(Delay, MaxDelay)
|
|
VFractionalDelays = VectorMin(VFractionalDelays, VMaxDelay);
|
|
|
|
// To ensure fractional delays between -0.5 - 0.5, subtract limit then add back in.
|
|
// Delay = Delay + 0.5
|
|
VFractionalDelays = VectorAdd(VFractionalDelays, GlobalVectorConstants::FloatOneHalf);
|
|
// Delay.floor = Floor(Delay)
|
|
VectorRegister4Float VFloorDelays = VectorFloor(VFractionalDelays);
|
|
// Delay.frac = Delay - Delay.floor
|
|
VFractionalDelays = VectorSubtract(VFractionalDelays, VFloorDelays);
|
|
|
|
// Reintroduing 0.5 previously removed.
|
|
// alpha = Delay.frac - 0.5
|
|
VectorRegister4Float VCoefficients = VectorSubtract(VFractionalDelays, GlobalVectorConstants::FloatOneHalf);
|
|
// denom = 2.5 - alpha
|
|
VectorRegister4Float VDenominator = VectorSubtract(VTwoAndAHalf, VFractionalDelays);
|
|
// coef = alpha / (2.5 - alpha)
|
|
VCoefficients = VectorDivide(VCoefficients, VDenominator);
|
|
VectorStore(VCoefficients, &Coefficients[i]);
|
|
|
|
// Delay.int = int(Delay.floor)
|
|
VectorRegister4Int VIntegerDelays = VectorFloatToInt(VFloorDelays);
|
|
VectorRegister4Int VIntegerDelayOffset = VectorIntLoadAligned(&IntegerDelayOffsetData[i]);
|
|
// Delay.int += BufferIdx
|
|
VectorIntStoreAligned(VectorIntSubtract(VIntegerDelayOffset, VIntegerDelays), &IntegerDelayData[i]);
|
|
}
|
|
|
|
// Update delay line.
|
|
DelayLine->AddSamples(InSamples, InNum);
|
|
|
|
// All pass filter delay line to get fractional delay
|
|
const float* DelayData = DelayLine->InspectSamples(InNum + MaxDelay);
|
|
int32 DelayPos;
|
|
if (InNum > 0)
|
|
{
|
|
DelayPos = IntegerDelayData[0];
|
|
OutSamples[0] = DelayData[DelayPos - 1] + CoefficientsData[0] * DelayData[DelayPos] - CoefficientsData[0] * Z1;
|
|
for (int32 i = 1; i < InNum; i++)
|
|
{
|
|
DelayPos = IntegerDelayData[i];
|
|
OutSamples[i] = DelayData[DelayPos - 1] + CoefficientsData[i] * DelayData[DelayPos] - CoefficientsData[i] * OutSamples[i - 1];
|
|
}
|
|
|
|
Z1 = OutSamples[InNum - 1];
|
|
}
|
|
|
|
// Remove unneeded delay line.
|
|
DelayLine->RemoveSamples(InNum);
|
|
}
|
|
}
|