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

182 lines
4.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DSP/Phaser.h"
namespace Audio
{
FPhaser::FPhaser()
: ControlSampleCount(0)
, ControlRate(256)
, Frequency(0.2f)
, WetLevel(0.4f)
, Feedback(0.2f)
, LFOType(ELFO::Sine)
, NumChannels(0)
, bIsBiquadPhase(true)
{
}
FPhaser::~FPhaser()
{
}
void FPhaser::Init(const float SampleRate, const int32 InNumChannels)
{
check(NumChannels <= MaxNumChannels);
NumChannels = InNumChannels;
// Initialize all the APFs
for (int32 Channel = 0; Channel < NumChannels; ++Channel)
{
for (int32 ApfIndex = 0; ApfIndex < NumApfs; ++ApfIndex)
{
APFs[Channel][ApfIndex].Init(SampleRate, 1, EBiquadFilter::AllPass);
}
}
const float ControlSampleRate = SampleRate / ControlRate;
LFO.Init(ControlSampleRate);
LFO.SetFrequency(Frequency);
LFO.SetType(LFOType);
LFO.Update();
LFO.Start();
// Setup the LFO oscillation ranges for APF cutoff frequencies
// Use ratios of current sample rate
const float SampleRateRatio = SampleRate / 48000.0f;
APFFrequencyRanges[0].X = 16.0f * SampleRateRatio;
APFFrequencyRanges[0].Y = 1600.0f * SampleRateRatio;
APFFrequencyRanges[1].X = 33.0f * SampleRateRatio;
APFFrequencyRanges[1].Y = 3300.0f * SampleRateRatio;
APFFrequencyRanges[2].X = 48.0f * SampleRateRatio;
APFFrequencyRanges[2].Y = 4800.0f * SampleRateRatio;
APFFrequencyRanges[3].X = 98.0f * SampleRateRatio;
APFFrequencyRanges[3].Y = 9800.0f * SampleRateRatio;
APFFrequencyRanges[4].X = 160.0f * SampleRateRatio;
APFFrequencyRanges[4].Y = 16000.0f * SampleRateRatio;
// Make sure final APF frequency won't go above Nyquist
APFFrequencyRanges[5].X = 220.0f * SampleRateRatio;
APFFrequencyRanges[5].Y = 22000.0f * SampleRateRatio;
FeedbackFrame[0] = 0.0f;
FeedbackFrame[1] = 0.0f;
}
void FPhaser::SetFrequency(const float InFreqHz)
{
if (InFreqHz != Frequency)
{
Frequency = FMath::Max(InFreqHz, SMALL_NUMBER);
LFO.SetFrequency(Frequency);
LFO.Update();
}
}
void FPhaser::SetWetLevel(const float InWetLevel)
{
if (InWetLevel != WetLevel)
{
WetLevel = FMath::Clamp(InWetLevel, 0.0f, 1.0f);
}
}
void FPhaser::SetFeedback(const float InFeedback)
{
if (Feedback != InFeedback)
{
Feedback = FMath::Clamp(InFeedback, 0.0f, 1.0f);
}
}
void FPhaser::SetLFOType(const ELFO::Type InLFOType)
{
if (InLFOType != LFOType)
{
LFOType = InLFOType;
LFO.SetType(LFOType);
LFO.Update();
}
}
void FPhaser::SetQuadPhase(const bool bInQuadPhase)
{
bIsBiquadPhase = bInQuadPhase;
}
void FPhaser::ComputeNewCoefficients(const int32 ChannelIndex, const float LFOValue)
{
for (int32 APFIndex = 0; APFIndex < NumApfs; ++APFIndex)
{
// Get the APF biquad
FBiquadFilter& BiquadFilter = APFs[ChannelIndex][APFIndex];
// Compute a new cutoff frequency
FVector2D& FreqRange = APFFrequencyRanges[APFIndex];
const float NewFrequencyCutoff = FMath::Lerp(FreqRange.X, FreqRange.Y, LFOValue);
BiquadFilter.SetFrequency(NewFrequencyCutoff);
}
}
void FPhaser::ProcessAudioFrame(const float* InFrame, float* OutFrame)
{
ControlSampleCount = ControlSampleCount & (ControlRate - 1);
if (ControlSampleCount == 0)
{
float LFOQuadOutput = 0.0f;
float LFOOutput = LFO.Generate(&LFOQuadOutput);
// Convert to unipolar
LFOOutput = Audio::GetUnipolar(LFOOutput);
LFOOutput = FMath::Clamp(LFOOutput, 0.0f, 1.0f);
// Use the LFO to compute new filter coefficients
ComputeNewCoefficients(0, LFOOutput);
ComputeNewCoefficients(1, bIsBiquadPhase ? GetUnipolar(LFOQuadOutput) : LFOOutput);
}
++ControlSampleCount;
float InputAudioFrame[2];
for (int32 Channel = 0; Channel < NumChannels; ++Channel)
{
InputAudioFrame[Channel] = InFrame[Channel] + FeedbackFrame[Channel] * Feedback;
float InSample = InputAudioFrame[Channel];
float OutSample = 0.0f;
// Feed the audio through the APF chain
for (int32 ApfIndex = 0; ApfIndex < NumApfs; ++ApfIndex)
{
APFs[Channel][ApfIndex].ProcessAudioFrame(&InSample, &OutSample);
InSample = OutSample;
}
// Store the last output sample in the feedback frame
FeedbackFrame[Channel] = OutSample;
// Mix in the wet level for the output
OutFrame[Channel] = WetLevel * OutSample + (1.0f - WetLevel) * InFrame[Channel];
}
}
void FPhaser::ProcessAudio(const float* InBuffer, const int32 InNumSamples, float* OutBuffer)
{
for (int32 SampleIndex = 0; SampleIndex < InNumSamples; SampleIndex += NumChannels)
{
ProcessAudioFrame(&InBuffer[SampleIndex], &OutBuffer[SampleIndex]);
}
}
}