Link errors when creating new node types

Hi everyone,

I’m trying to create my own pin type for the vertex interface of my node. For now, I’m just trying to implement a basic new pin with TCHAR, just to make sure that everything works with a simple type.

I am developing a plugin called MetaCsound, not to confuse by MetaSound. Because of that, it has its own namespace and module macro METACSOUND_API. In a header file of my plugin, I have the following line:

DECLARE_METASOUND_DATA_REFERENCE_TYPES(TCHAR, METACSOUND_API, FCharTypeInfo, FCharReadRef, FCharWriteRef);

But then, when I try to create the new pin in my node, in a file of the same module, linker errors start to appear.

FCsoundOperator.cpp.obj : error LNK2001: unresolved external symbol "public: static wchar_t const * const Metasound::TDataReferenceTypeInfo<wchar_t>::TypeName" (?TypeName@?$TDataReferenceTypeInfo@_W@Metasound@@2PEB_WEB)
FCsoundOperator.cpp.obj : error LNK2001: unresolved external symbol "public: static void const * const Metasound::TDataReferenceTypeInfo<wchar_t>::TypeId" (?TypeId@?$TDataReferenceTypeInfo@_W@Metasound@@2QEBXEB)

The current build.cs file has lots of dependencies, just to make sure that this is not a problem. Currently it looks like this:

PublicDependencyModuleNames.AddRange(
    new string[]
    {
        "Core",
        "MetasoundFrontend",
        "MetasoundGraphCore",
        "MetasoundEditor",
        "Projects",
        "SignalProcessing",
        "AudioExtensions",
        "MetasoundEngine",
        "CoreUObject",
        "Engine",
    }
);

I don’t really understand whats happening. Any advice on this would be very helpful.

Thank you for your time.

Albert

Did you include #include "MetasoundPrimitives.h" where you use any part of MetasoundFrontend (TDataReferenceTypeInfo)?

From the error it looks like FCsoundOperator.cpp is using it and not including the reference to the header making it unknown to the compiler.

I have tried including this header file everywhere now, but it has not worked. I don’t think that the primitives file is the problem here, because it only has pin declarations to other built-in types, but not the one that I’m creating, the TCHAR.

is the MetasoundFrontend plugin enabled in you uproject file under plugins? The project might not be loading up the dependency.
Also make sure the plugin order is correct. Make sure your plugin loads after the other plugins it’s dependent on.

LoadingPhase can impact this, calling a plugin too early will cause errors.

        EarliestPossible,
        PostConfigInit,
        PostSplashScreen,
        PreEarlyLoadingScreen,
        PreLoadingScreen,
        PreDefault,
        Default,
        PostDefault,
        PostEngineInit,
        None,
        Max,

Also if you are referencing variables of a namespace out of the namespace make sure you add the leading namespace before the variable definition
example

DECLARE_METASOUND_DATA_REFERENCE_TYPES(AudioModulation::FSoundModulatorAsset, AUDIOMODULATION_API, FSoundModulatorAssetTypeInfo, FSoundModulatorAssetReadRef, FSoundModulatorAssetWriteRef)

I have been trying the things that you have said, but the problem still persists. I have payed extra attention to the included header files and to the order of dependencies. I have simplified the namespaces as well, and I have removed the files generated by Visual Studio and regenerated them again with Unreal Engine (Intermediate, Binaries…)

You can see the current state of the project here: GitHub - AlbertMadrenys/MetaCsoundProject: Upcomming plugin for Unreal Engine that aims to integrate the audio signal processing engine Csound within the MetaSounds environment.

This are the public dependencies of the project build.cs file (MetaCsound is the new plugin):

PublicDependencyModuleNames.AddRange
(
	new string[] {
		"Core",
		"CoreUObject",
		"Engine",
		"InputCore",
		"EnhancedInput",

		"Projects",
		"SignalProcessing",
		"AudioExtensions",

		"MetasoundEngine",
		"MetasoundGraphCore",
		"MetasoundFrontend",
		"MetasoundEditor",

		"MetaCsound",
	}
);

And this are the public dependencies of the plugin (MetaCsound):

PublicDependencyModuleNames.AddRange(
new string[]
{
	"Core",
	"CoreUObject",
	"Engine",
	"Projects",
	"SignalProcessing",
	"AudioExtensions",
	"MetasoundEngine",
	"MetasoundGraphCore",
	"MetasoundFrontend",
	"MetasoundEditor",
}
);

This is the module and plugin part of the .uproject file. Notice that the module name of the project is called “Project2”, and that the MetaCsound pluguin is loaded at PostDefault

"Modules": [
	{
		"Name": "MyProject2",
		"Type": "Runtime",
		"LoadingPhase": "Default"
	}
],
"Plugins": [
	{
		"Name": "ModelingToolsEditorMode",
		"Enabled": true,
		"TargetAllowList": [
			"Editor"
		]
	},
	{
		"Name": "Metasound",
		"Type": "Runtime",
		"LoadingPhase": "Default",
		"Enabled": true
	},
	{
		"Name": "MetaCsound",
		"Type": "Runtime",
		"LoadingPhase": "PostDefault",
		"Enabled": true
	},
	{
		"Name": "VisualStudioTools",
		"Enabled": true,
		"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/362651520df94e4fa65492dbcba44ae2",
		"SupportedTargetPlatforms": [
			"Win64"
		]
	}
]

This is the .uplugin file counterpart:

"Modules": [
	{
		"Name": "MetaCsound",
		"Type": "Runtime",
		"LoadingPhase": "PostDefault"
	}
],
"Plugins": [
	{
		"Name": "Metasound",
		"Type": "Runtime",
		"LoadingPhase": "Default",
		"Enabled": true
	}
]

In the header file TCsoundOperator, I have the following include directives:

#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 "MetasoundFacade.h"				 // FNodeFacade class, eliminates the need for a fair amount of boilerplate code
#include "Containers/Array.h"

// WIP trying to create my own pin type
#include "MetasoundDataTypeRegistrationMacro.h"
#include "MetasoundDataReferenceMacro.h"
#include "MetasoundDataReference.h"
#include "MetasoundVariable.h"

#include "MetaCsound.h"

And inside the namespace Metasound, I have the following macro:

DECLARE_METASOUND_DATA_REFERENCE_TYPES(TCHAR, METACSOUND_API, FCharTypeInfo, FCharReadRef, FCharWriteRef);

Now, on the counterpart of this header file, TCsoundOperator.cpp, I have the following include directives:

#include "TCsoundOperator.h"

#include "MetasoundExecutableOperator.h"     // TExecutableOperator class
#include "MetasoundPrimitives.h"             // ReadRef and WriteRef descriptions for bool, int32, float, and string
#include "MetasoundParamHelper.h"            // METASOUND_PARAM and METASOUND_GET_PARAM family of macros
#include "Containers/Array.h"

// WIP trying to create my own pin type
#include "MetasoundDataReference.h"
#include "MetasoundDataTypeRegistrationMacro.h"
#include "MetasoundDataReferenceMacro.h"
#include "MetasoundVariable.h"

#include "MetaCsound.h"

In this file, if I uncomment the line 73, the linker error occurs. The line is the following:

 //Metasound::FCharReadRef readRef = Metasound::FCharReadRef(Metasound::FCharReadRef::CreateNew());

Hopefully this long post is not too overwhelming. As the problem still has not been solved, am I missing something?

Thanks

Ok looking how to compile it.
So far I’ve narrowed down that I need csound64.dll in C:/Program Files/Csound6_x64/bin/csound64.dll.
It probably should already reside in the plugin and be dynamically loaded.

Do I have to set up an environment path to csound.hpp?

Guessing it should be in the build paths
C:/Program Files/Csound6_x64/include/csound/

Yes, sorry for that. The final version will have their own Csound .dll and .lib residing inside, and probably the equivalent files for other OS. Feel free to change the paths to suit your own computer.

You can download Csound here: Download

In the build.cs file, here’s how to include csound.hpp:

PublicIncludePaths.AddRange(
	new string[]
	{
		"C:/Program Files/Csound6_x64/include/csound/",
	}
	);

Ok got all of the directories hooked up and compiled. I’ll try digging into the error itself now.

I got it a little further along.

modified the header of TCsoundOperator.h adding in the _API before class definition

#pragma once

// Is this still necessary? WIP
#ifdef LIKELY
    #undef LIKELY
#endif

#ifdef UNLIKELY
    #undef UNLIKELY
#endif

THIRD_PARTY_INCLUDES_START
#include <csound.hpp>
THIRD_PARTY_INCLUDES_END

#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 "MetasoundFacade.h"				 // FNodeFacade class, eliminates the need for a fair amount of boilerplate code
#include "Containers/Array.h"

// WIP trying to create my own pin type
#include "MetasoundDataTypeRegistrationMacro.h"
#include "MetasoundDataReferenceMacro.h"
#include "MetasoundDataReference.h"
#include "MetasoundVariable.h"

#include "MetaCsound.h"

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







// WIP: Should I use namespace MetaCsound?
namespace Metasound
{






    namespace CsoundNode
    {
        METASOUND_PARAM(PlayTrig, "Play", "Starts playing Csound");
        METASOUND_PARAM(StopTrig, "Stop", "Stops the Csound performace");
        METASOUND_PARAM(FilePath, "File", "Path of the .csd file to be executed by Csound");
        METASOUND_PARAM(EvStr, "Event String", "The string that contains a Csound event");
        METASOUND_PARAM(EvTrig, "Event Trigger", "Triggers the Csound event descrived by EventString");
        METASOUND_PARAM(FinTrig, "On Finished", "Triggers when the Csound score has finished");

        METASOUND_PARAM(InA, "In Audio {0}", "Input audio {0}");
        METASOUND_PARAM(OutA, "Out Audio {0}", "Output audio {0}");
        METASOUND_PARAM(InK, "In Control {0}", "Input control {0}");
        METASOUND_PARAM(OutK, "Out Control {0}", "Output control {0}");
    }

      

    //#if WITH_METASOUND_FRONTEND
  //  DECLARE_METASOUND_DATA_REFERENCE_TYPES(TCHAR, METACSOUND_API, FCharTypeInfo, FCharReadRef, FCharWriteRef);

    DECLARE_METASOUND_DATA_REFERENCE_TYPES(TCHAR, METACSOUND_API, FCharTypeInfo, FCharReadRef, FCharWriteRef);

    //
//#define DECLARE_METASOUND_DATA_REFERENCE_TYPES(DataType, ModuleApi, DataTypeInfoTypeName, DataReadReferenceTypeName, DataWriteReferenceTypeName) \

    //#endif

    template<typename DerivedOperator>
    class METACSOUND_API  TCsoundOperator : public TExecutableOperator<DerivedOperator>
    {

    protected:
        // Protected constructor
        TCsoundOperator(const FOperatorSettings& InSettings,
            const FTriggerReadRef& InPlayTrigger,
            const FTriggerReadRef& InStopTrigger,
            const FStringReadRef& InFilePath,
            const TArray<FAudioBufferReadRef>& InAudioRefs,
            const int32& InNumOutAudioChannels,
            const TArray<FFloatReadRef>& InControlRefs,
            const int32& InNumOutControlChannels,
            const FStringReadRef& InEventString,
            const FTriggerReadRef& InEventTrigger
        );

    public:

        // Primary node functionality
        void Execute();

        static const FNodeClassMetadata& GetNodeInfo();
        static const FVertexInterface& DeclareVertexInterface();

        // WIP use of BindInputs and BindOutputs instead?
        // Allows MetaSound graph to interact with your node's inputs
        virtual FDataReferenceCollection GetInputs() const override final;
        // Allows MetaSound graph to interact with your node's outputs
        virtual FDataReferenceCollection GetOutputs() const override final;

        static TUniquePtr<IOperator> CreateOperator(const FCreateOperatorParams& InParams, TArray<TUniquePtr<IOperatorBuildError>>& OutErrors);

    private:

        FTriggerReadRef PlayTrigger;
        FTriggerReadRef StopTrigger;
        FStringReadRef FilePath;
        FStringReadRef EventString;
        FTriggerReadRef EventTrigger;
        FTriggerWriteRef FinishedTrigger;

        TArray<FAudioBufferReadRef> AudioInRefs;
        TArray<const float*> BuffersIn;
        TArray<FAudioBufferWriteRef> AudioOutRefs;
        TArray<float*> BuffersOut;

        TArray<FFloatReadRef> ControlInRefs;
        TArray<FString> ControlInNames;
        TArray<FFloatWriteRef> ControlOutRefs;
        TArray<FString> ControlOutNames;

        FOperatorSettings OpSettings;
        Csound CsoundInstance;
        int32 SpIndex;

        double* Spin, * Spout;
        int32 CsoundNchnlsIn, CsoundNchnlsOut, MinAudioIn, MinAudioOut;
        int32 CsoundKsmps;

        enum class METACSOUND_API  EOpState : uint8
        {
            Stopped,
            Playing,
            Error
        };

        EOpState OpState;

        void Play(int32 CurrentFrame);
        void Stop(int32 StopFrame = 0);
        void ClearChannels(int32 StopFrame = 0);
        void CsoundPerformKsmps(int32 CurrentFrame);
    };

    class METACSOUND_API FCsoundOperator2 : public TCsoundOperator<FCsoundOperator2>
    {
    public:
        // WIP create struct that has all of the arguments, to avoid such long constructors?
        FCsoundOperator2(const FOperatorSettings& InSettings,
            const FTriggerReadRef& InPlayTrigger,
            const FTriggerReadRef& InStopTrigger,
            const FStringReadRef& InFilePath,
            const TArray<FAudioBufferReadRef>& InAudioRefs,
            const int32& InNumOutAudioChannels,
            const TArray<FFloatReadRef>& InControlRefs,
            const int32& InNumOutControlChannels,
            const FStringReadRef& InEventString,
            const FTriggerReadRef& InEventTrigger
        )
        : TCsoundOperator(
            InSettings, InPlayTrigger, InStopTrigger, InFilePath,
            InAudioRefs, InNumOutAudioChannels, InControlRefs, InNumOutControlChannels,
            InEventString, InEventTrigger
        )
        { }

        // WIP inline?
        static const FNodeClassName GetClassName() 
        {
            // WIP What is Audio? Could we use "Csound" instead?
            return { TEXT("MetaCsound"), TEXT("Csound 2"), TEXT("Audio") }; 
        }

        static const FText GetDisplayName()
        {
            return LOCTEXT("MetaCsound_Node2DisplayName", "Csound 2");
        }

        static const FText GetDescription()
        {
            return LOCTEXT("MetaCsound_Node2Desc", "Csound 2 description");
        }

        static constexpr int32 NumAudioChannelsIn = 2;
        static constexpr int32 NumAudioChannelsOut = 2;
        static constexpr int32 NumControlChannelsIn = 2;
        static constexpr int32 NumControlChannelsOut = 2;
    };

    class METACSOUND_API FCsoundNode2 : public FNodeFacade
    {
    public:
        FCsoundNode2(const FNodeInitData& InitData) : FNodeFacade(InitData.InstanceName, InitData.InstanceID,
            TFacadeOperatorClass<FCsoundOperator2>())
        { }
    };

    // Register node
    METASOUND_REGISTER_NODE(FCsoundNode2); // WIP Node registration using module startup/shutdown?

    class METACSOUND_API FCsoundOperator4 : public TCsoundOperator<FCsoundOperator4>
    {
    public:
        
        FCsoundOperator4(const FOperatorSettings& InSettings,
            const FTriggerReadRef& InPlayTrigger,
            const FTriggerReadRef& InStopTrigger,
            const FStringReadRef& InFilePath,
            const TArray<FAudioBufferReadRef>& InAudioRefs,
            const int32& InNumOutAudioChannels,
            const TArray<FFloatReadRef>& InControlRefs,
            const int32& InNumOutControlChannels,
            const FStringReadRef& InEventString,
            const FTriggerReadRef& InEventTrigger
        )
        : TCsoundOperator(
            InSettings, InPlayTrigger, InStopTrigger, InFilePath,
            InAudioRefs, InNumOutAudioChannels, InControlRefs, InNumOutControlChannels,
            InEventString, InEventTrigger
        )
        { }

        // WIP inline?
        static const FNodeClassName GetClassName()
        {
            // WIP What is Audio? Could we change it to something like "Csound" instead?
            return { TEXT("MetaCsound"), TEXT("Csound 4"), TEXT("Audio") };
        }

        static const FText GetDisplayName()
        {
            return LOCTEXT("MetaCsound_Node2DisplayName", "Csound 4");
        }

        static const FText GetDescription()
        {
            return LOCTEXT("MetaCsound_Node2Desc", "Csound 4 description");
        }

        static constexpr int32 NumAudioChannelsIn = 4;
        static constexpr int32 NumAudioChannelsOut = 4;
        static constexpr int32 NumControlChannelsIn = 4;
        static constexpr int32 NumControlChannelsOut = 4;
    };

    class METACSOUND_API FCsoundNode4 : public FNodeFacade
    {
    public:
        FCsoundNode4(const FNodeInitData& InitData) : FNodeFacade(InitData.InstanceName, InitData.InstanceID,
            TFacadeOperatorClass<FCsoundOperator4>())
        { }
    };

    // Register node
    METASOUND_REGISTER_NODE(FCsoundNode4);
    
}

#undef LOCTEXT_NAMESPACE

now the structs
Metasound::TDataReferenceTypeInfo<TCHAR>::TypeId;
Metasound::FCharTypeInfo::TypeId;
and
Metasound::FBoolTypeInfo::TypeName;
compile fine

the only struct that is giving problems is FCharReadRef but to use createnew it might require a default constructor??

::CreateNew() from the documentation seems to denote that it is supposed to be used in conjunction with TDataReadReference as a wrapper. I don’t think you can use it directly.

I think that like other metasound plugins you probably need to pass into the operator

ReadRef(TDataWriteReference<Metasound::FCharReadRef>::CreateNew()) 

in the input parameters before you open the curly brace {

and define TDataWriteReference< Metasound::FCharReadRef > ReadRef; as a private variable in the header

I think the main error is that you are declaring the variables below the curly brace so they are not input pins but part of the function body at that point.

You would need to modify the header pass in parameters too
So the const struct inName and then the passed parameter name, like the rest of the parameters play etc

Okay, now the repository is updated with your suggestions. When added _API macro in the class definition, I can successfully access the TypeID and TypeName of both FCharReadRef and TDataReadReference<TCHAR>.

Now, I have added a TCHAR read reference as a private member of the class. That was my initial plan, but was removed and simplified so I could make the linker error appear just by commenting or uncommenting some lines.

When using both FCharReadRef or TDataReadReference<TCHAR> as private members, the linker error continues to appear. I have tried both expressions, but both have failed.

Ok after looking into the code and having a more thorough read through I noticed that you might have broken your constructors

Example

Metasound::TCsoundOperator<DerivedOperator>::TCsoundOperator(

/// this is the first part of the default templated contructor

    const FOperatorSettings& InSettings,  => settings
    const FTriggerReadRef& InPlayTrigger, => Ftrigger read
    const FTriggerReadRef& InStopTrigger,
    const FStringReadRef& InFilePath,
    const TArray<FAudioBufferReadRef>& InAudioRefs,
    const int32& InNumOutAudioChannels,
    const TArray<FFloatReadRef>& InControlRefs,
    const int32& InNumOutControlChannels,
    const FStringReadRef& InEventString,
    const FTriggerReadRef& InEventTrigger,
    const TDataReadReference<TCHAR>& InCharReadRef)

// here are the refined implementations  that implment the direct varialbles
// they should overlap with the order of the template and pass in the correct class type mirroring the template version (or further inherited variables)

    : PlayTrigger(InPlayTrigger) -> this is play trigger and not settings
    , StopTrigger(InStopTrigger)
    , FilePath(InFilePath)
    , EventString(InEventString)
    , EventTrigger(InEventTrigger)
    , FinishedTrigger(FTriggerWriteRef::CreateNew(InSettings))  
    , AudioInRefs(InAudioRefs)
    , BuffersIn()
    , AudioOutRefs()
    , BuffersOut()
    , ControlInRefs(InControlRefs)
    , ControlInNames()
    , ControlOutRefs()
    , ControlOutNames()
    , OpSettings(InSettings)
    , CsoundInstance()
    , SpIndex(0)
    , Spin(nullptr)
    , Spout(nullptr)
    , OpState(EOpState::Stopped)
    , CharReadRef(InCharReadRef)
{
}

No wonder the contructors are collapsing later upon adding the the new parameters as these parts are passing in the wrong variables.

Later on even when adding the parameters the constructor would throw errors.
The base parameter amount and type need to line up 1:1

Cleaned up FSoundOperator to the point where It compiles with FCharReadRef in the inputs passing in all of the parameters. No errors.

turned off FCsoundOperator2 and FCsoundOperator4 for now as they still throw errors

How can I check the inputs? Is this a node in metasounds or a new parameter? (Haven’t used meta sounds myself)

Where can I enable the new operator?