You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#jira UE-152354 #rb phil.popp #preflight 62853718614041edb79f8bb0 #p4v-cherrypick 20265411 #preflight 628eceb7a3384b0a96b92c64 #ROBOMERGE-AUTHOR: anna.lantz #ROBOMERGE-SOURCE: CL 20404406 in //UE5/Release-5.0/... via CL 20406514 #ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v949-20362246) [CL 20406985 by anna lantz in ue5-main branch]
1560 lines
57 KiB
C++
1560 lines
57 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
#include "MetasoundOscillatorNodes.h"
|
|
|
|
#include "DSP/Dsp.h"
|
|
|
|
#include "MetasoundAudioBuffer.h"
|
|
#include "MetasoundEnumRegistrationMacro.h"
|
|
#include "MetasoundExecutableOperator.h"
|
|
#include "MetasoundFacade.h"
|
|
#include "MetasoundNodeRegistrationMacro.h"
|
|
#include "MetasoundDataTypeRegistrationMacro.h"
|
|
#include "MetasoundParamHelper.h"
|
|
#include "MetasoundPrimitives.h"
|
|
#include "MetasoundStandardNodesCategories.h"
|
|
#include "MetasoundStandardNodesNames.h"
|
|
#include "MetasoundTime.h"
|
|
#include "MetasoundTrigger.h"
|
|
#include "MetasoundVertex.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "MetasoundStandardNodes_OscillatorNodes"
|
|
|
|
namespace Metasound
|
|
{
|
|
#pragma region Common
|
|
|
|
// Contained here are a small selection of block generators and sample generators.
|
|
// They are templatized to allow quick construction of new oscillators, but aren't considered
|
|
// optimal (as they contain many branches) and they should be only as a default
|
|
// until Vectorized versions replace them.
|
|
|
|
namespace Generators
|
|
{
|
|
// Standard params passed to the Generate Block templates below
|
|
struct FGeneratorArgs
|
|
{
|
|
float SampleRate = 0.f;
|
|
float FrequencyHz = 0.f;
|
|
float GlideEaseFactor = 0.0f;
|
|
float PulseWidth = 0.f;
|
|
bool BiPolar = true;
|
|
TArrayView<float> AlignedBuffer;
|
|
TArrayView<const float> FM;
|
|
};
|
|
|
|
// Functor used when we need to wrap the phase of some of the oscillators. Sinf for example does not care
|
|
struct FWrapPhase
|
|
{
|
|
FORCEINLINE void operator()(float& InPhase) const
|
|
{
|
|
while (InPhase >= 1.f)
|
|
{
|
|
InPhase -= 1.f;
|
|
}
|
|
|
|
while (InPhase < 0.0f)
|
|
{
|
|
InPhase += 1.f;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Functor that does nothing to the phase and lets it climb indefinitely
|
|
struct FPhaseLetClimb
|
|
{
|
|
FORCEINLINE void operator()(const float&) const {};
|
|
};
|
|
|
|
// Smooth out the edges of the saw based on its current frequency
|
|
// using a polynomial to smooth it at the discontinuity. This
|
|
// limits aliasing by avoiding the infinite frequency at the discontinuity.
|
|
FORCEINLINE float PolySmoothSaw(const float InPhase, const float InPhaseDelta)
|
|
{
|
|
float Output = 0.0f;
|
|
float AbsolutePhaseDelta = FMath::Abs(InPhaseDelta);
|
|
|
|
// The current phase is on the left side of discontinuity
|
|
if (InPhase > 1.0f - AbsolutePhaseDelta)
|
|
{
|
|
const float Dist = (InPhase - 1.0f) / AbsolutePhaseDelta;
|
|
Output = -Dist * Dist - 2.0f * Dist - 1.0f;
|
|
}
|
|
// The current phase is on the right side of the discontinuity
|
|
else if (InPhase < AbsolutePhaseDelta)
|
|
{
|
|
// Distance into polynomial
|
|
const float Dist = InPhase / AbsolutePhaseDelta;
|
|
Output = Dist * Dist - 2.0f * Dist + 1.0f;
|
|
}
|
|
return Output;
|
|
}
|
|
|
|
// Functor that does the a generic generate block with a supplied Oscillator.
|
|
// The PhaseWrap functor controls how the phase is accumulated and wrapped
|
|
template<
|
|
typename Oscillator,
|
|
typename PhaseWrap = FWrapPhase
|
|
>
|
|
struct TGenerateBlock
|
|
{
|
|
float Phase = 0.f;
|
|
Oscillator Osc;
|
|
PhaseWrap Wrap;
|
|
float CurrentFade = 0.0f;
|
|
float FadeSmooth = 0.01f;
|
|
float CurrentFreq = -1.f;
|
|
|
|
void operator()(const FGeneratorArgs& InArgs)
|
|
{
|
|
int32 RemainingSamplesInBlock = InArgs.AlignedBuffer.Num();
|
|
float* Out = InArgs.AlignedBuffer.GetData();
|
|
const float OneOverSampleRate = 1.f / InArgs.SampleRate;
|
|
|
|
// TODO: break this into separate calls because there is a lot of code duplication to prevent per sample branching
|
|
if (InArgs.BiPolar)
|
|
{
|
|
// Constant Freq between this an last update?
|
|
if (FMath::IsNearlyEqual(InArgs.FrequencyHz, CurrentFreq) || CurrentFreq < 0.f || FMath::IsNearlyEqual(InArgs.GlideEaseFactor, 1.0f))
|
|
{
|
|
CurrentFreq = InArgs.FrequencyHz;
|
|
const float DeltaPhase = InArgs.FrequencyHz * OneOverSampleRate;
|
|
while (RemainingSamplesInBlock > 0)
|
|
{
|
|
Wrap(Phase);
|
|
|
|
float Output = Osc(Phase, DeltaPhase, InArgs);
|
|
Output *= CurrentFade;
|
|
*Out++ = Output;
|
|
|
|
CurrentFade = CurrentFade + FadeSmooth * (1.0f - CurrentFade);
|
|
|
|
Phase += DeltaPhase;
|
|
|
|
RemainingSamplesInBlock--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (RemainingSamplesInBlock > 0)
|
|
{
|
|
Wrap(Phase);
|
|
|
|
const float DeltaPhase = CurrentFreq * OneOverSampleRate;
|
|
|
|
float Output = Osc(Phase, DeltaPhase, InArgs);
|
|
Output *= CurrentFade;
|
|
*Out++ = Output;
|
|
|
|
CurrentFade = CurrentFade + FadeSmooth * (1.0f - CurrentFade);
|
|
|
|
Phase += DeltaPhase;
|
|
|
|
// lerp frequency based on the glide factor
|
|
CurrentFreq = CurrentFreq + InArgs.GlideEaseFactor * (InArgs.FrequencyHz - CurrentFreq);
|
|
|
|
RemainingSamplesInBlock--;
|
|
}
|
|
}
|
|
}
|
|
else // unipolar
|
|
{
|
|
// Constant Freq between this an last update?
|
|
if (FMath::IsNearlyEqual(InArgs.FrequencyHz, CurrentFreq) || CurrentFreq < 0.f || FMath::IsNearlyEqual(InArgs.GlideEaseFactor, 1.0f))
|
|
{
|
|
CurrentFreq = InArgs.FrequencyHz;
|
|
const float DeltaPhase = InArgs.FrequencyHz * OneOverSampleRate;
|
|
while (RemainingSamplesInBlock > 0)
|
|
{
|
|
Wrap(Phase);
|
|
|
|
float Output = Osc(Phase, DeltaPhase, InArgs);
|
|
Output = Audio::GetUnipolar(Output);
|
|
Output *= CurrentFade;
|
|
*Out++ = Output;
|
|
|
|
CurrentFade = CurrentFade + FadeSmooth * (1.0f - CurrentFade);
|
|
|
|
Phase += DeltaPhase;
|
|
|
|
RemainingSamplesInBlock--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (RemainingSamplesInBlock > 0)
|
|
{
|
|
Wrap(Phase);
|
|
|
|
const float DeltaPhase = CurrentFreq * OneOverSampleRate;
|
|
|
|
float Output = Osc(Phase, DeltaPhase, InArgs);
|
|
Output = Audio::GetUnipolar(Output);
|
|
Output *= CurrentFade;
|
|
*Out++ = Output;
|
|
|
|
CurrentFade = CurrentFade + FadeSmooth * (1.0f - CurrentFade);
|
|
|
|
Phase += DeltaPhase;
|
|
|
|
// lerp frequency based on the glide factor
|
|
CurrentFreq = CurrentFreq + InArgs.GlideEaseFactor * (InArgs.FrequencyHz - CurrentFreq);
|
|
|
|
RemainingSamplesInBlock--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Functor that does the a generic generate block with a supplied Oscillator and applies an FM signal
|
|
// to the frequency per sample.
|
|
template<
|
|
typename Oscillator,
|
|
typename PhaseWrap = FWrapPhase
|
|
>
|
|
struct TGenerateBlockFM
|
|
{
|
|
float Phase = 0.f;
|
|
Oscillator Osc;
|
|
PhaseWrap Wrap;
|
|
|
|
float CurrentFade = 0.0f;
|
|
float FadeSmooth = 0.01f;
|
|
float CurrentFreq = -1.f;
|
|
|
|
void operator()(const FGeneratorArgs& InArgs)
|
|
{
|
|
float Nyquist = InArgs.SampleRate / 2.0f;
|
|
int32 RemainingSamplesInBlock = InArgs.AlignedBuffer.Num();
|
|
float* Out = InArgs.AlignedBuffer.GetData();
|
|
const float* FM = InArgs.FM.GetData();
|
|
const float OneOverSampleRate = 1.f / InArgs.SampleRate;
|
|
|
|
// TODO: break this into separate calls because there is a lot of code duplication to prevent per sample branching
|
|
if (InArgs.BiPolar)
|
|
{
|
|
// Constant Base Freq between this and last update?
|
|
if (FMath::IsNearlyEqual(InArgs.FrequencyHz, CurrentFreq) || CurrentFreq < 0.f || FMath::IsNearlyEqual(InArgs.GlideEaseFactor, 1.0f))
|
|
{
|
|
while (RemainingSamplesInBlock > 0)
|
|
{
|
|
const float PerSampleFreq = FMath::Clamp(InArgs.FrequencyHz + *FM++, -Nyquist, Nyquist);
|
|
const float DeltaPhase = PerSampleFreq * OneOverSampleRate;
|
|
|
|
Wrap(Phase);
|
|
|
|
float Output = Osc(Phase, DeltaPhase, InArgs);
|
|
Output *= CurrentFade;
|
|
*Out++ = Output;
|
|
|
|
CurrentFade = CurrentFade + FadeSmooth * (1.0f - CurrentFade);
|
|
|
|
Phase += DeltaPhase;
|
|
|
|
RemainingSamplesInBlock--;
|
|
}
|
|
|
|
CurrentFreq = InArgs.FrequencyHz;
|
|
}
|
|
else
|
|
{
|
|
while (RemainingSamplesInBlock > 0)
|
|
{
|
|
const float ModulatedFreqSum = FMath::Clamp(CurrentFreq + *FM++, -Nyquist, Nyquist);
|
|
const float DeltaPhase = ModulatedFreqSum * OneOverSampleRate;
|
|
|
|
Wrap(Phase);
|
|
|
|
float Output = Osc(Phase, DeltaPhase, InArgs);
|
|
Output *= CurrentFade;
|
|
*Out++ = Output;
|
|
|
|
CurrentFade = CurrentFade + FadeSmooth * (1.0f - CurrentFade);
|
|
|
|
Phase += DeltaPhase;
|
|
|
|
// lerp frequency based on the glide factor
|
|
CurrentFreq = CurrentFreq + InArgs.GlideEaseFactor * (InArgs.FrequencyHz - CurrentFreq);
|
|
|
|
RemainingSamplesInBlock--;
|
|
}
|
|
}
|
|
}
|
|
else // unipolar
|
|
{
|
|
// Constant Base Freq between this and last update?
|
|
if (FMath::IsNearlyEqual(InArgs.FrequencyHz, CurrentFreq) || CurrentFreq < 0.f || FMath::IsNearlyEqual(InArgs.GlideEaseFactor, 1.0f))
|
|
{
|
|
while (RemainingSamplesInBlock > 0)
|
|
{
|
|
const float PerSampleFreq = InArgs.FrequencyHz + *FM++;
|
|
const float DeltaPhase = PerSampleFreq * OneOverSampleRate;
|
|
|
|
Wrap(Phase);
|
|
|
|
float Output = Osc(Phase, DeltaPhase, InArgs);
|
|
Output = Audio::GetUnipolar(Output);
|
|
Output *= CurrentFade;
|
|
*Out++ = Output;
|
|
|
|
CurrentFade = CurrentFade + FadeSmooth * (1.0f - CurrentFade);
|
|
|
|
Phase += DeltaPhase;
|
|
|
|
RemainingSamplesInBlock--;
|
|
}
|
|
|
|
CurrentFreq = InArgs.FrequencyHz;
|
|
}
|
|
else
|
|
{
|
|
while (RemainingSamplesInBlock > 0)
|
|
{
|
|
const float ModulatedFreqSum = CurrentFreq + *FM++;
|
|
const float DeltaPhase = ModulatedFreqSum * OneOverSampleRate;
|
|
|
|
Wrap(Phase);
|
|
|
|
float Output = Osc(Phase, DeltaPhase, InArgs);
|
|
Output = Audio::GetUnipolar(Output);
|
|
Output *= CurrentFade;
|
|
*Out++ = Output;
|
|
|
|
CurrentFade = CurrentFade + FadeSmooth * (1.0f - CurrentFade);
|
|
|
|
Phase += DeltaPhase;
|
|
|
|
// lerp frequency based on the glide factor
|
|
CurrentFreq = CurrentFreq + InArgs.GlideEaseFactor * (InArgs.FrequencyHz - CurrentFreq);
|
|
|
|
RemainingSamplesInBlock--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Sine types and generators.
|
|
struct FSinfGenerator
|
|
{
|
|
FORCEINLINE float operator()(float InPhase, float, const FGeneratorArgs&)
|
|
{
|
|
static constexpr float TwoPi = 2.f * PI;
|
|
return FMath::Sin(InPhase * TwoPi);
|
|
}
|
|
};
|
|
struct FBhaskaraGenerator
|
|
{
|
|
float operator()(float InPhase, float, const FGeneratorArgs&) const
|
|
{
|
|
static const float TwoPi = 2.f * PI;
|
|
float PhaseRadians = InPhase * TwoPi;
|
|
|
|
float InBhaskaraDomain = (PhaseRadians < 0) ? PhaseRadians + PI : PhaseRadians - PI;
|
|
return Audio::FastSin3(InBhaskaraDomain); // Expects [-PI, PI]
|
|
}
|
|
};
|
|
struct FSineWaveTableGenerator
|
|
{
|
|
static const TArray<float>& GetWaveTable()
|
|
{
|
|
auto MakeSineTable = []() -> const TArray<float>
|
|
{
|
|
int32 TableSize = 4096;
|
|
// Generate the table
|
|
TArray<float> WaveTable;
|
|
WaveTable.AddUninitialized(TableSize);
|
|
float* WaveTableData = WaveTable.GetData();
|
|
for (int32 i = 0; i < TableSize; ++i)
|
|
{
|
|
static const float TwoPi = 2.f * PI;
|
|
float Phase = (float)i / TableSize;
|
|
WaveTableData[i] = FMath::Sin(Phase * TwoPi);
|
|
}
|
|
return WaveTable;
|
|
};
|
|
|
|
static const TArray<float> SineWaveTable = MakeSineTable();
|
|
return SineWaveTable;
|
|
}
|
|
|
|
float operator()(float InPhase, float, const FGeneratorArgs&) const
|
|
{
|
|
const TArray<float>& WaveTable = GetWaveTable();
|
|
int32 LastIndex = WaveTable.Num() - 1;
|
|
int32 TableIndex = (int32)(InPhase * (float)(LastIndex));
|
|
TableIndex = FMath::Wrap(TableIndex, 0, LastIndex);
|
|
return WaveTable[TableIndex];
|
|
}
|
|
};
|
|
|
|
struct F2DRotatorGenerateBlock
|
|
{
|
|
Audio::FSinOsc2DRotation Rotator;
|
|
F2DRotatorGenerateBlock(float InStartingLinearPhase)
|
|
: Rotator{ 2.f * PI * InStartingLinearPhase }
|
|
{}
|
|
void operator()(const FGeneratorArgs& Args)
|
|
{
|
|
Rotator.GenerateBuffer(Args.SampleRate, Args.FrequencyHz, Args.AlignedBuffer.GetData(), Args.AlignedBuffer.Num());
|
|
if (!Args.BiPolar)
|
|
{
|
|
Audio::ConvertBipolarBufferToUnipolar(Args.AlignedBuffer.GetData(), Args.AlignedBuffer.Num());
|
|
}
|
|
}
|
|
};
|
|
|
|
using FSinf = TGenerateBlock<FSinfGenerator>;
|
|
using FSinfWithFm = TGenerateBlockFM<FSinfGenerator>;
|
|
|
|
using FBhaskara = TGenerateBlock<FBhaskaraGenerator>;
|
|
using FBhaskaraWithFm = TGenerateBlockFM<FBhaskaraGenerator>;
|
|
|
|
using FSineWaveTable = TGenerateBlock<FSineWaveTableGenerator>;
|
|
using FSineWaveTableWithFm = TGenerateBlockFM<FSineWaveTableGenerator>;
|
|
|
|
// Saws.
|
|
struct FSawGenerator
|
|
{
|
|
FORCEINLINE float operator()(float InPhase, float, const FGeneratorArgs&)
|
|
{
|
|
return -1.f + (2.f * InPhase);
|
|
}
|
|
};
|
|
struct FSawPolySmoothGenerator
|
|
{
|
|
FORCEINLINE float operator()(float InPhase, float InPhaseDelta, const FGeneratorArgs&)
|
|
{
|
|
// Two-sided wave-shaped sawtooth
|
|
static const float A = Audio::FastTanh(1.5f);
|
|
float Output = Audio::GetBipolar(InPhase);
|
|
Output = Audio::FastTanh(1.5f * Output) / A;
|
|
Output += PolySmoothSaw(InPhase, InPhaseDelta);
|
|
return Output;
|
|
}
|
|
};
|
|
|
|
using FSaw = TGenerateBlock<FSawGenerator>;
|
|
using FSawWithFm = TGenerateBlockFM<FSawGenerator>;
|
|
|
|
using FSawPolysmooth = TGenerateBlock<FSawPolySmoothGenerator>;
|
|
using FSawPolysmoothWithFm = TGenerateBlockFM<FSawPolySmoothGenerator>;
|
|
|
|
// Square.
|
|
struct FSquareGenerator
|
|
{
|
|
FORCEINLINE float operator()(float InPhase, float InPhaseDelta, const FGeneratorArgs& InArgs)
|
|
{
|
|
float PulseWidthToUse = (InPhaseDelta >= 0) ? InArgs.PulseWidth : 1 - InArgs.PulseWidth;
|
|
|
|
return InPhase >= PulseWidthToUse ? 1.f : -1.f;
|
|
}
|
|
};
|
|
struct FSquarePolysmoothGenerator
|
|
{
|
|
float operator()(float InPhase, float InPhaseDelta, const FGeneratorArgs& InArgs)
|
|
{
|
|
// Taken piecemeal from Osc.cpp
|
|
// Lots of branches.
|
|
|
|
// First generate a smoothed sawtooth
|
|
float SquareSaw1 = Audio::GetBipolar(InPhase);
|
|
SquareSaw1 += PolySmoothSaw(InPhase, InPhaseDelta);
|
|
|
|
// Create a second sawtooth that is phase-shifted based on the pulsewidth
|
|
float NewPhase = 0.0f;
|
|
if (InPhaseDelta > 0.0f)
|
|
{
|
|
NewPhase = InPhase + InArgs.PulseWidth;
|
|
if (NewPhase >= 1.0f)
|
|
{
|
|
NewPhase -= 1.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NewPhase = InPhase - InArgs.PulseWidth;
|
|
if (NewPhase <= 0.0f)
|
|
{
|
|
NewPhase += 1.0f;
|
|
}
|
|
}
|
|
|
|
float SquareSaw2 = Audio::GetBipolar(NewPhase);
|
|
SquareSaw2 += PolySmoothSaw(NewPhase, InPhaseDelta);
|
|
|
|
// Subtract 2 saws, then apply DC correction
|
|
// Simplified version of
|
|
// float Output = 0.5f * SquareSaw1 - 0.5f * SquareSaw2;
|
|
// Output = 2.0f * (Output + InArgs.PulseWidth) - 1.0f;
|
|
if (InPhaseDelta > 0.0)
|
|
{
|
|
return SquareSaw1 - SquareSaw2 + 2.0f * (InArgs.PulseWidth - 0.5f);
|
|
}
|
|
else
|
|
{
|
|
return SquareSaw1 - SquareSaw2 + 2.0f * (0.5f - InArgs.PulseWidth);
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
using FSquare = TGenerateBlock<FSquareGenerator>;
|
|
using FSquareWithFm = TGenerateBlockFM<FSquareGenerator>;
|
|
|
|
using FSquarePolysmooth = TGenerateBlock<FSquarePolysmoothGenerator>;
|
|
using FSquarePolysmoothWithFm = TGenerateBlockFM<FSquarePolysmoothGenerator>;
|
|
|
|
// Triangle.
|
|
struct FTriangleGenerator
|
|
{
|
|
float TriangleSign = -1.0f;
|
|
float PrevPhase = -1.0f;
|
|
float DPW_z1 = 0.0f;
|
|
|
|
float operator()(float InPhase, float InPhaseDelta, const FGeneratorArgs& InArgs)
|
|
{
|
|
constexpr float FASTASIN_HALF_PI{ 1.5707963050f };
|
|
float PhaseRadians = (InPhase * PI * 2.f);
|
|
float Output = FMath::FastAsin(Audio::FastSin3(PhaseRadians - PI)) / FASTASIN_HALF_PI;
|
|
return Output;
|
|
}
|
|
};
|
|
|
|
using FTriangle = TGenerateBlock<FTriangleGenerator>;
|
|
using FTriangleWithFm = TGenerateBlockFM<FTriangleGenerator>;
|
|
|
|
// TODO: make a true polysmooth version
|
|
using FTrianglePolysmooth = TGenerateBlock<FTriangleGenerator>;
|
|
using FTrianglePolysmoothWithFm = TGenerateBlockFM<FTriangleGenerator>;
|
|
}
|
|
|
|
namespace OscillatorCommonVertexNames
|
|
{
|
|
METASOUND_PARAM(EnabledPin, "Enabled", "Enable the oscillator.")
|
|
METASOUND_PARAM(BiPolarPin, "Bi Polar", "If the output is Bi-Polar (-1..1) or Uni-Polar (0..1)")
|
|
METASOUND_PARAM(FrequencyModPin, "Modulation","Modulation Frequency Input (for doing FM)")
|
|
METASOUND_PARAM(OscBaseFrequencyPin, "Frequency", "Base Frequency of Oscillator in Hz.")
|
|
METASOUND_PARAM(OscPhaseResetPin, "Sync", "Phase Reset")
|
|
METASOUND_PARAM(PhaseOffsetPin, "Phase Offset", "Phase Offset In Degrees (0..360)")
|
|
METASOUND_PARAM(GlideFactorPin, "Glide", "The amount of glide to use when changing frequencies. 0.0 = no glide, 1.0 = lots of glide.")
|
|
METASOUND_PARAM(AudioOutPin, "Audio", "The output audio")
|
|
}
|
|
|
|
// Base class of Oscillator factories which holds common the interface.
|
|
class FOscilatorFactoryBase : public IOperatorFactory
|
|
{
|
|
public:
|
|
// Common to all Oscillators.
|
|
static FVertexInterface GetCommmonVertexInterface()
|
|
{
|
|
using namespace OscillatorCommonVertexNames;
|
|
|
|
static const FVertexInterface Interface
|
|
{
|
|
FInputVertexInterface{
|
|
TInputDataVertex<bool>(METASOUND_GET_PARAM_NAME_AND_METADATA(EnabledPin), true),
|
|
TInputDataVertex<bool>(METASOUND_GET_PARAM_NAME_AND_METADATA(BiPolarPin), true),
|
|
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(OscBaseFrequencyPin), 440.f),
|
|
TInputDataVertex<FAudioBuffer>(METASOUND_GET_PARAM_NAME_AND_METADATA(FrequencyModPin)),
|
|
TInputDataVertex<FTrigger>(METASOUND_GET_PARAM_NAME_AND_METADATA(OscPhaseResetPin)),
|
|
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(PhaseOffsetPin), 0.f),
|
|
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(GlideFactorPin), 0.f)
|
|
},
|
|
FOutputVertexInterface{
|
|
TOutputDataVertex<FAudioBuffer>(METASOUND_GET_PARAM_NAME_AND_METADATA(AudioOutPin))
|
|
}
|
|
};
|
|
return Interface;
|
|
}
|
|
};
|
|
|
|
// Common set of construct params for each of the Oscillators.
|
|
struct FOscillatorOperatorConstructParams
|
|
{
|
|
const FOperatorSettings& Settings;
|
|
FBoolReadRef Enabled;
|
|
FFloatReadRef BaseFrequency;
|
|
FFloatReadRef PhaseOffset;
|
|
FTriggerReadRef PhaseReset;
|
|
FFloatReadRef GlideFactor;
|
|
FBoolReadRef BiPolar;
|
|
};
|
|
|
|
// Base Oscillator Operator CRTP.
|
|
// Expects your Derived class to implement a Generate(int32 InStartFrame, int32 InEndFrame, float InFreq)
|
|
template<typename GeneratorPolicy, typename Derived>
|
|
class TOscillatorOperatorBase : public TExecutableOperator<Derived>
|
|
{
|
|
public:
|
|
TOscillatorOperatorBase(const FOscillatorOperatorConstructParams& InConstructParams)
|
|
: Generator{*InConstructParams.PhaseOffset}
|
|
, SampleRate(InConstructParams.Settings.GetSampleRate())
|
|
, Nyquist(InConstructParams.Settings.GetSampleRate() / 2.0f)
|
|
, Enabled(InConstructParams.Enabled)
|
|
, BaseFrequency(InConstructParams.BaseFrequency)
|
|
, PhaseReset(InConstructParams.PhaseReset)
|
|
, PhaseOffset(InConstructParams.PhaseOffset)
|
|
, GlideFactor(InConstructParams.GlideFactor)
|
|
, BiPolar(InConstructParams.BiPolar)
|
|
, AudioBuffer(FAudioBufferWriteRef::CreateNew(InConstructParams.Settings))
|
|
{
|
|
check(AudioBuffer->Num() == InConstructParams.Settings.GetNumFramesPerBlock());
|
|
}
|
|
|
|
FDataReferenceCollection GetInputs() const override
|
|
{
|
|
using namespace OscillatorCommonVertexNames;
|
|
|
|
FDataReferenceCollection InputDataReferences;
|
|
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(EnabledPin), Enabled);
|
|
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(OscBaseFrequencyPin), BaseFrequency);
|
|
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(PhaseOffsetPin), PhaseOffset);
|
|
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(OscPhaseResetPin), PhaseReset);
|
|
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(GlideFactorPin), GlideFactor);
|
|
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(BiPolarPin), BiPolar);
|
|
return InputDataReferences;
|
|
}
|
|
|
|
FDataReferenceCollection GetOutputs() const override
|
|
{
|
|
using namespace OscillatorCommonVertexNames;
|
|
|
|
FDataReferenceCollection OutputDataReferences;
|
|
OutputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(AudioOutPin), AudioBuffer);
|
|
return OutputDataReferences;
|
|
}
|
|
|
|
void ResetPhase(float InPhaseInDegrees)
|
|
{
|
|
float LinearPhase = FMath::Clamp(InPhaseInDegrees, 0.f, 360.f) / 360.f;
|
|
|
|
// Recreate the generator type with the phase requested.
|
|
Generator = GeneratorPolicy{ LinearPhase };
|
|
}
|
|
|
|
void Execute()
|
|
{
|
|
// Clamp frequencies into Nyquist range.
|
|
const float ClampedFreq = FMath::Clamp(*BaseFrequency, -Nyquist, Nyquist);
|
|
const float ClampedGlideEase = Audio::GetLogFrequencyClamped(*GlideFactor, { 0.0f, 1.0f }, { 1.0f, 0.0001f });
|
|
AudioBuffer->Zero();
|
|
|
|
Derived* Self = static_cast<Derived*>(this);
|
|
PhaseReset->ExecuteBlock
|
|
(
|
|
[Self, ClampedFreq, ClampedGlideEase](int32 InFrameStart, int32 InFrameEnd)
|
|
{
|
|
Self->Generate(InFrameStart, InFrameEnd, ClampedFreq, ClampedGlideEase);
|
|
},
|
|
[Self, ClampedFreq, ClampedGlideEase](int32 InFrameStart, int32 InFrameEnd)
|
|
{
|
|
Self->ResetPhase(*Self->PhaseOffset);
|
|
Self->Generate(InFrameStart, InFrameEnd, ClampedFreq, ClampedGlideEase);
|
|
}
|
|
);
|
|
}
|
|
|
|
protected:
|
|
GeneratorPolicy Generator;
|
|
float SampleRate;
|
|
float Nyquist;
|
|
|
|
FBoolReadRef Enabled;
|
|
FFloatReadRef BaseFrequency;
|
|
FTriggerReadRef PhaseReset;
|
|
FFloatReadRef PhaseOffset;
|
|
FFloatReadRef GlideFactor;
|
|
FBoolReadRef BiPolar;
|
|
|
|
FAudioBufferWriteRef AudioBuffer;
|
|
};
|
|
|
|
// Generic Oscillator operator for NON-FM Operators.
|
|
template<typename GeneratorPolicy>
|
|
class TOscillatorOperator final : public TOscillatorOperatorBase<GeneratorPolicy, TOscillatorOperator<GeneratorPolicy>>
|
|
{
|
|
using Super = TOscillatorOperatorBase<GeneratorPolicy, TOscillatorOperator<GeneratorPolicy>>;
|
|
public:
|
|
TOscillatorOperator(const FOscillatorOperatorConstructParams& InConstructParams)
|
|
: Super(InConstructParams)
|
|
{}
|
|
|
|
void Generate(int32 InStartFrame, int32 InEndFrame, float InClampedFreq, float InClampedGlideEase)
|
|
{
|
|
int32 NumFrames = InEndFrame - InStartFrame;
|
|
|
|
if (*this->Enabled && NumFrames > 0)
|
|
{
|
|
this->Generator(
|
|
{
|
|
this->SampleRate,
|
|
InClampedFreq,
|
|
InClampedGlideEase,
|
|
0.f,
|
|
*this->BiPolar,
|
|
MakeArrayView(this->AudioBuffer->GetData() + InStartFrame, NumFrames), // Not aligned.
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
// Generic Oscillator operator for FM Operators.
|
|
template<typename TGeneratorPolicy>
|
|
class TOscillatorOperatorFM final : public TOscillatorOperatorBase<TGeneratorPolicy, TOscillatorOperatorFM<TGeneratorPolicy>>
|
|
{
|
|
using Super = TOscillatorOperatorBase<TGeneratorPolicy, TOscillatorOperatorFM<TGeneratorPolicy>>;
|
|
public:
|
|
TOscillatorOperatorFM(const FOscillatorOperatorConstructParams& InCommonParams, const FAudioBufferReadRef& InFmData)
|
|
: Super(InCommonParams), Fm(InFmData)
|
|
{}
|
|
|
|
FDataReferenceCollection GetInputs() const override
|
|
{
|
|
using namespace OscillatorCommonVertexNames;
|
|
|
|
FDataReferenceCollection Inputs = Super::GetInputs();
|
|
Inputs.AddDataReadReference(METASOUND_GET_PARAM_NAME(FrequencyModPin), Fm);
|
|
return Inputs;
|
|
}
|
|
|
|
void Generate(int32 InStartFrame, int32 InEndFrame, float InClampedFreq, float InClampedGlideEase)
|
|
{
|
|
int32 NumFrames = InEndFrame - InStartFrame;
|
|
if (*this->Enabled && NumFrames > 0)
|
|
{
|
|
this->Generator(
|
|
{
|
|
this->SampleRate,
|
|
InClampedFreq,
|
|
InClampedGlideEase,
|
|
0.f,
|
|
*this->BiPolar,
|
|
MakeArrayView(this->AudioBuffer->GetData() + InStartFrame, NumFrames), // Not aligned.
|
|
MakeArrayView(this->Fm->GetData() + InStartFrame, NumFrames) // Not aligned.
|
|
});
|
|
}
|
|
}
|
|
|
|
private:
|
|
FAudioBufferReadRef Fm;
|
|
};
|
|
|
|
FOscilatorNodeBase::FOscilatorNodeBase(const FVertexName& InInstanceName, const FGuid& InInstanceID, const FNodeClassMetadata& InInfo, const TSharedRef<IOperatorFactory, ESPMode::ThreadSafe>& InFactory, float InDefaultFrequency, float InDefaultGlideFactor, bool bInDefaultEnablement)
|
|
: FNode(InInstanceName, InInstanceID, InInfo)
|
|
, Factory(InFactory)
|
|
, VertexInterface(GetMetadata().DefaultInterface)
|
|
, DefaultFrequency(InDefaultFrequency)
|
|
, DefaultGlideFactor(InDefaultGlideFactor)
|
|
, bDefaultEnablement(bInDefaultEnablement)
|
|
{}
|
|
|
|
#pragma endregion Common
|
|
|
|
#pragma region Sine
|
|
|
|
enum class ESineGenerationType
|
|
{
|
|
Rotation,
|
|
Sinf,
|
|
Bhaskara,
|
|
Wavetable,
|
|
};
|
|
|
|
DECLARE_METASOUND_ENUM(ESineGenerationType, ESineGenerationType::Wavetable, METASOUNDSTANDARDNODES_API,
|
|
FEnumSineGenerationType, FEnumSineGenerationTypeInfo, FEnumSineGenerationTypeReadRef, FEnumSineGenerationTypeWriteRef);
|
|
|
|
DEFINE_METASOUND_ENUM_BEGIN(ESineGenerationType, FEnumSineGenerationType, "SineGenerationType")
|
|
DEFINE_METASOUND_ENUM_ENTRY(ESineGenerationType::Rotation, "RotationDescription", "2D Rotation", "RotationDescriptionTT", "Rotates around the unit circle generate the sine. Note: Glide and audio rate FM modulation is not supported with the 2D rotator."),
|
|
DEFINE_METASOUND_ENUM_ENTRY(ESineGenerationType::Sinf, "SinfDescription", "Pure Math", "SinfDescriptionTT", "Uses the standard math library (Sinf) to generate the sine (most expensive)"),
|
|
DEFINE_METASOUND_ENUM_ENTRY(ESineGenerationType::Bhaskara, "BhaskaraDescription", "Bhaskara", "BhaskaraDescriptionTT", "Sine approximation using Bhaskara technique discovered in 7th century"),
|
|
DEFINE_METASOUND_ENUM_ENTRY(ESineGenerationType::Wavetable, "WavetableDescription", "Wavetable", "WavetableDescriptionTT", "Uses a wavetable to generate the sine"),
|
|
DEFINE_METASOUND_ENUM_END()
|
|
|
|
namespace SineOscilatorVertexNames
|
|
{
|
|
METASOUND_PARAM(SineType, "Type", "Type of the Sinewave Generator")
|
|
}
|
|
|
|
class FSineOscilatorNode::FFactory : public FOscilatorFactoryBase
|
|
{
|
|
public:
|
|
FFactory() = default;
|
|
|
|
static const FNodeClassMetadata& GetNodeInfo()
|
|
{
|
|
auto InitNodeInfo = []() -> FNodeClassMetadata
|
|
{
|
|
FNodeClassMetadata Info;
|
|
Info.ClassName = { StandardNodes::Namespace, TEXT("Sine"), StandardNodes::AudioVariant };
|
|
Info.MajorVersion = 1;
|
|
Info.MinorVersion = 1;
|
|
Info.DisplayName = METASOUND_LOCTEXT("Metasound_SineNodeDisplayName", "Sine");
|
|
Info.Description = METASOUND_LOCTEXT("Metasound_SineNodeDescription", "Emits an audio signal of a sinusoid.");
|
|
Info.Author = PluginAuthor;
|
|
Info.PromptIfMissing = PluginNodeMissingPrompt;
|
|
Info.DefaultInterface = GetVertexInterface();
|
|
Info.CategoryHierarchy.Emplace(NodeCategories::Generators);
|
|
return Info;
|
|
};
|
|
static const FNodeClassMetadata Info = InitNodeInfo();
|
|
return Info;
|
|
}
|
|
static const FVertexInterface& GetVertexInterface()
|
|
{
|
|
using namespace SineOscilatorVertexNames;
|
|
|
|
auto MakeInterface = []() -> FVertexInterface
|
|
{
|
|
FVertexInterface Interface = GetCommmonVertexInterface();
|
|
Interface.GetInputInterface().Add(
|
|
TInputDataVertex<FEnumSineGenerationType>(METASOUND_GET_PARAM_NAME_AND_METADATA(SineType), static_cast<int32>(ESineGenerationType::Wavetable))
|
|
);
|
|
return Interface;
|
|
};
|
|
static const FVertexInterface Interface = MakeInterface();
|
|
return Interface;
|
|
}
|
|
|
|
TUniquePtr<IOperator> CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) override
|
|
{
|
|
const FSineOscilatorNode& SineNode = static_cast<const FSineOscilatorNode&>(InParams.Node);
|
|
const FInputVertexInterfaceData& InputData = InParams.InputData;
|
|
const FOperatorSettings& Settings = InParams.OperatorSettings;
|
|
using namespace Generators;
|
|
using namespace OscillatorCommonVertexNames;
|
|
using namespace SineOscilatorVertexNames;
|
|
|
|
FOscillatorOperatorConstructParams OpParams
|
|
{
|
|
Settings,
|
|
InputData.GetOrConstructDataReadReference<bool>(METASOUND_GET_PARAM_NAME(EnabledPin), SineNode.GetDefaultEnablement()),
|
|
InputData.GetOrConstructDataReadReference<float>(METASOUND_GET_PARAM_NAME(OscBaseFrequencyPin), SineNode.GetDefaultFrequency()),
|
|
InputData.GetOrConstructDataReadReference<float>(METASOUND_GET_PARAM_NAME(PhaseOffsetPin), SineNode.GetDefaultPhaseOffset()),
|
|
InputData.GetOrConstructDataReadReference<FTrigger>(METASOUND_GET_PARAM_NAME(OscPhaseResetPin), Settings),
|
|
InputData.GetOrConstructDataReadReference<float>(METASOUND_GET_PARAM_NAME(GlideFactorPin), SineNode.GetDefaultGlideFactor()),
|
|
InputData.GetOrConstructDataReadReference<bool>(METASOUND_GET_PARAM_NAME(BiPolarPin), true)
|
|
};
|
|
|
|
// TODO: Make this a static prop. For now its a pin.
|
|
|
|
// Check to see if we have an FM input connected.
|
|
bool bHasFM = InputData.IsVertexBound(METASOUND_GET_PARAM_NAME(FrequencyModPin));
|
|
FEnumSineGenerationTypeReadRef Type = InputData.GetOrConstructDataReadReference<FEnumSineGenerationType>(METASOUND_GET_PARAM_NAME(SineType), ESineGenerationType::Wavetable);
|
|
if (bHasFM)
|
|
{
|
|
|
|
// FM Oscillators.
|
|
FAudioBufferReadRef FmBuffer = InputData.GetDataReadReference<FAudioBuffer>(METASOUND_GET_PARAM_NAME(FrequencyModPin));
|
|
switch (*Type)
|
|
{
|
|
default:
|
|
case ESineGenerationType::Sinf: return MakeUnique<TOscillatorOperatorFM<FSinfWithFm>>(OpParams, FmBuffer);
|
|
case ESineGenerationType::Bhaskara: return MakeUnique<TOscillatorOperatorFM<FBhaskaraWithFm>>(OpParams, FmBuffer);
|
|
case ESineGenerationType::Wavetable: return MakeUnique<TOscillatorOperatorFM<FSineWaveTableWithFm>>(OpParams, FmBuffer);
|
|
}
|
|
}
|
|
else //HasFM
|
|
{
|
|
switch (*Type)
|
|
{
|
|
default:
|
|
case ESineGenerationType::Sinf: return MakeUnique<TOscillatorOperator<FSinf>>(OpParams);
|
|
case ESineGenerationType::Rotation: return MakeUnique<TOscillatorOperator<F2DRotatorGenerateBlock>>(OpParams);
|
|
case ESineGenerationType::Bhaskara: return MakeUnique<TOscillatorOperator<FBhaskara>>(OpParams);
|
|
case ESineGenerationType::Wavetable: return MakeUnique<TOscillatorOperator<FSineWaveTable>>(OpParams);
|
|
}
|
|
} // HasFM
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
FSineOscilatorNode::FSineOscilatorNode(const FVertexName& InInstanceName, const FGuid& InInstanceID, float InDefaultFrequency, float InDefautlGlideFactor, bool bInDefaultEnablement)
|
|
: FOscilatorNodeBase(InInstanceName, InInstanceID, FFactory::GetNodeInfo(), MakeShared<FFactory, ESPMode::ThreadSafe>(), InDefaultFrequency, InDefautlGlideFactor, bInDefaultEnablement )
|
|
{}
|
|
|
|
FSineOscilatorNode::FSineOscilatorNode(const FNodeInitData& InInitData)
|
|
: FSineOscilatorNode(InInitData.InstanceName, InInitData.InstanceID, 440.0f, 0.0f, true)
|
|
{}
|
|
|
|
METASOUND_REGISTER_NODE(FSineOscilatorNode);
|
|
|
|
#pragma endregion Sine
|
|
|
|
#pragma region Saw
|
|
|
|
enum class ESawGenerationType
|
|
{
|
|
PolySmooth,
|
|
Trivial,
|
|
Wavetable
|
|
};
|
|
|
|
DECLARE_METASOUND_ENUM(ESawGenerationType, ESawGenerationType::PolySmooth, METASOUNDSTANDARDNODES_API,
|
|
FEnumSawGenerationType, FEnumSawGenerationTypeInfo, FSawGenerationTypeReadRef, FEnumSawGenerationTypeWriteRef);
|
|
|
|
DEFINE_METASOUND_ENUM_BEGIN(ESawGenerationType, FEnumSawGenerationType, "SawGenerationType")
|
|
DEFINE_METASOUND_ENUM_ENTRY(ESawGenerationType::PolySmooth, "SawPolySmoothDescription", "Poly Smooth", "PolySmoothDescriptionTT", "PolySmooth (i.e. BLEP)"),
|
|
DEFINE_METASOUND_ENUM_ENTRY(ESawGenerationType::Trivial, "SawTrivialDescription", "Trivial", "TrivialDescriptionTT", "The most basic raw implementation"),
|
|
//DEFINE_METASOUND_ENUM_ENTRY(ESawGenerationType::Wavetable, "SawWavetableDescription", "Wavetable", "SawWavetableDescriptionTT", "Use a Wavetable iterpolation to generate the Waveform")
|
|
DEFINE_METASOUND_ENUM_END()
|
|
|
|
namespace SawOscilatorVertexNames
|
|
{
|
|
METASOUND_PARAM(SawType, "Type", "Type of the Saw Generator")
|
|
}
|
|
|
|
class FSawOscilatorNode::FFactory : public FOscilatorFactoryBase
|
|
{
|
|
public:
|
|
FFactory() = default;
|
|
|
|
TUniquePtr<IOperator> CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) override;
|
|
|
|
static const FNodeClassMetadata& GetNodeInfo()
|
|
{
|
|
auto InitNodeInfo = []() -> FNodeClassMetadata
|
|
{
|
|
FNodeClassMetadata Info;
|
|
Info.ClassName = { StandardNodes::Namespace, TEXT("Saw"), StandardNodes::AudioVariant };
|
|
Info.MajorVersion = 1;
|
|
Info.MinorVersion = 0;
|
|
Info.DisplayName = METASOUND_LOCTEXT("Metasound_SawNodeDisplayName", "Saw");
|
|
Info.Description = METASOUND_LOCTEXT("Metasound_SawNodeDescription", "Emits a Saw wave");
|
|
Info.Author = PluginAuthor;
|
|
Info.PromptIfMissing = PluginNodeMissingPrompt;
|
|
Info.DefaultInterface = GetVertexInterface();
|
|
Info.CategoryHierarchy.Emplace(NodeCategories::Generators);
|
|
return Info;
|
|
};
|
|
static const FNodeClassMetadata Info = InitNodeInfo();
|
|
return Info;
|
|
}
|
|
|
|
static const FVertexInterface& GetVertexInterface()
|
|
{
|
|
using namespace SawOscilatorVertexNames;
|
|
|
|
auto MakeInterface = []() -> FVertexInterface
|
|
{
|
|
FVertexInterface Interface = GetCommmonVertexInterface();
|
|
Interface.GetInputInterface().Add(
|
|
TInputDataVertex<FEnumSawGenerationType>(METASOUND_GET_PARAM_NAME_AND_METADATA(SawType))
|
|
);
|
|
return Interface;
|
|
};
|
|
static const FVertexInterface Interface = MakeInterface();
|
|
return Interface;
|
|
}
|
|
};
|
|
|
|
TUniquePtr<Metasound::IOperator> FSawOscilatorNode::FFactory::CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults)
|
|
{
|
|
const FSawOscilatorNode& Node = static_cast<const FSawOscilatorNode&>(InParams.Node);
|
|
const FInputVertexInterfaceData& InputData = InParams.InputData;
|
|
const FOperatorSettings& Settings = InParams.OperatorSettings;
|
|
using namespace Generators;
|
|
using namespace OscillatorCommonVertexNames;
|
|
using namespace SawOscilatorVertexNames;
|
|
|
|
FSawGenerationTypeReadRef Type = InputData.GetOrConstructDataReadReference<FEnumSawGenerationType>(METASOUND_GET_PARAM_NAME(SawType));
|
|
|
|
FOscillatorOperatorConstructParams OpParams
|
|
{
|
|
Settings,
|
|
InputData.GetOrConstructDataReadReference<bool>(METASOUND_GET_PARAM_NAME(EnabledPin), Node.GetDefaultEnablement()),
|
|
InputData.GetOrConstructDataReadReference<float>(METASOUND_GET_PARAM_NAME(OscBaseFrequencyPin), Node.GetDefaultFrequency()),
|
|
InputData.GetOrConstructDataReadReference<float>(METASOUND_GET_PARAM_NAME(PhaseOffsetPin), Node.GetDefaultPhaseOffset()),
|
|
InputData.GetOrConstructDataReadReference<FTrigger>(METASOUND_GET_PARAM_NAME(OscPhaseResetPin), Settings),
|
|
InputData.GetOrConstructDataReadReference<float>(METASOUND_GET_PARAM_NAME(GlideFactorPin), Node.GetDefaultGlideFactor()),
|
|
InputData.GetOrConstructDataReadReference<bool>(METASOUND_GET_PARAM_NAME(BiPolarPin), true)
|
|
};
|
|
|
|
bool bHasFM = InputData.IsVertexBound(METASOUND_GET_PARAM_NAME(FrequencyModPin));
|
|
|
|
if (bHasFM)
|
|
{
|
|
FAudioBufferReadRef FmBuffer = InputData.GetDataReadReference<FAudioBuffer>(METASOUND_GET_PARAM_NAME(FrequencyModPin));
|
|
switch (*Type)
|
|
{
|
|
default:
|
|
case ESawGenerationType::Trivial: return MakeUnique<TOscillatorOperatorFM<FSawWithFm>>(OpParams, FmBuffer);
|
|
case ESawGenerationType::PolySmooth: return MakeUnique<TOscillatorOperatorFM<FSawPolysmoothWithFm>>(OpParams, FmBuffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (*Type)
|
|
{
|
|
default:
|
|
case ESawGenerationType::Trivial: return MakeUnique<TOscillatorOperator<FSaw>>(OpParams);
|
|
case ESawGenerationType::PolySmooth: return MakeUnique<TOscillatorOperator<FSawPolysmooth>>(OpParams);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
FSawOscilatorNode::FSawOscilatorNode(const FVertexName& InInstanceName, const FGuid& InInstanceID, float InDefaultFrequency, float InDefaultGlideFactor, bool bInDefaultEnablement)
|
|
: FOscilatorNodeBase(InInstanceName, InInstanceID, FFactory::GetNodeInfo(), MakeShared<FFactory, ESPMode::ThreadSafe>(), InDefaultFrequency, InDefaultGlideFactor, bInDefaultEnablement)
|
|
{}
|
|
|
|
FSawOscilatorNode::FSawOscilatorNode(const FNodeInitData& InInitData)
|
|
: FSawOscilatorNode(InInitData.InstanceName, InInitData.InstanceID, 440.0f, 0.0f, true)
|
|
{}
|
|
|
|
METASOUND_REGISTER_NODE(FSawOscilatorNode);
|
|
|
|
#pragma endregion Saw
|
|
|
|
#pragma region Square
|
|
|
|
enum class ESquareGenerationType
|
|
{
|
|
PolySmooth,
|
|
Trivial,
|
|
Wavetable
|
|
};
|
|
|
|
DECLARE_METASOUND_ENUM(ESquareGenerationType, ESquareGenerationType::PolySmooth, METASOUNDSTANDARDNODES_API,
|
|
FEnumSquareGenerationType, FEnumSquareGenerationTypeInfo, FSquareGenerationTypeReadRef, FEnumSquareGenerationTypeWriteRef);
|
|
|
|
DEFINE_METASOUND_ENUM_BEGIN(ESquareGenerationType, FEnumSquareGenerationType, "SquareGenerationType")
|
|
DEFINE_METASOUND_ENUM_ENTRY(ESquareGenerationType::PolySmooth, "SquarePolySmoothDescription", "Poly Smooth", "PolySmoothDescriptionTT", "PolySmooth (i.e. BLEP)"),
|
|
DEFINE_METASOUND_ENUM_ENTRY(ESquareGenerationType::Trivial, "SquareTrivialDescription", "Trivial", "SquareTrivialDescriptionTT", "The most basic raw implementation. Does not obey pulse width."),
|
|
//DEFINE_METASOUND_ENUM_ENTRY(ESquareGenerationType::Wavetable, "SquareWavetableDescription", "Wavetable", "SquareWavetableDescriptionTT", "Use a Wavetable interpolation to generate the Waveform")
|
|
DEFINE_METASOUND_ENUM_END()
|
|
|
|
template<typename GeneratorPolicy>
|
|
class FSquareOperator final : public TOscillatorOperatorBase<GeneratorPolicy, FSquareOperator<GeneratorPolicy>>
|
|
{
|
|
using Super = TOscillatorOperatorBase<GeneratorPolicy, FSquareOperator<GeneratorPolicy>>;
|
|
public:
|
|
FSquareOperator(const FOscillatorOperatorConstructParams& InConstructParams, const FFloatReadRef& InPulseWidth)
|
|
: Super(InConstructParams)
|
|
, PulseWidth(InPulseWidth)
|
|
{}
|
|
void Generate(int32 InStartFrame, int32 InEndFrame, float InClampedFreq, float InClampedGlideEase)
|
|
{
|
|
int32 NumFrames = InEndFrame - InStartFrame;
|
|
float ClampedPulseWidth = FMath::Clamp(*PulseWidth, 0.0f, 1.0f);
|
|
if (*this->Enabled && NumFrames > 0)
|
|
{
|
|
this->Generator(
|
|
{
|
|
this->SampleRate,
|
|
InClampedFreq,
|
|
InClampedGlideEase,
|
|
ClampedPulseWidth,
|
|
*this->BiPolar,
|
|
MakeArrayView(this->AudioBuffer->GetData() + InStartFrame, NumFrames), // Not aligned.
|
|
});
|
|
}
|
|
}
|
|
|
|
private:
|
|
FFloatReadRef PulseWidth;
|
|
};
|
|
|
|
template<typename GeneratorPolicy>
|
|
class FSquareOperatorFM final : public TOscillatorOperatorBase<GeneratorPolicy, FSquareOperatorFM<GeneratorPolicy>>
|
|
{
|
|
using Super = TOscillatorOperatorBase<GeneratorPolicy, FSquareOperatorFM<GeneratorPolicy>>;
|
|
public:
|
|
FSquareOperatorFM(const FOscillatorOperatorConstructParams& InConstructParams, const FFloatReadRef& InPulseWidth, const FAudioBufferReadRef& InFm)
|
|
: Super(InConstructParams)
|
|
, PulseWidth(InPulseWidth)
|
|
, FM(InFm)
|
|
{
|
|
check(InFm->GetData());
|
|
check(InConstructParams.Settings.GetNumFramesPerBlock() == InFm->Num());
|
|
}
|
|
void Generate(int32 InStartFrame, int32 InEndFrame, float InClampedFreq, float InClampedGlideEase)
|
|
{
|
|
int32 NumFrames = InEndFrame - InStartFrame;
|
|
float ClampedPulseWidth = FMath::Clamp(*PulseWidth, 0.0f, 1.0f);
|
|
if (*this->Enabled && NumFrames > 0)
|
|
{
|
|
this->Generator(
|
|
{
|
|
this->SampleRate,
|
|
InClampedFreq,
|
|
InClampedGlideEase,
|
|
ClampedPulseWidth,
|
|
*this->BiPolar,
|
|
MakeArrayView(this->AudioBuffer->GetData() + InStartFrame, NumFrames), // Not aligned.
|
|
MakeArrayView(this->FM->GetData() + InStartFrame, NumFrames), // Not aligned.
|
|
});
|
|
}
|
|
}
|
|
|
|
private:
|
|
FFloatReadRef PulseWidth;
|
|
FAudioBufferReadRef FM;
|
|
};
|
|
|
|
namespace SquareOscillatorVertexNames
|
|
{
|
|
METASOUND_PARAM(SquarePulseWidthPin, "Pulse Width", "The Width of the square part of the wave")
|
|
METASOUND_PARAM(SquareTypePin, "Type", "The generator type to make the squarewave")
|
|
}
|
|
|
|
class FSquareOscilatorNode::FFactory : public FOscilatorFactoryBase
|
|
{
|
|
public:
|
|
FFactory() = default;
|
|
|
|
TUniquePtr<IOperator> CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) override
|
|
{
|
|
using namespace SquareOscillatorVertexNames;
|
|
|
|
const FSquareOscilatorNode& Node = static_cast<const FSquareOscilatorNode&>(InParams.Node);
|
|
const FInputVertexInterfaceData& InputData = InParams.InputData;
|
|
const FOperatorSettings& Settings = InParams.OperatorSettings;
|
|
using namespace Generators;
|
|
using namespace OscillatorCommonVertexNames;
|
|
|
|
FOscillatorOperatorConstructParams OpParams
|
|
{
|
|
Settings,
|
|
InputData.GetOrConstructDataReadReference<bool>(METASOUND_GET_PARAM_NAME(EnabledPin), Node.GetDefaultEnablement()),
|
|
InputData.GetOrConstructDataReadReference<float>(METASOUND_GET_PARAM_NAME(OscBaseFrequencyPin), Node.GetDefaultFrequency()),
|
|
InputData.GetOrConstructDataReadReference<float>(METASOUND_GET_PARAM_NAME(PhaseOffsetPin), Node.GetDefaultPhaseOffset()),
|
|
InputData.GetOrConstructDataReadReference<FTrigger>(METASOUND_GET_PARAM_NAME(OscPhaseResetPin), Settings),
|
|
InputData.GetOrConstructDataReadReference<float>(METASOUND_GET_PARAM_NAME(GlideFactorPin), Node.GetDefaultGlideFactor()),
|
|
InputData.GetOrConstructDataReadReference<bool>(METASOUND_GET_PARAM_NAME(BiPolarPin), true)
|
|
};
|
|
|
|
FSquareGenerationTypeReadRef Type = InputData.GetOrConstructDataReadReference<FEnumSquareGenerationType>(METASOUND_GET_PARAM_NAME(SquareTypePin));
|
|
|
|
bool bHasFM = InputData.IsVertexBound(METASOUND_GET_PARAM_NAME(FrequencyModPin));
|
|
FFloatReadRef PulseWidth = InputData.GetOrConstructDataReadReference<float>(METASOUND_GET_PARAM_NAME(SquarePulseWidthPin), 0.5f);
|
|
|
|
if (bHasFM)
|
|
{
|
|
FAudioBufferReadRef FmBuffer = InputData.GetDataReadReference<FAudioBuffer>(METASOUND_GET_PARAM_NAME(FrequencyModPin));
|
|
switch (*Type)
|
|
{
|
|
default:
|
|
case ESquareGenerationType::Trivial: return MakeUnique<FSquareOperatorFM<FSquareWithFm>>(OpParams, PulseWidth, FmBuffer);
|
|
case ESquareGenerationType::PolySmooth: return MakeUnique<FSquareOperatorFM<FSquarePolysmoothWithFm>>(OpParams, PulseWidth, FmBuffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (*Type)
|
|
{
|
|
default:
|
|
case ESquareGenerationType::Trivial: return MakeUnique<FSquareOperator<FSquare>>(OpParams, PulseWidth);
|
|
case ESquareGenerationType::PolySmooth: return MakeUnique<FSquareOperator<FSquarePolysmooth>>(OpParams, PulseWidth);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static const FNodeClassMetadata& GetNodeInfo()
|
|
{
|
|
auto InitNodeInfo = []() -> FNodeClassMetadata
|
|
{
|
|
FNodeClassMetadata Info;
|
|
Info.ClassName = { StandardNodes::Namespace, TEXT("Square"), StandardNodes::AudioVariant };
|
|
Info.MajorVersion = 1;
|
|
Info.MinorVersion = 0;
|
|
Info.DisplayName = METASOUND_LOCTEXT("Metasound_SquareNodeDisplayName", "Square");
|
|
Info.Description = METASOUND_LOCTEXT("Metasound_SquareNodeDescription", "Emits a Square wave");
|
|
Info.Author = PluginAuthor;
|
|
Info.PromptIfMissing = PluginNodeMissingPrompt;
|
|
Info.DefaultInterface = GetVertexInterface();
|
|
Info.CategoryHierarchy.Emplace(NodeCategories::Generators);
|
|
return Info;
|
|
};
|
|
static const FNodeClassMetadata Info = InitNodeInfo();
|
|
return Info;
|
|
}
|
|
static const FVertexInterface& GetVertexInterface()
|
|
{
|
|
using namespace SquareOscillatorVertexNames;
|
|
|
|
auto MakeInterface = []() -> FVertexInterface
|
|
{
|
|
FVertexInterface Interface = GetCommmonVertexInterface();
|
|
Interface.GetInputInterface().Add(TInputDataVertex<FEnumSquareGenerationType>(METASOUND_GET_PARAM_NAME_AND_METADATA(SquareTypePin)));
|
|
Interface.GetInputInterface().Add(TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(SquarePulseWidthPin), 0.5f));
|
|
return Interface;
|
|
};
|
|
static const FVertexInterface Interface = MakeInterface();
|
|
return Interface;
|
|
}
|
|
private:
|
|
};
|
|
|
|
FSquareOscilatorNode::FSquareOscilatorNode(const FVertexName& InInstanceName, const FGuid& InInstanceID, float InDefaultFrequency, float InDefaultGlideFactor, bool bInDefaultEnablement)
|
|
: FOscilatorNodeBase(InInstanceName, InInstanceID, FFactory::GetNodeInfo(), MakeShared<FFactory, ESPMode::ThreadSafe>(), InDefaultFrequency, InDefaultGlideFactor, bInDefaultEnablement)
|
|
{}
|
|
|
|
FSquareOscilatorNode::FSquareOscilatorNode(const FNodeInitData& InInitData)
|
|
: FSquareOscilatorNode(InInitData.InstanceName, InInitData.InstanceID, 440.0f, 0.0f, true)
|
|
{}
|
|
|
|
METASOUND_REGISTER_NODE(FSquareOscilatorNode)
|
|
#pragma endregion Square
|
|
|
|
#pragma region Triangle
|
|
|
|
enum class ETriangleGenerationType
|
|
{
|
|
PolySmooth,
|
|
Trivial,
|
|
Wavetable
|
|
};
|
|
|
|
DECLARE_METASOUND_ENUM(ETriangleGenerationType, ETriangleGenerationType::PolySmooth, METASOUNDSTANDARDNODES_API,
|
|
FEnumTriangleGenerationType, FEnumTriangleGenerationTypeInfo, FTriangleGenerationTypeReadRef, FEnumTriangleGenerationTypeWriteRef);
|
|
|
|
DEFINE_METASOUND_ENUM_BEGIN(ETriangleGenerationType, FEnumTriangleGenerationType, "TriangleGenerationType")
|
|
DEFINE_METASOUND_ENUM_ENTRY(ETriangleGenerationType::PolySmooth, "TrianglePolySmoothDescription", "Poly Smooth", "PolySmoothDescriptionTT", "PolySmooth (i.e. BLEP)"),
|
|
DEFINE_METASOUND_ENUM_ENTRY(ETriangleGenerationType::Trivial, "TriangleTrivialDescription", "Trivial", "TriangleTrivialDescriptionTT", "The most basic raw implementation"),
|
|
//DEFINE_METASOUND_ENUM_ENTRY(ETriangleGenerationType::Wavetable, "TriangleWavetableDescription", "Wavetable", "TriangleWavetableDescriptionTT", "Use a Wavetable iterpolation to generate the Waveform")
|
|
DEFINE_METASOUND_ENUM_END()
|
|
|
|
namespace TriangleOscilatorVertexNames
|
|
{
|
|
METASOUND_PARAM(TriangeTypePin, "Type", "The generator type to make the triangle wave")
|
|
}
|
|
|
|
class FTriangleOscilatorNode::FFactory : public FOscilatorFactoryBase
|
|
{
|
|
public:
|
|
FFactory() = default;
|
|
|
|
TUniquePtr<IOperator> CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) override
|
|
{
|
|
const FTriangleOscilatorNode& Node = static_cast<const FTriangleOscilatorNode&>(InParams.Node);
|
|
const FInputVertexInterfaceData& InputData = InParams.InputData;
|
|
const FOperatorSettings& Settings = InParams.OperatorSettings;
|
|
using namespace Generators;
|
|
using namespace OscillatorCommonVertexNames;
|
|
using namespace TriangleOscilatorVertexNames;
|
|
|
|
FTriangleGenerationTypeReadRef Type = InputData.GetOrConstructDataReadReference<FEnumTriangleGenerationType>(METASOUND_GET_PARAM_NAME(TriangeTypePin));
|
|
|
|
FOscillatorOperatorConstructParams OpParams
|
|
{
|
|
Settings,
|
|
InputData.GetOrConstructDataReadReference<bool>(METASOUND_GET_PARAM_NAME(EnabledPin), Node.GetDefaultEnablement()),
|
|
InputData.GetOrConstructDataReadReference<float>(METASOUND_GET_PARAM_NAME(OscBaseFrequencyPin), Node.GetDefaultFrequency()),
|
|
InputData.GetOrConstructDataReadReference<float>(METASOUND_GET_PARAM_NAME(PhaseOffsetPin), Node.GetDefaultPhaseOffset()),
|
|
InputData.GetOrConstructDataReadReference<FTrigger>(METASOUND_GET_PARAM_NAME(OscPhaseResetPin), Settings),
|
|
InputData.GetOrConstructDataReadReference<float>(METASOUND_GET_PARAM_NAME(GlideFactorPin), Node.GetDefaultGlideFactor()),
|
|
InputData.GetOrConstructDataReadReference<bool>(METASOUND_GET_PARAM_NAME(BiPolarPin), true)
|
|
};
|
|
|
|
bool bHasFM = InputData.IsVertexBound(METASOUND_GET_PARAM_NAME(FrequencyModPin));
|
|
|
|
if (bHasFM)
|
|
{
|
|
FAudioBufferReadRef FmBuffer = InputData.GetDataReadReference<FAudioBuffer>(METASOUND_GET_PARAM_NAME(FrequencyModPin));
|
|
switch (*Type)
|
|
{
|
|
default:
|
|
case ETriangleGenerationType::PolySmooth: return MakeUnique<TOscillatorOperatorFM<FTrianglePolysmoothWithFm>>(OpParams, FmBuffer);
|
|
case ETriangleGenerationType::Trivial: return MakeUnique<TOscillatorOperatorFM<FTriangleWithFm>>(OpParams, FmBuffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (*Type)
|
|
{
|
|
default:
|
|
case ETriangleGenerationType::PolySmooth: return MakeUnique<TOscillatorOperator<FTrianglePolysmooth>>(OpParams);
|
|
case ETriangleGenerationType::Trivial: return MakeUnique<TOscillatorOperator<FTriangle>>(OpParams);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static const FNodeClassMetadata& GetNodeInfo()
|
|
{
|
|
auto InitNodeInfo = []() -> FNodeClassMetadata
|
|
{
|
|
FNodeClassMetadata Info;
|
|
Info.ClassName = { StandardNodes::Namespace, TEXT("Triangle"), StandardNodes::AudioVariant };
|
|
Info.MajorVersion = 1;
|
|
Info.MinorVersion = 0;
|
|
Info.DisplayName = METASOUND_LOCTEXT("Metasound_TriangleNodeDisplayName", "Triangle");
|
|
Info.Description = METASOUND_LOCTEXT("Metasound_TriangleNodeDescription", "Emits a Triangle wave");
|
|
Info.Author = PluginAuthor;
|
|
Info.PromptIfMissing = PluginNodeMissingPrompt;
|
|
Info.DefaultInterface = GetVertexInterface();
|
|
Info.CategoryHierarchy.Emplace(NodeCategories::Generators);
|
|
return Info;
|
|
};
|
|
static const FNodeClassMetadata Info = InitNodeInfo();
|
|
return Info;
|
|
}
|
|
|
|
static const FVertexInterface& GetVertexInterface()
|
|
{
|
|
using namespace TriangleOscilatorVertexNames;
|
|
|
|
auto MakeInterface = []() -> FVertexInterface
|
|
{
|
|
FVertexInterface Interface = GetCommmonVertexInterface();
|
|
Interface.GetInputInterface().Add(
|
|
TInputDataVertex<FEnumTriangleGenerationType>(METASOUND_GET_PARAM_NAME_AND_METADATA(TriangeTypePin))
|
|
);
|
|
return Interface;
|
|
};
|
|
static const FVertexInterface Interface = MakeInterface();
|
|
return Interface;
|
|
}
|
|
};
|
|
|
|
FTriangleOscilatorNode::FTriangleOscilatorNode(const FVertexName& InInstanceName, const FGuid& InInstanceID, float InDefaultFrequency, float InDefaultGlideFactor, bool bInDefaultEnablement)
|
|
: FOscilatorNodeBase(InInstanceName, InInstanceID, FFactory::GetNodeInfo(), MakeShared<FFactory, ESPMode::ThreadSafe>(), InDefaultFrequency, InDefaultGlideFactor, bInDefaultEnablement)
|
|
{
|
|
|
|
}
|
|
|
|
FTriangleOscilatorNode::FTriangleOscilatorNode(const FNodeInitData& InInitData)
|
|
: FTriangleOscilatorNode(InInitData.InstanceName, InInitData.InstanceID, 440.0f, 0.0f, true)
|
|
{}
|
|
|
|
METASOUND_REGISTER_NODE(FTriangleOscilatorNode);
|
|
#pragma endregion Triangle
|
|
|
|
#pragma region LFO
|
|
|
|
enum class ELfoWaveshapeType
|
|
{
|
|
Sine,
|
|
Saw,
|
|
Triangle,
|
|
Square,
|
|
};
|
|
|
|
DECLARE_METASOUND_ENUM(ELfoWaveshapeType, ELfoWaveshapeType::Sine, METASOUNDSTANDARDNODES_API,
|
|
FEnumLfoWaveshapeType, FEnumLfoWaveshapeTypeInfo, FEnumLfoWaveshapeTypeReadRef, FEnumLfoWaveshapeTypeWriteRef);
|
|
|
|
DEFINE_METASOUND_ENUM_BEGIN(ELfoWaveshapeType, FEnumLfoWaveshapeType, "LfoWaveshapeType")
|
|
DEFINE_METASOUND_ENUM_ENTRY(ELfoWaveshapeType::Sine, "LfoWaveShapeSineDescription", "Sine", "LfoWaveShapeSineDescriptionTT", "Sinewave Low Frequency Oscillator"),
|
|
DEFINE_METASOUND_ENUM_ENTRY(ELfoWaveshapeType::Saw, "LfoWaveShapeSawDescription", "Saw", "LfoWaveShapeSawDescriptionTT", "Sawtooth Low Frequency Oscillator"),
|
|
DEFINE_METASOUND_ENUM_ENTRY(ELfoWaveshapeType::Triangle, "LfoWaveShapeTriangleDescription", "Triangle", "LfoWaveShapeTriangleDescriptionTT", "Triangle shape Frequency Oscillator"),
|
|
DEFINE_METASOUND_ENUM_ENTRY(ELfoWaveshapeType::Square, "LfoWaveShapeSquareDescription", "Square", "LfoWaveShapeSquareDescriptionTT", "Square shape Low Frequency Oscillator")
|
|
DEFINE_METASOUND_ENUM_END()
|
|
|
|
namespace LfoVertexNames
|
|
{
|
|
// Common pins
|
|
METASOUND_PARAM(WaveshapePin, "Shape", "Waveshape of the LFO")
|
|
METASOUND_PARAM(LfoOutPin, "Out", "Output of the LFO (blockrate)")
|
|
METASOUND_PARAM(LfoBaseFrequencyPin, "Frequency", "Frequency of LFO (Hz), clamped at blockrate")
|
|
|
|
METASOUND_PARAM(MinOutputValuePin, "Min Value", "The minimum output value.")
|
|
METASOUND_PARAM(MaxOutputValuePin, "Max Value", "The maximum output value.")
|
|
METASOUND_PARAM(LfoPhaseResetPin, "Sync", "Phase Reset (block rate only)")
|
|
METASOUND_PARAM(PhaseOffsetPin, "Phase Offset", "Phase Offset In Degrees (0..360)")
|
|
METASOUND_PARAM(LfoPulseWidthPin, "Pulse Width", "Pulse Width (0..1)")
|
|
}
|
|
|
|
// Blockrate All-Purpose Oscillator
|
|
class FLfoOperator : public TExecutableOperator<FLfoOperator>
|
|
{
|
|
public:
|
|
static const FVertexInterface& GetVertexInterface()
|
|
{
|
|
using namespace LfoVertexNames;
|
|
|
|
static const FVertexInterface Interface
|
|
{
|
|
FInputVertexInterface{
|
|
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(LfoBaseFrequencyPin), 5.f),
|
|
TInputDataVertex<FEnumLfoWaveshapeType>(METASOUND_GET_PARAM_NAME_AND_METADATA(WaveshapePin)),
|
|
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(MinOutputValuePin), -1.0f),
|
|
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(MaxOutputValuePin), 1.0f),
|
|
TInputDataVertex<FTrigger>(METASOUND_GET_PARAM_NAME_AND_METADATA(LfoPhaseResetPin)),
|
|
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(PhaseOffsetPin), 0.f),
|
|
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(LfoPulseWidthPin), 0.5f)
|
|
},
|
|
FOutputVertexInterface{
|
|
TOutputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(LfoOutPin))
|
|
}
|
|
};
|
|
return Interface;
|
|
}
|
|
|
|
static const FNodeClassMetadata& GetNodeInfo()
|
|
{
|
|
auto InitNodeInfo = []() -> FNodeClassMetadata
|
|
{
|
|
FNodeClassMetadata Info;
|
|
Info.ClassName = { StandardNodes::Namespace, TEXT("LFO"), StandardNodes::AudioVariant };
|
|
Info.MajorVersion = 1;
|
|
Info.MinorVersion = 0;
|
|
Info.DisplayName = METASOUND_LOCTEXT("Metasound_LfoNodeDisplayName", "LFO");
|
|
Info.Description = METASOUND_LOCTEXT("Metasound_LfoNodeDescription", "Low frequency oscillator < blockrate");
|
|
Info.Author = PluginAuthor;
|
|
Info.PromptIfMissing = PluginNodeMissingPrompt;
|
|
Info.DefaultInterface = GetVertexInterface();
|
|
Info.CategoryHierarchy.Emplace(NodeCategories::Generators);
|
|
return Info;
|
|
};
|
|
static const FNodeClassMetadata Info = InitNodeInfo();
|
|
return Info;
|
|
}
|
|
|
|
static TUniquePtr<IOperator> CreateOperator(const FCreateOperatorParams& InParams, FBuildErrorArray& OutErrors)
|
|
{
|
|
const FLfoNode& Node = static_cast<const FLfoNode&>(InParams.Node);
|
|
const FDataReferenceCollection& InputCol = InParams.InputDataReferences;
|
|
const FOperatorSettings& Settings = InParams.OperatorSettings;
|
|
const FInputVertexInterface& InputInterface = GetVertexInterface().GetInputInterface();
|
|
|
|
using namespace LfoVertexNames;
|
|
|
|
return MakeUnique<FLfoOperator>(
|
|
Settings
|
|
, InputCol.GetDataReadReferenceOrConstructWithVertexDefault<float>(InputInterface, METASOUND_GET_PARAM_NAME(LfoBaseFrequencyPin), Settings)
|
|
, InputCol.GetDataReadReferenceOrConstruct<FEnumLfoWaveshapeType>(METASOUND_GET_PARAM_NAME(WaveshapePin))
|
|
, InputCol.GetDataReadReferenceOrConstructWithVertexDefault<float>(InputInterface, METASOUND_GET_PARAM_NAME(MinOutputValuePin), Settings)
|
|
, InputCol.GetDataReadReferenceOrConstructWithVertexDefault<float>(InputInterface, METASOUND_GET_PARAM_NAME(MaxOutputValuePin), Settings)
|
|
, InputCol.GetDataReadReferenceOrConstruct<FTrigger>(METASOUND_GET_PARAM_NAME(LfoPhaseResetPin), Settings)
|
|
, InputCol.GetDataReadReferenceOrConstructWithVertexDefault<float>(InputInterface, METASOUND_GET_PARAM_NAME(PhaseOffsetPin), Settings)
|
|
, InputCol.GetDataReadReferenceOrConstructWithVertexDefault<float>(InputInterface, METASOUND_GET_PARAM_NAME(LfoPulseWidthPin), Settings)
|
|
);
|
|
}
|
|
|
|
FLfoOperator(const FOperatorSettings& InSettings, FFloatReadRef&& InFrequency, FEnumLfoWaveshapeTypeReadRef&& InType, FFloatReadRef&& InMinValue, FFloatReadRef&& InMaxValue,
|
|
FTriggerReadRef&& InPhaseReset, FFloatReadRef&& InPhaseOffset, FFloatReadRef&& InPulseWidth)
|
|
: BlockRate{InSettings.GetActualBlockRate()}
|
|
, Phase{0.0f}
|
|
, Frequency{MoveTemp(InFrequency)}
|
|
, Waveshape{MoveTemp(InType)}
|
|
, MinValue{MoveTemp(InMinValue)}
|
|
, MaxValue{MoveTemp(InMaxValue)}
|
|
, PhaseReset{MoveTemp(InPhaseReset)}
|
|
, PhaseOffset{MoveTemp(InPhaseOffset)}
|
|
, PulseWidth{MoveTemp(InPulseWidth)}
|
|
, Output{FFloatWriteRef::CreateNew(0.f)}
|
|
{
|
|
ResetPhase();
|
|
}
|
|
|
|
FDataReferenceCollection GetInputs() const override
|
|
{
|
|
using namespace LfoVertexNames;
|
|
|
|
FDataReferenceCollection InputDataReferences;
|
|
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(LfoBaseFrequencyPin), Frequency);
|
|
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(WaveshapePin), Waveshape);
|
|
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(MinOutputValuePin), MinValue);
|
|
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(MaxOutputValuePin), MaxValue);
|
|
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(PhaseOffsetPin), PhaseOffset);
|
|
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(LfoPhaseResetPin), PhaseReset);
|
|
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(LfoPulseWidthPin), PulseWidth);
|
|
return InputDataReferences;
|
|
}
|
|
|
|
FDataReferenceCollection GetOutputs() const override
|
|
{
|
|
using namespace LfoVertexNames;
|
|
|
|
FDataReferenceCollection OutputDataReferences;
|
|
OutputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(LfoOutPin), Output);
|
|
return OutputDataReferences;
|
|
}
|
|
|
|
void ResetPhase()
|
|
{
|
|
float ClampedDegrees = FMath::Clamp(*PhaseOffset, 0.f, 360.f);
|
|
Phase = ClampedDegrees / 360.f;
|
|
}
|
|
|
|
void Execute()
|
|
{
|
|
using namespace Generators;
|
|
|
|
// Prevent LFO going faster than the block rate Nyquist
|
|
const float Nyquist = BlockRate / 2.f;
|
|
const float ClampedFreq = FMath::Clamp(*Frequency, 0.f, Nyquist);
|
|
const float DeltaPhase = ClampedFreq * (1.f / BlockRate);
|
|
const float ClampPulseWidth = FMath::Clamp(*PulseWidth, 0.f, 1.f);
|
|
|
|
FGeneratorArgs Args{ BlockRate, ClampedFreq, 0.0f, ClampPulseWidth };
|
|
|
|
// We are not sample accurate.
|
|
if (PhaseReset->IsTriggeredInBlock())
|
|
{
|
|
ResetPhase();
|
|
}
|
|
|
|
// Wrap phase. (0..1)
|
|
Wrap(Phase);
|
|
|
|
float Value = 0.f;
|
|
switch (*Waveshape)
|
|
{
|
|
case ELfoWaveshapeType::Sine:
|
|
{
|
|
Value = SineGenerator(Phase, DeltaPhase, Args);
|
|
break;
|
|
}
|
|
case ELfoWaveshapeType::Saw:
|
|
{
|
|
Value = SawGenerator(Phase, DeltaPhase, Args);
|
|
break;
|
|
}
|
|
case ELfoWaveshapeType::Triangle:
|
|
{
|
|
Value = TriangleGenerator(Phase, DeltaPhase, Args);
|
|
break;
|
|
}
|
|
case ELfoWaveshapeType::Square:
|
|
{
|
|
Value = SquareGenerator(Phase, DeltaPhase, Args);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
checkNoEntry();
|
|
break;
|
|
}
|
|
}
|
|
|
|
Value = FMath::GetMappedRangeValueClamped(FVector2f{ -1.0f, 1.0f }, FVector2f{ *MinValue, *MaxValue }, Value);
|
|
|
|
*Output = Value;
|
|
|
|
Phase += DeltaPhase;
|
|
}
|
|
|
|
private:
|
|
float BlockRate = 0.f;
|
|
float Phase = 0.f;
|
|
Generators::FWrapPhase Wrap;
|
|
FFloatReadRef Frequency;
|
|
FEnumLfoWaveshapeTypeReadRef Waveshape;
|
|
FFloatReadRef MinValue;
|
|
FFloatReadRef MaxValue;
|
|
FTriggerReadRef PhaseReset;
|
|
FFloatReadRef PhaseOffset;
|
|
FFloatReadRef PulseWidth;
|
|
FFloatWriteRef Output;
|
|
|
|
Generators::FSawPolySmoothGenerator SawGenerator;
|
|
Generators::FSineWaveTableGenerator SineGenerator;
|
|
Generators::FTriangleGenerator TriangleGenerator;
|
|
Generators::FSquarePolysmoothGenerator SquareGenerator;
|
|
|
|
};
|
|
|
|
FLfoNode::FLfoNode(const FNodeInitData& InInitData)
|
|
: FNodeFacade(InInitData.InstanceName, InInitData.InstanceID, TFacadeOperatorClass<FLfoOperator>())
|
|
{}
|
|
|
|
METASOUND_REGISTER_NODE(FLfoNode);
|
|
|
|
#pragma endregion LFO
|
|
}
|
|
#undef LOCTEXT_NAMESPACE //MetasoundStandardNodes
|
|
|