// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "DSP/BufferVectorOperations.h" #include "DSP/FloatArrayMath.h" #include "MetasoundAudioBuffer.h" #include "MetasoundBuilderInterface.h" #include "MetasoundDataReference.h" #include "MetasoundDataReferenceCollection.h" #include "MetasoundExecutableOperator.h" #include "MetasoundFacade.h" #include "MetasoundNode.h" #include "MetasoundNodeRegistrationMacro.h" #include "MetasoundNodeInterface.h" #include "MetasoundOperatorInterface.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" #define DEFINE_METASOUND_MATHOP(OpName, DataTypeName, DataClass, Description, Keywords) \ class F##OpName##DataTypeName##Node \ : public TNodeFacade, DataClass, DataClass>>\ { \ public: \ using TNodeFacade::TNodeFacade; \ static FNodeClassName GetClassName() { return { ::Metasound::StandardNodes::Namespace, TEXT(#OpName), TEXT(#DataTypeName)}; } \ static FText GetDisplayName() { return MathOpNames::Get##OpName##DisplayName(); } \ static FText GetDescription() { return Description; } \ static FName GetImageName() { return "MetasoundEditor.Graph.Node.Math." #OpName; } \ static TArray GetKeywords() { return Keywords; } \ }; #define DEFINE_METASOUND_OPERAND_TYPED_MATHOP(OpName, DataTypeName, DataClass, OperandTypeName, OperandDataClass, Description, Keywords) \ class F##OpName##DataTypeName##OperandTypeName##Node \ : public TNodeFacade, DataClass, OperandDataClass>> \ { \ public: \ using TNodeFacade::TNodeFacade; \ static FNodeClassName GetClassName() { return {::Metasound::StandardNodes::Namespace, TEXT(#OpName), TEXT(#DataTypeName " by " #OperandTypeName)}; } \ static FText GetDisplayName() { return MathOpNames::Get##OpName##DisplayName(); } \ static FText GetDescription() { return Description; } \ static FName GetImageName() { return "MetasoundEditor.Graph.Node.Math." #OpName; } \ static TArray GetKeywords() { return Keywords; } \ }; namespace Metasound { namespace MathOpNames { METASOUND_PARAM(PrimaryOperand, "PrimaryOperand", "Initial operand") METASOUND_PARAM(AdditionalOperands, "AdditionalOperands", "Additional operand(s)") METASOUND_PARAM(OutMath, "Out", "Math operation result.") METASOUND_PARAM(OutAudio, "Out", "Resulting buffer.") static const TArray AddKeywords = { METASOUND_LOCTEXT("AddMathKeyword", "+") }; static const TArray SubtractKeywords = { METASOUND_LOCTEXT("SubtractMathKeyword", "-") }; static const TArray MultiplyKeywords = { METASOUND_LOCTEXT("MultiplyMathKeyword", "*") }; static const TArray DivideKeywords = { METASOUND_LOCTEXT("DivideMathKeyword", "/") }; static const TArray PowerKeywords = { METASOUND_LOCTEXT("PowerMathKeyword", "^") }; static const TArray ModuloKeywords = { METASOUND_LOCTEXT("ModuloMathKeyword", "%") }; static const TArray MultiplyAudioFloatKeywords = { MultiplyKeywords[0], METASOUND_LOCTEXT("MultiplyGainKeyword", "gain"), // DSP/Modular synth term METASOUND_LOCTEXT("MultiplyVCAKeyword", "vca"), // Voltage Controlled Amplifier (DAW/Electronics term) }; template const FText GetAddDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("AddNodeDisplayNamePattern1", "Add ({0})", GetMetasoundDataTypeDisplayText()); return DisplayName; } template const FText GetAddDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("AddNodeDisplayNamePattern2", "Add ({0} to {1})", GetMetasoundDataTypeDisplayText(), GetMetasoundDataTypeDisplayText()); return DisplayName; } template const FText GetSubtractDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("SubtractNodeDisplayNamePattern", "Subtract ({0})", GetMetasoundDataTypeDisplayText()); return DisplayName; } template const FText GetSubtractDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("SubtractNodeOperandTypedDisplayNamePattern", "Subtract ({0} from {1})", GetMetasoundDataTypeDisplayText(), GetMetasoundDataTypeDisplayText()); return DisplayName; } template const FText GetMultiplyDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("MultiplyNodeDisplayNamePattern", "Multiply ({0})", GetMetasoundDataTypeDisplayText()); return DisplayName; } template const FText GetMultiplyDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("MultiplyNodeOperandTypedDisplayNamePattern", "Multiply ({0} by {1})", GetMetasoundDataTypeDisplayText(), GetMetasoundDataTypeDisplayText()); return DisplayName; } template const FText GetDivideDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("DivideNodeDisplayNamePattern", "Divide ({0})", GetMetasoundDataTypeDisplayText()); return DisplayName; } template const FText GetDivideDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("DivideNodeOperandTypedDisplayNamePattern", "Divide ({0} by {1})", GetMetasoundDataTypeDisplayText(), GetMetasoundDataTypeDisplayText()); return DisplayName; } template const FText GetModuloDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("ModuloNodeDisplayNamePattern", "Modulo ({0})", GetMetasoundDataTypeDisplayText()); return DisplayName; } template const FText GetModuloDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("ModuloNodeOperandTypedDisplayNamePattern", "Modulo ({0} by {1})", GetMetasoundDataTypeDisplayText(), GetMetasoundDataTypeDisplayText()); return DisplayName; } template const FText GetPowerDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("PowerNodeDisplayNamePattern", "Power ({0})", GetMetasoundDataTypeDisplayText()); return DisplayName; } template const FText GetPowerDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("PowerNodeOperandTypedDisplayNamePattern", "Power ({0} to the power of {1})", GetMetasoundDataTypeDisplayText(), GetMetasoundDataTypeDisplayText()); return DisplayName; } template const FText GetLogarithmDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("LogNodeDisplayNamePattern", "Log ({0})", GetMetasoundDataTypeDisplayText()); return DisplayName; } template const FText GetLogarithmDisplayName() { static const FText DisplayName = METASOUND_LOCTEXT_FORMAT("LogarithmNodeOperandTypedDisplayNamePattern", "Log ({0}-Base logarithm of {1})", GetMetasoundDataTypeDisplayText(), GetMetasoundDataTypeDisplayText()); return DisplayName; } } template class TMathOperator : public TExecutableOperator> { private: using TDataClassReadRef = TDataReadReference; using TOperandDataClassReadRef = TDataReadReference; using TDataClassWriteRef = TDataWriteReference; TMathOpClass InstanceData; TDataClassReadRef PrimaryOperandRef; TArray AdditionalOperandRefs; TDataClassWriteRef ValueRef; public: static const FNodeClassMetadata& GetNodeInfo() { auto InitNodeInfo = []() -> FNodeClassMetadata { FNodeDisplayStyle DisplayStyle; DisplayStyle.ImageName = TDerivedClass::GetImageName(); DisplayStyle.bShowName = false; DisplayStyle.bShowInputNames = false; DisplayStyle.bShowOutputNames = false; FNodeClassMetadata Info; Info.ClassName = TDerivedClass::GetClassName(); Info.MajorVersion = 1; Info.MinorVersion = 1; Info.DisplayName = TDerivedClass::GetDisplayName(); Info.Description = TDerivedClass::GetDescription(); Info.Author = PluginAuthor; Info.PromptIfMissing = PluginNodeMissingPrompt; Info.DefaultInterface = TMathOpClass::GetVertexInterface(); Info.DisplayStyle = DisplayStyle; Info.CategoryHierarchy = { NodeCategories::Math }; Info.Keywords = TDerivedClass::GetKeywords(); return Info; }; static const FNodeClassMetadata Info = InitNodeInfo(); return Info; } void Reset(const IOperator::FResetParams& InParams) { TMathOpClass::Calculate(InstanceData, PrimaryOperandRef, AdditionalOperandRefs, ValueRef); } void Execute() { TMathOpClass::Calculate(InstanceData, PrimaryOperandRef, AdditionalOperandRefs, ValueRef); } static TUniquePtr CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) { using namespace MathOpNames; const FInputVertexInterfaceData& InputData = InParams.InputData; TDataClassReadRef PrimaryOperand = InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(PrimaryOperand), InParams.OperatorSettings); // TODO: Support dynamic number of inputs TOperandDataClassReadRef Op1 = InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(AdditionalOperands), InParams.OperatorSettings); TArray AdditionalOperandRefs = { Op1 }; return MakeUnique(InParams, PrimaryOperand, AdditionalOperandRefs); } TMathOperator(const FBuildOperatorParams& InParams, const TDataClassReadRef& InPrimaryOperand, const TArray& InAdditionalOperands) : PrimaryOperandRef(InPrimaryOperand) , AdditionalOperandRefs(InAdditionalOperands) , ValueRef(TDataWriteReferenceFactory::CreateAny(InParams.OperatorSettings)) { Reset(InParams); } virtual void BindInputs(FInputVertexInterfaceData& InOutVertexData) override { using namespace MathOpNames; InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandRef); InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalOperandRefs[0]); } virtual void BindOutputs(FOutputVertexInterfaceData& InOutVertexData) override { using namespace MathOpNames; InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(OutMath), ValueRef); } }; // Standard Math Operations template class TMathOpAdd { public: using TDataClassReadRef = TDataReadReference; using TDataClassWriteRef = TDataWriteReference; using TOperandDataClassReadRef = TDataReadReference; static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpAddNode_InitialTooltip", "Initial addend.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalAddendsMetadata { METASOUND_LOCTEXT("MathOpAddNode_AddendsTooltip", "Additional addend(s).") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FDataVertexMetadata OutputMetadata { METASOUND_LOCTEXT("MathOpAddNode_OutTooltip", "Math operation result") // description , METASOUND_GET_PARAM_DISPLAYNAME(OutMath) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata, static_cast(0)), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalAddendsMetadata, static_cast(0)) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME(OutMath), OutputMetadata) ) ); return Interface; } static void Calculate(TMathOpAdd& InInstanceData, const TDataClassReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, TDataClassWriteRef& OutResult) { if (!ensure(!InAdditionalOperands.IsEmpty())) { return; } *OutResult = *InPrimaryOperand; for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { *OutResult += *InAdditionalOperands[i]; } } }; template class TMathOpSubtract { public: using TDataClassReadRef = TDataReadReference; using TDataClassWriteRef = TDataWriteReference; using TOperandDataClassReadRef = TDataReadReference; static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpSubtractNode_MinuendTooltip", "Minuend.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalOperandsMetadata { METASOUND_LOCTEXT("MathOpSubtractNode_SubtrahendsTooltip", "Subtrahend(s).") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FDataVertexMetadata OutputMetadata { METASOUND_LOCTEXT("MathOpSubtractNode_OutTooltip", "Subtraction result") // description , METASOUND_GET_PARAM_DISPLAYNAME(OutMath) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata, static_cast(0)), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalOperandsMetadata, static_cast(0)) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME(OutMath), OutputMetadata) ) ); return Interface; } static void Calculate(TMathOpSubtract& InInstanceData, const TDataClassReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, TDataClassWriteRef& OutResult) { if (!ensure(!InAdditionalOperands.IsEmpty())) { return; } *OutResult = *InPrimaryOperand; for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { *OutResult -= *InAdditionalOperands[i]; } } }; template class TMathOpMultiply { public: using TDataClassReadRef = TDataReadReference; using TDataClassWriteRef = TDataWriteReference; using TOperandDataClassReadRef = TDataReadReference; static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpMultiplyNode_InitMultiplicandTooltip", "Initial multiplicand.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalOperandsMetadata { METASOUND_LOCTEXT("MathOpMultiplyNode_MultiplicandsTooltip", "Additional multiplicand(s).") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FDataVertexMetadata OutputMetadata { METASOUND_LOCTEXT("MathOpMultiplyNode_ResultTooltip", "Multiplication result") // description , METASOUND_GET_PARAM_DISPLAYNAME(OutMath) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata, static_cast(1)), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalOperandsMetadata, static_cast(1)) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME(OutMath), OutputMetadata) ) ); return Interface; } static void Calculate(TMathOpMultiply& InInstanceData, const TDataClassReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, TDataClassWriteRef& OutResult) { if (!ensure(!InAdditionalOperands.IsEmpty())) { return; } *OutResult = *InPrimaryOperand; for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { *OutResult *= *InAdditionalOperands[i]; } } }; template class TMathOpDivide { public: using TDataClassReadRef = TDataReadReference; using TDataClassWriteRef = TDataWriteReference; using TOperandDataClassReadRef = TDataReadReference; static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpDivideNode_DividendTooltip", "Dividend.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalOperandsMetadata { METASOUND_LOCTEXT("MathOpDivideNode_DivisorsTooltip", "Divisor(s).") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FDataVertexMetadata OutputMetadata { METASOUND_LOCTEXT("MathOpDivideNode_OutTooltip", "Division result") // description , METASOUND_GET_PARAM_DISPLAYNAME(OutMath) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata, static_cast(1)), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalOperandsMetadata, static_cast(1)) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME(OutMath), OutputMetadata) ) ); return Interface; } static void Calculate(TMathOpDivide& InInstanceData, const TDataClassReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, TDataClassWriteRef& OutResult) { if (!ensure(!InAdditionalOperands.IsEmpty())) { return; } *OutResult = *InPrimaryOperand; for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { const TDataClass& OperandValue = *InAdditionalOperands[i]; if (OperandValue == static_cast(0)) { // TODO: Error here return; } *OutResult /= OperandValue; } } }; template class TMathOpModulo { public: using TDataClassReadRef = TDataReadReference; using TDataClassWriteRef = TDataWriteReference; using TOperandDataClassReadRef = TDataReadReference; static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpModuloNode_DividendTooltip", "Dividend.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalOperandsMetadata { METASOUND_LOCTEXT("MathOpModuloNode_DivisorsTooltip", "Divisor(s).") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FDataVertexMetadata OutputMetadata { METASOUND_LOCTEXT("MathOpModuloNode_OutTooltip", "Modulo result") // description , METASOUND_GET_PARAM_DISPLAYNAME(OutMath) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata, static_cast(1)), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalOperandsMetadata, static_cast(1)) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME(OutMath), OutputMetadata) ) ); return Interface; } static void Calculate(TMathOpModulo& InInstanceData, const TDataClassReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, TDataClassWriteRef& OutResult) { if (!ensure(!InAdditionalOperands.IsEmpty())) { return; } *OutResult = *InPrimaryOperand; // TODO: chaining modulo operations doesn't make too much sense... how do we forbid additional operands in some math ops? for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { const TDataClass& OperandValue = *InAdditionalOperands[i]; if (OperandValue == static_cast(0)) { // TODO: Error here return; } *OutResult %= OperandValue; } } }; template class TMathOpPower { public: using TDataClassReadRef = TDataReadReference; using TDataClassWriteRef = TDataWriteReference; using TOperandDataClassReadRef = TDataReadReference; static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpPowerNode_Base", "The base of the power") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalOperandsMetadata { METASOUND_LOCTEXT("MathOpPowerNode_Exponent", "The exponent to take the base to the power of") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FDataVertexMetadata OutputMetadata { METASOUND_LOCTEXT("MathOpPowerNode_Result", "Returns Base to the Exponent power") // description , METASOUND_GET_PARAM_DISPLAYNAME(OutMath) // display name }; static const FVertexInterface DefaultInterface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalOperandsMetadata) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME(OutMath), OutputMetadata) ) ); return DefaultInterface; } static void Calculate(TMathOpPower& InInstanceData, const TDataClassReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, TDataClassWriteRef& OutResult) { if (!ensure(!InAdditionalOperands.IsEmpty())) { return; } *OutResult = *InPrimaryOperand; for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { const TDataClass& OperandValue = *InAdditionalOperands[i]; *OutResult = FMath::Pow(*OutResult, OperandValue); } } }; template class TMathOpLogarithm { public: using TDataClassReadRef = TDataReadReference; using TDataClassWriteRef = TDataWriteReference; using TOperandDataClassReadRef = TDataReadReference; static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpLogarithmNode_Base", "The base of the logarithm") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalOperandsMetadata { METASOUND_LOCTEXT("MathOpLogarithmNode_Value", "The value to find the logarithm of") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FDataVertexMetadata OutputMetadata { METASOUND_LOCTEXT("MathOpLogarithmNode_Result", "The logarithm of the inputted value") // description , METASOUND_GET_PARAM_DISPLAYNAME(OutMath) // display name }; static const FVertexInterface DefaultInterface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalOperandsMetadata) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME(OutMath), OutputMetadata) ) ); return DefaultInterface; } static void Calculate(TMathOpLogarithm& InInstanceData, const TDataClassReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, TDataClassWriteRef& OutResult) { if (!ensure(!InAdditionalOperands.IsEmpty())) { return; } *OutResult = *InPrimaryOperand; //TODO: Find out how to disallow it from additional inputs, it doesn't really make sense in the context of logarithms for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { const TDataClass& OperandValue = *InAdditionalOperands[i]; *OutResult = FMath::LogX(FMath::Max(SMALL_NUMBER, *OutResult), FMath::Max(SMALL_NUMBER, OperandValue)); } } }; // Specialized Math Operations template <> class TMathOpAdd { public: static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpAddAudioBufferNameTooltip", "First addend.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalOperandsMetadata { METASOUND_LOCTEXT("MathOpAddAudioBufferAdditionalTooltip", "Additional addends.") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalOperandsMetadata) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutAudio)) ) ); return Interface; } static void Calculate(TMathOpAdd& InInstanceData, FAudioBufferReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, FAudioBufferWriteRef& OutResult) { if (!ensure(!InAdditionalOperands.IsEmpty())) { OutResult->Zero(); return; } const int32 NumSamples = InAdditionalOperands[0]->Num(); if (!ensure(NumSamples == OutResult->Num())) { // TODO: Error OutResult->Zero(); return; } FMemory::Memcpy(OutResult->GetData(), InPrimaryOperand->GetData(), sizeof(float) * NumSamples); for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { const FAudioBuffer& Operand = *InAdditionalOperands[i]; if (!ensure(NumSamples == Operand.Num())) { // TODO: Error OutResult->Zero(); return; } TArrayView OperandDataView(InAdditionalOperands[i]->GetData(), NumSamples); TArrayView OutDataView(OutResult->GetData(), NumSamples); if (NumSamples % AUDIO_NUM_FLOATS_PER_VECTOR_REGISTER) { Audio::ArrayMixIn(OperandDataView, OutDataView); } else { const float StartGain = 1.0f; const float EndGain = 1.0f; Audio::ArrayMixIn(OperandDataView, OutDataView, StartGain, EndGain); } } } }; template <> class TMathOpAdd { public: static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpAddTimeTooltip", "First addend.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalOperandsMetadata { METASOUND_LOCTEXT("MathOpAddTimeAdditionalalTooltip", "Additional addends.") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FDataVertexMetadata OutputMetadata { METASOUND_LOCTEXT("MathOpOutTooltip", "Math operation result") // description , METASOUND_GET_PARAM_DISPLAYNAME(OutMath) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata, 0.0f), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalOperandsMetadata, 0.0f) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME(OutMath), OutputMetadata) ) ); return Interface; } static void Calculate(TMathOpAdd& InInstanceData, const FTimeReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, FTimeWriteRef& OutResult) { if (!ensure(!InAdditionalOperands.IsEmpty())) { return; } *OutResult = *InPrimaryOperand; for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { *OutResult += *InAdditionalOperands[i]; } } }; template <> class TMathOpAdd { public: static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpAddAudioTooltip", "Audio Buffer to add offset(s) to.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalOperandsMetadata { METASOUND_LOCTEXT("MathOpAddAdditionalTooltip", "Float addends of which to offset buffer samples.") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalOperandsMetadata, 0.0f) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutAudio)) ) ); return Interface; } static void Calculate(TMathOpAdd& InInstanceData, const FAudioBufferReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, FAudioBufferWriteRef& OutResult) { FMemory::Memcpy(OutResult->GetData(), InPrimaryOperand->GetData(), sizeof(float) * OutResult->Num()); TArrayView OutResultView(OutResult->GetData(), OutResult->Num()); for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { Audio::ArrayAddConstantInplace(OutResultView, *InAdditionalOperands[i]); } } }; template <> class TMathOpSubtract { public: static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpBuffersMinuendTooltip", "Initial buffer to act as minuend.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalAddendsMetadata { METASOUND_LOCTEXT("MathOpSubtractBuffersSubtrahendsTooltip", "Additional buffers to act as subtrahend(s).") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalAddendsMetadata) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutAudio)) ) ); return Interface; } static void Calculate(TMathOpSubtract& InInstanceData, FAudioBufferReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, FAudioBufferWriteRef& OutResult) { if (!ensure(!InAdditionalOperands.IsEmpty())) { OutResult->Zero(); return; } const int32 NumSamples = InAdditionalOperands[0]->Num(); if (!ensure(NumSamples == OutResult->Num())) { // TODO: Error OutResult->Zero(); return; } FMemory::Memcpy(OutResult->GetData(), InPrimaryOperand->GetData(), sizeof(float) * NumSamples); for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { const FAudioBuffer& Operand = *InAdditionalOperands[i]; if (!ensure(NumSamples == Operand.Num())) { // TODO: Error OutResult->Zero(); return; } TArrayView OperandDataView(InAdditionalOperands[i]->GetData(), NumSamples); TArrayView OutDataView(OutResult->GetData(), NumSamples); Audio::ArraySubtractInPlace2(OutDataView, OperandDataView); } } }; template <> class TMathOpSubtract { public: static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpTimeNode_MinuendTooltip", "Time minuend.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalAddendsMetadata { METASOUND_LOCTEXT("MathOpTimeNode_SubtrahendsTooltip", "Time subtrahends.") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FDataVertexMetadata OutputMetadata { METASOUND_LOCTEXT("MathOpTimeNode_SubtractOutTooltip", "Resulting time value") // description , METASOUND_GET_PARAM_DISPLAYNAME(OutMath) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata, 0.0f), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalAddendsMetadata, 0.0f) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME(OutMath), OutputMetadata) ) ); return Interface; } static void Calculate(TMathOpSubtract& InInstanceData, const FTimeReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, FTimeWriteRef& OutResult) { if (!ensure(!InAdditionalOperands.IsEmpty())) { return; } *OutResult = *InPrimaryOperand; for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { *OutResult -= *InAdditionalOperands[i]; } } }; template <> class TMathOpMultiply { public: static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpAudioMultiplyNode_InitMultiplicandTooltip", "Initial audio to multiply.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalAddendsMetadata { METASOUND_LOCTEXT("MathOpAudioMultiplyNode_MultiplicandsTooltip", "Additional audio to multiply sample-by-sample.") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalAddendsMetadata) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutAudio)) ) ); return Interface; } static void Calculate(TMathOpMultiply& InInstanceData, const FAudioBufferReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, FAudioBufferWriteRef& OutResult) { if (!ensure(!InAdditionalOperands.IsEmpty())) { OutResult->Zero(); return; } const int32 NumSamples = InPrimaryOperand->Num(); if (!ensure(NumSamples == OutResult->Num())) { // TODO: Error OutResult->Zero(); return; } if (InAdditionalOperands.Num() > 0) { const FAudioBuffer& Operand = *InAdditionalOperands[0]; if (!ensure(NumSamples == Operand.Num())) { // TODO: Error OutResult->Zero(); return; } TArrayView OperandADataView(InPrimaryOperand->GetData(), NumSamples); TArrayView OperandBDataView(Operand.GetData(), NumSamples); TArrayView OutDataView(OutResult->GetData(), NumSamples); Audio::ArrayMultiply(OperandADataView, OperandBDataView, OutDataView); } else { FMemory::Memcpy(OutResult->GetData(), InPrimaryOperand->GetData(), sizeof(float) * NumSamples); } for (int32 i = 1; i < InAdditionalOperands.Num(); ++i) { const FAudioBuffer& Operand = *InAdditionalOperands[i]; if (!ensure(NumSamples == Operand.Num())) { // TODO: Error OutResult->Zero(); return; } TArrayView OperandDataView(InAdditionalOperands[i]->GetData(), NumSamples); TArrayView OutDataView(OutResult->GetData(), NumSamples); Audio::ArrayMultiplyInPlace(OperandDataView, OutDataView); } } }; template <> class TMathOpMultiply { bool bInit = false; float LastGain = 0.0f; public: static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpAudioFloatMultiplyNode_FloatTooltip", "Audio multiplicand.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalOperandsMetadata { METASOUND_LOCTEXT("MathOpAudioFloatMultiplyNode_MultiplicandTooltip", "Float multiplicand to apply sample-by-sample to audio. Interpolates over buffer size on value change.") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalOperandsMetadata, 1.0f) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutAudio)) ) ); return Interface; } static void Calculate(TMathOpMultiply& InInstanceData, const FAudioBufferReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, FAudioBufferWriteRef& OutResult) { constexpr float MaxGain = TNumericLimits::Max() / 1024.f; constexpr float MinGain = -MaxGain; if (!InInstanceData.bInit) { InInstanceData.bInit = true; InInstanceData.LastGain = 1.0f; for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { InInstanceData.LastGain *= FMath::Clamp(*InAdditionalOperands[i], MinGain, MaxGain); } } FMemory::Memcpy(OutResult->GetData(), InPrimaryOperand->GetData(), sizeof(float) * InPrimaryOperand->Num()); float NewGain = 1.0f; for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { const float Gain = FMath::Clamp(*InAdditionalOperands[i], MinGain, MaxGain); Audio::ArrayFade(*OutResult, InInstanceData.LastGain, Gain); NewGain *= Gain; } InInstanceData.LastGain = NewGain; } }; template <> class TMathOpMultiply { public: static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpTimeFloatMultiplyNode_MultiplyFloatTooltip", "Time multiplicand.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalOperandsMetadata { METASOUND_LOCTEXT("MathOpTimeFloatMultiplyNode_MultiplicandsTooltip", "Float multiplicand(s).") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FDataVertexMetadata OutputMetadata { METASOUND_LOCTEXT("MathOpTimeFloatMultiplyNode_OutTooltip", "Time float multiplication output") // description , METASOUND_GET_PARAM_DISPLAYNAME(OutMath) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata, 1.0f), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalOperandsMetadata, 1.0f) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME(OutMath), OutputMetadata) ) ); return Interface; } static void Calculate(TMathOpMultiply& InInstanceData, const FTimeReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, FTimeWriteRef& OutResult) { *OutResult = *InPrimaryOperand; if (!ensure(!InAdditionalOperands.IsEmpty())) { return; } for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { const float OperandValue = *InAdditionalOperands[i]; *OutResult *= OperandValue; } } }; template <> class TMathOpDivide { public: static const FVertexInterface& GetVertexInterface() { using namespace MathOpNames; static const FDataVertexMetadata PrimaryOperandMetadata { METASOUND_LOCTEXT("MathOpTimeFloatDivideNode_DividendFloatTooltip", "Time dividend.") // description , METASOUND_GET_PARAM_DISPLAYNAME(PrimaryOperand) // display name }; static const FDataVertexMetadata AdditionalOperandsMetadata { METASOUND_LOCTEXT("MathOpTimeFloatDivideNode_DivisorsTooltip", "Float divisor(s).") // description , METASOUND_GET_PARAM_DISPLAYNAME(AdditionalOperands) // display name }; static const FDataVertexMetadata OutputMetadata { METASOUND_LOCTEXT("MathOpTimeFloatDivideNode_OutTooltip", "Time divide-by-float output") // description , METASOUND_GET_PARAM_DISPLAYNAME(OutMath) // display name }; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME(PrimaryOperand), PrimaryOperandMetadata, 1.0f), TInputDataVertex(METASOUND_GET_PARAM_NAME(AdditionalOperands), AdditionalOperandsMetadata, 1.0f) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME(OutMath), OutputMetadata) ) ); return Interface; } static void Calculate(TMathOpDivide& InInstanceData, const FTimeReadRef& InPrimaryOperand, const TArray& InAdditionalOperands, FTimeWriteRef& OutResult) { *OutResult = *InPrimaryOperand; if (!ensure(!InAdditionalOperands.IsEmpty())) { return; } for (int32 i = 0; i < InAdditionalOperands.Num(); ++i) { const float OperandValue = *InAdditionalOperands[i]; if (OperandValue == 0.0f) { // TODO: Error here continue; } *OutResult /= OperandValue; } } }; template using TMathOpNode = TNodeFacade>; // Definitions DEFINE_METASOUND_MATHOP(Add, Float, float, METASOUND_LOCTEXT("MathAddFloatNode_Description", "Adds floats."), MathOpNames::AddKeywords) DEFINE_METASOUND_MATHOP(Add, Int32, int32, METASOUND_LOCTEXT("MathAddInt32Node_Description", "Adds int32s."), MathOpNames::AddKeywords) DEFINE_METASOUND_MATHOP(Add, Audio, FAudioBuffer, METASOUND_LOCTEXT("MathAddBufferNode_Description", "Adds buffers together by sample."), MathOpNames::AddKeywords) DEFINE_METASOUND_MATHOP(Add, Time, FTime, METASOUND_LOCTEXT("MathAddTimeNode_Description", "Adds time values."), MathOpNames::AddKeywords) DEFINE_METASOUND_OPERAND_TYPED_MATHOP(Add, Audio, FAudioBuffer, Float, float, METASOUND_LOCTEXT("MathAddAudioFloatNode_Description", "Add floats to buffer sample-by-sample."), MathOpNames::AddKeywords) DEFINE_METASOUND_MATHOP(Subtract, Float, float, METASOUND_LOCTEXT("MathSubractFloatNode_Description", "Subtracts floats."), MathOpNames::SubtractKeywords) DEFINE_METASOUND_MATHOP(Subtract, Int32, int32, METASOUND_LOCTEXT("MathSubractInt32Node_Description", "Subtracts int32s."), MathOpNames::SubtractKeywords) DEFINE_METASOUND_MATHOP(Subtract, Audio, FAudioBuffer, METASOUND_LOCTEXT("MathSubtractBufferNode_Description", "Subtracts buffers sample-by-sample."), MathOpNames::SubtractKeywords) DEFINE_METASOUND_MATHOP(Subtract, Time, FTime, METASOUND_LOCTEXT("MathSubractTimeNode_Description", "Subtracts time values."), MathOpNames::SubtractKeywords) DEFINE_METASOUND_MATHOP(Multiply, Float, float, METASOUND_LOCTEXT("MathMultiplyFloatNode_Description", "Multiplies floats."), MathOpNames::MultiplyKeywords) DEFINE_METASOUND_MATHOP(Multiply, Int32, int32, METASOUND_LOCTEXT("MathMultiplyInt32Node_Description", "Multiplies int32s."), MathOpNames::MultiplyKeywords) DEFINE_METASOUND_MATHOP(Multiply, Audio, FAudioBuffer, METASOUND_LOCTEXT("MathMultiplyBufferNode_Description", "Multiplies buffers together sample-by-sample."), MathOpNames::MultiplyKeywords) DEFINE_METASOUND_OPERAND_TYPED_MATHOP(Multiply, Audio, FAudioBuffer, Float, float, METASOUND_LOCTEXT("MathMultiplyAudioByFloatDescription", "Multiplies buffer by float scalars."), MathOpNames::MultiplyAudioFloatKeywords) DEFINE_METASOUND_OPERAND_TYPED_MATHOP(Multiply, Time, FTime, Float, float, METASOUND_LOCTEXT("MathMultiplyTimeNode_Description", "Scales time by floats."), MathOpNames::MultiplyKeywords) DEFINE_METASOUND_MATHOP(Divide, Float, float, METASOUND_LOCTEXT("MathDivideFloatNode_Description", "Divide float by another float."), MathOpNames::DivideKeywords) DEFINE_METASOUND_MATHOP(Divide, Int32, int32, METASOUND_LOCTEXT("MathDivideInt32Node_Description", "Divide int32 by another int32."), MathOpNames::DivideKeywords) DEFINE_METASOUND_OPERAND_TYPED_MATHOP(Divide, Time, FTime, Float, float, METASOUND_LOCTEXT("MathDivideTimeNode_Description", "Divides time by floats."), MathOpNames::DivideKeywords) DEFINE_METASOUND_MATHOP(Modulo, Int32, int32, METASOUND_LOCTEXT("MathModulusInt32Node_Description", "Modulo int32 by another int32."), MathOpNames::ModuloKeywords) DEFINE_METASOUND_MATHOP(Power, Float, float, METASOUND_LOCTEXT("MathPowerFloatNode_Description", "Raise float to the power of another float."), MathOpNames::PowerKeywords) DEFINE_METASOUND_MATHOP(Logarithm, Float, float, METASOUND_LOCTEXT("MathLogarithmFloatNode_Description", "Calculate float-base logarithm of another float."), TArray()) METASOUND_REGISTER_NODE(FAddFloatNode) METASOUND_REGISTER_NODE(FAddInt32Node) METASOUND_REGISTER_NODE(FAddTimeNode) METASOUND_REGISTER_NODE(FAddAudioNode) METASOUND_REGISTER_NODE(FAddAudioFloatNode) METASOUND_REGISTER_NODE(FSubtractFloatNode) METASOUND_REGISTER_NODE(FSubtractInt32Node) METASOUND_REGISTER_NODE(FSubtractTimeNode) METASOUND_REGISTER_NODE(FSubtractAudioNode) METASOUND_REGISTER_NODE(FMultiplyAudioNode) METASOUND_REGISTER_NODE(FMultiplyAudioFloatNode) METASOUND_REGISTER_NODE(FMultiplyFloatNode) METASOUND_REGISTER_NODE(FMultiplyInt32Node) METASOUND_REGISTER_NODE(FMultiplyTimeFloatNode) METASOUND_REGISTER_NODE(FDivideFloatNode) METASOUND_REGISTER_NODE(FDivideInt32Node) METASOUND_REGISTER_NODE(FDivideTimeFloatNode) METASOUND_REGISTER_NODE(FModuloInt32Node) METASOUND_REGISTER_NODE(FPowerFloatNode) METASOUND_REGISTER_NODE(FLogarithmFloatNode) } #undef LOCTEXT_NAMESPACE // MetasoundMathOpNode