// Copyright Epic Games, Inc. All Rights Reserved. #include "Interfaces/MetasoundInterface.h" #include "Algo/Transform.h" #include "AudioParameterControllerInterface.h" #include "IAudioParameterInterfaceRegistry.h" #include "IAudioParameterTransmitter.h" #include "Interfaces/MetasoundFrontendInterfaceRegistry.h" #include "Interfaces/MetasoundFrontendSourceInterface.h" #include "Interfaces/MetasoundInputFormatInterfaces.h" #include "Interfaces/MetasoundOutputFormatInterfaces.h" #include "Metasound.h" #include "MetasoundFrontendDataTypeRegistry.h" #include "MetasoundFrontendDocument.h" #include "MetasoundFrontendTransform.h" #include "MetasoundLog.h" #include "MetasoundParameterTransmitter.h" #include "MetasoundSource.h" #include "MetasoundUObjectRegistry.h" #include "Templates/SharedPointer.h" #include "Templates/UniquePtr.h" #include "UObject/Class.h" #include "UObject/NoExportTypes.h" namespace Metasound::Engine { // Entry for registered interface. class FInterfaceRegistryEntry : public Frontend::IInterfaceRegistryEntry { const FInterfaceRegistryUClassOptions* FindOptionsByClassName(FName ClassName) const { auto NameIsEqual = [&ClassName](const FInterfaceRegistryUClassOptions& InOptions) { return InOptions.ClassName == ClassName; }; return Options.UClassOptions.FindByPredicate(NameIsEqual); } public: FInterfaceRegistryEntry( const FMetasoundFrontendInterface& InInterface, TUniquePtr&& InUpdateTransform, FInterfaceRegistryOptions&& InOptions ) : Interface(InInterface) , UpdateTransform(MoveTemp(InUpdateTransform)) , Options(MoveTemp(InOptions)) { } virtual bool EditorCanAddOrRemove(FName InUClassName) const override { if (const FInterfaceRegistryUClassOptions* UClassOptions = FindOptionsByClassName(InUClassName)) { return UClassOptions->bEditorCanAddOrRemove; } return false; } virtual bool UClassIsSupported(FName InUClassName) const override { if (const FInterfaceRegistryUClassOptions* UClassOptions = FindOptionsByClassName(InUClassName)) { return true; } // TODO: Support child asset class types. return false; } virtual bool IsDefault(FName InUClassName) const override { if (const FInterfaceRegistryUClassOptions* UClassOptions = FindOptionsByClassName(InUClassName)) { return UClassOptions->bIsDefault; } return false; } virtual FName GetRouterName() const override { return Options.InputSystemName; } virtual const FMetasoundFrontendInterface& GetInterface() const override { return Interface; } virtual bool UpdateRootGraphInterface(Frontend::FDocumentHandle InDocument) const override { if (UpdateTransform.IsValid()) { return UpdateTransform->Transform(InDocument); } return false; } private: FMetasoundFrontendInterface Interface; TArray OutputBindings; TUniquePtr UpdateTransform; FInterfaceRegistryOptions Options; }; FMetasoundFrontendInterface ConvertParameterToFrontendInterface(const Audio::FParameterInterface& InInterface) { using namespace Frontend; auto ResolveMemberDataType = [](FName DataType, EAudioParameterType ParamType) { if (!DataType.IsNone()) { const bool bIsRegisteredType = Frontend::IDataTypeRegistry::Get().IsRegistered(DataType); if (ensureAlwaysMsgf(bIsRegisteredType, TEXT("Attempting to register Interface member with unregistered DataType '%s'."), *DataType.ToString())) { return DataType; } } return ConvertParameterToDataType(ParamType); }; FMetasoundFrontendInterface FrontendInterface; FrontendInterface.Version = { InInterface.GetName(), FMetasoundFrontendVersionNumber { InInterface.GetVersion().Major, InInterface.GetVersion().Minor } }; // Transfer all input data from AudioExtension interface struct to FrontendInterface { Algo::Transform(InInterface.GetInputs(), FrontendInterface.Inputs, [&](const Audio::FParameterInterface::FInput& Input) { FMetasoundFrontendClassInput ClassInput; ClassInput.Name = Input.InitValue.ParamName; ClassInput.DefaultLiteral = FMetasoundFrontendLiteral(Input.InitValue); ClassInput.TypeName = ResolveMemberDataType(Input.DataType, Input.InitValue.ParamType); #if WITH_EDITOR // Interfaces should never serialize text to avoid desync between // copied versions serialized in assets and those defined in code. ClassInput.Metadata.SetSerializeText(false); ClassInput.Metadata.SetDisplayName(Input.DisplayName); ClassInput.Metadata.SetDescription(Input.Description); ClassInput.Metadata.SortOrderIndex = Input.SortOrderIndex; FrontendInterface.AddSortOrderToInputStyle(Input.SortOrderIndex); // Setup required inputs by telling the style that the input is required // This will later be validated against. if (!Input.RequiredText.IsEmpty()) { FrontendInterface.AddRequiredInputToStyle(Input.InitValue.ParamName, Input.RequiredText); } #endif // WITH_EDITOR ClassInput.VertexID = FGuid::NewGuid(); return ClassInput; }); } // Transfer all output data from AudioExtension interface struct to FrontendInterface { Algo::Transform(InInterface.GetOutputs(), FrontendInterface.Outputs, [&](const Audio::FParameterInterface::FOutput& Output) { FMetasoundFrontendClassOutput ClassOutput; ClassOutput.Name = Output.ParamName; ClassOutput.TypeName = ResolveMemberDataType(Output.DataType, Output.ParamType); #if WITH_EDITOR // Interfaces should never serialize text to avoid desync between // copied versions serialized in assets and those defined in code. ClassOutput.Metadata.SetSerializeText(false); ClassOutput.Metadata.SetDisplayName(Output.DisplayName); ClassOutput.Metadata.SetDescription(Output.Description); ClassOutput.Metadata.SortOrderIndex = Output.SortOrderIndex; FrontendInterface.AddSortOrderToOutputStyle(Output.SortOrderIndex); // Setup required outputs by telling the style that the output is required // This will later be validated against. if (!Output.RequiredText.IsEmpty()) { FrontendInterface.AddRequiredOutputToStyle(Output.ParamName, Output.RequiredText); } #endif // WITH_EDITOR ClassOutput.VertexID = FGuid::NewGuid(); return ClassOutput; }); } Algo::Transform(InInterface.GetEnvironment(), FrontendInterface.Environment, [&](const Audio::FParameterInterface::FEnvironmentVariable& Environment) { FMetasoundFrontendClassEnvironmentVariable EnvironmentVariable; EnvironmentVariable.Name = Environment.ParamName; // Disabled as it isn't used to infer type when getting/setting at a lower level. // TODO: Either remove type info for environment variables all together or enforce type. // EnvironmentVariable.TypeName = ResolveMemberDataType(Environment.DataType, Environment.ParamType); return EnvironmentVariable; }); return FrontendInterface; } void RegisterAudioFormatInterfaces() { using namespace Frontend; const FName SourceClassName = UMetaSoundSource::StaticClass()->GetFName(); const FName PatchClassName = UMetaSoundPatch::StaticClass()->GetFName(); // Input Formats { RegisterInterface(InputFormatMonoInterface::CreateInterface(), nullptr, FInterfaceRegistryOptions { IDataReference::RouterName, { { PatchClassName, false /* bIsDefault */, true /* bEditorCanAddOrRemove */ }, { SourceClassName }, } }); RegisterInterface(InputFormatStereoInterface::CreateInterface(), nullptr, FInterfaceRegistryOptions { IDataReference::RouterName, { { PatchClassName, false /* bIsDefault */, true /* bEditorCanAddOrRemove */ }, { SourceClassName }, } }); } // Output Formats { RegisterInterface(OutputFormatMonoInterface::CreateInterface(), nullptr, FInterfaceRegistryOptions { IDataReference::RouterName, { { PatchClassName, false /* bIsDefault */, true /* bEditorCanAddOrRemove */ }, { SourceClassName, true /* bIsDefault */, false /* bEditorCanAddOrRemove */ }, } }); RegisterInterface(OutputFormatStereoInterface::CreateInterface(), nullptr, FInterfaceRegistryOptions { IDataReference::RouterName, { { PatchClassName, false /* bIsDefault */, true /* bEditorCanAddOrRemove */ }, { SourceClassName }, } }); RegisterInterface(OutputFormatQuadInterface::CreateInterface(), nullptr, FInterfaceRegistryOptions { IDataReference::RouterName, { { PatchClassName, false /* bIsDefault */, true /* bEditorCanAddOrRemove */ }, { SourceClassName }, } }); RegisterInterface(OutputFormatFiveDotOneInterface::CreateInterface(), nullptr, FInterfaceRegistryOptions { IDataReference::RouterName, { { PatchClassName, false /* bIsDefault */, true /* bEditorCanAddOrRemove */ }, { SourceClassName }, } }); RegisterInterface(OutputFormatSevenDotOneInterface::CreateInterface(), nullptr, FInterfaceRegistryOptions { IDataReference::RouterName, { { PatchClassName, false /* bIsDefault */, true /* bEditorCanAddOrRemove */ }, { SourceClassName }, } }); } } void RegisterExternalInterfaces() { using namespace Frontend; // Register External Interfaces (Interfaces defined externally & can be managed directly by end-user). auto RegisterExternalInterface = [](Audio::FParameterInterfacePtr Interface) { FInterfaceRegistryOptions Options{ Audio::IParameterTransmitter::RouterName }; // Currently, no externally defined interfaces can be added as default for protection against undesired // interfaces automatically being added when creating a new MetaSound asset through the editor. Also, // all parameter interfaces are enabled in the editor for addition/removal. constexpr bool bIsDefault = false; constexpr bool bEditorCanAddOrRemove = true; bool bInterfaceSupportsPatch = true; bool bInterfaceSupportsSource = true; const TArray SupportedUClasses = Interface->FindSupportedUClasses(); if (!SupportedUClasses.IsEmpty()) { bInterfaceSupportsPatch = false; bInterfaceSupportsSource = false; for (const UClass* SupportedUClass : SupportedUClasses) { const UClass* PatchClass = UMetaSoundPatch::StaticClass(); check(PatchClass); bInterfaceSupportsPatch |= PatchClass->IsChildOf(SupportedUClass); const UClass* SourceClass = UMetaSoundSource::StaticClass(); check(SourceClass); bInterfaceSupportsSource |= SourceClass->IsChildOf(SupportedUClass); } } const bool bSupported = bInterfaceSupportsSource || bInterfaceSupportsPatch; if (ensureAlwaysMsgf(bSupported, TEXT("Parameter interface '%s' type not supported by MetaSounds"), *Interface->GetName().ToString())) { const FMetasoundFrontendInterface FrontendInterface = ConvertParameterToFrontendInterface(*Interface); if (bInterfaceSupportsPatch) { const FName ClassName = UMetaSoundPatch::StaticClass()->GetFName(); Options.UClassOptions.Add(FInterfaceRegistryUClassOptions{ ClassName, bIsDefault, bEditorCanAddOrRemove }); } if (bInterfaceSupportsSource) { const FName ClassName = UMetaSoundSource::StaticClass()->GetFName(); Options.UClassOptions.Add(FInterfaceRegistryUClassOptions{ ClassName, bIsDefault, bEditorCanAddOrRemove }); } IInterfaceRegistry::Get().RegisterInterface(MakeUnique(FrontendInterface, nullptr, MoveTemp(Options))); } }; Audio::IAudioParameterInterfaceRegistry::Get().IterateInterfaces(RegisterExternalInterface); Audio::IAudioParameterInterfaceRegistry::Get().OnRegistration(MoveTemp(RegisterExternalInterface)); } void RegisterInterface(const FMetasoundFrontendInterface& InInterface, TUniquePtr&& InUpdateTransform, FInterfaceRegistryOptions&& InOptions) { using namespace Frontend; IInterfaceRegistry::Get().RegisterInterface(MakeUnique(InInterface, MoveTemp(InUpdateTransform), MoveTemp(InOptions))); } void RegisterInterface(Audio::FParameterInterfacePtr Interface, TUniquePtr&& InUpdateTransform, FInterfaceRegistryOptions&& InOptions) { using namespace Frontend; const FMetasoundFrontendInterface FrontendInterface = ConvertParameterToFrontendInterface(*Interface); IInterfaceRegistry::Get().RegisterInterface(MakeUnique(FrontendInterface, MoveTemp(InUpdateTransform), MoveTemp(InOptions))); } void RegisterInterfaceForSingleClass(const UClass& InClass, Audio::FParameterInterfacePtr Interface, TUniquePtr&& InUpdateTransform, bool bInIsDefault, bool bInEditorCanAddOrRemove, FName InRouterName) { using namespace Frontend; const FMetasoundFrontendInterface FrontendInterface = ConvertParameterToFrontendInterface(*Interface); RegisterInterface(FrontendInterface, MoveTemp(InUpdateTransform), FInterfaceRegistryOptions { InRouterName, { { InClass.GetFName(), bInIsDefault, bInEditorCanAddOrRemove } } }); } void RegisterInterfaceForSingleClass(const UClass& InClass, const FMetasoundFrontendInterface& InInterface, TUniquePtr&& InUpdateTransform, bool bInIsDefault, bool bInEditorCanAddOrRemove, FName InRouterName) { using namespace Frontend; RegisterInterface(InInterface, MoveTemp(InUpdateTransform), FInterfaceRegistryOptions { InRouterName, { { InClass.GetFName(), bInIsDefault, bInEditorCanAddOrRemove } } }); } void RegisterInterfaces() { using namespace Frontend; // Default Source Interfaces { RegisterInterfaceForSingleClass(*UMetaSoundSource::StaticClass(), SourceInterface::CreateInterface(), MakeUnique(), true, false); RegisterInterfaceForSingleClass(*UMetaSoundSource::StaticClass(), SourceOneShotInterface::CreateInterface(), nullptr, true, true); } RegisterAudioFormatInterfaces(); RegisterExternalInterfaces(); } } // namespace Metasound::Engine