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

313 lines
9.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MetasoundNodeRegistrationMacro.h"
#include "MetasoundAudioBuffer.h"
#include "CoreMinimal.h"
#include "DSP/BufferVectorOperations.h"
#include "DSP/FloatArrayMath.h"
#include "Internationalization/Text.h"
#include "MetasoundExecutableOperator.h"
#include "MetasoundFacade.h"
#include "MetasoundParamHelper.h"
#include "MetasoundPrimitives.h"
#include "MetasoundStandardNodesCategories.h"
#include "MetasoundStandardNodesNames.h"
#include "MetasoundTrigger.h"
#include "MetasoundVertex.h"
#define LOCTEXT_NAMESPACE "MetasoundStandardNodes_CrossfadeNode"
#define REGISTER_CROSSFADE_NODE(DataType, Number) \
using FCrossfadeNode##DataType##_##Number = TCrossfadeNode<DataType, Number>; \
METASOUND_REGISTER_NODE(FCrossfadeNode##DataType##_##Number) \
namespace Metasound
{
namespace CrossfadeVertexNames
{
METASOUND_PARAM(InputCrossfadeValue, "Crossfade Value", "Crossfade value to crossfade across inputs. Output will be the float value between adjacent whole number values.")
METASOUND_PARAM(OutputTrigger, "Out", "Output value.")
const FVertexName GetInputName(uint32 InIndex)
{
return *FString::Format(TEXT("In {0}"), { InIndex });
}
const FText GetInputDescription(uint32 InIndex)
{
return METASOUND_LOCTEXT_FORMAT("CrossfadeInputDesc", "Cross fade {0} input.", InIndex);
}
const FText GetInputDisplayName(uint32 InIndex)
{
return METASOUND_LOCTEXT_FORMAT("CrossfadeInputDisplayName", "In {0}", InIndex);
}
}
template<typename ValueType, uint32 NumInputs>
class TCrossfadeHelper
{
};
// Partial specialization for float
template<uint32 NumInputs>
class TCrossfadeHelper<float, NumInputs>
{
public:
TCrossfadeHelper(int32 NumFramesPerBlock) {}
void GetCrossfadeOutput(int32 IndexA, int32 IndexB, float Alpha, const TArray<FFloatReadRef>& InCurrentValues, float& OutValue)
{
const FFloatReadRef& InA = InCurrentValues[IndexA];
const FFloatReadRef& InB = InCurrentValues[IndexB];
OutValue = FMath::Lerp(*InA, *InB, Alpha);
}
};
// Partial specialization for FAudioBuffer
template<uint32 NumInputs>
class TCrossfadeHelper<FAudioBuffer, NumInputs>
{
public:
TCrossfadeHelper(int32 InNumFramesPerBlock)
: NumFramesPerBlock(InNumFramesPerBlock)
{
PrevGains.AddZeroed(NumInputs);
CurrentGains.AddZeroed(NumInputs);
NeedsMixing.AddZeroed(NumInputs);
}
void GetCrossfadeOutput(int32 IndexA, int32 IndexB, float Alpha, const TArray<FAudioBufferReadRef>& InAudioBuffersValues, FAudioBuffer& OutAudioBuffer)
{
// Determine the gains
for (int32 i = 0; i < NumInputs; ++i)
{
if (i == IndexA)
{
CurrentGains[i] = 1.0f - Alpha;
NeedsMixing[i] = true;
}
else if (i == IndexB)
{
CurrentGains[i] = Alpha;
NeedsMixing[i] = true;
}
else
{
CurrentGains[i] = 0.0f;
// If we were already at 0.0f, don't need to do any mixing!
if (PrevGains[i] == 0.0f)
{
NeedsMixing[i] = false;
}
}
}
// Zero the output buffer so we can mix into it
OutAudioBuffer.Zero();
TArrayView<float> OutAudioBufferView(OutAudioBuffer.GetData(), OutAudioBuffer.Num());
// Now write to the scratch buffers w/ fade buffer fast given the new inputs
for (int32 i = 0; i < NumInputs; ++i)
{
// Only need to do anything on an input if either curr or prev is non-zero
if (NeedsMixing[i])
{
// Copy the input to the output
const FAudioBufferReadRef& InBuff = InAudioBuffersValues[i];
TArrayView<const float> BufferView((*InBuff).GetData(), NumFramesPerBlock);
const float* BufferPtr = (*InBuff).GetData();
// mix in and fade to the target gain values
Audio::ArrayMixIn(BufferView, OutAudioBufferView, PrevGains[i], CurrentGains[i]);
}
}
// Copy the CurrentGains to PrevGains
PrevGains = CurrentGains;
}
private:
int32 NumFramesPerBlock = 0;
TArray<float> PrevGains;
TArray<float> CurrentGains;
TArray<bool> NeedsMixing;
};
template<typename ValueType, uint32 NumInputs>
class TCrossfadeOperator : public TExecutableOperator<TCrossfadeOperator<ValueType, NumInputs>>
{
public:
static const FVertexInterface& GetVertexInterface()
{
using namespace CrossfadeVertexNames;
auto CreateDefaultInterface = []() -> FVertexInterface
{
FInputVertexInterface InputInterface;
InputInterface.Add(TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(InputCrossfadeValue)));
for (uint32 i = 0; i < NumInputs; ++i)
{
const FDataVertexMetadata InputMetadata
{
GetInputDescription(i),
GetInputDisplayName(i)
};
InputInterface.Add(TInputDataVertex<ValueType>(GetInputName(i), InputMetadata));
}
FOutputVertexInterface OutputInterface;
OutputInterface.Add(TOutputDataVertex<ValueType>(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputTrigger)));
return FVertexInterface(InputInterface, OutputInterface);
};
static const FVertexInterface DefaultInterface = CreateDefaultInterface();
return DefaultInterface;
}
static const FNodeClassMetadata& GetNodeInfo()
{
auto CreateNodeClassMetadata = []() -> FNodeClassMetadata
{
FName DataTypeName = GetMetasoundDataTypeName<ValueType>();
FName OperatorName = *FString::Printf(TEXT("Trigger Route (%s, %d)"), *DataTypeName.ToString(), NumInputs);
FText NodeDisplayName = METASOUND_LOCTEXT_FORMAT("CrossfadeDisplayNamePattern", "Crossfade ({0}, {1})", GetMetasoundDataTypeDisplayText<ValueType>(), NumInputs);
const FText NodeDescription = METASOUND_LOCTEXT("CrossfadeDescription", "Crossfades inputs to outputs.");
FVertexInterface NodeInterface = GetVertexInterface();
FNodeClassMetadata Metadata
{
FNodeClassName { "Crossfade", OperatorName, DataTypeName },
1, // Major Version
0, // Minor Version
NodeDisplayName,
NodeDescription,
PluginAuthor,
PluginNodeMissingPrompt,
NodeInterface,
{ NodeCategories::Envelopes },
{ },
FNodeDisplayStyle()
};
return Metadata;
};
static const FNodeClassMetadata Metadata = CreateNodeClassMetadata();
return Metadata;
}
static TUniquePtr<IOperator> CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults)
{
using namespace CrossfadeVertexNames;
const FInputVertexInterfaceData& InputData = InParams.InputData;
FFloatReadRef CrossfadeValue = InputData.GetOrCreateDefaultDataReadReference<float>(METASOUND_GET_PARAM_NAME(InputCrossfadeValue), InParams.OperatorSettings);
TArray<TDataReadReference<ValueType>> InputValues;
for (uint32 i = 0; i < NumInputs; ++i)
{
InputValues.Add(InputData.GetOrCreateDefaultDataReadReference<ValueType>(GetInputName(i), InParams.OperatorSettings));
}
return MakeUnique<TCrossfadeOperator<ValueType, NumInputs>>(InParams.OperatorSettings, CrossfadeValue, MoveTemp(InputValues));
}
TCrossfadeOperator(const FOperatorSettings& InSettings, const FFloatReadRef& InCrossfadeValue, TArray<TDataReadReference<ValueType>>&& InInputValues)
: CrossfadeValue(InCrossfadeValue)
, InputValues(MoveTemp(InInputValues))
, OutputValue(TDataWriteReferenceFactory<ValueType>::CreateAny(InSettings))
, Crossfader(InSettings.GetNumFramesPerBlock())
{
PerformCrossfadeOutput();
}
virtual ~TCrossfadeOperator() = default;
virtual void BindInputs(FInputVertexInterfaceData& InOutVertexData) override
{
using namespace CrossfadeVertexNames;
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InputCrossfadeValue), CrossfadeValue);
for (uint32 i = 0; i < NumInputs; ++i)
{
InOutVertexData.BindReadVertex(GetInputName(i), InputValues[i]);
}
}
virtual void BindOutputs(FOutputVertexInterfaceData& InOutVertexData) override
{
using namespace CrossfadeVertexNames;
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(OutputTrigger), OutputValue);
}
void PerformCrossfadeOutput()
{
// Clamp the cross fade value based on the number of inputs
float CurrentCrossfadeValue = FMath::Clamp(*CrossfadeValue, 0.0f, (float)(NumInputs - 1));
// Only update the cross fade state if anything has changed
if (!FMath::IsNearlyEqual(CurrentCrossfadeValue, PrevCrossfadeValue))
{
PrevCrossfadeValue = CurrentCrossfadeValue;
IndexA = (int32)FMath::Floor(CurrentCrossfadeValue);
IndexB = FMath::Clamp(IndexA + 1, 0.0f, (float)(NumInputs - 1));
Alpha = CurrentCrossfadeValue - (float)IndexA;
}
// Need to call this each block in case inputs have changed
Crossfader.GetCrossfadeOutput(IndexA, IndexB, Alpha, InputValues, *OutputValue);
}
void Reset(const IOperator::FResetParams& InParams)
{
PerformCrossfadeOutput();
}
void Execute()
{
PerformCrossfadeOutput();
}
private:
FFloatReadRef CrossfadeValue;
TArray<TDataReadReference<ValueType>> InputValues;
TDataWriteReference<ValueType> OutputValue;
float PrevCrossfadeValue = -1.0f;
int32 IndexA = 0;
int32 IndexB = 0;
float Alpha = 0.0f;
TCrossfadeHelper<ValueType, NumInputs> Crossfader;
};
template<typename ValueType, uint32 NumInputs>
using TCrossfadeNode = TNodeFacade<TCrossfadeOperator<ValueType, NumInputs>>;
REGISTER_CROSSFADE_NODE(float, 2);
REGISTER_CROSSFADE_NODE(float, 3);
REGISTER_CROSSFADE_NODE(float, 4);
REGISTER_CROSSFADE_NODE(float, 5);
REGISTER_CROSSFADE_NODE(float, 6);
REGISTER_CROSSFADE_NODE(float, 7);
REGISTER_CROSSFADE_NODE(float, 8);
REGISTER_CROSSFADE_NODE(FAudioBuffer, 2);
REGISTER_CROSSFADE_NODE(FAudioBuffer, 3);
REGISTER_CROSSFADE_NODE(FAudioBuffer, 4);
REGISTER_CROSSFADE_NODE(FAudioBuffer, 5);
REGISTER_CROSSFADE_NODE(FAudioBuffer, 6);
REGISTER_CROSSFADE_NODE(FAudioBuffer, 7);
REGISTER_CROSSFADE_NODE(FAudioBuffer, 8);
}
#undef LOCTEXT_NAMESPACE