// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Templates/AlignmentTemplates.h" #include "MetasoundFrontendLiteral.h" #include "MetasoundDataReferenceMacro.h" #include "MetasoundDataTypeRegistrationMacro.h" #include "MetasoundRouter.h" #include "MetasoundFrontendDocument.h" #include "MetasoundParameterPack.generated.h" #define UE_API METASOUNDFRONTEND_API class UMetaSoundSource; // A structure that encapsulates a "parameter tag" in the raw 'bag-o-bytes' // that will be the parameter storage. It lets us walk through memory to // find a parameter with a specific name and specific type. namespace MetasoundParameterPackPrivate { struct FMetasoundParameterPackItemBase { FMetasoundParameterPackItemBase(const FName& InName, const FName& InTypeName, uint16 InNextOffset, uint16 InValueOffset) : Name(InName) , TypeName(InTypeName) , NextHeaderOffset(InNextOffset) , ValueOffset(InValueOffset) {} FMetasoundParameterPackItemBase(const FMetasoundParameterPackItemBase& Other) = default; void* GetPayload() { uint8* ptr = reinterpret_cast(this); return ptr + ValueOffset; } FName Name; FName TypeName; uint16 NextHeaderOffset; uint16 ValueOffset; }; template struct TRootValueTypeHelper { using Type = T; }; template struct TRootValueTypeHelper { using Type = Metasound::TParamPackFixedArray; }; template struct TRootValueType : TRootValueTypeHelper, T, MaxNumItems> {}; // A template that lets us stick an arbitrary data type into the raw // 'bag-o-bytes' that is the parameter storage. Uses the base class // above. template struct FMetasoundParameterPackItem : public FMetasoundParameterPackItemBase { using ThisType = FMetasoundParameterPackItem; FMetasoundParameterPackItem(const FName& InName, const FName& InTypeName, const T& InValue) : FMetasoundParameterPackItemBase(InName, InTypeName, SizeOfParam(InValue), offsetof(FMetasoundParameterPackItem, Value)) , Value(InValue) { if constexpr (TIsTArray_V) { check(InValue.Num() <= MaxNumItems); } } FMetasoundParameterPackItem(const FMetasoundParameterPackItem& Other) = default; typename TRootValueType::Type Value; static uint16 SizeOfParam(const T& InValue) { return AlignArbitrary((uint16)sizeof(ThisType), alignof(std::max_align_t)); } }; } // This next class owns the 'bag-o-bytes' that holds all of the parameters. struct FMetasoundParameterPackStorage { TArray Storage; int32 StorageBytesInUse = 0; FMetasoundParameterPackStorage() = default; FMetasoundParameterPackStorage(const FMetasoundParameterPackStorage& Other) : Storage(Other.Storage) , StorageBytesInUse(Other.StorageBytesInUse) {} FMetasoundParameterPackStorage(FMetasoundParameterPackStorage&& Other) : Storage(MoveTemp(Other.Storage)) , StorageBytesInUse(Other.StorageBytesInUse) { Other.StorageBytesInUse = 0; } struct ParameterIterator { uint8* CurrentNode; uint8* EndAddress; ParameterIterator(uint8* Start, uint8* End) : CurrentNode(Start) , EndAddress(End) {} ParameterIterator& operator++() { using namespace MetasoundParameterPackPrivate; if (!CurrentNode) { return *this; } MetasoundParameterPackPrivate::FMetasoundParameterPackItemBase * ParamAddress = reinterpret_cast(CurrentNode); CurrentNode += ParamAddress->NextHeaderOffset; if (CurrentNode >= EndAddress) { CurrentNode = nullptr; } return *this; } ParameterIterator operator++(int) { ParameterIterator ReturnValue = *this; ++*this; return ReturnValue; } bool operator==(const ParameterIterator& Other) const { return CurrentNode == Other.CurrentNode; } bool operator!=(const ParameterIterator& Other) const { return CurrentNode != Other.CurrentNode; } MetasoundParameterPackPrivate::FMetasoundParameterPackItemBase* operator->() { using namespace MetasoundParameterPackPrivate; return reinterpret_cast(CurrentNode); } MetasoundParameterPackPrivate::FMetasoundParameterPackItemBase* GetParameterBase() { using namespace MetasoundParameterPackPrivate; return reinterpret_cast(CurrentNode); } }; ParameterIterator begin() { if (Storage.IsEmpty()) { return ParameterIterator(nullptr, nullptr); } return ParameterIterator(&Storage[0], &Storage[0] + StorageBytesInUse); } ParameterIterator end() { return ParameterIterator(nullptr, nullptr); } template MetasoundParameterPackPrivate::FMetasoundParameterPackItem* FindParameter(const FName& ParamName, const FName& InTypeName) { using namespace MetasoundParameterPackPrivate; if (Storage.IsEmpty()) { return nullptr; } FMetasoundParameterPackItemBase* ParamWalker = reinterpret_cast(&Storage[0]); int32 StorageOffset = 0; while (ParamWalker) { if (ParamWalker->Name == ParamName) { if (ParamWalker->TypeName == InTypeName) { return static_cast*>(ParamWalker); } return nullptr; } StorageOffset += ParamWalker->NextHeaderOffset; if (StorageOffset < StorageBytesInUse) { ParamWalker = reinterpret_cast(&Storage[StorageOffset]); } else { break; } } return nullptr; } // non-class version template std::enable_if_t,T*> AddParameter(const FName& Name, const FName& InTypeName, const T& InValue) { using namespace MetasoundParameterPackPrivate; int32 NextParamLocation = AlignArbitrary(StorageBytesInUse, alignof(std::max_align_t)); int32 TotalStorageNeeded = NextParamLocation + FMetasoundParameterPackItem::SizeOfParam(InValue); if (TotalStorageNeeded > Storage.Num()) { Storage.AddUninitialized(TotalStorageNeeded - Storage.Num()); } FMetasoundParameterPackItem* Destination = new (reinterpret_cast*>(&Storage[NextParamLocation])) FMetasoundParameterPackItem(Name, InTypeName, InValue); StorageBytesInUse = NextParamLocation + Destination->NextHeaderOffset; return &Destination->Value; } // class version template std::enable_if_t && !TIsTArray_V, T*> AddParameter(const FName& Name, const FName& InTypeName, const T& InValue) { using namespace MetasoundParameterPackPrivate; int32 NextParamLocation = AlignArbitrary(StorageBytesInUse, alignof(std::max_align_t)); int32 TotalStorageNeeded = NextParamLocation + FMetasoundParameterPackItem::SizeOfParam(InValue); if (TotalStorageNeeded > Storage.Num()) { Storage.AddUninitialized(TotalStorageNeeded - Storage.Num()); } FMetasoundParameterPackItem* Destination = new (reinterpret_cast*>(&Storage[NextParamLocation])) FMetasoundParameterPackItem(Name, InTypeName, InValue); StorageBytesInUse = NextParamLocation + Destination->NextHeaderOffset; return &Destination->Value; } // array version template std::enable_if_t && TIsTArray_V, void> AddArrayParameter(const FName& Name, const FName& InTypeName, const T& InValue) { using namespace MetasoundParameterPackPrivate; check(InValue.Num() <= MaxNumItems); int32 NextParamLocation = AlignArbitrary(StorageBytesInUse, alignof(std::max_align_t)); int32 TotalStorageNeeded = NextParamLocation + FMetasoundParameterPackItem::SizeOfParam(InValue); if (TotalStorageNeeded > Storage.Num()) { Storage.AddUninitialized(TotalStorageNeeded - Storage.Num()); } FMetasoundParameterPackItem* Destination = new (reinterpret_cast*>(&Storage[NextParamLocation])) FMetasoundParameterPackItem(Name, InTypeName, InValue); StorageBytesInUse = NextParamLocation + Destination->NextHeaderOffset; } }; using FSharedMetasoundParameterStoragePtr = TSharedPtr; UENUM(BlueprintType) enum class ESetParamResult : uint8 { Succeeded, Failed }; // Here is the UObject BlueprintType that can be used in c++ and blueprint code. It holds a FMetasoundParamStorage // instance and can pass it along to the audio system's SetObjectParameter function via an AudioProxy. UCLASS(MinimalAPI, BlueprintType,meta = (DisplayName = "MetaSoundParameterPack")) class UMetasoundParameterPack : public UObject, public IAudioProxyDataFactory { GENERATED_BODY() public: UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack") static UE_API UMetasoundParameterPack* MakeMetasoundParameterPack(); UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = ReturnValue)) UE_API ESetParamResult SetBool(FName ParameterName, bool InValue, bool OnlyIfExists = true); UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = ReturnValue)) UE_API ESetParamResult SetInt(FName ParameterName, int32 InValue, bool OnlyIfExists = true); UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = ReturnValue)) UE_API ESetParamResult SetFloat(FName ParameterName, float InValue, bool OnlyIfExists = true); UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = ReturnValue)) UE_API ESetParamResult SetString(FName ParameterName, const FString& InValue, bool OnlyIfExists = true); UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = ReturnValue)) UE_API ESetParamResult SetTrigger(FName ParameterName, bool OnlyIfExists = true); UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = "Result")) UE_API bool GetBool(FName ParameterName, ESetParamResult& Result) const; UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = "Result")) UE_API int32 GetInt(FName ParameterName, ESetParamResult& Result) const; UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = "Result")) UE_API float GetFloat(FName ParameterName, ESetParamResult& Result) const; UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = "Result")) UE_API FString GetString(FName ParameterName, ESetParamResult& Result) const; UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = "Result")) UE_API bool GetTrigger(FName ParameterName, ESetParamResult& Result) const; UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = ReturnValue)) UE_API bool HasBool(FName ParameterName) const; UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = ReturnValue)) UE_API bool HasInt(FName ParameterName) const; UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = ReturnValue)) UE_API bool HasFloat(FName ParameterName) const; UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = ReturnValue)) UE_API bool HasString(FName ParameterName) const; UFUNCTION(BlueprintCallable, Category = "MetaSoundParameterPack", meta = (ExpandEnumAsExecs = ReturnValue)) UE_API bool HasTrigger(FName ParameterName) const; UE_API void AddBoolParameter(FName Name, bool InValue); UE_API void AddIntParameter(FName Name, int32 InValue); UE_API void AddFloatParameter(FName Name, float InValue); UE_API void AddStringParameter(FName Name, const FString& InValue); UE_API void AddTriggerParameter(FName Name, bool InValue = true); // YIKES! BEWARE: If the returned pointers are only valid until another parameter is added! UE_API bool* GetBoolParameterPtr(FName Name) const; UE_API int32* GetIntParameterPtr(FName Name) const; UE_API float* GetFloatParameterPtr(FName Name) const; UE_API FString* GetStringParameterPtr(FName Name) const; UE_API bool* GetTriggerParameterPtr(FName Name) const; template ESetParamResult SetParameter(const FName& Name, const FName& InTypeName, const T& InValue, bool OnlyIfExists = true) { typename MetasoundParameterPackPrivate::TRootValueType::Type* TheParam = GetParameterPtr(Name, InTypeName); if (TheParam) { *TheParam = InValue; return ESetParamResult::Succeeded; } else if (!OnlyIfExists) { // TODO Add test to see if we are allowed to create it! AddParameter(Name, InTypeName, InValue); return ESetParamResult::Succeeded; } return ESetParamResult::Failed; } template ESetParamResult SetArrayParameter(const FName& Name, const FName& InTypeName, const T& InValue, bool OnlyIfExists = true) { static_assert(TIsTArray_V); if (!ensureMsgf(InValue.Num() <= MaxNumItems, TEXT("Too many items in source array \"%s\"! Max is %d."), *Name.ToString(), MaxNumItems)) { return ESetParamResult::Failed; } typename MetasoundParameterPackPrivate::TRootValueType::Type* TheParam = GetParameterPtr(Name, InTypeName); if (TheParam) { *TheParam = InValue; return ESetParamResult::Succeeded; } else if (!OnlyIfExists) { // TODO Add test to see if we are allowed to create it! AddArrayParameter(Name, InTypeName, InValue); return ESetParamResult::Succeeded; } return ESetParamResult::Failed; } template bool HasParameter(const FName& Name, const FName& InTypeName) const { if (!ParameterStorage.IsValid()) { return false; } return ParameterStorage->FindParameter(Name, InTypeName) != nullptr; } template void AddParameter(const FName& Name, const FName& InTypeName, const T& InValue) { if (!ParameterStorage.IsValid()) { ParameterStorage = MakeShared(); } ParameterStorage->AddParameter(Name, InTypeName, InValue); } template void AddArrayParameter(const FName& Name, const FName& InTypeName, const T& InValue) { if (!ParameterStorage.IsValid()) { ParameterStorage = MakeShared(); } ParameterStorage->AddArrayParameter(Name, InTypeName, InValue); } template T GetParameter(const FName& Name, const FName& InTypeName, ESetParamResult& Result) const { T* TheParameter = GetParameterPtr(Name, InTypeName); if (!TheParameter) { Result = ESetParamResult::Failed; return T(); } Result = ESetParamResult::Succeeded; return *TheParameter; } template void GetParameter(const FName& Name, const FName& InTypeName, TArray& ResultArray, ESetParamResult& Result) const { typename MetasoundParameterPackPrivate::TRootValueType::Type* TheParameter = GetParameterPtr(Name, InTypeName); if (!TheParameter) { Result = ESetParamResult::Failed; return; } TheParameter->CopyToArray(ResultArray); Result = ESetParamResult::Succeeded; } UE_API TSharedPtr CreateProxyData(const Audio::FProxyDataInitParams& InitParams) override; // A couple of utilities for use by MetasoundAssetBase and MetasoundGenerator to set // up the routing for parameter packs UE_DEPRECATED(5.3, "FSendAddress are no longer used to communicate with MetaSound instances.") static UE_API Metasound::FSendAddress CreateSendAddressFromEnvironment(const Metasound::FMetasoundEnvironment& InEnvironment); static UE_API FMetasoundFrontendClassInput GetClassInput(); UE_API FSharedMetasoundParameterStoragePtr GetParameterStorage() const; UE_API FSharedMetasoundParameterStoragePtr GetCopyOfParameterStorage() const; private: template typename MetasoundParameterPackPrivate::TRootValueType::Type* GetParameterPtr(const FName& Name, const FName& InTypeName) const { if (!ParameterStorage.IsValid()) { return nullptr; } auto TheParameter = ParameterStorage->FindParameter(Name, InTypeName); if (!TheParameter) { return nullptr; } return &TheParameter->Value; } FSharedMetasoundParameterStoragePtr ParameterStorage; }; // Here is the proxy that UMetasoundParameterPack creates when asked for a proxy by the audio system class FMetasoundParameterPackProxy : public Audio::TProxyData { public: IMPL_AUDIOPROXY_CLASS(FMetasoundParameterPackProxy); explicit FMetasoundParameterPackProxy(FSharedMetasoundParameterStoragePtr& Data) : ParameterStoragePtr(Data) {} FSharedMetasoundParameterStoragePtr GetParamStorage() { return ParameterStoragePtr; } private: FSharedMetasoundParameterStoragePtr ParameterStoragePtr; }; // And finally... A type we can register with Metasound that holds a TSharedPtr to an FMetasoundParamStorage instance. // It can be created by various systems in Metasound given a proxy. class FMetasoundParameterStorageWrapper { FSharedMetasoundParameterStoragePtr ParameterStoragePtr; public: FMetasoundParameterStorageWrapper() = default; FMetasoundParameterStorageWrapper(const FMetasoundParameterStorageWrapper&) = default; FMetasoundParameterStorageWrapper& operator=(const FMetasoundParameterStorageWrapper& Other) = default; FMetasoundParameterStorageWrapper(const TSharedPtr& InInitData) { if (InInitData.IsValid()) { if (InInitData->CheckTypeCast()) { // should we be getting handed a SharedPtr here? FMetasoundParameterPackProxy& AsParameterPack = InInitData->GetAs(); ParameterStoragePtr = AsParameterPack.GetParamStorage(); } } } FSharedMetasoundParameterStoragePtr Get() const { return ParameterStoragePtr; } bool IsPackValid() const { return ParameterStoragePtr.IsValid(); } const FSharedMetasoundParameterStoragePtr& GetPackProxy() const { return ParameterStoragePtr; } const FMetasoundParameterPackStorage* operator->() const { return ParameterStoragePtr.Get(); } FMetasoundParameterPackStorage* operator->() { return ParameterStoragePtr.Get(); } friend FORCEINLINE uint32 GetTypeHash(const FMetasoundParameterStorageWrapper& InParameterStorageWrapper) { if (InParameterStorageWrapper.IsPackValid()) { return GetTypeHash(InParameterStorageWrapper->Storage); } return INDEX_NONE; } }; DECLARE_METASOUND_DATA_REFERENCE_TYPES(FMetasoundParameterStorageWrapper, METASOUNDFRONTEND_API, FMetasoundParameterStorageWrapperTypeInfo, FMetasoundParameterStorageWrapperReadRef, FMetasoundParameterStorageWrapperWriteRef) #undef UE_API