You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- Add missing exponential factor to Modulation generator - Fix bounds restriction calculation for expontential factor - Add LFO ResetPhase implementation to override initial location in oscilator on rising edge where necessary (for modulation LFO, user range limited to unipolar but hardly ever wants to start at midpoint). - Add bipolar option to avoid oneshot/bipolar conflation for certain types of LFOs - Add back implementation for 5.1 deprecated function to avoid breaking licensees #rb phil.popp [REVIEW] phil.popp #jira UE-127886 #preflight 6346eed9b757820b7ae99b76 [CL 22483928 by rob gay in ue5-main branch]
307 lines
6.0 KiB
C++
307 lines
6.0 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "DSP/LFO.h"
|
|
#include "DSP/Dsp.h"
|
|
|
|
|
|
namespace Audio
|
|
{
|
|
FLFO::FLFO()
|
|
: LFOType(ELFO::Sine)
|
|
, LFOMode(ELFOMode::Sync)
|
|
, ExponentialFactor(3.5f)
|
|
, RSHCounter(INDEX_NONE)
|
|
, RSHValue(0.0f)
|
|
, ModScale(1.0f)
|
|
, ModAdd(0.0f)
|
|
, LastOutput(0.0f)
|
|
, LoopCount(0.0f)
|
|
, QuadLastOutput(0.0f)
|
|
, bBipolar(true)
|
|
{
|
|
}
|
|
|
|
void FLFO::Init(const float InSampleRate, const int32 InVoiceId, FModulationMatrix* InMatrix, const int32 ModMatrixStage)
|
|
{
|
|
IOscBase::Init(InSampleRate, InVoiceId, InMatrix, ModMatrixStage);
|
|
|
|
if (ModMatrix)
|
|
{
|
|
ModNormalPhase = ModMatrix->CreatePatchSource(InVoiceId);
|
|
ModQuadPhase = ModMatrix->CreatePatchSource(InVoiceId);
|
|
|
|
#if MOD_MATRIX_DEBUG_NAMES
|
|
ModNormalPhase.Name = TEXT("ModNormalPhase");
|
|
ModQuadPhase.Name = TEXT("ModQuadPhase");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void FLFO::Start()
|
|
{
|
|
if (LFOMode == ELFOMode::Sync || LFOMode == ELFOMode::OneShot)
|
|
{
|
|
Reset();
|
|
}
|
|
else if (!bIsPlaying)
|
|
{
|
|
ResetPhase();
|
|
}
|
|
|
|
bIsPlaying = true;
|
|
}
|
|
|
|
void FLFO::Stop()
|
|
{
|
|
bIsPlaying = false;
|
|
}
|
|
|
|
void FLFO::Reset()
|
|
{
|
|
// reset base class first
|
|
IOscBase::Reset();
|
|
|
|
ResetPhase();
|
|
|
|
RSHValue = 0.0f;
|
|
RSHCounter = INDEX_NONE;
|
|
}
|
|
|
|
void FLFO::ResetPhase()
|
|
{
|
|
// Reset loop count
|
|
LoopCount = 0.0f;
|
|
|
|
// Set initial default phase to zero crossing and rising edge where
|
|
// possible (omits "phase offset" input to allow for client system
|
|
// to set to non-zero if desired).
|
|
switch (LFOType)
|
|
{
|
|
case ELFO::Sine:
|
|
case ELFO::Triangle:
|
|
{
|
|
Phase = bBipolar ? 0.25f : 0.0f;
|
|
}
|
|
break;
|
|
|
|
case ELFO::DownSaw:
|
|
case ELFO::UpSaw:
|
|
{
|
|
Phase = bBipolar ? 0.5f : 0.0f;
|
|
}
|
|
break;
|
|
|
|
case ELFO::Exponential:
|
|
{
|
|
Phase = bBipolar ? FMath::Pow(0.5f, 1.0f / ExponentialFactor) : 0.0f;
|
|
}
|
|
break;
|
|
|
|
case ELFO::RandomSampleHold:
|
|
case ELFO::Square:
|
|
default:
|
|
{
|
|
static_assert(static_cast<int32>(ELFO::NumLFOTypes) == 7, "Possible missing switch case coverage");
|
|
Phase = 0.0f;
|
|
}
|
|
break;
|
|
};
|
|
}
|
|
|
|
void FLFO::SetBipolar(const bool bInBipolar)
|
|
{
|
|
bBipolar = bInBipolar;
|
|
}
|
|
|
|
void FLFO::SetPhaseOffset(const float InOffset)
|
|
{
|
|
PhaseOffset = FMath::Fmod(FMath::Max(0.0f, InOffset), 1.0f);
|
|
}
|
|
|
|
void FLFO::SetType(const ELFO::Type InLFOType)
|
|
{
|
|
LFOType = InLFOType;
|
|
}
|
|
|
|
ELFO::Type FLFO::GetType() const
|
|
{
|
|
return LFOType;
|
|
}
|
|
|
|
void FLFO::SetMode(const ELFOMode::Type InLFOMode)
|
|
{
|
|
LFOMode = InLFOMode;
|
|
}
|
|
|
|
ELFOMode::Type FLFO::GetMode() const
|
|
{
|
|
return LFOMode;
|
|
}
|
|
|
|
void FLFO::SetExponentialFactor(const float InExpFactor)
|
|
{
|
|
ExponentialFactor = FMath::Max(InExpFactor, UE_SMALL_NUMBER);
|
|
}
|
|
|
|
FPatchSource FLFO::GetModSourceNormalPhase() const
|
|
{
|
|
return ModNormalPhase;
|
|
}
|
|
|
|
FPatchSource FLFO::GetModSourceQuadPhase() const
|
|
{
|
|
return ModQuadPhase;
|
|
}
|
|
|
|
float FLFO::Generate(float* QuadPhaseOutput)
|
|
{
|
|
// If the LFO isn't playing, return last computed value for both output & quad.
|
|
if (!bIsPlaying)
|
|
{
|
|
if (QuadPhaseOutput)
|
|
{
|
|
*QuadPhaseOutput = QuadLastOutput;
|
|
}
|
|
|
|
return LastOutput;
|
|
}
|
|
|
|
WrapPhase();
|
|
LastOutput = ComputeLFO(GetPhase(), QuadPhaseOutput);
|
|
|
|
// Update the LFO phase after computing LFO values
|
|
LoopCount += PhaseInc;
|
|
UpdatePhase();
|
|
|
|
// If in oneshot mode, check if wrapped and if so, turn the LFO off and compute last value.
|
|
if (LFOMode == ELFOMode::OneShot)
|
|
{
|
|
if (LoopCount >= 1.0f)
|
|
{
|
|
bIsPlaying = false;
|
|
LoopCount = 0.0f;
|
|
}
|
|
}
|
|
|
|
// Return the output
|
|
return LastOutput;
|
|
}
|
|
|
|
float FLFO::ComputeLFO(const float InPhase, float* OutQuad)
|
|
{
|
|
float Output = 0.0f;
|
|
float QuadOutput = 0.0f;
|
|
|
|
const float CurPhase = FMath::Fmod(InPhase + PhaseOffset, 1.0f);
|
|
const float QuadPhase = FMath::Fmod(InPhase + PhaseOffset + 0.25f , 1.0f);
|
|
|
|
switch (LFOType)
|
|
{
|
|
case ELFO::Sine:
|
|
{
|
|
// Must subtract pi and flip sign to guarantee in valid range for FastSin function,
|
|
// yet still starts on rising edge from 0 crossing.
|
|
auto ComputeSine = [](float InputPhase)
|
|
{
|
|
if (InputPhase > 0.5f)
|
|
{
|
|
InputPhase -= 1.0f;
|
|
}
|
|
const float Angle = 2.0f * InputPhase * PI;
|
|
return 0.5f * Audio::FastSin(Angle) + 0.5f;
|
|
};
|
|
Output = ComputeSine(CurPhase);
|
|
QuadOutput = ComputeSine(QuadPhase);
|
|
}
|
|
break;
|
|
|
|
case ELFO::UpSaw:
|
|
{
|
|
Output = CurPhase;
|
|
QuadOutput = QuadPhase;
|
|
}
|
|
break;
|
|
|
|
case ELFO::DownSaw:
|
|
{
|
|
Output = 1.0f - CurPhase;
|
|
QuadOutput = 1.0f - QuadPhase;
|
|
}
|
|
break;
|
|
|
|
case ELFO::Square:
|
|
{
|
|
Output = CurPhase > PulseWidth ? 0.0f : 1.0f;
|
|
QuadOutput = QuadPhase > PulseWidth ? 0.0f : 1.0f;
|
|
}
|
|
break;
|
|
|
|
case ELFO::Triangle:
|
|
{
|
|
Output = 1.0f - FMath::Abs(GetBipolar(CurPhase));
|
|
QuadOutput = 1.0f - FMath::Abs(GetBipolar(QuadPhase));
|
|
}
|
|
break;
|
|
|
|
case ELFO::Exponential:
|
|
{
|
|
Output = FMath::Pow(CurPhase, ExponentialFactor);
|
|
QuadOutput = FMath::Pow(QuadPhase, ExponentialFactor);
|
|
}
|
|
break;
|
|
|
|
case ELFO::RandomSampleHold:
|
|
{
|
|
const float FrequencyThreshold = SampleRate / Freq;
|
|
if (RSHCounter > (uint32)FrequencyThreshold)
|
|
{
|
|
RSHCounter = 0;
|
|
RSHValue = FMath::FRand();
|
|
}
|
|
else
|
|
{
|
|
++RSHCounter;
|
|
}
|
|
|
|
Output = RSHValue;
|
|
QuadOutput = RSHValue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (bBipolar)
|
|
{
|
|
Output = GetBipolar(Output);
|
|
QuadOutput = GetBipolar(QuadOutput);
|
|
}
|
|
|
|
const float MaxGain = Gain * ExternalGainMod;
|
|
Output = Output * MaxGain;
|
|
QuadOutput = QuadOutput * MaxGain;
|
|
|
|
// If we have a mod matrix, then mix in the destination data
|
|
// This allows LFO's (or envelopes, etc) to modulation this LFO
|
|
if (ModMatrix)
|
|
{
|
|
ModMatrix->GetDestinationValue(VoiceId, ModScaleDest, ModAdd);
|
|
ModMatrix->GetDestinationValue(VoiceId, ModAddDest, ModScale);
|
|
|
|
Output = Output * ModScale + ModAdd;
|
|
QuadOutput = QuadOutput * ModScale + ModAdd;
|
|
|
|
// Write out the modulations
|
|
ModMatrix->SetSourceValue(VoiceId, ModNormalPhase, Output);
|
|
ModMatrix->SetSourceValue(VoiceId, ModQuadPhase, QuadOutput);
|
|
}
|
|
|
|
QuadLastOutput = QuadOutput;
|
|
|
|
if (OutQuad)
|
|
{
|
|
*OutQuad = QuadOutput;
|
|
}
|
|
|
|
return Output;
|
|
}
|
|
} // namespace Audio
|