// Copyright Epic Games, Inc. All Rights Reserved. #include "MetasoundBuilderSubsystem.h" #include "Algo/Find.h" #include "Algo/Transform.h" #include "Components/AudioComponent.h" #include "Engine/Engine.h" #include "HAL/IConsoleManager.h" #include "Interfaces/MetasoundOutputFormatInterfaces.h" #include "Interfaces/MetasoundFrontendSourceInterface.h" #include "Metasound.h" #include "MetasoundAssetSubsystem.h" #include "MetasoundDataReference.h" #include "MetasoundDynamicOperatorTransactor.h" #include "MetasoundFrontendDataTypeRegistry.h" #include "MetasoundFrontendDocument.h" #include "MetasoundFrontendRegistries.h" #include "MetasoundFrontendSearchEngine.h" #include "MetasoundFrontendTransform.h" #include "MetasoundGeneratorHandle.h" #include "MetasoundLog.h" #include "MetasoundParameterTransmitter.h" #include "MetasoundSource.h" #include "MetasoundTrace.h" #include "MetasoundUObjectRegistry.h" #include "MetasoundVertex.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MetasoundBuilderSubsystem) namespace Metasound::Engine { namespace BuilderSubsystemPrivate { template FMetasoundFrontendLiteral CreatePODMetaSoundLiteral(const TLiteralType& Value, FName& OutDataType) { OutDataType = GetMetasoundDataTypeName(); FMetasoundFrontendLiteral Literal; Literal.Set(Value); return Literal; } TUniquePtr CreateDynamicNodeFromFrontendLiteral(const FName DataType, const FMetasoundFrontendLiteral& InLiteral) { using namespace Frontend; FLiteral ValueLiteral = InLiteral.ToLiteral(DataType); // TODO: Node name "Literal" is always the same. Consolidate and deprecate providing unique node name to avoid unnecessary FName table bloat. FLiteralNodeConstructorParams Params { "Literal", FGuid::NewGuid(), MoveTemp(ValueLiteral) }; return IDataTypeRegistry::Get().CreateLiteralNode(DataType, MoveTemp(Params)); } template BuilderClass& CreateTransientBuilder(FName BuilderName = { }) { const EObjectFlags NewObjectFlags = RF_Public | RF_Transient; UPackage* TransientPackage = GetTransientPackage(); const FName ObjectName = MakeUniqueObjectName(TransientPackage, BuilderClass::StaticClass(), BuilderName); TObjectPtr NewBuilder = NewObject(TransientPackage, ObjectName, NewObjectFlags); check(NewBuilder); NewBuilder->InitFrontendBuilder(); return *NewBuilder.Get(); } } // namespace BuilderSubsystemPrivate } // namespace Metasound::Engine FMetaSoundBuilderNodeOutputHandle UMetaSoundBuilderBase::AddGraphInputNode(FName Name, FName DataType, FMetasoundFrontendLiteral DefaultValue, EMetaSoundBuilderResult& OutResult, bool bIsConstructorInput) { using namespace Metasound::Frontend; FMetaSoundBuilderNodeOutputHandle NewHandle; if (IDataTypeRegistry::Get().FindDataTypeRegistryEntry(DataType) == nullptr) { UE_LOG(LogMetaSound, Error, TEXT("AddGraphInputNode Failed on builder '%s' when attempting to add '%s': '%s' is not a registered DataType"), *GetName(), *Name.ToString(), *DataType.ToString()); } else { const FMetasoundFrontendNode* Node = Builder.FindGraphInputNode(Name); if (Node) { UE_LOG(LogMetaSound, Warning, TEXT("AddGraphInputNode Failed: Input Node already exists with name '%s'; returning handle to existing node which may or may not match requested DataType '%s'"), *Name.ToString(), *DataType.ToString()); } else { FMetasoundFrontendClassInput Description; Description.Name = Name; Description.TypeName = DataType; Description.NodeID = FGuid::NewGuid(); Description.VertexID = FGuid::NewGuid(); Description.DefaultLiteral = static_cast(DefaultValue); Description.AccessType = bIsConstructorInput ? EMetasoundFrontendVertexAccessType::Value : EMetasoundFrontendVertexAccessType::Reference; Node = Builder.AddGraphInput(Description); } if (Node) { const TArray& Outputs = Node->Interface.Outputs; checkf(!Outputs.IsEmpty(), TEXT("Node should be initialized and have one output.")); NewHandle.NodeID = Node->GetID(); NewHandle.VertexID = Outputs.Last().VertexID; } } OutResult = NewHandle.IsSet() ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; return NewHandle; } FMetaSoundBuilderNodeInputHandle UMetaSoundBuilderBase::AddGraphOutputNode(FName Name, FName DataType, FMetasoundFrontendLiteral DefaultValue, EMetaSoundBuilderResult& OutResult, bool bIsConstructorOutput) { using namespace Metasound::Frontend; FMetaSoundBuilderNodeInputHandle NewHandle; if (IDataTypeRegistry::Get().FindDataTypeRegistryEntry(DataType) == nullptr) { UE_LOG(LogMetaSound, Error, TEXT("AddGraphOutputNode Failed on builder '%s' when attempting to add '%s': '%s' is not a registered DataType"), *GetName(), *Name.ToString(), *DataType.ToString()); } else { const FMetasoundFrontendNode* Node = Builder.FindGraphOutputNode(Name); if (Node) { UE_LOG(LogMetaSound, Warning, TEXT("AddGraphOutputNode Failed: Output Node already exists with name '%s'; returning handle to existing node which may or may not match requested DataType '%s'"), *Name.ToString(), *DataType.ToString()); } else { FMetasoundFrontendClassOutput Description; Description.Name = Name; Description.TypeName = DataType; Description.NodeID = FGuid::NewGuid(); Description.VertexID = FGuid::NewGuid(); Description.AccessType = bIsConstructorOutput ? EMetasoundFrontendVertexAccessType::Value : EMetasoundFrontendVertexAccessType::Reference; Node = Builder.AddGraphOutput(Description); } if (Node) { const TArray& Inputs = Node->Interface.Inputs; checkf(!Inputs.IsEmpty(), TEXT("Node should be initialized and have one input.")); const FGuid& VertexID = Inputs.Last().VertexID; if (Builder.SetNodeInputDefault(Node->GetID(), VertexID, DefaultValue)) { NewHandle.NodeID = Node->GetID(); NewHandle.VertexID = VertexID; } } } OutResult = NewHandle.IsSet() ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; return NewHandle; } void UMetaSoundBuilderBase::AddInterface(FName InterfaceName, EMetaSoundBuilderResult& OutResult) { const bool bInterfaceAdded = Builder.AddInterface(InterfaceName); OutResult = bInterfaceAdded ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } FMetaSoundNodeHandle UMetaSoundBuilderBase::AddNode(const TScriptInterface& NodeClass, EMetaSoundBuilderResult& OutResult) { using namespace Metasound; FMetaSoundNodeHandle NewHandle; if (NodeClass) { if (FMetasoundAssetBase* MetaSoundAsset = IMetasoundUObjectRegistry::Get().GetObjectAsAssetBase(NodeClass.GetObject())) { MetaSoundAsset->RegisterGraphWithFrontend(); const IMetaSoundDocumentInterface* Interface = NodeClass.GetInterface(); check(Interface); const FMetasoundFrontendDocument& NodeClassDoc = Interface->GetDocument(); const FMetasoundFrontendGraphClass& NodeClassGraph = NodeClassDoc.RootGraph; if (const FMetasoundFrontendNode* NewNode = Builder.AddGraphNode(NodeClassGraph)) { NewHandle.NodeID = NewNode->GetID(); } } } OutResult = NewHandle.IsSet() ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; return NewHandle; } FMetaSoundNodeHandle UMetaSoundBuilderBase::AddNodeByClassName(const FMetasoundFrontendClassName& ClassName, int32 MajorVersion, EMetaSoundBuilderResult& OutResult) { using namespace Metasound; using namespace Metasound::Frontend; FMetaSoundNodeHandle NewHandle; if (const FMetasoundFrontendNode* NewNode = Builder.AddNodeByClassName(ClassName, MajorVersion)) { NewHandle.NodeID = NewNode->GetID(); } OutResult = NewHandle.IsSet() ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; return NewHandle; } bool UMetaSoundBuilderBase::ContainsNode(const FMetaSoundNodeHandle& NodeHandle) const { return Builder.ContainsNode(NodeHandle.NodeID); } bool UMetaSoundBuilderBase::ContainsNodeInput(const FMetaSoundBuilderNodeInputHandle& InputHandle) const { return Builder.FindNodeInput(InputHandle.NodeID, InputHandle.VertexID) != nullptr; } bool UMetaSoundBuilderBase::ContainsNodeOutput(const FMetaSoundBuilderNodeOutputHandle& OutputHandle) const { return Builder.FindNodeOutput(OutputHandle.NodeID, OutputHandle.VertexID) != nullptr; } void UMetaSoundBuilderBase::ConnectNodes(const FMetaSoundBuilderNodeOutputHandle& NodeOutputHandle, const FMetaSoundBuilderNodeInputHandle& NodeInputHandle, EMetaSoundBuilderResult& OutResult) { using namespace Metasound::Frontend; OutResult = EMetaSoundBuilderResult::Failed; FMetasoundFrontendEdge NewEdge { NodeOutputHandle.NodeID, NodeOutputHandle.VertexID, NodeInputHandle.NodeID, NodeInputHandle.VertexID }; const EInvalidEdgeReason InvalidEdgeReason = Builder.IsValidEdge(NewEdge); if (InvalidEdgeReason == Metasound::Frontend::EInvalidEdgeReason::None) { #if !NO_LOGGING const FMetasoundFrontendNode* OldOutputNode = nullptr; const FMetasoundFrontendVertex* OldOutputVertex = nullptr; if (Builder.IsNodeInputConnected(NodeInputHandle.NodeID, NodeInputHandle.VertexID)) { OldOutputVertex = Builder.FindNodeOutputConnectedToNodeInput(NodeInputHandle.NodeID, NodeInputHandle.VertexID, &OldOutputNode); } #endif // !NO_LOGGING const bool bRemovedEdge = Builder.RemoveEdgeToNodeInput(NodeInputHandle.NodeID, NodeInputHandle.VertexID); Builder.AddEdge(MoveTemp(NewEdge)); #if !NO_LOGGING if (bRemovedEdge) { checkf(OldOutputNode, TEXT("MetaSound edge was removed from output but output node not found.")); checkf(OldOutputVertex, TEXT("MetaSound edge was removed from output but output vertex not found.")); const FMetasoundFrontendNode* InputNode = Builder.FindNode(NodeInputHandle.NodeID); checkf(InputNode, TEXT("Edge was deemed valid but input parent node is missing")); const FMetasoundFrontendVertex* InputVertex = Builder.FindNodeInput(NodeInputHandle.NodeID, NodeInputHandle.VertexID); checkf(InputVertex, TEXT("Edge was deemed valid but input is missing")); const FMetasoundFrontendNode* OutputNode = Builder.FindNode(NodeOutputHandle.NodeID); checkf(OutputNode, TEXT("Edge was deemed valid but output parent node is missing")); const FMetasoundFrontendVertex* OutputVertex = Builder.FindNodeOutput(NodeOutputHandle.NodeID, NodeOutputHandle.VertexID); checkf(OutputVertex, TEXT("Edge was deemed valid but output is missing")); UE_LOG(LogMetaSound, Verbose, TEXT("Removed connection from node output '%s:%s' to node '%s:%s' in order to connect to node output '%s:%s'"), *OldOutputNode->Name.ToString(), *OldOutputVertex->Name.ToString(), *InputNode->Name.ToString(), *InputVertex->Name.ToString(), *OutputNode->Name.ToString(), *OutputVertex->Name.ToString()); } #endif // !NO_LOGGING OutResult = EMetaSoundBuilderResult::Succeeded; } else { UE_LOG(LogMetaSound, Warning, TEXT("Builder '%s' 'ConnectNodes' failed: '%s'"), *GetName(), *LexToString(InvalidEdgeReason)); } } void UMetaSoundBuilderBase::ConnectNodesByInterfaceBindings(const FMetaSoundNodeHandle& FromNodeHandle, const FMetaSoundNodeHandle& ToNodeHandle, EMetaSoundBuilderResult& OutResult) { const bool bEdgesAdded = Builder.AddEdgesByNodeClassInterfaceBindings(FromNodeHandle.NodeID, ToNodeHandle.NodeID); OutResult = bEdgesAdded ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } TArray UMetaSoundBuilderBase::ConnectNodeOutputsToMatchingGraphInterfaceOutputs(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult) { TArray NewEdges; const bool bEdgesAdded = Builder.AddEdgesFromMatchingInterfaceNodeOutputsToGraphOutputs(NodeHandle.NodeID, NewEdges); OutResult = bEdgesAdded ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; TArray ConnectedVertices; Algo::Transform(NewEdges, ConnectedVertices, [this](const FMetasoundFrontendEdge* NewEdge) { const FMetasoundFrontendVertex* Vertex = Builder.FindNodeInput(NewEdge->ToNodeID, NewEdge->ToVertexID); checkf(Vertex, TEXT("Edge connection reported success but vertex not found.")); return FMetaSoundBuilderNodeInputHandle(NewEdge->ToNodeID, Vertex->VertexID); }); return ConnectedVertices; } TArray UMetaSoundBuilderBase::ConnectNodeInputsToMatchingGraphInterfaceInputs(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult) { TArray NewEdges; const bool bEdgesAdded = Builder.AddEdgesFromMatchingInterfaceNodeInputsToGraphInputs(NodeHandle.NodeID, NewEdges); OutResult = bEdgesAdded ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; TArray ConnectedVertices; Algo::Transform(NewEdges, ConnectedVertices, [this](const FMetasoundFrontendEdge* NewEdge) { const FMetasoundFrontendVertex* Vertex = Builder.FindNodeOutput(NewEdge->FromNodeID, NewEdge->FromVertexID); checkf(Vertex, TEXT("Edge connection reported success but vertex not found.")); return FMetaSoundBuilderNodeOutputHandle(NewEdge->ToNodeID, Vertex->VertexID); }); return ConnectedVertices; } void UMetaSoundBuilderBase::ConnectNodeOutputToGraphOutput(FName GraphOutputName, const FMetaSoundBuilderNodeOutputHandle& NodeOutputHandle, EMetaSoundBuilderResult& OutResult) { using namespace Metasound::Frontend; OutResult = EMetaSoundBuilderResult::Failed; if (const FMetasoundFrontendNode* GraphOutputNode = Builder.FindGraphOutputNode(GraphOutputName)) { const FMetasoundFrontendVertex& InputVertex = GraphOutputNode->Interface.Inputs.Last(); FMetasoundFrontendEdge NewEdge { NodeOutputHandle.NodeID, NodeOutputHandle.VertexID, GraphOutputNode->GetID(), InputVertex.VertexID }; const EInvalidEdgeReason InvalidEdgeReason = Builder.IsValidEdge(NewEdge); if (InvalidEdgeReason == EInvalidEdgeReason::None) { Builder.RemoveEdgeToNodeInput(GraphOutputNode->GetID(), InputVertex.VertexID); Builder.AddEdge(MoveTemp(NewEdge)); OutResult = EMetaSoundBuilderResult::Succeeded; } else { UE_LOG(LogMetaSound, Warning, TEXT("Builder '%s' 'ConnectNodeOutputToGraphOutput' failed: '%s'"), *GetName(), *LexToString(InvalidEdgeReason)); } } } void UMetaSoundBuilderBase::ConnectNodeInputToGraphInput(FName GraphInputName, const FMetaSoundBuilderNodeInputHandle& NodeInputHandle, EMetaSoundBuilderResult& OutResult) { using namespace Metasound::Frontend; OutResult = EMetaSoundBuilderResult::Failed; if (const FMetasoundFrontendNode* GraphInputNode = Builder.FindGraphInputNode(GraphInputName)) { const FMetasoundFrontendVertex& OutputVertex = GraphInputNode->Interface.Outputs.Last(); FMetasoundFrontendEdge NewEdge { GraphInputNode->GetID(), OutputVertex.VertexID, NodeInputHandle.NodeID, NodeInputHandle.VertexID }; const EInvalidEdgeReason InvalidEdgeReason = Builder.IsValidEdge(NewEdge); if (InvalidEdgeReason == EInvalidEdgeReason::None) { Builder.RemoveEdgeToNodeInput(NodeInputHandle.NodeID, NodeInputHandle.VertexID); Builder.AddEdge(MoveTemp(NewEdge)); OutResult = EMetaSoundBuilderResult::Succeeded; } else { UE_LOG(LogMetaSound, Warning, TEXT("Builder '%s' 'ConnectNodeInputToGraphInput' failed: '%s'"), *GetName(), *LexToString(InvalidEdgeReason)); } } } void UMetaSoundBuilderBase::ConvertFromPreset(EMetaSoundBuilderResult& OutResult) { const bool bSuccess = Builder.ConvertFromPreset(); OutResult = bSuccess ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } void UMetaSoundBuilderBase::ConvertToPreset(const TScriptInterface& ReferencedNodeClass, EMetaSoundBuilderResult& OutResult) { const IMetaSoundDocumentInterface* ReferencedInterface = ReferencedNodeClass.GetInterface(); if (!ReferencedInterface) { OutResult = EMetaSoundBuilderResult::Failed; return; } // Ensure the referenced node class isn't transient if (Cast(ReferencedInterface)) { UE_LOG(LogMetaSound, Warning, TEXT("Transient document builders cannot be referenced when converting builder '%s' to a preset. Build the referenced node class an asset first or use an existing asset instead"), *GetName()); OutResult = EMetaSoundBuilderResult::Failed; return; } // Ensure the referenced node class is a matching object type const UClass& BaseMetaSoundClass = ReferencedInterface->GetBaseMetaSoundUClass(); UObject* ReferencedObject = ReferencedNodeClass.GetObject(); if (!ReferencedObject || (ReferencedObject && !ReferencedObject->IsA(&BaseMetaSoundClass))) { UE_LOG(LogMetaSound, Warning, TEXT("The referenced node type must match the base MetaSound class when converting builder '%s' to a preset (ex. source preset must reference another source)"), *GetName()); OutResult = EMetaSoundBuilderResult::Failed; return; } // Ensure the referenced node is registered if (FMetasoundAssetBase* ReferencedMetaSoundAsset = Metasound::IMetasoundUObjectRegistry::Get().GetObjectAsAssetBase(ReferencedObject)) { ReferencedMetaSoundAsset->RegisterGraphWithFrontend(); } const FMetasoundFrontendDocument& ReferencedDocument = ReferencedInterface->GetDocument(); if (Builder.ConvertToPreset(ReferencedDocument)) { OutResult = EMetaSoundBuilderResult::Succeeded; } else { OutResult = EMetaSoundBuilderResult::Failed; } } void UMetaSoundBuilderBase::DisconnectNodes(const FMetaSoundBuilderNodeOutputHandle& NodeOutputHandle, const FMetaSoundBuilderNodeInputHandle& NodeInputHandle, EMetaSoundBuilderResult& OutResult) { const bool bEdgeRemoved = Builder.RemoveEdge(FMetasoundFrontendEdge { NodeOutputHandle.NodeID, NodeOutputHandle.VertexID, NodeInputHandle.NodeID, NodeInputHandle.VertexID, }); OutResult = bEdgeRemoved ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } void UMetaSoundBuilderBase::DisconnectNodeInput(const FMetaSoundBuilderNodeInputHandle& NodeInputHandle, EMetaSoundBuilderResult& OutResult) { const bool bEdgeRemoved = Builder.RemoveEdgeToNodeInput(NodeInputHandle.NodeID, NodeInputHandle.VertexID); OutResult = bEdgeRemoved ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } void UMetaSoundBuilderBase::DisconnectNodeOutput(const FMetaSoundBuilderNodeOutputHandle& NodeOutputHandle, EMetaSoundBuilderResult& OutResult) { const bool bEdgeRemoved = Builder.RemoveEdgesFromNodeOutput(NodeOutputHandle.NodeID, NodeOutputHandle.VertexID); OutResult = bEdgeRemoved ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } void UMetaSoundBuilderBase::DisconnectNodesByInterfaceBindings(const FMetaSoundNodeHandle& FromNodeHandle, const FMetaSoundNodeHandle& ToNodeHandle, EMetaSoundBuilderResult& OutResult) { const bool bEdgesRemoved = Builder.RemoveEdgesByNodeClassInterfaceBindings(FromNodeHandle.NodeID, ToNodeHandle.NodeID); OutResult = bEdgesRemoved ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } FMetaSoundBuilderNodeInputHandle UMetaSoundBuilderBase::FindNodeInputByName(const FMetaSoundNodeHandle& NodeHandle, FName InputName, EMetaSoundBuilderResult& OutResult) { if (const FMetasoundFrontendNode* Node = Builder.FindNode(NodeHandle.NodeID)) { const TArray& InputVertices = Node->Interface.Inputs; auto FindByNamePredicate = [&InputName](const FMetasoundFrontendVertex& Vertex) { return Vertex.Name == InputName; }; if (const FMetasoundFrontendVertex* Input = InputVertices.FindByPredicate(FindByNamePredicate)) { OutResult = EMetaSoundBuilderResult::Succeeded; return FMetaSoundBuilderNodeInputHandle(Node->GetID(), Input->VertexID); } FString NodeClassName = TEXT("N/A"); if (const FMetasoundFrontendClass* Class = Builder.FindDependency(Node->ClassID)) { NodeClassName = Class->Metadata.GetClassName().ToString(); } UE_LOG(LogMetaSound, Display, TEXT("Builder '%s' failed to find node input '%s': Node class '%s' contains no such input"), *GetName(), *InputName.ToString(), *NodeClassName); } else { UE_LOG(LogMetaSound, Display, TEXT("Builder '%s' failed to find node input '%s': Node with ID '%s' not found"), *GetName(), *InputName.ToString(), *NodeHandle.NodeID.ToString()); } OutResult = EMetaSoundBuilderResult::Failed; return { }; } TArray UMetaSoundBuilderBase::FindNodeInputs(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult) { return FindNodeInputsByDataType(NodeHandle, OutResult, { }); } TArray UMetaSoundBuilderBase::FindNodeInputsByDataType(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult, FName DataType) { TArray FoundVertices; if (Builder.ContainsNode(NodeHandle.NodeID)) { TArray Vertices = Builder.FindNodeInputs(NodeHandle.NodeID, DataType); Algo::Transform(Vertices, FoundVertices, [&NodeHandle](const FMetasoundFrontendVertex* Vertex) { return FMetaSoundBuilderNodeInputHandle(NodeHandle.NodeID, Vertex->VertexID); }); OutResult = EMetaSoundBuilderResult::Succeeded; } else { UE_LOG(LogMetaSound, Display, TEXT("Failed to find node inputs by data type with builder '%s'. Node of with ID '%s' not found"), *GetName(), *NodeHandle.NodeID.ToString()); OutResult = EMetaSoundBuilderResult::Failed; } return FoundVertices; } FMetaSoundBuilderNodeOutputHandle UMetaSoundBuilderBase::FindNodeOutputByName(const FMetaSoundNodeHandle& NodeHandle, FName OutputName, EMetaSoundBuilderResult& OutResult) { if (const FMetasoundFrontendNode* Node = Builder.FindNode(NodeHandle.NodeID)) { const TArray& OutputVertices = Node->Interface.Outputs; auto FindByNamePredicate = [&OutputName](const FMetasoundFrontendVertex& Vertex) { return Vertex.Name == OutputName; }; if (const FMetasoundFrontendVertex* Output = OutputVertices.FindByPredicate(FindByNamePredicate)) { OutResult = EMetaSoundBuilderResult::Succeeded; return FMetaSoundBuilderNodeOutputHandle(Node->GetID(), Output->VertexID); } FString NodeClassName = TEXT("N/A"); if (const FMetasoundFrontendClass* Class = Builder.FindDependency(Node->ClassID)) { NodeClassName = Class->Metadata.GetClassName().ToString(); } UE_LOG(LogMetaSound, Display, TEXT("Builder '%s' failed to find node output '%s': Node class '%s' contains no such output"), *GetName(), *OutputName.ToString(), *NodeClassName); } else { UE_LOG(LogMetaSound, Display, TEXT("Builder '%s' failed to find node output '%s': Node with ID '%s' not found"), *GetName(), *OutputName.ToString(), *NodeHandle.NodeID.ToString()); } OutResult = EMetaSoundBuilderResult::Failed; return { }; } TArray UMetaSoundBuilderBase::FindNodeOutputs(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult) { return FindNodeOutputsByDataType(NodeHandle, OutResult, { }); } TArray UMetaSoundBuilderBase::FindNodeOutputsByDataType(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult, FName DataType) { TArray FoundVertices; if (Builder.ContainsNode(NodeHandle.NodeID)) { TArray Vertices = Builder.FindNodeOutputs(NodeHandle.NodeID, DataType); Algo::Transform(Vertices, FoundVertices, [&NodeHandle](const FMetasoundFrontendVertex* Vertex) { return FMetaSoundBuilderNodeOutputHandle(NodeHandle.NodeID, Vertex->VertexID); }); OutResult = EMetaSoundBuilderResult::Succeeded; } else { UE_LOG(LogMetaSound, Display, TEXT("Failed to find node outputs by data type with builder '%s'. Node of with ID '%s' not found"), *GetName(), *NodeHandle.NodeID.ToString()); OutResult = EMetaSoundBuilderResult::Failed; } return FoundVertices; } TArray UMetaSoundBuilderBase::FindInterfaceInputNodes(FName InterfaceName, EMetaSoundBuilderResult& OutResult) { TArray NodeHandles; TArray Nodes; if (Builder.FindInterfaceInputNodes(InterfaceName, Nodes)) { Algo::Transform(Nodes, NodeHandles, [this](const FMetasoundFrontendNode* Node) { check(Node); return FMetaSoundNodeHandle { Node->GetID() }; }); OutResult = EMetaSoundBuilderResult::Succeeded; } else { UE_LOG(LogMetaSound, Display, TEXT("'%s' interface not found on builder '%s'. No input nodes returned"), *InterfaceName.ToString(), *GetName()); OutResult = EMetaSoundBuilderResult::Failed; } return NodeHandles; } TArray UMetaSoundBuilderBase::FindInterfaceOutputNodes(FName InterfaceName, EMetaSoundBuilderResult& OutResult) { TArray NodeHandles; TArray Nodes; if (Builder.FindInterfaceOutputNodes(InterfaceName, Nodes)) { Algo::Transform(Nodes, NodeHandles, [this](const FMetasoundFrontendNode* Node) { check(Node); return FMetaSoundNodeHandle { Node->GetID() }; }); OutResult = EMetaSoundBuilderResult::Succeeded; } else { OutResult = EMetaSoundBuilderResult::Failed; } return NodeHandles; } FMetaSoundNodeHandle UMetaSoundBuilderBase::FindGraphInputNode(FName InputName, EMetaSoundBuilderResult& OutResult) { if (const FMetasoundFrontendNode* GraphInputNode = Builder.FindGraphInputNode(InputName)) { OutResult = EMetaSoundBuilderResult::Succeeded; return FMetaSoundNodeHandle { GraphInputNode->GetID() }; } UE_LOG(LogMetaSound, Display, TEXT("Failed to find graph input by name '%s' with builder '%s'"), *InputName.ToString(), *GetName()); OutResult = EMetaSoundBuilderResult::Failed; return { }; } FMetaSoundNodeHandle UMetaSoundBuilderBase::FindGraphOutputNode(FName OutputName, EMetaSoundBuilderResult& OutResult) { if (const FMetasoundFrontendNode* GraphOutputNode = Builder.FindGraphOutputNode(OutputName)) { OutResult = EMetaSoundBuilderResult::Succeeded; return FMetaSoundNodeHandle{ GraphOutputNode->GetID() }; } UE_LOG(LogMetaSound, Display, TEXT("Failed to find graph output by name '%s' with builder '%s'"), *OutputName.ToString(), *GetName()); OutResult = EMetaSoundBuilderResult::Failed; return { }; } UMetaSoundBuilderDocument* UMetaSoundBuilderBase::CreateTransientDocumentObject() const { UMetaSoundBuilderDocument* DocObject = NewObject(); DocObject->SetBaseMetaSoundUClass(GetBuilderUClass()); return DocObject; } void UMetaSoundBuilderBase::InitFrontendBuilder() { UMetaSoundBuilderDocument* DocObject = CreateTransientDocumentObject(); Builder = FMetaSoundFrontendDocumentBuilder(DocObject); Builder.InitDocument(); } void UMetaSoundBuilderBase::InitNodeLocations() { Builder.InitNodeLocations(); } UObject* UMetaSoundBuilderBase::GetReferencedPresetAsset() const { using namespace Metasound::Frontend; if (!IsPreset()) { return nullptr; } // Find the single external node which is the referenced preset asset, // and find the asset with its registry key auto FindExternalNode = [this](const FMetasoundFrontendNode& Node) { const FMetasoundFrontendClass* Class = Builder.FindDependency(Node.ClassID); return Class->Metadata.GetType() == EMetasoundFrontendClassType::External; }; const FMetasoundFrontendNode* Node = Builder.GetDocument().RootGraph.Graph.Nodes.FindByPredicate(FindExternalNode); if (Node != nullptr) { const FMetasoundFrontendClass* NodeClass = Builder.FindDependency(Node->ClassID); const FNodeRegistryKey NodeClassRegistryKey = NodeRegistryKey::CreateKey(NodeClass->Metadata); if (FMetasoundAssetBase* Asset = IMetaSoundAssetManager::GetChecked().TryLoadAssetFromKey(NodeClassRegistryKey)) { return Asset->GetOwningAsset(); } } return nullptr; } bool UMetaSoundBuilderBase::InterfaceIsDeclared(FName InterfaceName) const { return Builder.IsInterfaceDeclared(InterfaceName); } bool UMetaSoundBuilderBase::IsPreset() const { return Builder.IsPreset(); } bool UMetaSoundBuilderBase::NodesAreConnected(const FMetaSoundBuilderNodeOutputHandle& OutputHandle, const FMetaSoundBuilderNodeInputHandle& InputHandle) const { const FMetasoundFrontendEdge Edge = { OutputHandle.NodeID, OutputHandle.VertexID, InputHandle.NodeID, InputHandle.VertexID }; return Builder.ContainsEdge(Edge); } bool UMetaSoundBuilderBase::NodeInputIsConnected(const FMetaSoundBuilderNodeInputHandle& InputHandle) const { return Builder.IsNodeInputConnected(InputHandle.NodeID, InputHandle.VertexID); } bool UMetaSoundBuilderBase::NodeOutputIsConnected(const FMetaSoundBuilderNodeOutputHandle& OutputHandle) const { return Builder.IsNodeOutputConnected(OutputHandle.NodeID, OutputHandle.VertexID); } void UMetaSoundBuilderBase::RemoveGraphInput(FName Name, EMetaSoundBuilderResult& OutResult) { const bool bRemoved = Builder.RemoveGraphInput(Name); OutResult = bRemoved ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } void UMetaSoundBuilderBase::RemoveGraphOutput(FName Name, EMetaSoundBuilderResult& OutResult) { const bool bRemoved = Builder.RemoveGraphOutput(Name); OutResult = bRemoved ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } void UMetaSoundBuilderBase::RemoveInterface(FName InterfaceName, EMetaSoundBuilderResult& OutResult) { const bool bInterfaceRemoved = Builder.RemoveInterface(InterfaceName); OutResult = bInterfaceRemoved ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } void UMetaSoundBuilderBase::RemoveNode(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult) { const bool bNodeRemoved = Builder.RemoveNode(NodeHandle.NodeID); OutResult = bNodeRemoved ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } void UMetaSoundBuilderBase::RemoveNodeInputDefault(const FMetaSoundBuilderNodeInputHandle& InputHandle, EMetaSoundBuilderResult& OutResult) { const bool bInputDefaultRemoved = Builder.RemoveNodeInputDefault(InputHandle.NodeID, InputHandle.VertexID); OutResult = bInputDefaultRemoved ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } void UMetaSoundBuilderBase::RenameRootGraphClass(const FMetasoundFrontendClassName& InName) { Builder.RenameRootGraphClass(InName); } #if WITH_EDITOR void UMetaSoundBuilderBase::ReloadCache() { Builder.ReloadCache(); } void UMetaSoundBuilderBase::SetAuthor(const FString& InAuthor) { Builder.SetAuthor(InAuthor); } #endif // WITH_EDITOR void UMetaSoundBuilderBase::SetGraphInputDefault(FName InputName, const FMetasoundFrontendLiteral& Literal, EMetaSoundBuilderResult& OutResult) { const bool bInputDefaultSet = Builder.SetGraphInputDefault(InputName, Literal); OutResult = bInputDefaultSet ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } void UMetaSoundBuilderBase::SetNodeInputDefault(const FMetaSoundBuilderNodeInputHandle& InputHandle, const FMetasoundFrontendLiteral& Literal, EMetaSoundBuilderResult& OutResult) { const bool bInputDefaultSet = Builder.SetNodeInputDefault(InputHandle.NodeID, InputHandle.VertexID, Literal); OutResult = bInputDefaultSet ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } #if WITH_EDITOR void UMetaSoundBuilderBase::SetNodeLocation(const FMetaSoundNodeHandle& InNodeHandle, const FVector2D& InLocation, EMetaSoundBuilderResult& OutResult) { const bool bLocationSet = Builder.SetNodeLocation(InNodeHandle.NodeID, InLocation); OutResult = bLocationSet ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } #endif // WITH_EDITOR FMetaSoundNodeHandle UMetaSoundBuilderBase::FindNodeInputParent(const FMetaSoundBuilderNodeInputHandle& InputHandle, EMetaSoundBuilderResult& OutResult) { if (Builder.ContainsNode(InputHandle.NodeID)) { OutResult = EMetaSoundBuilderResult::Succeeded; return FMetaSoundNodeHandle { InputHandle.NodeID }; } OutResult = EMetaSoundBuilderResult::Failed; return { }; } FMetaSoundNodeHandle UMetaSoundBuilderBase::FindNodeOutputParent(const FMetaSoundBuilderNodeOutputHandle& OutputHandle, EMetaSoundBuilderResult& OutResult) { if (Builder.ContainsNode(OutputHandle.NodeID)) { OutResult = EMetaSoundBuilderResult::Succeeded; return FMetaSoundNodeHandle{ OutputHandle.NodeID }; } OutResult = EMetaSoundBuilderResult::Failed; return { }; } FMetasoundFrontendVersion UMetaSoundBuilderBase::FindNodeClassVersion(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult) { if (const FMetasoundFrontendNode* Node = Builder.FindNode(NodeHandle.NodeID)) { if (const FMetasoundFrontendClass* Class = Builder.FindDependency(Node->ClassID)) { OutResult = EMetaSoundBuilderResult::Succeeded; return FMetasoundFrontendVersion { Class->Metadata.GetClassName().GetFullName(), Class->Metadata.GetVersion() }; } } OutResult = EMetaSoundBuilderResult::Failed; return FMetasoundFrontendVersion::GetInvalid(); } FMetasoundFrontendClassName UMetaSoundBuilderBase::GetRootGraphClassName() const { return Builder.GetDocument().RootGraph.Metadata.GetClassName(); } void UMetaSoundBuilderBase::GetNodeInputData(const FMetaSoundBuilderNodeInputHandle& InputHandle, FName& Name, FName& DataType, EMetaSoundBuilderResult& OutResult) { if (const FMetasoundFrontendVertex* Vertex = Builder.FindNodeInput(InputHandle.NodeID, InputHandle.VertexID)) { Name = Vertex->Name; DataType = Vertex->TypeName; OutResult = EMetaSoundBuilderResult::Succeeded; } else { Name = { }; DataType = { }; OutResult = EMetaSoundBuilderResult::Failed; } } FMetasoundFrontendLiteral UMetaSoundBuilderBase::GetNodeInputDefault(const FMetaSoundBuilderNodeInputHandle& InputHandle, EMetaSoundBuilderResult& OutResult) { if (const FMetasoundFrontendLiteral* Default = Builder.GetNodeInputDefault(InputHandle.NodeID, InputHandle.VertexID)) { OutResult = EMetaSoundBuilderResult::Succeeded; return *Default; } OutResult = EMetaSoundBuilderResult::Failed; return { }; } FMetasoundFrontendLiteral UMetaSoundBuilderBase::GetNodeInputClassDefault(const FMetaSoundBuilderNodeInputHandle& InputHandle, EMetaSoundBuilderResult& OutResult) { if (const FMetasoundFrontendLiteral* Default = Builder.GetNodeInputClassDefault(InputHandle.NodeID, InputHandle.VertexID)) { OutResult = EMetaSoundBuilderResult::Succeeded; return *Default; } OutResult = EMetaSoundBuilderResult::Failed; return { }; } void UMetaSoundBuilderBase::GetNodeOutputData(const FMetaSoundBuilderNodeOutputHandle& OutputHandle, FName& Name, FName& DataType, EMetaSoundBuilderResult& OutResult) { if (const FMetasoundFrontendVertex* Vertex = Builder.FindNodeOutput(OutputHandle.NodeID, OutputHandle.VertexID)) { Name = Vertex->Name; DataType = Vertex->TypeName; OutResult = EMetaSoundBuilderResult::Succeeded; } else { Name = { }; DataType = { }; OutResult = EMetaSoundBuilderResult::Failed; } } void UMetaSoundBuilderBase::UpdateDependencyClassNames(const TMap& OldToNewReferencedClassNames) { Builder.UpdateDependencyClassNames(OldToNewReferencedClassNames); } TScriptInterface UMetaSoundPatchBuilder::Build(UObject* Parent, const FMetaSoundBuilderOptions& InBuilderOptions) const { return &BuildInternal(Parent, InBuilderOptions); } const UClass& UMetaSoundPatchBuilder::GetBuilderUClass() const { return *UMetaSoundPatch::StaticClass(); } UMetaSoundBuilderBase& UMetaSoundBuilderSubsystem::AttachBuilderToAssetChecked(UObject& InObject) const { const UClass* BaseClass = InObject.GetClass(); if (BaseClass == UMetaSoundSource::StaticClass()) { UMetaSoundSourceBuilder* NewBuilder = AttachSourceBuilderToAsset(CastChecked(&InObject)); return *NewBuilder; } else if (BaseClass == UMetaSoundPatch::StaticClass()) { UMetaSoundPatchBuilder* NewBuilder = AttachPatchBuilderToAsset(CastChecked(&InObject)); return *NewBuilder; } else { checkf(false, TEXT("UClass '%s' is not a base MetaSound that supports attachment via the MetaSoundBuilderSubsystem"), *BaseClass->GetFullName()); return Metasound::Engine::BuilderSubsystemPrivate::CreateTransientBuilder(); } } UMetaSoundPatchBuilder* UMetaSoundBuilderSubsystem::AttachPatchBuilderToAsset(UMetaSoundPatch* InPatch) const { if (InPatch) { return &AttachBuilderToAssetCheckedPrivate(InPatch); } return nullptr; } UMetaSoundSourceBuilder* UMetaSoundBuilderSubsystem::AttachSourceBuilderToAsset(UMetaSoundSource* InSource) const { if (InSource) { UMetaSoundSourceBuilder& SourceBuilder = AttachBuilderToAssetCheckedPrivate(InSource); SourceBuilder.AuditionSound = InSource; return &SourceBuilder; } return nullptr; } void UMetaSoundSourceBuilder::Audition(UObject* Parent, UAudioComponent* AudioComponent, FOnCreateAuditionGeneratorHandleDelegate CreateGenerator, bool bLiveUpdatesEnabled) { using namespace Metasound::Engine; using namespace Metasound::Engine::BuilderSubsystemPrivate; using namespace Metasound::Frontend; METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(UMetaSoundSourceBuilder::Audition); if (!AudioComponent) { UE_LOG(LogMetaSound, Error, TEXT("Failed to audition MetaSoundBuilder '%s': No AudioComponent supplied"), *GetFullName()); return; } if (!bIsAttached) { FMetaSoundBuilderOptions BuilderOptions; BuilderOptions.bAddToRegistry = true; if (AuditionSound.IsValid()) { BuilderOptions.ExistingMetaSound = AuditionSound.Get(); if (USoundBase* Sound = AudioComponent->GetSound()) { if (Sound != AuditionSound) { UE_LOG(LogMetaSound, Warning, TEXT("MetaSoundBuilder '%s' supplied AudioComponent with unlinked sound '%s'. Stopping sound and replacing with builder's audition sound."), *GetFullName(), *Sound->GetFullName()); } } } else { BuilderOptions.Name = MakeUniqueObjectName(nullptr, UMetaSoundSource::StaticClass(), FName(GetName() + TEXT("_Audition"))); } AuditionSound = &BuildInternal(Parent, BuilderOptions); } AudioComponent->SetSound(AuditionSound.Get()); const bool bEnableDynamicGenerators = bLiveUpdatesEnabled; AuditionSound->SetDynamicGeneratorEnabled(bEnableDynamicGenerators); if (CreateGenerator.IsBound()) { UMetasoundGeneratorHandle* NewHandle = UMetasoundGeneratorHandle::CreateMetaSoundGeneratorHandle(AudioComponent); checkf(NewHandle, TEXT("BindToGeneratorDelegate Failed when attempting to audition MetaSoundSource builder '%s'"), *GetName()); CreateGenerator.Execute(NewHandle); } AudioComponent->Play(); } bool UMetaSoundSourceBuilder::ExecuteAuditionableTransaction(FAuditionableTransaction Transaction) const { using namespace Metasound::Engine::BuilderSubsystemPrivate; using namespace Metasound::DynamicGraph; METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(UMetaSoundSourceBuilder::ExecuteAuditionableTransaction); if (AuditionSound.IsValid()) { TSharedPtr Transactor = AuditionSound->GetDynamicGeneratorTransactor(); if (Transactor.IsValid()) { return Transaction(*Transactor); } } return false; } TScriptInterface UMetaSoundSourceBuilder::Build(UObject* Parent, const FMetaSoundBuilderOptions& InBuilderOptions) const { return &BuildInternal(Parent, InBuilderOptions); } void UMetaSoundSourceBuilder::InitFrontendBuilder() { using namespace Metasound::Frontend; TSharedRef DocumentDelegates = MakeShared(); InitDelegates(*DocumentDelegates); UMetaSoundBuilderDocument* DocObject = CreateTransientDocumentObject(); Builder = FMetaSoundFrontendDocumentBuilder(DocObject, DocumentDelegates); Builder.InitDocument(); } const Metasound::Engine::FOutputAudioFormatInfoPair* UMetaSoundSourceBuilder::FindOutputAudioFormatInfo() const { using namespace Metasound::Engine; const FOutputAudioFormatInfoMap& FormatInfo = GetOutputAudioFormatInfo(); auto Predicate = [this](const FOutputAudioFormatInfoPair& Pair) { const FMetasoundFrontendDocument& Document = Builder.GetDocument(); return Document.Interfaces.Contains(Pair.Value.InterfaceVersion); }; return Algo::FindByPredicate(FormatInfo, Predicate); } const UClass& UMetaSoundSourceBuilder::GetBuilderUClass() const { return *UMetaSoundSource::StaticClass(); } bool UMetaSoundSourceBuilder::GetLiveUpdatesEnabled() const { using namespace Metasound::Engine::BuilderSubsystemPrivate; if (!AuditionSound.IsValid()) { return false; } return AuditionSound->GetDynamicGeneratorTransactor().IsValid(); } void UMetaSoundSourceBuilder::InitDelegates(Metasound::Frontend::FDocumentModifyDelegates& OutDocumentDelegates) const { OutDocumentDelegates.EdgeDelegates.OnEdgeAdded.AddUObject(this, &UMetaSoundSourceBuilder::OnEdgeAdded); OutDocumentDelegates.EdgeDelegates.OnRemoveSwappingEdge.AddUObject(this, &UMetaSoundSourceBuilder::OnRemoveSwappingEdge); OutDocumentDelegates.InterfaceDelegates.OnInputAdded.AddUObject(this, &UMetaSoundSourceBuilder::OnInputAdded); OutDocumentDelegates.InterfaceDelegates.OnOutputAdded.AddUObject(this, &UMetaSoundSourceBuilder::OnOutputAdded); OutDocumentDelegates.InterfaceDelegates.OnRemovingInput.AddUObject(this, &UMetaSoundSourceBuilder::OnRemovingInput); OutDocumentDelegates.InterfaceDelegates.OnRemovingOutput.AddUObject(this, &UMetaSoundSourceBuilder::OnRemovingOutput); OutDocumentDelegates.NodeDelegates.OnNodeAdded.AddUObject(this, &UMetaSoundSourceBuilder::OnNodeAdded); OutDocumentDelegates.NodeDelegates.OnNodeInputLiteralSet.AddUObject(this, &UMetaSoundSourceBuilder::OnNodeInputLiteralSet); OutDocumentDelegates.NodeDelegates.OnRemoveSwappingNode.AddUObject(this, &UMetaSoundSourceBuilder::OnRemoveSwappingNode); OutDocumentDelegates.NodeDelegates.OnRemovingNodeInputLiteral.AddUObject(this, &UMetaSoundSourceBuilder::OnRemovingNodeInputLiteral); } void UMetaSoundSourceBuilder::OnEdgeAdded(int32 EdgeIndex) const { using namespace Metasound::DynamicGraph; const FMetasoundFrontendDocument& Doc = Builder.GetDocument(); const FMetasoundFrontendEdge& NewEdge = Doc.RootGraph.Graph.Edges[EdgeIndex]; ExecuteAuditionableTransaction([this, &NewEdge](Metasound::DynamicGraph::FDynamicOperatorTransactor& Transactor) { const FMetaSoundFrontendDocumentBuilder& Builder = GetConstBuilder(); const FMetasoundFrontendVertex* FromNodeOutput = Builder.FindNodeOutput(NewEdge.FromNodeID, NewEdge.FromVertexID); const FMetasoundFrontendVertex* ToNodeInput = Builder.FindNodeInput(NewEdge.ToNodeID, NewEdge.ToVertexID); if (FromNodeOutput && ToNodeInput) { Transactor.AddDataEdge(NewEdge.FromNodeID, FromNodeOutput->Name, NewEdge.ToNodeID, ToNodeInput->Name); return true; } return false; }); } TOptional UMetaSoundSourceBuilder::CreateDataReference( const Metasound::FOperatorSettings& InOperatorSettings, FName DataType, const Metasound::FLiteral& InLiteral, Metasound::EDataReferenceAccessType AccessType) { using namespace Metasound; using namespace Metasound::Frontend; return IDataTypeRegistry::Get().CreateDataReference(DataType, AccessType, InLiteral, InOperatorSettings); }; void UMetaSoundSourceBuilder::OnInputAdded(int32 InputIndex) const { using namespace Metasound::DynamicGraph; ExecuteAuditionableTransaction([this, InputIndex](FDynamicOperatorTransactor& Transactor) { using namespace Metasound; using namespace Metasound::Frontend; const FMetasoundFrontendDocument& Doc = Builder.GetDocument(); const FMetasoundFrontendGraphClass& GraphClass = Doc.RootGraph; const FMetasoundFrontendClassInput& NewInput = GraphClass.Interface.Inputs[InputIndex]; const FLiteral NewInputLiteral = NewInput.DefaultLiteral.ToLiteral(NewInput.TypeName); Transactor.AddInputDataDestination(NewInput.NodeID, NewInput.Name, NewInputLiteral, &UMetaSoundSourceBuilder::CreateDataReference); return true; }); } void UMetaSoundSourceBuilder::OnNodeAdded(int32 NodeIndex) const { using namespace Metasound::DynamicGraph; ExecuteAuditionableTransaction([this, NodeIndex](FDynamicOperatorTransactor& Transactor) { using namespace Metasound; using namespace Metasound::Frontend; const FMetasoundFrontendDocument& Doc = Builder.GetDocument(); const FMetasoundFrontendGraphClass& GraphClass = Doc.RootGraph; const FMetasoundFrontendNode& AddedNode = GraphClass.Graph.Nodes[NodeIndex]; const FMetasoundFrontendClass* NodeClass = Builder.FindDependency(AddedNode.ClassID); checkf(NodeClass, TEXT("Node successfully added to graph but document is missing associated dependency")); const FNodeRegistryKey& ClassKey = NodeRegistryKey::CreateKey(NodeClass->Metadata); FMetasoundFrontendRegistryContainer& NodeRegistry = *FMetasoundFrontendRegistryContainer::Get(); IDataTypeRegistry& DataTypeRegistry = IDataTypeRegistry::Get(); TUniquePtr NewNode; switch (NodeClass->Metadata.GetType()) { case EMetasoundFrontendClassType::VariableDeferredAccessor: case EMetasoundFrontendClassType::VariableAccessor: case EMetasoundFrontendClassType::VariableMutator: case EMetasoundFrontendClassType::External: case EMetasoundFrontendClassType::Graph: { const Metasound::FNodeInitData InitData { AddedNode.Name, AddedNode.GetID() }; NewNode = NodeRegistry.CreateNode(ClassKey, InitData); } break; case EMetasoundFrontendClassType::Input: { const FName& DataTypeName = NodeClass->Metadata.GetClassName().Name; const FMetasoundFrontendVertex& InputVertex = AddedNode.Interface.Inputs.Last(); const FMetasoundFrontendLiteral& DefaultLiteral = NodeClass->Interface.Inputs.Last().DefaultLiteral; FInputNodeConstructorParams InitData { AddedNode.Name, AddedNode.GetID(), InputVertex.Name, DefaultLiteral.ToLiteral(DataTypeName) }; NewNode = DataTypeRegistry.CreateInputNode(DataTypeName, MoveTemp(InitData)); } break; case EMetasoundFrontendClassType::Variable: { const FName& DataTypeName = NodeClass->Metadata.GetClassName().Name; FDefaultLiteralNodeConstructorParams InitData { AddedNode.Name, AddedNode.GetID(), DataTypeRegistry.CreateDefaultLiteral(DataTypeName)}; NewNode = DataTypeRegistry.CreateVariableNode(DataTypeName, MoveTemp(InitData)); } break; case EMetasoundFrontendClassType::Literal: { const FName& DataTypeName = NodeClass->Metadata.GetClassName().Name; FDefaultLiteralNodeConstructorParams InitData { AddedNode.Name, AddedNode.GetID(), DataTypeRegistry.CreateDefaultLiteral(DataTypeName)}; NewNode = DataTypeRegistry.CreateLiteralNode(DataTypeName, MoveTemp(InitData)); } break; case EMetasoundFrontendClassType::Output: { const FName& DataTypeName = NodeClass->Metadata.GetClassName().Name; const FMetasoundFrontendVertex& OutputVertex = AddedNode.Interface.Outputs.Last(); FDefaultNamedVertexNodeConstructorParams InitData { AddedNode.Name, AddedNode.GetID(), OutputVertex.Name }; NewNode = DataTypeRegistry.CreateOutputNode(DataTypeName, MoveTemp(InitData)); } break; case EMetasoundFrontendClassType::Template: default: static_assert(static_cast(EMetasoundFrontendClassType::Invalid) == 10, "Possible missed EMetasoundFrontendClassType case coverage"); }; if (!NewNode.IsValid()) { UE_LOG(LogMetaSound, Error, TEXT("Builder '%s' failed to create and forward added node '%s' to live update transactor."), *GetName(), *AddedNode.Name.ToString()); return false; } Transactor.AddNode(AddedNode.GetID(), MoveTemp(NewNode)); return true; }); } void UMetaSoundSourceBuilder::OnNodeInputLiteralSet(int32 NodeIndex, int32 VertexIndex, int32 LiteralIndex) const { using namespace Metasound::DynamicGraph; const FMetasoundFrontendGraphClass& GraphClass = Builder.GetDocument().RootGraph; const FMetasoundFrontendNode& Node = GraphClass.Graph.Nodes[NodeIndex]; const FMetasoundFrontendVertex& Input = Node.Interface.Inputs[VertexIndex]; // Only send the literal down if not connected, as the graph core layer // will disconnect if a new literal is sent and edge already exists. if (!Builder.IsNodeInputConnected(Node.GetID(), Input.VertexID)) { ExecuteAuditionableTransaction([this, &Node, &Input, &LiteralIndex](FDynamicOperatorTransactor& Transactor) { using namespace Metasound; using namespace Metasound::Engine; const FMetasoundFrontendLiteral& InputDefault = Node.InputLiterals[LiteralIndex].Value; TUniquePtr LiteralNode = BuilderSubsystemPrivate::CreateDynamicNodeFromFrontendLiteral(Input.TypeName, InputDefault); Transactor.SetValue(Node.GetID(), Input.Name, MoveTemp(LiteralNode)); return true; }); } } void UMetaSoundSourceBuilder::OnOutputAdded(int32 OutputIndex) const { using namespace Metasound::DynamicGraph; ExecuteAuditionableTransaction([this, OutputIndex](FDynamicOperatorTransactor& Transactor) { using namespace Metasound::Frontend; const FMetasoundFrontendDocument& Doc = Builder.GetDocument(); const FMetasoundFrontendGraphClass& GraphClass = Doc.RootGraph; const FMetasoundFrontendClassOutput& NewOutput = GraphClass.Interface.Outputs[OutputIndex]; Transactor.AddOutputDataSource(NewOutput.NodeID, NewOutput.Name); return true; }); } void UMetaSoundSourceBuilder::OnRemoveSwappingEdge(int32 SwapIndex, int32 LastIndex) const { using namespace Metasound::DynamicGraph; const FMetasoundFrontendDocument& Doc = Builder.GetDocument(); const FMetasoundFrontendEdge& EdgeBeingRemoved = Doc.RootGraph.Graph.Edges[SwapIndex]; ExecuteAuditionableTransaction([this, EdgeBeingRemoved](FDynamicOperatorTransactor& Transactor) { using namespace Metasound; using namespace Metasound::Engine; const FMetaSoundFrontendDocumentBuilder& Builder = GetConstBuilder(); const FMetasoundFrontendVertex* FromNodeOutput = Builder.FindNodeOutput(EdgeBeingRemoved.FromNodeID, EdgeBeingRemoved.FromVertexID); const FMetasoundFrontendVertex* ToNodeInput = Builder.FindNodeInput(EdgeBeingRemoved.ToNodeID, EdgeBeingRemoved.ToVertexID); if (FromNodeOutput && ToNodeInput) { const FMetasoundFrontendLiteral* InputDefault = Builder.GetNodeInputDefault(EdgeBeingRemoved.ToNodeID, EdgeBeingRemoved.ToVertexID); if (!InputDefault) { InputDefault = Builder.GetNodeInputClassDefault(EdgeBeingRemoved.ToNodeID, EdgeBeingRemoved.ToVertexID); } if (ensureAlwaysMsgf(InputDefault, TEXT("Could not dynamically assign default literal upon removing edge: literal should be assigned by either the frontend document's input or the class definition"))) { TUniquePtr LiteralNode = BuilderSubsystemPrivate::CreateDynamicNodeFromFrontendLiteral(ToNodeInput->TypeName, *InputDefault); Transactor.RemoveDataEdge(EdgeBeingRemoved.FromNodeID, FromNodeOutput->Name, EdgeBeingRemoved.ToNodeID, ToNodeInput->Name, MoveTemp(LiteralNode)); return true; } } return false; }); } void UMetaSoundSourceBuilder::OnRemovingInput(int32 InputIndex) const { using namespace Metasound::DynamicGraph; ExecuteAuditionableTransaction([this, InputIndex](FDynamicOperatorTransactor& Transactor) { using namespace Metasound::Frontend; const FMetasoundFrontendDocument& Doc = Builder.GetDocument(); const FMetasoundFrontendGraphClass& GraphClass = Doc.RootGraph; const FMetasoundFrontendClassInput& InputBeingRemoved = GraphClass.Interface.Inputs[InputIndex]; Transactor.RemoveInputDataDestination(InputBeingRemoved.Name); return true; }); } void UMetaSoundSourceBuilder::OnRemoveSwappingNode(int32 SwapIndex, int32 LastIndex) const { using namespace Metasound::DynamicGraph; // Last index will just be re-added, so this aspect of the swap is ignored by transactor // (i.e. no sense removing and re-adding the node that is swapped from the end as this // would potentially disconnect that node in the runtime graph model). ExecuteAuditionableTransaction([this, SwapIndex](FDynamicOperatorTransactor& Transactor) { using namespace Metasound::Frontend; const FMetasoundFrontendDocument& Doc = Builder.GetDocument(); const FMetasoundFrontendGraphClass& GraphClass = Doc.RootGraph; const FMetasoundFrontendNode& NodeBeingRemoved = GraphClass.Graph.Nodes[SwapIndex]; const FGuid& NodeID = NodeBeingRemoved.GetID(); Transactor.RemoveNode(NodeID); return true; }); } void UMetaSoundSourceBuilder::OnRemovingNodeInputLiteral(int32 NodeIndex, int32 VertexIndex, int32 LiteralIndex) const { using namespace Metasound::DynamicGraph; const FMetasoundFrontendGraphClass& GraphClass = Builder.GetDocument().RootGraph; const FMetasoundFrontendNode& Node = GraphClass.Graph.Nodes[NodeIndex]; const FMetasoundFrontendVertex& Input = Node.Interface.Inputs[VertexIndex]; // Only send the literal down if not connected, as the graph core layer will disconnect. if (!Builder.IsNodeInputConnected(Node.GetID(), Input.VertexID)) { ExecuteAuditionableTransaction([this, &NodeIndex, &VertexIndex, &LiteralIndex](FDynamicOperatorTransactor& Transactor) { using namespace Metasound; using namespace Metasound::Engine; using namespace Metasound::Frontend; const TArray& Nodes = Builder.GetDocument().RootGraph.Graph.Nodes; const FMetasoundFrontendNode& Node = Nodes[NodeIndex]; const FMetasoundFrontendVertex& Input = Node.Interface.Inputs[VertexIndex]; const FMetasoundFrontendLiteral* InputDefault = Builder.GetNodeInputClassDefault(Node.GetID(), Input.VertexID); if (ensureAlwaysMsgf(InputDefault, TEXT("Could not dynamically assign default literal from class definition upon removing input '%s' literal: document's dependency entry invalid and has no default assigned"), *Input.Name.ToString())) { TUniquePtr LiteralNode = BuilderSubsystemPrivate::CreateDynamicNodeFromFrontendLiteral(Input.TypeName, *InputDefault); Transactor.SetValue(Node.GetID(), Input.Name, MoveTemp(LiteralNode)); return true; } return false; }); } } void UMetaSoundSourceBuilder::OnRemovingOutput(int32 OutputIndex) const { using namespace Metasound::DynamicGraph; ExecuteAuditionableTransaction([this, OutputIndex](FDynamicOperatorTransactor& Transactor) { using namespace Metasound::Frontend; const FMetasoundFrontendDocument& Doc = Builder.GetDocument(); const FMetasoundFrontendGraphClass& GraphClass = Doc.RootGraph; const FMetasoundFrontendClassOutput& OutputBeingRemoved = GraphClass.Interface.Outputs[OutputIndex]; Transactor.RemoveOutputDataSource(OutputBeingRemoved.Name); return true; }); } void UMetaSoundSourceBuilder::SetFormat(EMetaSoundOutputAudioFormat OutputFormat, EMetaSoundBuilderResult& OutResult) { using namespace Metasound::Engine; using namespace Metasound::Frontend; // Convert to non-preset MetaSoundSource since interface data is being altered Builder.ConvertFromPreset(); const FOutputAudioFormatInfoMap& FormatMap = GetOutputAudioFormatInfo(); // Determine which interfaces to add and remove from the document due to the // output format being changed. TArray OutputFormatsToAdd; if (const FOutputAudioFormatInfo* FormatInfo = FormatMap.Find(OutputFormat)) { OutputFormatsToAdd.Add(FormatInfo->InterfaceVersion); } TArray OutputFormatsToRemove; const FMetasoundFrontendDocument& Document = GetConstBuilder().GetDocument(); for (const FOutputAudioFormatInfoPair& Pair : FormatMap) { const FMetasoundFrontendVersion& FormatVersion = Pair.Value.InterfaceVersion; if (Document.Interfaces.Contains(FormatVersion)) { if (!OutputFormatsToAdd.Contains(FormatVersion)) { OutputFormatsToRemove.Add(FormatVersion); } } } FModifyInterfaceOptions Options(OutputFormatsToRemove, OutputFormatsToAdd); #if WITH_EDITORONLY_DATA Options.bSetDefaultNodeLocations = true; #endif // WITH_EDITORONLY_DATA const bool bSuccess = Builder.ModifyInterfaces(MoveTemp(Options)); OutResult = bSuccess ? EMetaSoundBuilderResult::Succeeded : EMetaSoundBuilderResult::Failed; } UMetaSoundPatchBuilder* UMetaSoundBuilderSubsystem::CreatePatchBuilder(FName BuilderName, EMetaSoundBuilderResult& OutResult) { OutResult = EMetaSoundBuilderResult::Succeeded; return &Metasound::Engine::BuilderSubsystemPrivate::CreateTransientBuilder(BuilderName); } UMetaSoundSourceBuilder* UMetaSoundBuilderSubsystem::CreateSourceBuilder( FName BuilderName, FMetaSoundBuilderNodeOutputHandle& OnPlayNodeOutput, FMetaSoundBuilderNodeInputHandle& OnFinishedNodeInput, TArray& AudioOutNodeInputs, EMetaSoundBuilderResult& OutResult, EMetaSoundOutputAudioFormat OutputFormat, bool bIsOneShot) { using namespace Metasound::Frontend; using namespace Metasound::Engine::BuilderSubsystemPrivate; OnPlayNodeOutput = { }; OnFinishedNodeInput = { }; AudioOutNodeInputs.Reset(); UMetaSoundSourceBuilder& NewBuilder = CreateTransientBuilder(BuilderName); OutResult = EMetaSoundBuilderResult::Succeeded; if (OutputFormat != EMetaSoundOutputAudioFormat::Mono) { NewBuilder.SetFormat(OutputFormat, OutResult); } if (OutResult == EMetaSoundBuilderResult::Succeeded) { TArray AudioOutputNodes; if (const Metasound::Engine::FOutputAudioFormatInfoPair* FormatInfo = NewBuilder.FindOutputAudioFormatInfo()) { AudioOutputNodes = NewBuilder.FindInterfaceOutputNodes(FormatInfo->Value.InterfaceVersion.Name, OutResult); } else { OutResult = EMetaSoundBuilderResult::Failed; } if (OutResult == EMetaSoundBuilderResult::Succeeded) { Algo::Transform(AudioOutputNodes, AudioOutNodeInputs, [&NewBuilder, &BuilderName](const FMetaSoundNodeHandle& AudioOutputNode) -> FMetaSoundBuilderNodeInputHandle { EMetaSoundBuilderResult Result; TArray Inputs = NewBuilder.FindNodeInputs(AudioOutputNode, Result); if (!Inputs.IsEmpty()) { return Inputs.Last(); } UE_LOG(LogMetaSound, Error, TEXT("Builder '%s' Creation Error: Failed to find expected audio output node input vertex. Returned vertices set may be incomplete."), *BuilderName.ToString()); return { }; }); } else { UE_LOG(LogMetaSound, Error, TEXT("Builder '%s' Creation Error: Failed to find expected audio output format and/or associated output nodes."), *BuilderName.ToString()); return nullptr; } } else { UE_LOG(LogMetaSound, Error, TEXT("Builder '%s' Creation Error: Failed to set output format when initializing."), *BuilderName.ToString()); return nullptr; } { FMetaSoundNodeHandle OnPlayNode = NewBuilder.FindGraphInputNode(SourceInterface::Inputs::OnPlay, OutResult); if (OutResult == EMetaSoundBuilderResult::Failed) { UE_LOG(LogMetaSound, Error, TEXT("Builder '%s' Creation Error: Failed to add required interface '%s' when attempting to create MetaSound Source Builder"), *BuilderName.ToString(), * SourceInterface::GetVersion().ToString()); return nullptr; } TArray Outputs = NewBuilder.FindNodeOutputs(OnPlayNode, OutResult); if (OutResult == EMetaSoundBuilderResult::Failed) { UE_LOG(LogMetaSound, Error, TEXT("Builder '%s' Creation Error: Failed to find output vertex for 'OnPlay' input node when attempting to create MetaSound Source Builder"), *BuilderName.ToString()); return nullptr; } check(!Outputs.IsEmpty()); OnPlayNodeOutput = Outputs.Last(); } if (bIsOneShot) { FMetaSoundNodeHandle OnFinishedNode = NewBuilder.FindGraphOutputNode(SourceOneShotInterface::Outputs::OnFinished, OutResult); if (OutResult == EMetaSoundBuilderResult::Failed) { UE_LOG(LogMetaSound, Error, TEXT("Builder '%s' Creation Error: Failed to add '%s' interface; interface definition may not be registered."), *BuilderName.ToString(), *SourceOneShotInterface::GetVersion().ToString()); } TArray Inputs = NewBuilder.FindNodeInputs(OnFinishedNode, OutResult); if (OutResult == EMetaSoundBuilderResult::Failed) { UE_LOG(LogMetaSound, Error, TEXT("Builder '%s' Creation Error: Failed to find input vertex for 'OnFinished' output node when attempting to create MetaSound Source Builder"), *BuilderName.ToString()); return nullptr; } check(!Inputs.IsEmpty()); OnFinishedNodeInput = Inputs.Last(); } else { NewBuilder.RemoveInterface(SourceOneShotInterface::GetVersion().Name, OutResult); } return &NewBuilder; } UMetaSoundPatchBuilder* UMetaSoundBuilderSubsystem::CreatePatchPresetBuilder(FName BuilderName, const TScriptInterface& ReferencedNodeClass, EMetaSoundBuilderResult& OutResult) { if (ReferencedNodeClass) { UMetaSoundPatchBuilder& Builder = Metasound::Engine::BuilderSubsystemPrivate::CreateTransientBuilder(BuilderName); Builder.ConvertToPreset(ReferencedNodeClass, OutResult); return &Builder; } OutResult = EMetaSoundBuilderResult::Failed; return nullptr; } UMetaSoundBuilderBase& UMetaSoundBuilderSubsystem::CreatePresetBuilder(FName BuilderName, const TScriptInterface& ReferencedPatchClass, EMetaSoundBuilderResult& OutResult) { const UClass& Class = ReferencedPatchClass->GetBaseMetaSoundUClass(); if (&Class == UMetaSoundSource::StaticClass()) { return *CreateSourcePresetBuilder(BuilderName, ReferencedPatchClass, OutResult); } else if (&Class == UMetaSoundPatch::StaticClass()) { return *CreatePatchPresetBuilder(BuilderName, ReferencedPatchClass, OutResult); } else { checkf(false, TEXT("UClass '%s' cannot be built to a MetaSound preset"), *Class.GetFullName()); return Metasound::Engine::BuilderSubsystemPrivate::CreateTransientBuilder(BuilderName); } } UMetaSoundSourceBuilder* UMetaSoundBuilderSubsystem::CreateSourcePresetBuilder(FName BuilderName, const TScriptInterface& ReferencedNodeClass, EMetaSoundBuilderResult& OutResult) { if (ReferencedNodeClass) { UMetaSoundSourceBuilder& Builder = Metasound::Engine::BuilderSubsystemPrivate::CreateTransientBuilder(); Builder.ConvertToPreset(ReferencedNodeClass, OutResult); return &Builder; } OutResult = EMetaSoundBuilderResult::Failed; return nullptr; } UMetaSoundBuilderSubsystem& UMetaSoundBuilderSubsystem::GetChecked() { checkf(GEngine, TEXT("Cannot access UMetaSoundBuilderSubsystem without engine loaded")); UMetaSoundBuilderSubsystem* BuilderSubsystem = GEngine->GetEngineSubsystem(); checkf(BuilderSubsystem, TEXT("Failed to find initialized 'UMetaSoundBuilderSubsystem")); return *BuilderSubsystem; } const UMetaSoundBuilderSubsystem& UMetaSoundBuilderSubsystem::GetConstChecked() { checkf(GEngine, TEXT("Cannot access UMetaSoundBuilderSubsystem without engine loaded")); UMetaSoundBuilderSubsystem* BuilderSubsystem = GEngine->GetEngineSubsystem(); checkf(BuilderSubsystem, TEXT("Failed to find initialized 'UMetaSoundBuilderSubsystem")); return *BuilderSubsystem; } FMetasoundFrontendLiteral UMetaSoundBuilderSubsystem::CreateBoolMetaSoundLiteral(bool Value, FName& OutDataType) { return Metasound::Engine::BuilderSubsystemPrivate::CreatePODMetaSoundLiteral(Value, OutDataType); } FMetasoundFrontendLiteral UMetaSoundBuilderSubsystem::CreateBoolArrayMetaSoundLiteral(const TArray& Value, FName& OutDataType) { return Metasound::Engine::BuilderSubsystemPrivate::CreatePODMetaSoundLiteral(Value, OutDataType); } FMetasoundFrontendLiteral UMetaSoundBuilderSubsystem::CreateFloatMetaSoundLiteral(float Value, FName& OutDataType) { return Metasound::Engine::BuilderSubsystemPrivate::CreatePODMetaSoundLiteral(Value, OutDataType); } FMetasoundFrontendLiteral UMetaSoundBuilderSubsystem::CreateFloatArrayMetaSoundLiteral(const TArray& Value, FName& OutDataType) { return Metasound::Engine::BuilderSubsystemPrivate::CreatePODMetaSoundLiteral(Value, OutDataType); } FMetasoundFrontendLiteral UMetaSoundBuilderSubsystem::CreateIntMetaSoundLiteral(int32 Value, FName& OutDataType) { return Metasound::Engine::BuilderSubsystemPrivate::CreatePODMetaSoundLiteral(Value, OutDataType); } FMetasoundFrontendLiteral UMetaSoundBuilderSubsystem::CreateIntArrayMetaSoundLiteral(const TArray& Value, FName& OutDataType) { return Metasound::Engine::BuilderSubsystemPrivate::CreatePODMetaSoundLiteral(Value, OutDataType); } FMetasoundFrontendLiteral UMetaSoundBuilderSubsystem::CreateStringMetaSoundLiteral(const FString& Value, FName& OutDataType) { return Metasound::Engine::BuilderSubsystemPrivate::CreatePODMetaSoundLiteral(Value, OutDataType); } FMetasoundFrontendLiteral UMetaSoundBuilderSubsystem::CreateStringArrayMetaSoundLiteral(const TArray& Value, FName& OutDataType) { return Metasound::Engine::BuilderSubsystemPrivate::CreatePODMetaSoundLiteral(Value, OutDataType); } FMetasoundFrontendLiteral UMetaSoundBuilderSubsystem::CreateObjectMetaSoundLiteral(UObject* Value) { FMetasoundFrontendLiteral Literal; Literal.Set(Value); return Literal; } FMetasoundFrontendLiteral UMetaSoundBuilderSubsystem::CreateObjectArrayMetaSoundLiteral(const TArray& Value) { FMetasoundFrontendLiteral Literal; Literal.Set(Value); return Literal; } FMetasoundFrontendLiteral UMetaSoundBuilderSubsystem::CreateMetaSoundLiteralFromParam(const FAudioParameter& Param) { return FMetasoundFrontendLiteral { Param }; } bool UMetaSoundBuilderSubsystem::DetachBuilderFromAsset(const FMetasoundFrontendClassName& InClassName) const { return AssetBuilders.Remove(InClassName.GetFullName()) > 0; } const Metasound::Frontend::FDocumentModifyDelegates* UMetaSoundBuilderSubsystem::FindModifyDelegates(const FMetasoundFrontendClassName& InClassName) const { using namespace Metasound::Frontend; TWeakObjectPtr BuilderPtr = AssetBuilders.FindRef(InClassName.GetFullName()); if (BuilderPtr.IsValid()) { return &BuilderPtr->GetConstBuilder().GetDocumentDelegates(); } return nullptr; } UMetaSoundBuilderBase* UMetaSoundBuilderSubsystem::FindBuilder(FName BuilderName) { return NamedBuilders.FindRef(BuilderName); } UMetaSoundPatchBuilder* UMetaSoundBuilderSubsystem::FindPatchBuilder(FName BuilderName) { if (UMetaSoundBuilderBase* Builder = FindBuilder(BuilderName)) { return Cast(Builder); } return nullptr; } UMetaSoundSourceBuilder* UMetaSoundBuilderSubsystem::FindSourceBuilder(FName BuilderName) { if (UMetaSoundBuilderBase* Builder = FindBuilder(BuilderName)) { return Cast(Builder); } return nullptr; } void UMetaSoundBuilderSubsystem::Initialize(FSubsystemCollectionBase& Collection) { using namespace Metasound::Frontend; IMetaSoundDocumentBuilderRegistry::Set([]() -> IMetaSoundDocumentBuilderRegistry& { check(GEngine); UMetaSoundBuilderSubsystem* BuilderSubsystem = GEngine->GetEngineSubsystem(); check(BuilderSubsystem); return static_cast(*BuilderSubsystem); }); } bool UMetaSoundBuilderSubsystem::IsInterfaceRegistered(FName InInterfaceName) const { using namespace Metasound::Frontend; FMetasoundFrontendInterface Interface; return ISearchEngine::Get().FindInterfaceWithHighestVersion(InInterfaceName, Interface); } #if WITH_EDITOR void UMetaSoundBuilderSubsystem::PostBuilderAssetTransaction(const FMetasoundFrontendClassName& InClassName) { TWeakObjectPtr BuilderPtr = AssetBuilders.FindRef(InClassName.GetFullName()); if (UMetaSoundBuilderBase* Builder = BuilderPtr.Get()) { Builder->ReloadCache(); } } #endif // WITH_EDITOR void UMetaSoundBuilderSubsystem::RegisterBuilder(FName BuilderName, UMetaSoundBuilderBase* Builder) { if (Builder) { NamedBuilders.FindOrAdd(BuilderName) = Builder; } } void UMetaSoundBuilderSubsystem::RegisterPatchBuilder(FName BuilderName, UMetaSoundPatchBuilder* Builder) { if (Builder) { NamedBuilders.FindOrAdd(BuilderName) = Builder; } } void UMetaSoundBuilderSubsystem::RegisterSourceBuilder(FName BuilderName, UMetaSoundSourceBuilder* Builder) { if (Builder) { NamedBuilders.FindOrAdd(BuilderName) = Builder; } } bool UMetaSoundBuilderSubsystem::UnregisterBuilder(FName BuilderName) { return NamedBuilders.Remove(BuilderName) > 0; } bool UMetaSoundBuilderSubsystem::UnregisterPatchBuilder(FName BuilderName) { return NamedBuilders.Remove(BuilderName) > 0; } bool UMetaSoundBuilderSubsystem::UnregisterSourceBuilder(FName BuilderName) { return NamedBuilders.Remove(BuilderName) > 0; }