Files
Rob Gay a883eee8cc - Fix for crash when attempting to add graph input to MetaSound while auditioning builder from BP
- Fix for ensure if DataType provided to add graph input/output MetaSound Builder is invalid
#rb phil.popp
#jira UE-192614
#rnx

[CL 27158677 by Rob Gay in 5.3 branch]
2023-08-16 18:30:52 -04:00

681 lines
38 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/Map.h"
#include "Delegates/IDelegateInstance.h"
#include "Engine/Engine.h"
#include "Interfaces/MetasoundOutputFormatInterfaces.h"
#include "MetasoundDocumentInterface.h"
#include "MetasoundDynamicOperatorTransactor.h"
#include "MetasoundFrontendController.h"
#include "MetasoundFrontendDocument.h"
#include "MetasoundFrontendDocumentBuilder.h"
#include "MetasoundFrontendDocumentCacheInterface.h"
#include "MetasoundFrontendDocumentModifyDelegates.h"
#include "Subsystems/EngineSubsystem.h"
#include "Templates/Function.h"
#include "UObject/NoExportTypes.h"
#include "UObject/Object.h"
#include "UObject/ObjectMacros.h"
#include "MetasoundBuilderSubsystem.generated.h"
// Forward Declarations
class UAudioComponent;
class UMetaSound;
class UMetaSoundPatch;
class UMetaSoundSource;
struct FMetasoundFrontendClassName;
struct FMetasoundFrontendVersion;
enum class EMetaSoundOutputAudioFormat : uint8;
namespace Metasound::Engine
{
struct FOutputAudioFormatInfo;
} // namespace Metasound::Engine
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnCreateAuditionGeneratorHandleDelegate, UMetasoundGeneratorHandle*, GeneratorHandle);
USTRUCT(BlueprintType, meta = (DisplayName = "MetaSound Node Input Handle"))
struct METASOUNDENGINE_API FMetaSoundBuilderNodeInputHandle : public FMetasoundFrontendVertexHandle
{
GENERATED_BODY()
public:
FMetaSoundBuilderNodeInputHandle() = default;
FMetaSoundBuilderNodeInputHandle(const FGuid InNodeID, const FGuid& InVertexID)
{
NodeID = InNodeID;
VertexID = InVertexID;
}
};
USTRUCT(BlueprintType, meta = (DisplayName = "MetaSound Node Output Handle"))
struct METASOUNDENGINE_API FMetaSoundBuilderNodeOutputHandle : public FMetasoundFrontendVertexHandle
{
GENERATED_BODY()
public:
FMetaSoundBuilderNodeOutputHandle() = default;
FMetaSoundBuilderNodeOutputHandle(const FGuid InNodeID, const FGuid& InVertexID)
{
NodeID = InNodeID;
VertexID = InVertexID;
}
};
USTRUCT(BlueprintType)
struct METASOUNDENGINE_API FMetaSoundNodeHandle
{
GENERATED_BODY()
UPROPERTY()
FGuid NodeID;
public:
FMetaSoundNodeHandle() = default;
FMetaSoundNodeHandle(const FGuid& InNodeID)
: NodeID(InNodeID)
{
}
// Returns whether or not the vertex handle is set (may or may not be
// valid depending on what builder context it is referenced against)
bool IsSet() const
{
return NodeID.IsValid();
}
};
USTRUCT(BlueprintType)
struct METASOUNDENGINE_API FMetaSoundBuilderOptions
{
GENERATED_BODY()
// Name of generated object. If object already exists, used as the base name to ensure new object is unique.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MetaSound|Builder")
FName Name;
// If true, this will force regeneration of the class identifier. If the resulting MetaSound is building over
// an existing document, this will effectively invalidate any referencing MetaSounds and register the MetaSound
// as a new entry in the Frontend.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MetaSound|Builder", meta = (AdvancedDisplay))
bool bForceUniqueClassName = false;
// If true, adds MetaSound to node registry, making it available
// for reference by other dynamically created MetaSounds.
UPROPERTY()
bool bAddToRegistry = true;
// If set, builder overwrites the given MetaSound's document with the builder's copy
// (ignores the Name field above).
UPROPERTY()
TScriptInterface<IMetaSoundDocumentInterface> ExistingMetaSound;
};
UENUM(BlueprintType)
enum class EMetaSoundBuilderResult : uint8
{
Succeeded,
Failed
};
/** Base implementation of MetaSound builder */
UCLASS(Abstract)
class METASOUNDENGINE_API UMetaSoundBuilderBase : public UObject
{
GENERATED_BODY()
public:
// Adds a graph input node with the given name, DataType, and sets the graph input to default value.
// Returns the new input node's output handle if it was successfully created, or an invalid handle if it failed.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult", AdvancedDisplay = "3"))
UPARAM(DisplayName = "Output Handle") FMetaSoundBuilderNodeOutputHandle AddGraphInputNode(FName Name, FName DataType, FMetasoundFrontendLiteral DefaultValue, EMetaSoundBuilderResult& OutResult, bool bIsConstructorInput = false);
// Adds a graph output node with the given name, DataType, and sets output node's input to default value.
// Returns the new output node's input handle if it was successfully created, or an invalid handle if it failed.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Input Handle") FMetaSoundBuilderNodeInputHandle AddGraphOutputNode(FName Name, FName DataType, FMetasoundFrontendLiteral DefaultValue, EMetaSoundBuilderResult& OutResult, bool bIsConstructorOutput = false);
// Adds an interface registered with the given name to the graph, adding associated input and output nodes.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void AddInterface(FName InterfaceName, EMetaSoundBuilderResult& OutResult);
// Adds a node to the graph using the provided MetaSound asset as its defining NodeClass.
// Returns a node handle to the created node if successful, or an invalid handle if it failed.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", DisplayName = "Add MetaSound Node From Asset Class", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Node Handle") FMetaSoundNodeHandle AddNode(const TScriptInterface<IMetaSoundDocumentInterface>& NodeClass, EMetaSoundBuilderResult& OutResult);
// Adds node referencing the highest native class version of the given class name to the document.
// Returns a node handle to the created node if successful, or an invalid handle if it failed.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", DisplayName = "Add MetaSound Node By ClassName", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Node Handle") FMetaSoundNodeHandle AddNodeByClassName(const FMetasoundFrontendClassName& ClassName, int32 MajorVersion, EMetaSoundBuilderResult& OutResult);
// Connects node output to a node input. Does *NOT* provide loop detection for performance reasons. Loop detection is checked on class registration when built or played.
// Returns succeeded if connection made, failed if connection already exists with input, the data types do not match, or the connection is not supported due to access type
// incompatibility (ex. constructor input to non-constructor input).
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void ConnectNodes(const FMetaSoundBuilderNodeOutputHandle& NodeOutputHandle, const FMetaSoundBuilderNodeInputHandle& NodeInputHandle, EMetaSoundBuilderResult& OutResult);
// Connects two nodes using defined MetaSound Interface Bindings registered with the MetaSound Interface registry.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void ConnectNodesByInterfaceBindings(const FMetaSoundNodeHandle& FromNodeHandle, const FMetaSoundNodeHandle& ToNodeHandle, EMetaSoundBuilderResult& OutResult);
// Connects a given node's outputs to all graph outputs for shared interfaces implemented on both the node's referenced class and the builder's MetaSound graph. Returns inputs of connected output nodes.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Connected Graph Output Node Inputs") TArray<FMetaSoundBuilderNodeInputHandle> ConnectNodeOutputsToMatchingGraphInterfaceOutputs(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult);
// Connects a given node's inputs to all graph inputs for shared interfaces implemented on both the node's referenced class and the builder's MetaSound graph. Returns outputs of connected input nodes.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Connected Graph Input Node Outputs") TArray<FMetaSoundBuilderNodeOutputHandle> ConnectNodeInputsToMatchingGraphInterfaceInputs(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult);
// Connects a given node output to the graph output with the given name.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void ConnectNodeOutputToGraphOutput(FName GraphOutputName, const FMetaSoundBuilderNodeOutputHandle& NodeOutputHandle, EMetaSoundBuilderResult& OutResult);
// Connects a given node input to the graph input with the given name.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void ConnectNodeInputToGraphInput(FName GraphInputName, const FMetaSoundBuilderNodeInputHandle& NodeInputHandle, EMetaSoundBuilderResult& OutResult);
// Returns whether node exists.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "IsValid") bool ContainsNode(const FMetaSoundNodeHandle& Node) const;
// Returns whether node input exists.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "IsValid") bool ContainsNodeInput(const FMetaSoundBuilderNodeInputHandle& Input) const;
// Returns whether node output exists.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "IsValid") bool ContainsNodeOutput(const FMetaSoundBuilderNodeOutputHandle& Output) const;
// Disconnects node output to a node input. Returns success if connection was removed, failed if not.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void DisconnectNodes(const FMetaSoundBuilderNodeOutputHandle& NodeOutputHandle, const FMetaSoundBuilderNodeInputHandle& NodeInputHandle, EMetaSoundBuilderResult& OutResult);
// Removes connection to a given node input. Returns success if connection was removed, failed if not.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void DisconnectNodeInput(const FMetaSoundBuilderNodeInputHandle& NodeInputHandle, EMetaSoundBuilderResult& OutResult);
// Removes all connections from a given node output. Returns success if all connections were removed, failed if not.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void DisconnectNodeOutput(const FMetaSoundBuilderNodeOutputHandle& NodeOutputHandle, EMetaSoundBuilderResult& OutResult);
// Disconnects two nodes using defined MetaSound Interface Bindings registered with the MetaSound Interface registry. Returns success if
// all connections were found and removed, failed if any connections were not.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void DisconnectNodesByInterfaceBindings(const FMetaSoundNodeHandle& FromNodeHandle, const FMetaSoundNodeHandle& ToNodeHandle, EMetaSoundBuilderResult& OutResult);
// Returns graph input node by the given name if it exists, or an invalid handle if not found.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Node Handle") FMetaSoundNodeHandle FindGraphInputNode(FName InputName, EMetaSoundBuilderResult& OutResult);
// Returns graph output node by the given name if it exists, or an invalid handle if not found.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Node Handle") FMetaSoundNodeHandle FindGraphOutputNode(FName OutputName, EMetaSoundBuilderResult& OutResult);
// Returns node input by the given name if it exists, or an invalid handle if not found.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Input Handle") FMetaSoundBuilderNodeInputHandle FindNodeInputByName(const FMetaSoundNodeHandle& NodeHandle, FName InputName, EMetaSoundBuilderResult& OutResult);
// Returns node output by the given name.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Input Handles") TArray<FMetaSoundBuilderNodeInputHandle> FindNodeInputs(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult);
// Returns node inputs by the given DataType (ex. "Audio", "Trigger", "String", "Bool", "Float", "Int32", etc.).
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Input Handles") TArray<FMetaSoundBuilderNodeInputHandle> FindNodeInputsByDataType(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult, FName DataType);
// Returns node output by the given name.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Output Handle") FMetaSoundBuilderNodeOutputHandle FindNodeOutputByName(const FMetaSoundNodeHandle& NodeHandle, FName OutputName, EMetaSoundBuilderResult& OutResult);
// Returns all node outputs.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Output Handles") TArray<FMetaSoundBuilderNodeOutputHandle> FindNodeOutputs(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult);
// Returns node outputs by the given DataType (ex. "Audio", "Trigger", "String", "Bool", "Float", "Int32", etc.).
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Output Handles") TArray<FMetaSoundBuilderNodeOutputHandle> FindNodeOutputsByDataType(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult, FName DataType);
// Returns input nodes associated with a given interface.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Input Node Handles") TArray<FMetaSoundNodeHandle> FindInterfaceInputNodes(FName InterfaceName, EMetaSoundBuilderResult& OutResult);
// Returns output nodes associated with a given interface.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Output Node Handles") TArray<FMetaSoundNodeHandle> FindInterfaceOutputNodes(FName InterfaceName, EMetaSoundBuilderResult& OutResult);
// Returns input's parent node if the input is valid, otherwise returns invalid node handle.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Node Handle") FMetaSoundNodeHandle FindNodeInputParent(const FMetaSoundBuilderNodeInputHandle& InputHandle, EMetaSoundBuilderResult& OutResult);
// Returns output's parent node if the input is valid, otherwise returns invalid node handle.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Node Handle") FMetaSoundNodeHandle FindNodeOutputParent(const FMetaSoundBuilderNodeOutputHandle& OutputHandle, EMetaSoundBuilderResult& OutResult);
// Returns output's parent node if the input is valid, otherwise returns invalid node handle.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Node ClassVersion") FMetasoundFrontendVersion FindNodeClassVersion(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult);
// Returns the document's root graph class name
FMetasoundFrontendClassName GetRootGraphClassName() const;
// Returns node input's data if valid (including things like name and datatype).
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void GetNodeInputData(const FMetaSoundBuilderNodeInputHandle& InputHandle, FName& Name, FName& DataType, EMetaSoundBuilderResult& OutResult);
// Returns node input's literal value if set on graph, otherwise fails and returns default literal.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
FMetasoundFrontendLiteral GetNodeInputDefault(const FMetaSoundBuilderNodeInputHandle& InputHandle, EMetaSoundBuilderResult& OutResult);
// Returns node input's class literal value if set, otherwise fails and returns default literal.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
FMetasoundFrontendLiteral GetNodeInputClassDefault(const FMetaSoundBuilderNodeInputHandle& InputHandle, EMetaSoundBuilderResult& OutResult);
// Returns node output's data if valid (including things like name and datatype).
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void GetNodeOutputData(const FMetaSoundBuilderNodeOutputHandle& OutputHandle, FName& Name, FName& DataType, EMetaSoundBuilderResult& OutResult);
// Return the asset referenced by this preset builder. Returns nullptr if the builder is not a preset.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UObject* GetReferencedPresetAsset() const;
// Returns if a given interface is declared.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "IsDeclared") bool InterfaceIsDeclared(FName InterfaceName) const;
// Returns if a given node output and node input are connected.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "Connected") bool NodesAreConnected(const FMetaSoundBuilderNodeOutputHandle& OutputHandle, const FMetaSoundBuilderNodeInputHandle& InputHandle) const;
// Returns if a given node input has connections.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "Connected") bool NodeInputIsConnected(const FMetaSoundBuilderNodeInputHandle& InputHandle) const;
// Returns if a given node output is connected.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "Connected") bool NodeOutputIsConnected(const FMetaSoundBuilderNodeOutputHandle& OutputHandle) const;
// Returns whether this is a preset.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
bool IsPreset() const;
// Converts this preset to a fully accessible MetaSound; sets result to succeeded if it was converted successfully and failed if it was not.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void ConvertFromPreset(EMetaSoundBuilderResult& OutResult);
// Convert this builder to a MetaSound source preset with the given referenced source builder
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void ConvertToPreset(const TScriptInterface<IMetaSoundDocumentInterface>& ReferencedNodeClass, EMetaSoundBuilderResult& OutResult);
// Removes graph input if it exists; sets result to succeeded if it was removed and failed if it was not.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void RemoveGraphInput(FName Name, EMetaSoundBuilderResult& OutResult);
// Removes graph output if it exists; sets result to succeeded if it was removed and failed if it was not.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void RemoveGraphOutput(FName Name, EMetaSoundBuilderResult& OutResult);
// Removes the interface with the given name from the builder's MetaSound. Removes any graph inputs
// and outputs associated with the given interface and their respective connections (if any).
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void RemoveInterface(FName InterfaceName, EMetaSoundBuilderResult& OutResult);
// Removes node and any associated connections from the builder's MetaSound.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void RemoveNode(const FMetaSoundNodeHandle& NodeHandle, EMetaSoundBuilderResult& OutResult);
// Removes node input literal default if set, reverting the value to be whatever the node class defaults the value to.
// Returns success if value was removed, false if not removed (i.e. wasn't set to begin with).
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void RemoveNodeInputDefault(const FMetaSoundBuilderNodeInputHandle& InputHandle, EMetaSoundBuilderResult& OutResult);
// Rename the document's root graph class with a guid and optional namespace and variant
void RenameRootGraphClass(const FMetasoundFrontendClassName& InName);
#if WITH_EDITOR
// Primarily used by editor transaction stack to avoid corruption when object is
// changed outside of the builder API. Generally discouraged for direct use otherwise.
void ReloadCache();
// Sets the author of the MetaSound.
void SetAuthor(const FString& InAuthor);
#endif // WITH_EDITOR
// Sets the node's input default value (used if no connection to the given node input is present)
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void SetNodeInputDefault(const FMetaSoundBuilderNodeInputHandle& NodeInputHandle, const FMetasoundFrontendLiteral& Literal, EMetaSoundBuilderResult& OutResult);
// Sets the input node's default value, overriding the default provided by the referenced graph if the graph is a preset.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void SetGraphInputDefault(FName InputName, const FMetasoundFrontendLiteral& Literal, EMetaSoundBuilderResult& OutResult);
// Update dependency class names given a map of old to new referenced class names.
void UpdateDependencyClassNames(const TMap<FMetasoundFrontendClassName, FMetasoundFrontendClassName>& OldToNewReferencedClassNames);
virtual TScriptInterface<IMetaSoundDocumentInterface> Build(UObject* Parent, const FMetaSoundBuilderOptions& Options) const PURE_VIRTUAL(UMetaSoundBuilderBase::Build, return { }; );
// Returns the base MetaSound UClass the builder is operating on (ex. MetaSoundSource, MetaSoundPatch, etc. not to be confused with the underlying document builder's MetaSound class type)
virtual const UClass& GetBuilderUClass() const PURE_VIRTUAL(UMetaSoundBuilderBase::Build, return *UClass::StaticClass(); );
// Initializes and ensures all nodes have a position (required prior to exporting to an asset if expected to be viewed in the editor).
void InitNodeLocations();
virtual void InitFrontendBuilder();
#if WITH_EDITOR
void SetNodeLocation(const FMetaSoundNodeHandle& InNodeHandle, const FVector2D& InLocation, EMetaSoundBuilderResult& OutResult);
#endif // WITH_EDITOR
protected:
const FMetaSoundFrontendDocumentBuilder& GetConstBuilder() const
{
return Builder;
}
// Runs build, conforming the document and corresponding object data on a MetaSound UObject to that managed by this builder.
template <typename UClassType>
UClassType& BuildInternal(UObject* Parent, const FMetaSoundBuilderOptions& BuilderOptions) const
{
using namespace Metasound::Frontend;
UClassType* MetaSound = nullptr;
const FMetasoundFrontendClassName* DocClassName = nullptr;
if (BuilderOptions.ExistingMetaSound)
{
MetaSound = CastChecked<UClassType>(BuilderOptions.ExistingMetaSound.GetObject());
// Always unregister if mutating existing object. If bAddToRegistry is set to false,
// leaving registered would result in any references to this MetaSound executing on
// out-of-date data. If bAddToRegistry is set, then it needs to be unregistered before
// being registered as it is below.
if (MetaSound)
{
// If MetaSound already exists, preserve the class name to avoid
// nametable bloat & preserve potentially existing references.
const FMetasoundFrontendDocument& ExistingDoc = CastChecked<const UClassType>(MetaSound)->GetDocumentChecked();
if (!BuilderOptions.bForceUniqueClassName)
{
DocClassName = &ExistingDoc.RootGraph.Metadata.GetClassName();
}
const FNodeRegistryKey& RegistryKey = MetaSound->GetRegistryKey();
if (NodeRegistryKey::IsValid(RegistryKey))
{
if (FMetasoundFrontendRegistryContainer::Get()->IsNodeRegistered(RegistryKey))
{
MetaSound->UnregisterGraphWithFrontend();
}
}
}
}
else
{
FName ObjectName = BuilderOptions.Name;
if (!ObjectName.IsNone())
{
ObjectName = MakeUniqueObjectName(Parent, UClassType::StaticClass(), BuilderOptions.Name);
}
if (!Parent)
{
Parent = GetTransientPackage();
}
MetaSound = NewObject<UClassType>(Parent, ObjectName, RF_Public | RF_Transient);
}
checkf(MetaSound, TEXT("Failed to build MetaSound from builder '%s'"), *GetPathName());
FMetasoundFrontendDocument NewDocument = GetConstBuilder().GetDocument();
{
// This is required to ensure the newly build document has a unique class
// identifier than that of the given builder's document copy to avoid collisions
// if added to the Frontend class registry (either below or at a later point in time).
constexpr bool bResetVersion = false;
FMetaSoundFrontendDocumentBuilder::InitGraphClassMetadata(NewDocument.RootGraph.Metadata, bResetVersion, DocClassName);
}
MetaSound->SetDocument(MoveTemp(NewDocument));
MetaSound->ConformObjectDataToInterfaces();
if (BuilderOptions.bAddToRegistry)
{
MetaSound->RegisterGraphWithFrontend();
}
UE_LOG(LogMetaSound, VeryVerbose, TEXT("MetaSound '%s' built from '%s'"), *BuilderOptions.Name.ToString(), *GetFullName());
return *MetaSound;
}
// Constructs a transient UMetaSoundBuilderDocument to be acted upon by this builder instance.
UMetaSoundBuilderDocument* CreateTransientDocumentObject() const;
UPROPERTY()
FMetaSoundFrontendDocumentBuilder Builder;
// If true, builder is attached to an existing asset (directly making changes to its document). If false, builder is
// operating on a transient document which must be built to an asset prior to use by the MetaSound Frontend where it
// is then available for referencing, playback, auditioning, etc. by core execution.
UPROPERTY()
bool bIsAttached = false;
// Friending allows for swapping the builder in certain circumstances where desired (eg. attaching a builder to an existing asset)
friend class UMetaSoundBuilderSubsystem;
};
/** Builder in charge of building a MetaSound Patch */
UCLASS(Transient, BlueprintType)
class METASOUNDENGINE_API UMetaSoundPatchBuilder : public UMetaSoundBuilderBase
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (WorldContext = "Parent"))
virtual UPARAM(DisplayName = "MetaSound") TScriptInterface<IMetaSoundDocumentInterface> Build(UObject* Parent, const FMetaSoundBuilderOptions& Options) const override;
virtual const UClass& GetBuilderUClass() const override;
};
/** Builder in charge of building a MetaSound Source */
UCLASS(Transient, BlueprintType)
class METASOUNDENGINE_API UMetaSoundSourceBuilder : public UMetaSoundBuilderBase
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (WorldContext = "Parent", AdvancedDisplay = "2"))
void Audition(UObject* Parent, UAudioComponent* AudioComponent, FOnCreateAuditionGeneratorHandleDelegate OnCreateGenerator, bool bLiveUpdatesEnabled = false);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (WorldContext = "Parent"))
virtual UPARAM(DisplayName = "MetaSound") TScriptInterface<IMetaSoundDocumentInterface> Build(UObject* Parent, const FMetaSoundBuilderOptions& Options) const override;
// Returns whether or not live updates are both globally enabled (via cvar) and are enabled on this builder's last built sound, which may or may not still be playing.
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
bool GetLiveUpdatesEnabled() const;
// Sets the output audio format of the source
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
void SetFormat(EMetaSoundOutputAudioFormat OutputFormat, EMetaSoundBuilderResult& OutResult);
const Metasound::Engine::FOutputAudioFormatInfoPair* FindOutputAudioFormatInfo() const;
virtual const UClass& GetBuilderUClass() const override;
virtual void InitFrontendBuilder() override;
private:
static TOptional<Metasound::FAnyDataReference> CreateDataReference(const Metasound::FOperatorSettings& InOperatorSettings, FName DataType, const Metasound::FLiteral& InLiteral, Metasound::EDataReferenceAccessType AccessType);
void InitDelegates(Metasound::Frontend::FDocumentModifyDelegates& OutDocumentDelegates) const;
void OnEdgeAdded(int32 EdgeIndex) const;
void OnInputAdded(int32 InputIndex) const;
void OnNodeAdded(int32 NodeIndex) const;
void OnNodeInputLiteralSet(int32 NodeIndex, int32 VertexIndex, int32 LiteralIndex) const;
void OnOutputAdded(int32 OutputIndex) const;
void OnRemoveSwappingEdge(int32 SwapIndex, int32 LastIndex) const;
void OnRemovingInput(int32 InputIndex) const;
void OnRemoveSwappingNode(int32 SwapIndex, int32 LastIndex) const;
void OnRemovingNodeInputLiteral(int32 NodeIndex, int32 VertexIndex, int32 LiteralIndex) const;
void OnRemovingOutput(int32 OutputIndex) const;
TWeakObjectPtr<UMetaSoundSource> AuditionSound;
using FAuditionableTransaction = TFunctionRef<bool(Metasound::DynamicGraph::FDynamicOperatorTransactor&)>;
bool ExecuteAuditionableTransaction(FAuditionableTransaction Transaction) const;
friend class UMetaSoundBuilderSubsystem;
};
/** The subsystem in charge of tracking MetaSound builders */
UCLASS()
class METASOUNDENGINE_API UMetaSoundBuilderSubsystem : public UEngineSubsystem, public Metasound::Frontend::IMetaSoundDocumentBuilderRegistry
{
GENERATED_BODY()
private:
UPROPERTY()
TMap<FName, TObjectPtr<UMetaSoundBuilderBase>> NamedBuilders;
UPROPERTY()
mutable TMap<FName, TWeakObjectPtr<UMetaSoundBuilderBase>> AssetBuilders;
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual const Metasound::Frontend::FDocumentModifyDelegates* FindModifyDelegates(const FMetasoundFrontendClassName& InClassName) const override;
static UMetaSoundBuilderSubsystem& GetChecked();
static const UMetaSoundBuilderSubsystem& GetConstChecked();
UMetaSoundBuilderBase& AttachBuilderToAssetChecked(UObject& InMetaSound) const;
// Moving toward becoming UFUNCTIONS in the future, but right now unguarded case
// where a user could improperly reference transient assets. Also, unsafe where
// legacy controller system are potentially mutating and not notifying delegates
// appropriately.
UMetaSoundPatchBuilder* AttachPatchBuilderToAsset(UMetaSoundPatch* InPatch) const;
UMetaSoundSourceBuilder* AttachSourceBuilderToAsset(UMetaSoundSource* InSource) const;
bool DetachBuilderFromAsset(const FMetasoundFrontendClassName& InClassName) const;
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Patch Builder") UMetaSoundPatchBuilder* CreatePatchBuilder(FName BuilderName, EMetaSoundBuilderResult& OutResult);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Source Builder") UMetaSoundSourceBuilder* CreateSourceBuilder(
FName BuilderName,
FMetaSoundBuilderNodeOutputHandle& OnPlayNodeOutput,
FMetaSoundBuilderNodeInputHandle& OnFinishedNodeInput,
TArray<FMetaSoundBuilderNodeInputHandle>& AudioOutNodeInputs,
EMetaSoundBuilderResult& OutResult,
EMetaSoundOutputAudioFormat OutputFormat = EMetaSoundOutputAudioFormat::Mono,
bool bIsOneShot = true);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Patch Preset Builder") UMetaSoundPatchBuilder* CreatePatchPresetBuilder(FName BuilderName, const TScriptInterface<IMetaSoundDocumentInterface>& ReferencedPatchClass, EMetaSoundBuilderResult& OutResult);
UMetaSoundBuilderBase& CreatePresetBuilder(FName BuilderName, const TScriptInterface<IMetaSoundDocumentInterface>& ReferencedPatchClass, EMetaSoundBuilderResult& OutResult);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (ExpandEnumAsExecs = "OutResult"))
UPARAM(DisplayName = "Source Preset Builder") UMetaSoundSourceBuilder* CreateSourcePresetBuilder(FName BuilderName, const TScriptInterface<IMetaSoundDocumentInterface>& ReferencedSourceClass, EMetaSoundBuilderResult& OutResult);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (DisplayName = "Create MetaSound Bool Literal"))
UPARAM(DisplayName = "Bool Literal") FMetasoundFrontendLiteral CreateBoolMetaSoundLiteral(bool Value, FName& DataType);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (DisplayName = "Create MetaSound Bool Array Literal"))
UPARAM(DisplayName = "Bool Array Literal") FMetasoundFrontendLiteral CreateBoolArrayMetaSoundLiteral(const TArray<bool>& Value, FName& DataType);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (DisplayName = "Create MetaSound Float Literal"))
UPARAM(DisplayName = "Float Literal") FMetasoundFrontendLiteral CreateFloatMetaSoundLiteral(float Value, FName& DataType);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (DisplayName = "Create MetaSound Float Array Literal"))
UPARAM(DisplayName = "Float Array Literal") FMetasoundFrontendLiteral CreateFloatArrayMetaSoundLiteral(const TArray<float>& Value, FName& DataType);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (DisplayName = "Create MetaSound Int Literal"))
UPARAM(DisplayName = "Int32 Literal") FMetasoundFrontendLiteral CreateIntMetaSoundLiteral(int32 Value, FName& DataType);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (DisplayName = "Create MetaSound Int Array Literal"))
UPARAM(DisplayName = "Int32 Array Literal") FMetasoundFrontendLiteral CreateIntArrayMetaSoundLiteral(const TArray<int32>& Value, FName& DataType);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (DisplayName = "Create MetaSound Object Literal"))
UPARAM(DisplayName = "Object Literal") FMetasoundFrontendLiteral CreateObjectMetaSoundLiteral(UObject* Value);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (DisplayName = "Create MetaSound Object Array Literal"))
UPARAM(DisplayName = "Object Array Literal") FMetasoundFrontendLiteral CreateObjectArrayMetaSoundLiteral(const TArray<UObject*>& Value);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (DisplayName = "Create MetaSound String Literal"))
UPARAM(DisplayName = "String Literal") FMetasoundFrontendLiteral CreateStringMetaSoundLiteral(const FString& Value, FName& DataType);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (DisplayName = "Create MetaSound String Array Literal"))
UPARAM(DisplayName = "String Array Literal") FMetasoundFrontendLiteral CreateStringArrayMetaSoundLiteral(const TArray<FString>& Value, FName& DataType);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder", meta = (DisplayName = "Create MetaSound Literal From AudioParameter"))
UPARAM(DisplayName = "Param Literal") FMetasoundFrontendLiteral CreateMetaSoundLiteralFromParam(const FAudioParameter& Param);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "Builder") UMetaSoundBuilderBase* FindBuilder(FName BuilderName);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "Patch Builder") UMetaSoundPatchBuilder* FindPatchBuilder(FName BuilderName);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "Source Builder") UMetaSoundSourceBuilder* FindSourceBuilder(FName BuilderName);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "Is Registered") bool IsInterfaceRegistered(FName InInterfaceName) const;
#if WITH_EDITOR
// Updates builder data should a builder-managed asset be updated via the transaction stack.
void PostBuilderAssetTransaction(const FMetasoundFrontendClassName& InClassName);
#endif // WITH_EDITOR
// Adds builder to subsystem's registry to make it persistent and easily accessible by multiple systems or Blueprints
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
void RegisterBuilder(FName BuilderName, UMetaSoundBuilderBase* Builder);
// Adds builder to subsystem's registry to make it persistent and easily accessible by multiple systems or Blueprints
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
void RegisterPatchBuilder(FName BuilderName, UMetaSoundPatchBuilder* Builder);
// Adds builder to subsystem's registry to make it persistent and easily accessible by multiple systems or Blueprints
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
void RegisterSourceBuilder(FName BuilderName, UMetaSoundSourceBuilder* Builder);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "Unregistered") bool UnregisterBuilder(FName BuilderName);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "Unregistered") bool UnregisterPatchBuilder(FName BuilderName);
UFUNCTION(BlueprintCallable, Category = "Audio|MetaSound|Builder")
UPARAM(DisplayName = "Unregistered") bool UnregisterSourceBuilder(FName BuilderName);
private:
template <typename BuilderClass>
BuilderClass& AttachBuilderToAssetCheckedPrivate(UObject* InMetaSoundObject) const
{
check(InMetaSoundObject);
check(InMetaSoundObject->IsAsset());
TScriptInterface<IMetaSoundDocumentInterface> DocInterface = InMetaSoundObject;
const FMetasoundFrontendDocument& Document = static_cast<const IMetaSoundDocumentInterface*>(DocInterface.GetInterface())->GetDocument();
const FName FullClassName = Document.RootGraph.Metadata.GetClassName().GetFullName();
TWeakObjectPtr<UMetaSoundBuilderBase> Builder = AssetBuilders.FindRef(FullClassName);
if (Builder.IsValid())
{
return *CastChecked<BuilderClass>(Builder.Get());
}
TObjectPtr<BuilderClass> NewBuilder = NewObject<BuilderClass>(InMetaSoundObject);
check(NewBuilder);
NewBuilder->Builder = FMetaSoundFrontendDocumentBuilder(DocInterface);
NewBuilder->bIsAttached = true;
TObjectPtr<UMetaSoundBuilderBase> NewBuilderBase = CastChecked<UMetaSoundBuilderBase>(NewBuilder);
AssetBuilders.Add(FullClassName, NewBuilderBase);
return *NewBuilder;
}
};