Files
UnrealEngineUWP/Engine/Source/Runtime/SignalProcessing/Private/InterpolatedMultiTapDelay.cpp
miles flanagan a9bd3f5860 Fix unit test + possible buffer overrun in interpolatedmultitapdelay
#rb charlie.huguenard
#rnx

[CL 28041034 by miles flanagan in ue5-main branch]
2023-09-20 12:33:44 -04:00

148 lines
4.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DSP/InterpolatedMultiTapDelay.h"
#include "DSP/FloatArrayMath.h"
#include "Math/VectorRegister.h"
namespace Audio
{
void FInterpolatedMultiTapDelay::Init(const int32 InBufferSizeSamples, const float InSampleRate)
{
WriteIndex = 0;
MsToSamples = InSampleRate / 1000.f;
DelayLine.Reset();
DelayLine.AddZeroed(AlignIndex(InBufferSizeSamples));
}
void FInterpolatedMultiTapDelay::Advance(const FAlignedFloatBuffer& InSamples)
{
check (InSamples.Num() % AUDIO_NUM_FLOATS_PER_VECTOR_REGISTER == 0);
const int32 InNumSamples = InSamples.Num();
if (InNumSamples <= 0)
{
return;
}
if (InNumSamples + WriteIndex > DelayLine.Num())
{
const int32 FirstHalfSize = DelayLine.Num() - WriteIndex;
const int32 SecondHalfSize = InNumSamples - FirstHalfSize;
if (FirstHalfSize > 0)
{
FMemory::Memcpy(&DelayLine[WriteIndex], InSamples.GetData(), FirstHalfSize * sizeof(float));
}
if (SecondHalfSize > 0)
{
FMemory::Memcpy(DelayLine.GetData(), &InSamples[FirstHalfSize], SecondHalfSize * sizeof(float));
}
WriteIndex = SecondHalfSize;
}
else
{
FMemory::Memcpy(&DelayLine[WriteIndex], InSamples.GetData(), InNumSamples * sizeof(float));
WriteIndex += InNumSamples;
WriteIndex = FMath::Wrap(WriteIndex, 0, DelayLine.Num());
}
}
void FInterpolatedMultiTapDelay::Read(const float StartDelayMSec, const float EndDelayMSec, FAlignedFloatBuffer& OutSamples)
{
const int32 OutputSamples = OutSamples.Num();
check (OutputSamples % AUDIO_NUM_FLOATS_PER_VECTOR_REGISTER == 0);
// likely to only run on the first frame, if not configured with enough memory
if (OutputSamples > DelayLine.Num())
{
DelayLine.SetNumZeroed(OutputSamples);
}
const int32 BufferSize = DelayLine.Num();
const int32 StartSample = AlignIndex(FMath::Wrap(WriteIndex - FMath::FloorToInt32(StartDelayMSec * MsToSamples), 0, BufferSize - 1));
const int32 EndSample = AlignIndex(FMath::Wrap(WriteIndex + OutputSamples - FMath::CeilToInt32(EndDelayMSec * MsToSamples), 0, BufferSize - 1));
float* OutputPtr = OutSamples.GetData();
if (StartSample >= EndSample)
{
const int32 DelaySamples = EndSample - (StartSample - BufferSize);
// block wraps
const int32 FirstBlockDelaySamples = BufferSize - StartSample;
const int32 SecondBlockDelaySamples = DelaySamples - FirstBlockDelaySamples;
const int32 FirstBlockOutputSamples = AlignIndex(((float)FirstBlockDelaySamples / (float)DelaySamples) * OutputSamples);
const int32 SecondBlockOuptutSamples = OutputSamples - FirstBlockOutputSamples;
ReadBlockInternal(StartSample, FirstBlockDelaySamples, FirstBlockOutputSamples, OutputPtr);
ReadBlockInternal(0, SecondBlockDelaySamples, SecondBlockOuptutSamples, OutputPtr + FirstBlockOutputSamples);
}
else
{
ReadBlockInternal(StartSample, EndSample - StartSample, OutputSamples, OutputPtr);
}
}
void FInterpolatedMultiTapDelay::Reset()
{
const int32 NumElements = DelayLine.Num();
DelayLine.Reset(NumElements);
DelayLine.AddZeroed(NumElements);
}
bool FInterpolatedMultiTapDelay::IsInitialized() const
{
return DelayLine.GetAllocatedSize() > 0 && MsToSamples > 1.f;
}
void FInterpolatedMultiTapDelay::ReadBlockInternal(const int32 StartSample, const int32 SamplesToRead, const int32 NumOutputSamples, float* OutSamples)
{
if (NumOutputSamples == 0 || SamplesToRead == 0)
{
return;
}
check(StartSample + SamplesToRead <= DelayLine.Num())
check(NumOutputSamples % AUDIO_NUM_FLOATS_PER_VECTOR_REGISTER == 0)
const float SampleStride = (float)SamplesToRead / (float)NumOutputSamples;
// protect against overflows while wrapping without branching in the main loop
int32 MainLoopOutputSamples = NumOutputSamples;
int32 MainLoopReadSamples = SamplesToRead;
if (StartSample + SamplesToRead == DelayLine.Num())
{
MainLoopOutputSamples -= AUDIO_NUM_FLOATS_PER_VECTOR_REGISTER;
MainLoopReadSamples -= FMath::RoundToInt32((float)AUDIO_NUM_FLOATS_PER_VECTOR_REGISTER * SampleStride);
}
ArrayInterpolate(&DelayLine[StartSample], OutSamples, MainLoopReadSamples, MainLoopOutputSamples);
float SampleIndex = MainLoopReadSamples;
// wrap in the loop only if close to wrapping: protects against rounding up past last index
for (int32 OutputIndex = MainLoopOutputSamples; OutputIndex < NumOutputSamples; OutputIndex++)
{
const int32 LeftSample = FMath::FloorToInt32(SampleIndex);
int32 RightSample = FMath::CeilToInt32(SampleIndex);
if (RightSample >= DelayLine.Num())
{
RightSample -= DelayLine.Num();
}
const float Frac = SampleIndex - LeftSample;
OutSamples[OutputIndex] = (Frac * DelayLine[LeftSample]) + ((1.f - Frac) * DelayLine[RightSample]);
SampleIndex += SampleStride;
}
}
int32 FInterpolatedMultiTapDelay::AlignIndex(const int32 InIndex) const
{
return InIndex - (InIndex % AUDIO_NUM_FLOATS_PER_VECTOR_REGISTER);
}
}