// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "MetasoundBuilderInterface.h" #include "MetasoundDataReferenceCollection.h" #include "MetasoundExecutableOperator.h" #include "MetasoundFacade.h" #include "MetasoundNode.h" #include "MetasoundNodeInterface.h" #include "MetasoundNodeRegistrationMacro.h" #include "MetasoundOperatorInterface.h" #include "MetasoundPrimitives.h" #include "MetasoundStandardNodesNames.h" #include "MetasoundStandardNodesCategories.h" #include "MetasoundTrigger.h" #include "MetasoundParamHelper.h" #define LOCTEXT_NAMESPACE "MetasoundStandardNodes_TriggerOnValueChangeNode" namespace Metasound { namespace TriggerOnValueChangeVertexNames { METASOUND_PARAM(InputValue, "Value", "The input value to watch. Whenever this changes, the output trigger is sent."); METASOUND_PARAM(OutputOnChange, "Trigger", "The output trigger."); } namespace TriggerOnValueChangePrivate { template static bool IsValueEqual(ValueType InValueA, ValueType InValueB) { return InValueA == InValueB; } static bool IsValueEqual(float InValueA, float InValueB) { return FMath::IsNearlyEqual(InValueA, InValueB); } } template class TTriggerOnValueChangeOperator : public TExecutableOperator> { public: static const FNodeClassMetadata& GetNodeInfo(); static const FVertexInterface& GetVertexInterface(); static TUniquePtr CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults); TTriggerOnValueChangeOperator(const FOperatorSettings& InSettings, const TDataReadReference& InValue); virtual void BindInputs(FInputVertexInterfaceData& InOutVertexData) override; virtual void BindOutputs(FOutputVertexInterfaceData& InOutVertexData) override; void Execute(); void Reset(const IOperator::FResetParams& InParams); private: // Parameter to watch for changes TDataReadReference ValueInput; // If gate is open, sends trigger FTriggerWriteRef TriggerOnChangeOutput; // Status of the gate ValueType PrevValue; }; template TTriggerOnValueChangeOperator::TTriggerOnValueChangeOperator(const FOperatorSettings& InSettings, const TDataReadReference& InValue) : ValueInput(InValue) , TriggerOnChangeOutput(FTriggerWriteRef::CreateNew(InSettings)) , PrevValue(*InValue) { } template void TTriggerOnValueChangeOperator::BindInputs(FInputVertexInterfaceData& InOutVertexData) { using namespace TriggerOnValueChangeVertexNames; InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InputValue), ValueInput); } template void TTriggerOnValueChangeOperator::BindOutputs(FOutputVertexInterfaceData& InOutVertexData) { using namespace TriggerOnValueChangeVertexNames; InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(OutputOnChange), TriggerOnChangeOutput); } template void TTriggerOnValueChangeOperator::Execute() { using namespace TriggerOnValueChangePrivate; TriggerOnChangeOutput->AdvanceBlock(); // If value changes, call the trigger at the start of the audio block if (!IsValueEqual(*ValueInput, PrevValue)) { PrevValue = *ValueInput; TriggerOnChangeOutput->TriggerFrame(0); } } template void TTriggerOnValueChangeOperator::Reset(const IOperator::FResetParams& InParams) { TriggerOnChangeOutput->Reset(); PrevValue = *ValueInput; } template TUniquePtr TTriggerOnValueChangeOperator::CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) { using namespace TriggerOnValueChangeVertexNames; const FInputVertexInterfaceData& InputData = InParams.InputData; TDataReadReference ValueIn = InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(InputValue), InParams.OperatorSettings); return MakeUnique(InParams.OperatorSettings, ValueIn); } template const FVertexInterface& TTriggerOnValueChangeOperator::GetVertexInterface() { using namespace TriggerOnValueChangeVertexNames; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(InputValue), static_cast(1)) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputOnChange)) ) ); return Interface; } template const FNodeClassMetadata& TTriggerOnValueChangeOperator::GetNodeInfo() { auto InitNodeInfo = []() -> FNodeClassMetadata { const FName DataTypeName = GetMetasoundDataTypeName(); const FName OperatorName = TEXT("Trigger On Value Change"); const FText NodeDisplayName = METASOUND_LOCTEXT_FORMAT("TriggerOnValueChangeName", "Trigger On Value Change ({0})", GetMetasoundDataTypeDisplayText()); const FText NodeDescription = METASOUND_LOCTEXT("TriggerOnValueChangeNameDesc", "Triggers when a given value changes."); FNodeClassMetadata Info; Info.ClassName = { Metasound::StandardNodes::Namespace, OperatorName, DataTypeName }; Info.MajorVersion = 1; Info.MinorVersion = 0; Info.DisplayName = NodeDisplayName; Info.Description = NodeDescription; Info.Author = PluginAuthor; Info.PromptIfMissing = PluginNodeMissingPrompt; Info.DefaultInterface = GetVertexInterface(); Info.CategoryHierarchy.Emplace(NodeCategories::Trigger); return Info; }; static const FNodeClassMetadata Info = InitNodeInfo(); return Info; } // Node Class template using TTriggerOnValueChangeNode = TNodeFacade>; using FTriggerOnInt32Change = TTriggerOnValueChangeNode; METASOUND_REGISTER_NODE(FTriggerOnInt32Change) using FTriggerOnFloatChange = TTriggerOnValueChangeNode; METASOUND_REGISTER_NODE(FTriggerOnFloatChange) using FTriggerOnBoolChange = TTriggerOnValueChangeNode; METASOUND_REGISTER_NODE(FTriggerOnBoolChange) } #undef LOCTEXT_NAMESPACE