182 lines
4.5 KiB
C++
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]);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|