Metasound Interfaces: creating a new interface in the same class as other interfaces breaks both interfaces

Main problem:

If I try to add a second Metasound interface in the same file as a pre-existing Metasound interface, both of them break and hit an ensure in the code.

More details:

I wrote a custom Metasound interface to make it easier to refer to the same inputs through code, with the same structure that Metasound’s Spatialization and OneShot interfaces are written.

Now I’m trying to add a second interface to the same file, and suddenly both of them break. I hit an ensure as described in repro steps with a screenshot, and neither of the interfaces show up as an option on Metasounds anymore.

I hope I’m missing something simple, but I’ve attached a fresh UE5.5.4 project that has the same issue. If you could help me figure out what’s going on, that would be really appreciated.

Steps to Reproduce
As an aside: "[Creating and packaging a repro project for [Content removed] link is broken, let me know if you need anything else from my test project.

Test project in blank template of UE5.5.4 is attached. It has two custom Metasound interfaces in TestMSInterface.h and .cpp files: Custom Interface One and Custom Interface Two.

To repro with the test project:

  • Open the project through your code editor
  • Try to open the MS_Test file
  • Observe:
    • The ensure gets hit because it cannot find the interface
    • [Image Removed]
    • The Custom Interface One is still visible in Inputs section of the Metasound, but it does not show up in the panel of Interfaces and we cannot see Custom Interface One and Custom Interface Two in the Add Interface drop down
    • [Image Removed]

If you try to repro this in your own project:

  1. Write a Metasound interface in a file
  2. Compile the project
  3. Write a second Metasound interface in the same file
  4. Observe both interfaces do not show up anymore

Ah, I figured this one out!

It was a mismatch between versions of the same interface.

We have two places in the interface code where we set the version:

  1. GetVersion() function
  2. GetInterface() function where we define FInterface : FParameterInterface(Your Interface Name, Your Version)

In the second example, Your Version was not using the GetVersion() function, it was a hard-coded value. So whenever the version in GetVersion() function changes, the version in FInterface remains the same, and that would confuse Metasounds and cause it to hide all affected interfaces in that file.

Not sure why it only occurs when there are at least two Metasound interfaces in the file. It doesn’t happen with one Metasound interface.

Oh interesting. I could have to do with how compilers link functions that are local to a CPP file. It’s hard to tell without seeing the code. But I’m glad you found a solve!

Ah yes, here is the code from the project I attached in case you want to ponder it in your free time. If you see anything interesting/suspicious about it, let me know. But yes, the issue is solved :slight_smile:

Here is the header file:

`UCLASS()
class TESTMSINTERFACE_API UTestMetasoundInterfaces : public UObject
{
GENERATED_BODY()

public:
virtual void PostInitProperties() override;

protected:
static void RegisterInterfaces();
};

namespace Audio
{
namespace InterfaceOne {
const extern FName InterfaceName;

namespace Inputs {
const extern FName InputOne;
}

const FMetasoundFrontendVersion& GetVersion();

Audio::FParameterInterfacePtr GetInterface();
}

namespace InterfaceTwo
{
const extern FName InterfaceName;
namespace Inputs
{
const extern FName InputTwo;
}

const FMetasoundFrontendVersion& GetVersion();

Audio::FParameterInterfacePtr GetInterface();
}
}`and the .cpp file is below. In .cpp, line 27 and line 41 in InterfaceOne have the version mismatch I was talking about.

`void UTestMetasoundInterfaces::PostInitProperties()
{
UObject::PostInitProperties();

RegisterInterfaces();
}

void UTestMetasoundInterfaces::RegisterInterfaces()
{
using namespace Metasound::Engine;
Audio::IAudioParameterInterfaceRegistry& InterfaceRegistry = Audio::IAudioParameterInterfaceRegistry::Get();

InterfaceRegistry.RegisterInterface(Audio::InterfaceOne::GetInterface());
InterfaceRegistry.RegisterInterface(Audio::InterfaceTwo::GetInterface());
}

#define LOCTEXT_NAMESPACE “AudioParameterInterface”
#define AUDIO_PARAMETER_INTERFACE_NAMESPACE “Custom.InterfaceOne”
namespace Audio
{
namespace InterfaceOne
{
const FName InterfaceName = AUDIO_PARAMETER_INTERFACE_NAMESPACE;

const FMetasoundFrontendVersion& GetVersion()
{
static const FMetasoundFrontendVersion Version = { AUDIO_PARAMETER_INTERFACE_NAMESPACE, { 1, 2 } };
return Version;
}

namespace Inputs
{
const FName InputOne = AUDIO_PARAMETER_INTERFACE_MEMBER_DEFINE(“InputOne”);
}

Audio::FParameterInterfacePtr GetInterface()
{
struct FInterface : public Audio::FParameterInterface
{
FInterface()
: FParameterInterface(InterfaceOne::InterfaceName, { 1, 0 })
{
Inputs =
{ {
FText(),
NSLOCTEXT(“InputOne”, “Test”, “test”),
Metasound::GetMetasoundDataTypeName(),
{Inputs::InputOne, 0},
FText(),
0
}
};
}

};
static FParameterInterfacePtr InterfacePtr;
if (!InterfacePtr.IsValid())
{
InterfacePtr = MakeShared();
}

return InterfacePtr;
}
}
#undef AUDIO_PARAMETER_INTERFACE_NAMESPACE

#define AUDIO_PARAMETER_INTERFACE_NAMESPACE “Custom.InterfaceTwo”

namespace InterfaceTwo
{
const FName InterfaceName = AUDIO_PARAMETER_INTERFACE_NAMESPACE;

const FMetasoundFrontendVersion& GetVersion()
{
static const FMetasoundFrontendVersion Version = { AUDIO_PARAMETER_INTERFACE_NAMESPACE, { 1, 2 } };
return Version;
}

namespace Inputs
{
const FName InputTwo = AUDIO_PARAMETER_INTERFACE_MEMBER_DEFINE(“InputTwo”);
}

Audio::FParameterInterfacePtr GetInterface()
{
struct FInterface : public Audio::FParameterInterface
{
FInterface()
: FParameterInterface(InterfaceOne::InterfaceName, { 1, 0 })
{
Inputs =
{ {
FText(),
NSLOCTEXT(“InputTwo”, “2Test”, “2test”),
Metasound::GetMetasoundDataTypeName(),
{Inputs::InputTwo, 0},
FText(),
0
}
};
}

};
static FParameterInterfacePtr InterfacePtr;
if (!InterfacePtr.IsValid())
{
InterfacePtr = MakeShared();
}

return InterfacePtr;
}
}

#undef AUDIO_PARAMETER_INTERFACE_NAMESPACE
}`

Ah I see what you’re talking about. Thanks for sharing. Seems like way we make people define interfaces could be improved if we look at it from a DRY perspective.