Files
UnrealEngine/Engine/Plugins/Runtime/Metasound/Source/MetasoundStandardNodes/Private/MetasoundLowFrequencyNoise.cpp
2025-05-18 13:04:45 +08:00

411 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#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"
#include "DSP/Dsp.h"
#define LOCTEXT_NAMESPACE "MetasoundStandardNodes_LowFrequencyNoise"
// Low Frequency Noise :
// Similar to an Oscillator has Frequency/Phase. At the start of each phase a random value is chosen between (0..1)
// Between each phase the output is an interpolation between the previous and the next value
// The interpolation method can be either, None (just snaps every new value), Linear (lerp last 2 points), Cubic (cubic between 4 points).
// Same as other oscillators phase can be reset and synced
namespace Metasound
{
enum class ELowFrequencyNoiseInterpType
{
None,
Linear,
Cubic,
};
DECLARE_METASOUND_ENUM(ELowFrequencyNoiseInterpType, ELowFrequencyNoiseInterpType::Cubic, METASOUNDSTANDARDNODES_API,
FEnumLowFrequencyNoiseInterpType, FEnumLowFrequencyNoiseInterpTypeInfo, FEnumLowFrequencyNoiseInterpTypeReadRef, FEnumLowFrequencyNoiseInterpTypeWriteRef);
DEFINE_METASOUND_ENUM_BEGIN(ELowFrequencyNoiseInterpType, FEnumLowFrequencyNoiseInterpType, "LowFrequencyNoiseInterpType")
DEFINE_METASOUND_ENUM_ENTRY(ELowFrequencyNoiseInterpType::None, "ELowFrequencyNoiseInterpTypeDescriptionNone", "None", "ELowFrequencyNoiseInterpTypeDescriptionNoneTT", "No interpolation"),
DEFINE_METASOUND_ENUM_ENTRY(ELowFrequencyNoiseInterpType::Linear, "ELowFrequencyNoiseInterpTypeDescriptionLinear", "Linear", "ELowFrequencyNoiseInterpTypeDescriptionLinearTT", "Linear interpolation"),
DEFINE_METASOUND_ENUM_ENTRY(ELowFrequencyNoiseInterpType::Cubic, "ELowFrequencyNoiseInterpTypeDescriptionCubic", "Cubic", "ELowFrequencyNoiseInterpTypeDescriptionCubicTT", "Cubic interpolation"),
DEFINE_METASOUND_ENUM_END()
namespace LowFrequencyNoiseVertexNames
{
METASOUND_PARAM(SyncPin, "Sync", "Reset Phase")
METASOUND_PARAM(RatePin, "Rate", "Rate of each new value in Hz (clamped at BlockRate, same as LFO).")
METASOUND_PARAM(RateJitterPin, "Rate Jitter", "How much % to randomly add (+/-) to Rate (0..1)")
METASOUND_PARAM(StepLimitPin, "Step Limit", "Limit the next (unit) random number in sequence (i.e. 1 no limit, 0.25 max step 25% (+/-) from current value")
METASOUND_PARAM(SeedPin, "Seed", "Value to seed the Random Number Generator (-1 will use system rand())")
METASOUND_PARAM(ResetSeedPin, "Reset Seed", "Resets the seed to what's set in the seed input")
METASOUND_PARAM(InterpTypePin, "Interpolation", "Interpolation algorthm to use (defaults is cubic)")
METASOUND_PARAM(MinOutputValuePin, "Min Value", "Minimum output value")
METASOUND_PARAM(MaxOutputValuePin, "Max Value", "Maximum output value")
METASOUND_PARAM(NormalizedOutputPin, "Normalized", "Value before scaling (0..1)")
METASOUND_PARAM(ScaledOutputPin, "Out", "Output value scaled (Min Value...Max Value)")
}
class FLowFrequencyNoiseOperator : public TExecutableOperator<FLowFrequencyNoiseOperator>
{
public:
// Helper to hide management of the state
// Multi-tap delay line of current/previous values.
struct FValueFIFO
{
enum { NumValues = 4, CurrentIndex = NumValues / 2};
float Values[NumValues]={0};
const int32 Num() const
{
return NumValues;
}
void Push(const float InNewValue)
{
// Shuffle down
for(int32 i=0; i < NumValues - 1; ++i)
{
Values[i] = Values[i+1];
}
// Add new
Values[NumValues - 1] = InNewValue;
}
// 0 is current, -1 previous, +1 future etc.
float GetValue(const int32 InOffsetFromCurrent=0) const
{
const int32 Index = CurrentIndex + InOffsetFromCurrent;
check(Index >=0 && Index < NumValues);
return Values[Index];
};
};
static const FVertexInterface& GetVertexInterface()
{
using namespace LowFrequencyNoiseVertexNames;
static const FVertexInterface Interface
{
FInputVertexInterface
{
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(RatePin), 5.f),
TInputDataVertex<int32>(METASOUND_GET_PARAM_NAME_AND_METADATA(SeedPin), -1),
TInputDataVertex<FTrigger>(METASOUND_GET_PARAM_NAME_AND_METADATA(ResetSeedPin)),
TInputDataVertex<FTrigger>(METASOUND_GET_PARAM_NAME_AND_METADATA(SyncPin)),
TInputDataVertex<FEnumLowFrequencyNoiseInterpType>(METASOUND_GET_PARAM_NAME_AND_METADATA(InterpTypePin), static_cast<int32>(ELowFrequencyNoiseInterpType::Cubic)),
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(RateJitterPin), 0.0f),
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(StepLimitPin), 1.f),
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(MinOutputValuePin), 0.0f),
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(MaxOutputValuePin), 1.0f),
},
FOutputVertexInterface
{
TOutputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(ScaledOutputPin)),
TOutputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(NormalizedOutputPin))
}
};
return Interface;
}
static const FNodeClassMetadata& GetNodeInfo()
{
auto InitNodeInfo = []() -> FNodeClassMetadata
{
FNodeClassMetadata Info;
Info.ClassName = { StandardNodes::Namespace, TEXT("Lfo Frequency Noise"), StandardNodes::AudioVariant };
Info.MajorVersion = 1;
Info.MinorVersion = 0;
Info.DisplayName = METASOUND_LOCTEXT("Metasound_LowFrequencyNoiseNodeDisplayName", "Low Frequency Noise");
Info.Description = METASOUND_LOCTEXT("Metasound_LowFrequencyNoiseNodeDescription", "Produces random values at a Rate and interpolates between them");
Info.Author = PluginAuthor;
Info.PromptIfMissing = PluginNodeMissingPrompt;
Info.DefaultInterface = GetVertexInterface();
Info.CategoryHierarchy.Emplace(NodeCategories::Generators);
Info.CategoryHierarchy.Emplace(NodeCategories::RandomUtils);
Info.Keywords.Emplace(METASOUND_LOCTEXT("Metasound_LowFrequencyNoiseNodeKeyword_Rand", "Rand"));
return Info;
};
static const FNodeClassMetadata Info = InitNodeInfo();
return Info;
}
struct FPinReadRefs
{
FFloatReadRef Rate;
FFloatReadRef RateJitter;
FFloatReadRef StepLimit;
FInt32ReadRef Seed;
FEnumLowFrequencyNoiseInterpTypeReadRef InterpType;
FTriggerReadRef ResetSeed;
FTriggerReadRef Sync;
FFloatReadRef MinOutput;
FFloatReadRef MaxOutput;
};
static TUniquePtr<IOperator> CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults)
{
const FOperatorSettings& Settings = InParams.OperatorSettings;
const FInputVertexInterfaceData& InputData = InParams.InputData;
using namespace LowFrequencyNoiseVertexNames;
FPinReadRefs Pins
{
InputData.GetOrCreateDefaultDataReadReference<float>(METASOUND_GET_PARAM_NAME(RatePin), Settings)
, InputData.GetOrCreateDefaultDataReadReference<float>(METASOUND_GET_PARAM_NAME(RateJitterPin), Settings)
, InputData.GetOrCreateDefaultDataReadReference<float>(METASOUND_GET_PARAM_NAME(StepLimitPin), Settings)
, InputData.GetOrCreateDefaultDataReadReference<int32>(METASOUND_GET_PARAM_NAME(SeedPin), Settings)
, InputData.GetOrCreateDefaultDataReadReference<FEnumLowFrequencyNoiseInterpType>(METASOUND_GET_PARAM_NAME(InterpTypePin), Settings)
, InputData.GetOrCreateDefaultDataReadReference<FTrigger>(METASOUND_GET_PARAM_NAME(ResetSeedPin), Settings)
, InputData.GetOrCreateDefaultDataReadReference<FTrigger>(METASOUND_GET_PARAM_NAME(SyncPin), Settings)
, InputData.GetOrCreateDefaultDataReadReference<float>(METASOUND_GET_PARAM_NAME(MinOutputValuePin), Settings)
, InputData.GetOrCreateDefaultDataReadReference<float>(METASOUND_GET_PARAM_NAME(MaxOutputValuePin), Settings)
};
return MakeUnique<FLowFrequencyNoiseOperator>(InParams, MoveTemp(Pins));
}
FLowFrequencyNoiseOperator(const FBuildOperatorParams& InParams, FPinReadRefs&& InPins)
: BlockRate{ InParams.OperatorSettings.GetActualBlockRate() }
, Pins{ MoveTemp(InPins) }
, ScaledOutput{ FFloatWriteRef::CreateNew(0.f) }
, NormalizedOutput{ FFloatWriteRef::CreateNew(0.f) }
{
Reset(InParams);
}
void Reset(const IOperator::FResetParams& InParams)
{
Phase = 0.f;
*ScaledOutput = 0.f;
*NormalizedOutput = 0.f;
SetSeedAndFillValues();
}
void SetSeedAndFillValues()
{
SetSeed(*Pins.Seed);
// Every time we change the seed, in order for the results to be deterministic
// we must also also flush and fill the value/delay line. This will create a discontinuation in the
// interpolation, but will be consistent.
for(int32 i = 0; i < Values.Num(); ++i)
{
Values.Push(GenerateRandomValue());
}
// The random jitter should be chosen again following the seed change.
CurrentRandomRate = GenerateRandomRate();
}
virtual void BindInputs(FInputVertexInterfaceData& InOutVertexData) override
{
using namespace LowFrequencyNoiseVertexNames;
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(RatePin), Pins.Rate);
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(RateJitterPin), Pins.RateJitter);
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(StepLimitPin), Pins.StepLimit);
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(SeedPin), Pins.Seed);
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(ResetSeedPin), Pins.ResetSeed);
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(SyncPin), Pins.Sync);
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InterpTypePin), Pins.InterpType);
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(MinOutputValuePin), Pins.MinOutput);
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(MaxOutputValuePin), Pins.MaxOutput);
}
virtual void BindOutputs(FOutputVertexInterfaceData& InOutVertexData) override
{
using namespace LowFrequencyNoiseVertexNames;
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(ScaledOutputPin), ScaledOutput);
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(NormalizedOutputPin), NormalizedOutput);
}
void SetSeed(int32 InSeedValue)
{
if (InSeedValue != -1)
{
RandomStream.Initialize(InSeedValue);
RandomJitter.Initialize(InSeedValue);
}
else
{
RandomStream.GenerateNewSeed();
RandomJitter.GenerateNewSeed();
}
CurrentSeed = InSeedValue;
}
// Generate a value between 0..1
float GenerateRandomValue()
{
float RandomFraction = RandomStream.GetFraction();
float PreviousValue = Values.GetValue(); // On init this will be zero.
float LimitClamped = FMath::Clamp(*Pins.StepLimit, 0.f, 1.f);
float RangeMin = FMath::Max(PreviousValue - LimitClamped, 0.f);
float RangeMax = FMath::Min(PreviousValue + LimitClamped, 1.f);
float LimitedRandom = FMath::GetRangeValue(FVector2d(RangeMin, RangeMax), RandomFraction);
return LimitedRandom;
}
float GenerateRandomRate()
{
const float Nyquist = BlockRate / 2.f;
float RandomFraction = RandomJitter.GetFraction();
float ClampedJitter = FMath::Clamp(*Pins.RateJitter, 0.f, 1.f);
float RangeMin = FMath::Max(*Pins.Rate - (*Pins.Rate * ClampedJitter), 0.f);
float RangeMax = FMath::Min(*Pins.Rate + (*Pins.Rate * ClampedJitter), Nyquist);
float JitteredRate = FMath::GetRangeValue(FVector2d(RangeMin, RangeMax), RandomFraction);
return JitteredRate;
}
// Taken from: http://paulbourke.net/miscellaneous/interpolation/
// Different from FMath::CubicInterp as its requires no tension points.
static float CubicInterpolate(
float Y0, float Y1,
float Y2, float Y3,
float MU)
{
float MU2 = MU * MU;
float A0 = Y3 - Y2 - Y0 + Y1;
float A1 = Y0 - Y1 - A0;
float A2 = Y2 - Y0;
float A3 = Y1;
return (A0 * MU * MU2 + A1 * MU2 + A2 * MU + A3);
}
float Interpolate() const
{
switch(*Pins.InterpType)
{
case ELowFrequencyNoiseInterpType::None:
{
return Values.GetValue(0);
}
case ELowFrequencyNoiseInterpType::Linear:
{
return FMath::Lerp(Values.GetValue(0), Values.GetValue(1), Phase);
}
default:
case ELowFrequencyNoiseInterpType::Cubic:
{
return CubicInterpolate(Values.GetValue(-2), Values.GetValue(-1), Values.GetValue(0), Values.GetValue(1), Phase);
}
}
}
void HandleSyncChanges()
{
if (Pins.Sync->IsTriggeredInBlock())
{
Phase = 0.f;
}
}
void HandleSeedChanges()
{
if (CurrentSeed != *Pins.Seed || Pins.ResetSeed->IsTriggeredInBlock())
{
SetSeedAndFillValues();
}
}
void HandleRateChanges()
{
if (!FMath::IsNearlyEqual(CurrentRate, *Pins.Rate))
{
CurrentRate = *Pins.Rate;
CurrentRandomRate = GenerateRandomRate();
}
}
void UpdateCurrentValue()
{
if (Phase > 1.f)
{
Values.Push(GenerateRandomValue());
Phase -= 1.f;
CurrentRandomRate = GenerateRandomRate();
}
}
void UpdateOutputs()
{
// Generate in-between values by interpolating (with a chosen algorithm)
float InterpolatedValue = Interpolate();
// Output the raw value (0..1) for the normalized output.
*NormalizedOutput = InterpolatedValue;
// Output the scaled version.
float OutputValue = FMath::GetMappedRangeValueClamped(
FVector2f{ 0.f, 1.0f },
FVector2f{ *Pins.MinOutput, *Pins.MaxOutput },
InterpolatedValue );
*ScaledOutput = OutputValue;
}
void UpdatePhase()
{
// Similar to an LFO, we calculate our rate as a fraction of the block-rate
check(BlockRate > 0.f)
const float Nyquist = BlockRate / 2.f;
const float ClampedFreq = FMath::Clamp(CurrentRandomRate, 0.f, Nyquist);
const float DeltaPhase = ClampedFreq * (1.f / BlockRate);
Phase += DeltaPhase;
}
void HandlePinChanges()
{
HandleRateChanges();
HandleSyncChanges();
HandleSeedChanges();
}
void Execute()
{
HandlePinChanges();
UpdateCurrentValue();
UpdateOutputs();
UpdatePhase();
}
private:
float BlockRate = 0.f;
float Phase = 0.f;
float CurrentRandomRate = 0.f;
float CurrentRate = 0.f;
int32 CurrentSeed = 0;
FValueFIFO Values;
FRandomStream RandomStream;
FRandomStream RandomJitter;
FPinReadRefs Pins;
FFloatWriteRef ScaledOutput; // Scaled output value (min/max)
FFloatWriteRef NormalizedOutput; // Unit value (0..1)
};
using FRandomLowFrequencyNoiseNode = TNodeFacade<FLowFrequencyNoiseOperator>;
METASOUND_REGISTER_NODE(FRandomLowFrequencyNoiseNode);
}
#undef LOCTEXT_NAMESPACE