Files
UnrealEngine/Engine/Source/Runtime/SignalProcessing/Public/SampleBuffer.h
2025-05-18 13:04:45 +08:00

492 lines
15 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "DSP/BufferVectorOperations.h"
#include "DSP/FloatArrayMath.h"
namespace Audio
{
typedef int16 DefaultUSoundWaveSampleType;
/************************************************************************/
/* TSampleBuffer<class SampleType> */
/* This class owns an audio buffer. */
/* To convert between fixed Q15 buffers and float buffers, */
/* Use the assignment operator. Example: */
/* */
/* TSampleBuffer<float> AFloatBuffer; */
/* TSampleBuffer<int16> AnIntBuffer = AFloatBuffer; */
/************************************************************************/
template <class SampleType = DefaultUSoundWaveSampleType>
class TSampleBuffer
{
private:
// raw PCM data buffer
TArray<SampleType> RawPCMData;
// The number of samples in the buffer
int32 NumSamples;
// The number of frames in the buffer
int32 NumFrames;
// The number of channels in the buffer
int32 NumChannels;
// The sample rate of the buffer
int32 SampleRate;
// The duration of the buffer in seconds
float SampleDuration;
public:
// Ensure that we can trivially copy construct private members between templated TSampleBuffers:
template <class> friend class TSampleBuffer;
FORCEINLINE TSampleBuffer()
: NumSamples(0)
, NumFrames(0)
, NumChannels(0)
, SampleRate(0)
, SampleDuration(0.0f)
{}
FORCEINLINE TSampleBuffer(const TSampleBuffer& Other)
{
NumSamples = Other.NumSamples;
NumFrames = Other.NumFrames;
NumChannels = Other.NumChannels;
SampleRate = Other.SampleRate;
SampleDuration = Other.SampleDuration;
RawPCMData.Reset(NumSamples);
RawPCMData.AddUninitialized(NumSamples);
FMemory::Memcpy(RawPCMData.GetData(), Other.RawPCMData.GetData(), NumSamples * sizeof(SampleType));
}
FORCEINLINE TSampleBuffer(const FAlignedFloatBuffer& InData, int32 InNumChannels, int32 InSampleRate)
{
*this = TSampleBuffer(InData.GetData(), InData.Num(), InNumChannels, InSampleRate);
}
FORCEINLINE TSampleBuffer(const float* InBufferPtr, int32 InNumSamples, int32 InNumChannels, int32 InSampleRate)
{
NumSamples = InNumSamples;
NumFrames = NumSamples / InNumChannels;
NumChannels = InNumChannels;
SampleRate = InSampleRate;
SampleDuration = ((float)NumFrames) / SampleRate;
RawPCMData.Reset(NumSamples);
RawPCMData.AddUninitialized(NumSamples);
if constexpr(std::is_same_v<SampleType, float>)
{
FMemory::Memcpy(RawPCMData.GetData(), InBufferPtr, NumSamples * sizeof(float));
}
else if constexpr(std::is_same_v<SampleType, int16>)
{
// Convert from float to int:
for (int32 SampleIndex = 0; SampleIndex < NumSamples; SampleIndex++)
{
RawPCMData[SampleIndex] = (int16)(FMath::Clamp(InBufferPtr[SampleIndex], -1.0f, 1.0f) * 32767.0f);
}
}
else
{
// for any other types, we don't know how to explicitly convert, so we fall back to casts:
for (int32 SampleIndex = 0; SampleIndex < NumSamples; SampleIndex++)
{
RawPCMData[SampleIndex] = (SampleType)(InBufferPtr[SampleIndex]);
}
}
}
FORCEINLINE TSampleBuffer(const int16* InBufferPtr, int32 InNumSamples, int32 InNumChannels, int32 InSampleRate)
{
NumSamples = InNumSamples;
NumFrames = NumSamples / InNumChannels;
NumChannels = InNumChannels;
SampleRate = InSampleRate;
SampleDuration = ((float)NumFrames) / SampleRate;
RawPCMData.Reset(NumSamples);
RawPCMData.AddUninitialized(NumSamples);
if constexpr(std::is_same_v<SampleType, int16>)
{
FMemory::Memcpy(RawPCMData.GetData(), InBufferPtr, NumSamples * sizeof(int16));
}
else if constexpr(std::is_same_v<SampleType, float>)
{
// Convert from int to float:
Audio::ArrayPcm16ToFloat(MakeArrayView(InBufferPtr, NumSamples), RawPCMData);
}
else
{
// for any other types, we don't know how to explicitly convert, so we fall back to casts:
for (int32 SampleIndex = 0; SampleIndex < NumSamples; SampleIndex++)
{
RawPCMData[SampleIndex] = (SampleType)(InBufferPtr[SampleIndex]);
}
}
}
// Vanilla assignment operator:
TSampleBuffer& operator=(const TSampleBuffer& Other)
{
NumSamples = Other.NumSamples;
NumFrames = Other.NumFrames;
NumChannels = Other.NumChannels;
SampleRate = Other.SampleRate;
SampleDuration = Other.SampleDuration;
RawPCMData.Reset(NumSamples);
RawPCMData.AddUninitialized(NumSamples);
FMemory::Memcpy(RawPCMData.GetData(), Other.RawPCMData.GetData(), NumSamples * sizeof(SampleType));
return *this;
}
//SampleType converting assignment operator:
template<class OtherSampleType>
TSampleBuffer& operator=(const TSampleBuffer<OtherSampleType>& Other)
{
NumSamples = Other.NumSamples;
NumFrames = Other.NumFrames;
NumChannels = Other.NumChannels;
SampleRate = Other.SampleRate;
SampleDuration = Other.SampleDuration;
RawPCMData.Reset(NumSamples);
RawPCMData.AddUninitialized(NumSamples);
if constexpr(std::is_same_v<SampleType, OtherSampleType>)
{
// If buffers are of the same type, copy over:
FMemory::Memcpy(RawPCMData.GetData(), Other.RawPCMData.GetData(), NumSamples * sizeof(SampleType));
}
else if constexpr(std::is_same_v<SampleType, int16> && std::is_same_v<OtherSampleType, float>)
{
// Convert from float to int:
Audio::ArrayFloatToPcm16(MakeArrayView(Other.RawPCMData), MakeArrayView(RawPCMData));
}
else if constexpr(std::is_same_v<SampleType, float> && std::is_same_v<OtherSampleType, int16>)
{
// Convert from int to float:
Audio::ArrayPcm16ToFloat(MakeArrayView(Other.RawPCMData), MakeArrayView(RawPCMData));
}
else
{
// for any other types, we don't know how to explicitly convert, so we fall back to casts:
for (int32 SampleIndex = 0; SampleIndex < NumSamples; SampleIndex++)
{
RawPCMData[SampleIndex] = (SampleType)Other.RawPCMData[SampleIndex];
}
}
return *this;
}
// copy from a container of the same element type
void CopyFrom(const TArray<SampleType>& InArray, int32 InNumChannels, int32 InSampleRate)
{
NumSamples = InArray.Num();
NumFrames = NumSamples / InNumChannels;
NumChannels = InNumChannels;
SampleRate = InSampleRate;
SampleDuration = ((float)NumFrames) / SampleRate;
RawPCMData.Reset(NumSamples);
RawPCMData.AddZeroed(NumSamples);
FMemory::Memcpy(RawPCMData.GetData(), InArray.GetData(), NumSamples * sizeof(SampleType));
}
// Append audio data to internal buffer of different sample type of this sample buffer
template<class OtherSampleType>
void Append(const OtherSampleType* InputBuffer, int32 InNumSamples)
{
int32 StartIndex = RawPCMData.AddUninitialized(InNumSamples);
if constexpr(std::is_same_v<SampleType, OtherSampleType>)
{
FMemory::Memcpy(&RawPCMData[StartIndex], InputBuffer, InNumSamples * sizeof(SampleType));
}
else
{
if constexpr(std::is_same_v<SampleType, int16> && std::is_same_v<OtherSampleType, float>)
{
// Convert from float to int:
Audio::ArrayFloatToPcm16(MakeArrayView(InputBuffer, InNumSamples), MakeArrayView(&RawPCMData[StartIndex], InNumSamples));
}
else if constexpr(std::is_same_v<SampleType, float> && std::is_same_v<OtherSampleType, int16>)
{
// Convert from int to float:
Audio::ArrayPcm16ToFloat(MakeArrayView(InputBuffer, InNumSamples), MakeArrayView(&RawPCMData[StartIndex], InNumSamples));
}
else
{
// for any other types, we don't know how to explicitly convert, so we fall back to casts:
for (int32 SampleIndex = 0; SampleIndex < InNumSamples; SampleIndex++)
{
RawPCMData[StartIndex + SampleIndex] = InputBuffer[SampleIndex];
}
}
}
// Update meta-data
NumSamples += InNumSamples;
NumFrames = NumSamples / NumChannels;
SampleDuration = (float)NumFrames / SampleRate;
}
// Overload of Append that also sets the number of channels and sample rate.
template<class OtherSampleType>
void Append(const OtherSampleType* InputBuffer, int32 InNumSamples, int32 InNumChannels, int32 InSampleRate)
{
NumChannels = InNumChannels;
SampleRate = InSampleRate;
Append(InputBuffer, InNumSamples);
}
~TSampleBuffer() {};
void Reset()
{
RawPCMData.Reset();
NumSamples = 0;
NumFrames = 0;
NumChannels = 0;
SampleRate = 0.0f;
SampleDuration = 0.0f;
}
// Gets the raw PCM data of the sound wave
FORCEINLINE const SampleType* GetData() const
{
return RawPCMData.GetData();
}
FORCEINLINE TArrayView<SampleType> GetArrayView()
{
return MakeArrayView(RawPCMData);
}
FORCEINLINE TArrayView<const SampleType> GetArrayView() const
{
return MakeArrayView(RawPCMData);
}
// Gets the number of samples of the sound wave
FORCEINLINE int32 GetNumSamples() const
{
return NumSamples;
}
// Gets the number of frames of the sound wave
FORCEINLINE int32 GetNumFrames() const
{
return NumFrames;
}
// Gets the number of channels of the sound wave
FORCEINLINE int32 GetNumChannels() const
{
return NumChannels;
}
// Gets the sample rate of the sound wave
FORCEINLINE int32 GetSampleRate() const
{
return SampleRate;
}
FORCEINLINE float GetSampleDuration() const
{
return SampleDuration;
}
void MixBufferToChannels(int32 InNumChannels)
{
if (!RawPCMData.Num() || InNumChannels <= 0)
{
return;
}
TUniquePtr<SampleType[]> TempBuffer;
TempBuffer.Reset(new SampleType[InNumChannels * NumFrames]);
FMemory::Memset(TempBuffer.Get(), 0, InNumChannels * NumFrames * sizeof(SampleType));
const SampleType* SrcBuffer = GetData();
// Downmixing using the channel modulo assumption:
// TODO: Use channel matrix for channel conversions.
for (int32 FrameIndex = 0; FrameIndex < NumFrames; FrameIndex++)
{
for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ChannelIndex++)
{
const int32 DstSampleIndex = FrameIndex * InNumChannels + (ChannelIndex % InNumChannels);
const int32 SrcSampleIndex = FrameIndex * NumChannels + ChannelIndex;
TempBuffer[DstSampleIndex] += SrcBuffer[SrcSampleIndex];
}
}
NumChannels = InNumChannels;
NumSamples = NumFrames * NumChannels;
// Resize our buffer and copy the result in:
RawPCMData.Reset(NumSamples);
RawPCMData.AddUninitialized(NumSamples);
FMemory::Memcpy(RawPCMData.GetData(), TempBuffer.Get(), NumSamples * sizeof(SampleType));
}
void Clamp(float Ceiling = 1.0f)
{
if constexpr(std::is_same_v<SampleType, float>)
{
// Float case:
float ClampMin = Ceiling * -1.0f;
for (int32 SampleIndex = 0; SampleIndex < RawPCMData.Num(); SampleIndex++)
{
RawPCMData[SampleIndex] = static_cast<SampleType>(FMath::Clamp<float>(RawPCMData[SampleIndex], ClampMin, Ceiling));
}
}
else if constexpr(std::is_same_v<SampleType, int16>)
{
// int16 case:
Ceiling = FMath::Clamp(Ceiling, 0.0f, 1.0f);
int16 ClampMax = static_cast<int16>(Ceiling * 32767.0f);
int16 ClampMin = static_cast<int16>(Ceiling * -32767.0f);
for (int32 SampleIndex = 0; SampleIndex < NumSamples; SampleIndex++)
{
RawPCMData[SampleIndex] = FMath::Clamp<int16>(RawPCMData[SampleIndex], ClampMin, ClampMax);
}
}
else
{
// Unknown type case:
float ClampMin = Ceiling * -1.0f;
for (int32 SampleIndex = 0; SampleIndex < RawPCMData.Num(); SampleIndex++)
{
RawPCMData[SampleIndex] = static_cast<SampleType>(FMath::Clamp<SampleType>(RawPCMData[SampleIndex], ClampMin, Ceiling));
}
}
}
/**
* Appends zeroes to the end of this buffer.
* If called with no arguments or NumFramesToAppend = 0, this will ZeroPad
*/
void ZeroPad(int32 NumFramesToAppend = 0)
{
if (!NumFramesToAppend)
{
NumFramesToAppend = FMath::RoundUpToPowerOfTwo(NumFrames) - NumFrames;
}
RawPCMData.AddZeroed(NumFramesToAppend * NumChannels);
NumFrames += NumFramesToAppend;
NumSamples = NumFrames * NumChannels;
}
void SetNumFrames(int32 InNumFrames)
{
RawPCMData.SetNum(InNumFrames * NumChannels);
NumFrames = RawPCMData.Num() / NumChannels;
NumSamples = RawPCMData.Num();
}
// InIndex [0.0f, NumSamples - 1.0f]
// OutFrame is the multichannel output for one index value
// Returns InIndex wrapped between 0.0 and NumFrames
float GetAudioFrameAtFractionalIndex(float InIndex, TArray<SampleType>& OutFrame) const
{
InIndex = FMath::Fmod(InIndex, static_cast<float>(NumFrames));
GetAudioFrameAtFractionalIndexInternal(InIndex, OutFrame);
return InIndex;
}
// InPhase [0, 1], wrapped, through duration of file (ignores sample rate)
// OutFrame is the multichannel output for one phase value
// Returns InPhase wrapped between 0.0 and 1.0
float GetAudioFrameAtPhase(float InPhase, TArray<SampleType>& OutFrame) const
{
InPhase = FMath::Fmod(InPhase, 1.0f);
GetAudioFrameAtFractionalIndexInternal(InPhase * NumFrames, OutFrame);
return InPhase;
}
// InTimeSec, get the value of the buffer at the given time (uses sample rate)
// OutFrame is the multichannel output for one time value
// Returns InTimeSec wrapped between 0.0 and (NumSamples / SampleRate)
float GetAudioFrameAtTime(float InTimeSec, TArray<SampleType>& OutFrame) const
{
if (InTimeSec >= SampleDuration)
{
InTimeSec -= SampleDuration;
}
check(InTimeSec >= 0.0f && InTimeSec <= SampleDuration);
GetAudioFrameAtFractionalIndexInternal(NumSamples * (InTimeSec / SampleDuration), OutFrame);
return InTimeSec;
}
private:
// Internal implementation. Called by all public GetAudioFrameAt_ _ _ _() functions
// public functions do range checking/wrapping and then call this function
void GetAudioFrameAtFractionalIndexInternal(float InIndex, TArray<SampleType>& OutFrame) const
{
const float Alpha = FMath::Fmod(InIndex, 1.0f);
const int32 WholeThisIndex = FMath::FloorToInt(InIndex);
int32 WholeNextIndex = WholeThisIndex + 1;
// check for interpolation between last and first frames
if (WholeNextIndex == NumFrames)
{
WholeNextIndex = 0;
}
// TODO: if(NumChannels < 4)... do the current (non vectorized) way
OutFrame.SetNumUninitialized(NumChannels);
for (int32 i = 0; i < NumChannels; ++i)
{
float SampleA, SampleB;
if constexpr(std::is_same_v<SampleType, float>)
{
SampleA = RawPCMData[(WholeThisIndex * NumChannels) + i];
SampleB = RawPCMData[(WholeNextIndex * NumChannels) + i];
OutFrame[i] = FMath::Lerp(SampleA, SampleB, Alpha);
}
else
{
SampleA = static_cast<float>(RawPCMData[(WholeThisIndex * NumChannels) + i]);
SampleB = static_cast<float>(RawPCMData[(WholeNextIndex * NumChannels) + i]);
OutFrame[i] = static_cast<SampleType>(FMath::Lerp(SampleA, SampleB, Alpha));
}
}
// TODO: else { do vectorized version }
// make new function in BufferVectorOperations.cpp
// (use FMath::Lerp() overload for VectorRegisters)
}
};
// FSampleBuffer is a strictly defined TSampleBuffer that uses the same sample format we use for USoundWaves.
typedef TSampleBuffer<> FSampleBuffer;
}