Files
UnrealEngine/Engine/Source/Runtime/SignalProcessing/Private/SampleBufferReader.cpp
2025-05-18 13:04:45 +08:00

391 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DSP/SampleBufferReader.h"
#include "DSP/SinOsc.h"
namespace Audio
{
FSampleBufferReader::FSampleBufferReader()
: BufferPtr(nullptr)
, BufferNumSamples(0)
, BufferNumFrames(0)
, BufferSampleRate(0)
, BufferNumChannels(0)
, FadeFrames(512)
, FadeValue(0.0f)
, FadeIncrement(1.0f / (float)FadeFrames)
, DeviceSampleRate(0.0f)
, BasePitch(1.0f)
, PitchScale(1.0f)
, CurrentFrameIndex(0)
, NextFrameIndex(0)
, AlphaLerp(0.0f)
, CurrentBufferFrameIndexInterpolated(0.0)
, PlaybackProgress(0.0f)
, ScrubAnchorFrame(0.0)
, ScrubMinFrame(0.0)
, ScrubMaxFrame(0.0)
, ScrubWidthFrames(0.0)
, CurrentSeekTime(0.0f)
, CurrentScrubWidthSec(0.0f)
, CurrentSeekType(ESeekType::FromBeginning)
, bWrap(false)
, bIsScrubMode(false)
, bIsFinished(false)
{
}
FSampleBufferReader::~FSampleBufferReader()
{
}
void FSampleBufferReader::Init(const int32 InSampleRate)
{
DeviceSampleRate = InSampleRate;
BufferPtr = nullptr;
BufferNumSamples = 0;
BufferNumFrames = 0;
BufferSampleRate = 0;
BufferNumChannels = 0;
CurrentFrameIndex = 0;
NextFrameIndex = 0;
AlphaLerp = 0.0f;
Pitch.Init(DeviceSampleRate);
Pitch.SetValue(1.0, 0.0f);
BasePitch = 1.0f;
bIsFinished = false;
CurrentBufferFrameIndexInterpolated = 0.0;
ScrubAnchorFrame = 0.0;
ScrubMinFrame = 0.0;
ScrubMaxFrame = 0.0;
// Default the scrub width to 0.1 seconds
bIsScrubMode = false;
ScrubWidthFrames = 0.1 * DeviceSampleRate;
PlaybackProgress = 0.0f;
}
void FSampleBufferReader::SetBuffer(const int16* InBufferPtr, const int32 InNumBufferSamples, const int32 InNumChannels, const int32 InBufferSampleRate)
{
// Re-init on setting a new buffer
Init(InBufferSampleRate);
BufferPtr = InBufferPtr;
BufferNumSamples = InNumBufferSamples;
BufferNumChannels = InNumChannels;
BufferSampleRate = InBufferSampleRate;
BufferNumFrames = BufferNumSamples / BufferNumChannels;
// This is the base pitch to use play at the "correct" sample rate for the buffer to sound correct on the output device sample rate
BasePitch = BufferSampleRate / DeviceSampleRate;
// Set the pitch to the previous pitch scale.
Pitch.SetValueInterrupt(PitchScale * BasePitch);
bIsFinished = false;
UpdateScrubMinAndMax();
}
void FSampleBufferReader::ClearBuffer()
{
BufferPtr = nullptr;
BufferNumSamples = 0;
BufferNumChannels = 0;
BufferSampleRate = 0;
BufferNumFrames = 0;
}
void FSampleBufferReader::UpdateSeekFrame()
{
if (BufferPtr)
{
check(BufferNumChannels > 0);
const float CurrentSeekFrame = ((float)BufferSampleRate * CurrentSeekTime);
if (CurrentSeekType == ESeekType::FromBeginning)
{
CurrentBufferFrameIndexInterpolated = (double)CurrentSeekFrame;
}
else if (CurrentSeekType == ESeekType::FromEnd)
{
CurrentBufferFrameIndexInterpolated = (double)(BufferNumFrames - CurrentSeekFrame - 1);
}
else
{
CurrentBufferFrameIndexInterpolated += (double)CurrentSeekFrame;
}
if (bWrap)
{
while (CurrentBufferFrameIndexInterpolated >= (double)BufferNumFrames)
{
CurrentBufferFrameIndexInterpolated -= (double)BufferNumFrames;
}
while (CurrentBufferFrameIndexInterpolated < 0.0)
{
CurrentBufferFrameIndexInterpolated += (double)BufferNumFrames;
}
check(CurrentBufferFrameIndexInterpolated >= 0.0 && CurrentBufferFrameIndexInterpolated < (double)BufferNumFrames);
}
else
{
CurrentBufferFrameIndexInterpolated = FMath::Clamp(CurrentBufferFrameIndexInterpolated, 0.0, (double)BufferNumFrames);
}
}
ScrubAnchorFrame = CurrentBufferFrameIndexInterpolated;
}
void FSampleBufferReader::SeekTime(const float InTimeSec, const ESeekType::Type InSeekType, const bool bInWrap)
{
CurrentSeekTime = InTimeSec;
CurrentSeekType = InSeekType;
bWrap = bInWrap;
if (bIsScrubMode)
{
UpdateSeekFrame();
UpdateScrubMinAndMax();
}
else
{
UpdateSeekFrame();
}
}
void FSampleBufferReader::SetScrubTimeWidth(const float InScrubTimeWidthSec)
{
CurrentScrubWidthSec = InScrubTimeWidthSec;
UpdateScrubMinAndMax();
}
void FSampleBufferReader::SetPitch(const float InPitch, const float InterpolationTimeSec)
{
PitchScale = InPitch;
Pitch.SetValue(PitchScale * BasePitch, InterpolationTimeSec);
}
void FSampleBufferReader::SetScrubMode(const bool bInIsScrubMode)
{
bIsScrubMode = bInIsScrubMode;
// Anchor the current frame index as the scrub anchor
ScrubAnchorFrame = CurrentBufferFrameIndexInterpolated;
UpdateSeekFrame();
UpdateScrubMinAndMax();
}
void FSampleBufferReader::UpdateScrubMinAndMax()
{
UpdateSeekFrame();
if (BufferNumFrames > 0)
{
ScrubWidthFrames = (double)(DeviceSampleRate * FMath::Max(CurrentScrubWidthSec, 0.001f));
ScrubWidthFrames = FMath::Min((double)(BufferNumFrames - 1), ScrubWidthFrames);
// Don't allow the scrub width to be less than 2 times the scrubwidth frames
ScrubWidthFrames = FMath::Max(ScrubWidthFrames, (double)2*FadeFrames);
ScrubMinFrame = ScrubAnchorFrame - 0.5 * ScrubWidthFrames;
ScrubMaxFrame = ScrubAnchorFrame + 0.5 * ScrubWidthFrames;
}
}
float FSampleBufferReader::GetSampleValue(const int16* InBuffer, const int32 SampleIndex)
{
int16 PCMSampleValue = InBuffer[SampleIndex];
return (float)PCMSampleValue / 32767.0f;
}
bool FSampleBufferReader::Generate(float* OutAudioBuffer, const int32 NumFrames, const int32 OutChannels, const bool bInWrap)
{
// Don't have a buffer yet, so fill in zeros, say we're not done
const int32 NumSamples = NumFrames * OutChannels;
if (!HasBuffer() || bIsFinished)
{
FMemory::Memzero(OutAudioBuffer, NumSamples * sizeof(float));
return false;
}
#if 0
static FSineOsc SineOsc(48000.0f, 440.0f, 0.2f);
int32 SampleIndex = 0;
for (int32 FrameIndex = 0; FrameIndex < NumFrames; ++FrameIndex)
{
const float Value = 0.1f * SineOsc.ProcessAudio();
for (int32 Channel = 0; Channel < OutChannels; ++Channel)
{
OutAudioBuffer[SampleIndex++] = Value;
}
}
#else
// We always want to wrap if we're in scrub mode
const bool bDoWrap = bInWrap || bIsScrubMode;
int32 OutSampleIndex = 0;
for (int32 i = 0; i < NumFrames && !bIsFinished; ++i)
{
float CurrentPitch = Pitch.GetNextValue();
// Don't let the pitch go to 0.
if (FMath::IsNearlyZero(CurrentPitch))
{
CurrentPitch = SMALL_NUMBER;
}
// We're going forward in the buffer
if (CurrentPitch > 0.0f)
{
CurrentFrameIndex = FMath::FloorToInt(CurrentBufferFrameIndexInterpolated);
NextFrameIndex = CurrentFrameIndex + 1;
AlphaLerp = CurrentBufferFrameIndexInterpolated - (double)CurrentFrameIndex;
if (!bIsScrubMode && !bWrap)
{
if (NextFrameIndex >= BufferNumFrames)
{
bIsFinished = true;
}
}
}
else
{
CurrentFrameIndex = FMath::CeilToInt(CurrentBufferFrameIndexInterpolated);
NextFrameIndex = CurrentFrameIndex - 1;
AlphaLerp = (double)CurrentFrameIndex - CurrentBufferFrameIndexInterpolated;
if (!bIsScrubMode && !bWrap)
{
if (NextFrameIndex < 0)
{
bIsFinished = true;
}
}
}
if (!bIsFinished)
{
// Check for scrub boundaries and wrap. Note that we've already wrapped on the buffer boundary at this point.
if (bWrap || bIsScrubMode)
{
int32 MinWrapFrame = 0;
int32 MaxWrapFrame = BufferNumFrames;
if (bIsScrubMode)
{
MinWrapFrame = ScrubMinFrame;
MaxWrapFrame = ScrubMaxFrame;
}
if (CurrentPitch > 0.0f && NextFrameIndex >= MaxWrapFrame)
{
NextFrameIndex = MinWrapFrame;
CurrentFrameIndex = (int32)(MaxWrapFrame - 1.0f);
CurrentBufferFrameIndexInterpolated = FMath::Fmod(CurrentBufferFrameIndexInterpolated, 1.0) + (double)(NextFrameIndex);
}
else if (NextFrameIndex < MinWrapFrame)
{
NextFrameIndex = (int32)(MaxWrapFrame - 1);
CurrentFrameIndex = (int32)MinWrapFrame;
CurrentBufferFrameIndexInterpolated = FMath::Fmod(CurrentBufferFrameIndexInterpolated, 1.0) + (double)NextFrameIndex;
}
CurrentFrameIndex = FMath::CeilToInt(CurrentBufferFrameIndexInterpolated);
NextFrameIndex = CurrentFrameIndex - 1;
AlphaLerp = (double)CurrentFrameIndex - CurrentBufferFrameIndexInterpolated;
FadeValue = 1.0f;
int32 MaxFadeInFrame = MinWrapFrame + FadeFrames;
if (CurrentFrameIndex >= MinWrapFrame && CurrentFrameIndex < MaxFadeInFrame)
{
FadeValue = (float)(CurrentFrameIndex - MinWrapFrame) / FadeFrames;
}
else if (CurrentFrameIndex >= MaxWrapFrame - FadeFrames && CurrentFrameIndex < MaxWrapFrame)
{
FadeValue = 1.0f - (float)(CurrentFrameIndex - (MaxWrapFrame - FadeFrames)) / FadeFrames;
}
}
else
{
FadeValue = 1.0f;
}
if (OutChannels == BufferNumChannels)
{
for (int32 Channel = 0; Channel < BufferNumChannels; ++Channel)
{
OutAudioBuffer[OutSampleIndex++] = FadeValue * GetSampleValueForChannel(Channel);
}
}
else if (OutChannels == 1 && BufferNumChannels == 2)
{
float LeftChannel = FadeValue * GetSampleValueForChannel(0);
float RightChannel = FadeValue * GetSampleValueForChannel(1);
OutAudioBuffer[OutSampleIndex++] = 0.5f * (LeftChannel + RightChannel);
}
else if (OutChannels == 2 && BufferNumChannels == 1)
{
float Sample = FadeValue * GetSampleValueForChannel(0);
OutAudioBuffer[OutSampleIndex++] = 0.5f * Sample;
OutAudioBuffer[OutSampleIndex++] = 0.5f * Sample;
}
CurrentBufferFrameIndexInterpolated += CurrentPitch;
}
}
#endif
if (OutSampleIndex < NumSamples)
{
FMemory::Memzero(&OutAudioBuffer[OutSampleIndex], (NumSamples - OutSampleIndex) * sizeof(float));
}
return bIsFinished;
}
static int32 WrapIndex(int32 Value, int32 Max)
{
if (Value < 0)
{
Value += Max;
}
else if (Value >= Max)
{
Value -= Max;
}
return Value;
}
float FSampleBufferReader::GetSampleValueForChannel(const int32 Channel)
{
if (BufferPtr)
{
// Wrap the current frame index
int32 WrappedCurrentFrameIndex = WrapIndex(CurrentFrameIndex, BufferNumFrames);
int32 WrappedNextFrameIndex = WrapIndex(NextFrameIndex, BufferNumFrames);
// Update the current playback time
PlaybackProgress = ((float)WrappedCurrentFrameIndex) / BufferSampleRate;
const int32 CurrentBufferSampleIndex = BufferNumChannels * WrappedCurrentFrameIndex + Channel;
const int32 NextBufferSampleIndex = BufferNumChannels * WrappedNextFrameIndex + Channel;
const float CurrentSampleValue = GetSampleValue(BufferPtr, CurrentBufferSampleIndex);
const float NextSampleValue = GetSampleValue(BufferPtr, NextBufferSampleIndex);
return FMath::Lerp(CurrentSampleValue, NextSampleValue, AlphaLerp);
}
return 0.0f;
}
}