Tutorial: Creating MetaSound Nodes in C++ Quickstart

This is a quickstart guide for making new types of MetaSound Nodes through C++ code.

https://dev.epicgames.com/community/learning/tutorials/ry7p/unreal-engine-creating-metasound-nodes-in-c-quickstart

4 Likes

I just get the error:
Cannot open include file: ‘MetasoundExecutableOperator.h’: No such file or directory

2 Likes

Found this template on the Facebook audio group, maybe it helps paint the full picture:

4 Likes

You probably need to add the relevant Metasound module to your project’s .build.cs file. Something like:

PrivateDependencyModuleNames.AddRange(new string[]
{
	"MetasoundEngine",
	"MetasoundFrontend"
});
3 Likes

@lantz.anna Any possibility of updating this tutorial for Unreal 5.1? I’m walking through it block-by-block and the very first block fails to work as expected because some of the types have been moved into the Metasound:: namespace.

There may be more incompatibilities… I’ve not gone through the full tutorial yet…

2 Likes

Hi I cant seem to create a new meta sound node to work

im in 5.3.1 and i keep having troubles with namespaces

Creating library C:\Users\tgall\Documents\Unreal Projects\AudioMixPlug\Plugins\MyCustomMetaSoundPlugin\Binaries\Win64\UnrealEditor-MyCustomMetaSoundPlugin.patch_14.lib and object C:\Users\tgall\Documents\Unreal Projects\AudioMixPlug\Plugins\MyCustomMetaSoundPlugin\Binaries\Win64\UnrealEditor-MyCustomMetaSoundPlugin.patch_14.exp
Module.MyCustomMetaSoundPlugin.cpp.obj : error LNK2001: unresolved external symbol “class FName const Metasound::StandardNodes::Namespace” (?Namespace@StandardNodes@Metasound@@3VFName@@B)
Module.MyCustomMetaSoundPlugin.cpp.obj : error LNK2001: unresolved external symbol “class FName const Metasound::StandardNodes::AudioVariant” (?AudioVariant@StandardNodes@Metasound@@3VFName@@B)
C:\Users\tgall\Documents\Unreal Projects\AudioMixPlug\Plugins\MyCustomMetaSoundPlugin\Binaries\Win64\UnrealEditor-MyCustomMetaSoundPlugin.patch_14.exe : fatal error LNK1120: 2 unresolved

Everythgin seems to compile until I use the line METASOUND_REGISTER_NODE(FTutorialNode);

my build.cs file contains

	PublicDependencyModuleNames.AddRange(
		new string[]
		{
			"Core",
			"MetasoundFrontend",
			"MetasoundGraphCore",
			// ... add other public dependencies that you statically link with here ...
		}
		);
		
	
	PrivateDependencyModuleNames.AddRange(
		new string[]
		{
			"CoreUObject",
			"Engine",
			"Slate",
			"SlateCore",
			"MetasoundEngine",
			"MetasoundEditor",
			"MetasoundFrontend",
			"MetasoundGraphCore",
			"AudioMixer",
			"SignalProcessing",
			// ... add private dependencies that you statically link with here ...	
		}

My Plugin file to hold the meta sounds

{
“FileVersion”: 3,
“Version”: 1,
“VersionName”: “1.0”,
“FriendlyName”: “MyCustomMetaSoundPlugin”,
“Description”: “”,
“Category”: “Other”,
“CreatedBy”: “”,
“CreatedByURL”: “”,
“DocsURL”: “”,
“MarketplaceURL”: “”,
“SupportURL”: “”,
“CanContainContent”: true,
“IsBetaVersion”: false,
“IsExperimentalVersion”: false,
“Installed”: false,
“Modules”: [
{
“Name”: “MyCustomMetaSoundPlugin”,
“Type”: “Runtime”,
“LoadingPhase”: “Default”
}
],

    "Plugins": [

    {

        "Name": "Metasound",

        "Enabled": true

    }

]

}

cant seem to get my head around this atall…

am i missing somehting extremly obvious

my cpp

include “MetasoundExecutableOperator.h” // TExecutableOperator class
include “MetasoundPrimitives.h” // ReadRef and WriteRef descriptions for bool, int32, float, and string
include “MetasoundNodeRegistrationMacro.h” // METASOUND_LOCTEXT and METASOUND_REGISTER_NODE macros
include “MetasoundStandardNodesNames.h” // StandardNodes namespace
include “MetasoundFacade.h” // FNodeFacade class, eliminates the need for a fair amount of boilerplate code
include “MetasoundParamHelper.h” // METASOUND_PARAM and METASOUND_GET_PARAM family of macros

// Required for ensuring the node is supported by all languages in engine. Must be unique per MetaSound.
#define LOCTEXT_NAMESPACE “MetasoundStandardNodes_MetaSoundTutorialNode”

namespace Metasound
{
// Vertex Names - define your node’s inputs and outputs here
namespace TutorialNodeNames
{
METASOUND_PARAM(InputAValue, “A”, “Input value A.”);
METASOUND_PARAM(InputBValue, “B”, “Input value B.”);

	METASOUND_PARAM(OutputValue, "Sum of A and B", "The sum of A and B.");
}


// Operator Class - defines the way your node is described, created and executed
class FTutorialOperator : public TExecutableOperator<FTutorialOperator>
{
	public:
		// Constructor
		FTutorialOperator(
			const FFloatReadRef& InAValue,
			const FFloatReadRef& InBValue)
			: InputA(InAValue)
			, InputB(InBValue)
			, TutorialNodeOutput(FFloatWriteRef::CreateNew(*InputA + *InputB))
		{
		}

		// Helper function for constructing vertex interface
		static const FVertexInterface& DeclareVertexInterface()
		{
			using namespace TutorialNodeNames;

			static const FVertexInterface Interface(
				FInputVertexInterface(
					TInputDataVertexModel<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(InputAValue)),
					TInputDataVertexModel<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(InputBValue))
				),
				FOutputVertexInterface(
					TOutputDataVertexModel<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputValue))
				)
			);

			return Interface;
		}

		// Retrieves necessary metadata about your node
		static const FNodeClassMetadata& GetNodeInfo()
		{
			auto CreateNodeClassMetadata = []() -> FNodeClassMetadata
			{
				FVertexInterface NodeInterface = DeclareVertexInterface();

				FNodeClassMetadata Metadata
				{
					FNodeClassName { StandardNodes::Namespace, "Tutorial Node", StandardNodes::AudioVariant }, 
					1, // Major Version
					0, // Minor Version
					METASOUND_LOCTEXT("TutorialNodeDisplayName", "Tutorial Node"),
					METASOUND_LOCTEXT("TutorialNodeDesc", "A simple node to demonstrate how to create new MetaSound nodes in C++. Adds two floats together"),
					PluginAuthor,
					PluginNodeMissingPrompt,
					NodeInterface,
					{ }, // Category Hierarchy 
					{ }, // Keywords for searching
					FNodeDisplayStyle{}
				};

				return Metadata;
			};

			static const FNodeClassMetadata Metadata = CreateNodeClassMetadata();
			return Metadata;
		}

		// Allows MetaSound graph to interact with your node's inputs
		virtual FDataReferenceCollection GetInputs() const override
		{
			using namespace TutorialNodeNames;

			FDataReferenceCollection InputDataReferences;

			InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(InputAValue), InputA);
			InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(InputBValue), InputB);

			return InputDataReferences;
		}

		// Allows MetaSound graph to interact with your node's outputs
		virtual FDataReferenceCollection GetOutputs() const override
		{
			using namespace TutorialNodeNames;

			FDataReferenceCollection OutputDataReferences;

			OutputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(OutputValue), TutorialNodeOutput);

			return OutputDataReferences;
		}

		// Used to instantiate a new runtime instance of your node
		static TUniquePtr<IOperator> CreateOperator(const FCreateOperatorParams& InParams, FBuildErrorArray& OutErrors)
		{
			using namespace TutorialNodeNames;

			const Metasound::FDataReferenceCollection& InputCollection = InParams.InputDataReferences;
			const Metasound::FInputVertexInterface& InputInterface = DeclareVertexInterface().GetInputInterface();

			TDataReadReference<float> InputA = InputCollection.GetDataReadReferenceOrConstructWithVertexDefault<float>(InputInterface, METASOUND_GET_PARAM_NAME(InputAValue), InParams.OperatorSettings);
			TDataReadReference<float> InputB = InputCollection.GetDataReadReferenceOrConstructWithVertexDefault<float>(InputInterface, METASOUND_GET_PARAM_NAME(InputBValue), InParams.OperatorSettings);

			return MakeUnique<FTutorialOperator>(InputA, InputB);
		}

		// Primary node functionality
		void Execute()
		{
			*TutorialNodeOutput = *InputA + *InputB;
		}

private:

	// Inputs
	FFloatReadRef InputA;
	FFloatReadRef InputB;

	// Outputs
	FFloatWriteRef TutorialNodeOutput;
};

// Node Class - Inheriting from FNodeFacade is recommended for nodes that have a static FVertexInterface
class FTutorialNode : public FNodeFacade
{
	public:
		FTutorialNode(const FNodeInitData& InitData)
			: FNodeFacade(InitData.InstanceName, InitData.InstanceID, TFacadeOperatorClass<FTutorialOperator>())
		{
		}
};

// Register node
METASOUND_REGISTER_NODE(FTutorialNode);

}

#undef LOCTEXT_NAMESPACE

Ive also been through Aarons tutorial for the pitch shifter , Same Issues…

1 Like

Hi my friend , I want create my custom node and use custom data type instead float , but this type can`t reflect correspond widget . Should I make-up anywhere?

Curve is my variable of custom type like WaveTableBank.

2 Likes

Hi, I am a game developer. Now I also want to create custom meta sound nodes to get curves, can you refer to how your code is composed? Thx!

1 Like

Hi, if I follow the Building Off Of Existing Templates code as you wrote at the end and then put in the custom plugin, how should I activate it? I currently created a plugin and then put Building Off Of Existing Templates code into it, but it doesn’t take effect in the engine…

1 Like

For anyone coming here in 5.5: looks like there’s been some API changes. There are now deprecation warnings for FCreateOperatorParams and TInputDataVertexModel, and it looks like a call to the function FMetasoundFrontendRegistryContainer::Get()->RegisterPendingNodes(); is needed in the StartupModule() function of whichever module you’re putting the node in for it to get registered properly (if you’re not getting any errors or warnings in the log, but the node isn’t showing up as an option in the graph, this is probably the issue. It took me a while to figure out :sweat_smile:)

The following altered node template is working for me on 5.5:

#include "TestMetaSoundNode.h"

#include "MetasoundExecutableOperator.h"         // TExecutableOperator class
#include "MetasoundPrimitives.h"                 // ReadRef and WriteRef descriptions for bool, int32, float, and string
#include "MetasoundNodeRegistrationMacro.h"      // METASOUND_LOCTEXT and METASOUND_REGISTER_NODE macros
#include "MetasoundStandardNodesNames.h"         // StandardNodes namespace
#include "MetasoundFacade.h"				     // FNodeFacade class, eliminates the need for a fair amount of boilerplate code
#include "MetasoundParamHelper.h"                // METASOUND_PARAM and METASOUND_GET_PARAM family of macros




// Required for ensuring the node is supported by all languages in engine. Must be unique per MetaSound.
#define LOCTEXT_NAMESPACE "MetasoundStandardNodes_MetaSoundTutorialNode"


namespace Metasound
{
	// Vertex Names - define your node's inputs and outputs here
	namespace TutorialNodeNames
	{
		METASOUND_PARAM(InputAValue, "A", "Input value A.");
		METASOUND_PARAM(InputBValue, "B", "Input value B.");

		METASOUND_PARAM(OutputValue, "Sum of A and B", "The sum of A and B.");
	}

	class BOOLEANOPERATORMETASOUNDS_API FTestClass
	{
		FTestClass() {};
		~FTestClass() {};
	};


	// Operator Class - defines the way your node is described, created and executed
	class BOOLEANOPERATORMETASOUNDS_API FTutorialOperator : public TExecutableOperator<FTutorialOperator>
	{
	public:
		// Constructor
		FTutorialOperator(
			const FFloatReadRef& InAValue,
			const FFloatReadRef& InBValue)
			: InputA(InAValue)
			, InputB(InBValue)
			, TutorialNodeOutput(FFloatWriteRef::CreateNew(*InputA + *InputB))
		{
		}

		// Helper function for constructing vertex interface
		static const FVertexInterface& DeclareVertexInterface()
		{
			using namespace TutorialNodeNames;

			static const FVertexInterface Interface(
				FInputVertexInterface(
					TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(InputAValue)),
					TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(InputBValue))
				),
				FOutputVertexInterface(
					TOutputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputValue))
				)
			);

			return Interface;
		}

		// Retrieves necessary metadata about your node
		static const FNodeClassMetadata& GetNodeInfo()
		{
			auto CreateNodeClassMetadata = []() -> FNodeClassMetadata
				{
					FVertexInterface NodeInterface = DeclareVertexInterface();

					FNodeClassMetadata Metadata
					{
						FNodeClassName { StandardNodes::Namespace, "Tutorial Node", StandardNodes::AudioVariant },
						1, // Major Version
						0, // Minor Version
						METASOUND_LOCTEXT("TutorialNodeDisplayName", "Tutorial Node"),
						METASOUND_LOCTEXT("TutorialNodeDesc", "A simple node to demonstrate how to create new MetaSound nodes in C++. Adds two floats together"),
						PluginAuthor,
						PluginNodeMissingPrompt,
						NodeInterface,
						{  }, // Category Hierarchy 
						{ METASOUND_LOCTEXT("TutorialNodeKeyword", "Tutorial")}, // Keywords for searching
						FNodeDisplayStyle{}
					};

					return Metadata;
				};

			static const FNodeClassMetadata Metadata = CreateNodeClassMetadata();
			return Metadata;
		}

		// Allows MetaSound graph to interact with your node's inputs
		virtual FDataReferenceCollection GetInputs() const override
		{
			using namespace TutorialNodeNames;

			FDataReferenceCollection InputDataReferences;

			InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(InputAValue), InputA);
			InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(InputBValue), InputB);

			return InputDataReferences;
		}

		// Allows MetaSound graph to interact with your node's outputs
		virtual FDataReferenceCollection GetOutputs() const override
		{
			using namespace TutorialNodeNames;

			FDataReferenceCollection OutputDataReferences;

			OutputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(OutputValue), TutorialNodeOutput);

			return OutputDataReferences;
		}


		// Used to instantiate a new runtime instance of your node - DEPRECATED
		/*
		static TUniquePtr<IOperator> CreateOperator(const FCreateOperatorParams& InParams, FBuildErrorArray& OutErrors)
		{
			using namespace TutorialNodeNames;

			const Metasound::FDataReferenceCollection& InputCollection = InParams.InputDataReferences;
			const Metasound::FInputVertexInterface& InputInterface = DeclareVertexInterface().GetInputInterface();

			TDataReadReference<float> InputA = InputCollection.GetDataReadReferenceOrConstructWithVertexDefault<float>(InputInterface, METASOUND_GET_PARAM_NAME(InputAValue), InParams.OperatorSettings);
			TDataReadReference<float> InputB = InputCollection.GetDataReadReferenceOrConstructWithVertexDefault<float>(InputInterface, METASOUND_GET_PARAM_NAME(InputBValue), InParams.OperatorSettings);

			return MakeUnique<FTutorialOperator>(InputA, InputB);
		}*/

		//New Operator API
		static TUniquePtr<IOperator> CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutErrors)
		{
			using namespace TutorialNodeNames;

			const FInputVertexInterfaceData& InputData = InParams.InputData;

			FFloatReadRef InputA = InputData.GetOrCreateDefaultDataReadReference<float>(METASOUND_GET_PARAM_NAME(InputAValue), InParams.OperatorSettings);
			FFloatReadRef InputB = InputData.GetOrCreateDefaultDataReadReference<float>(METASOUND_GET_PARAM_NAME(InputBValue), InParams.OperatorSettings);

			return MakeUnique<FTutorialOperator>(InputA, InputB);
		}

		// Primary node functionality
		void Execute()
		{
			*TutorialNodeOutput = *InputA + *InputB;
		}

	private:

		// Inputs
		FFloatReadRef InputA;
		FFloatReadRef InputB;

		// Outputs
		FFloatWriteRef TutorialNodeOutput;
	};

	// Node Class - Inheriting from FNodeFacade is recommended for nodes that have a static FVertexInterface
	class BOOLEANOPERATORMETASOUNDS_API FTutorialNode : public FNodeFacade
	{
	public:
		FTutorialNode(const FNodeInitData& InitData)
			: FNodeFacade(InitData.InstanceName, InitData.InstanceID, TFacadeOperatorClass<FTutorialOperator>())
		{
		}
	};

	// Register node
	METASOUND_REGISTER_NODE(FTutorialNode);
}

#undef LOCTEXT_NAMESPACE

But it also required putting the following in my plugin’s StartupModule function:

#include "BooleanOperatorMetaSounds.h"
#include "MetasoundDataTypeRegistrationMacro.h"

#define LOCTEXT_NAMESPACE "FBooleanOperatorMetaSoundsModule"

void FBooleanOperatorMetaSoundsModule::StartupModule()
{
	// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
	using namespace Metasound;

	FMetasoundFrontendRegistryContainer::Get()->RegisterPendingNodes();
}
1 Like