You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- Add missing move of transactor to generator init params #rb Phil.Popp #jira UE-181889 #rnx [CL 26128465 by Rob Gay in 5.3 branch]
1050 lines
31 KiB
C++
1050 lines
31 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
#include "MetasoundSource.h"
|
|
|
|
#include "Algo/Find.h"
|
|
#include "Algo/Transform.h"
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "AudioDeviceManager.h"
|
|
#include "IAudioParameterInterfaceRegistry.h"
|
|
#include "Interfaces/MetasoundOutputFormatInterfaces.h"
|
|
#include "Interfaces/MetasoundFrontendSourceInterface.h"
|
|
#include "Internationalization/Text.h"
|
|
#include "MetasoundAssetBase.h"
|
|
#include "MetasoundAssetManager.h"
|
|
#include "MetasoundAudioFormats.h"
|
|
#include "MetasoundBuilderSubsystem.h"
|
|
#include "MetasoundDynamicOperatorTransactor.h"
|
|
#include "MetasoundEngineAsset.h"
|
|
#include "MetasoundEngineEnvironment.h"
|
|
#include "MetasoundEnvironment.h"
|
|
#include "MetasoundFrontendController.h"
|
|
#include "MetasoundFrontendDataTypeRegistry.h"
|
|
#include "MetasoundFrontendDocumentBuilder.h"
|
|
#include "MetasoundFrontendQuery.h"
|
|
#include "MetasoundFrontendQuerySteps.h"
|
|
#include "MetasoundFrontendTransform.h"
|
|
#include "MetasoundGenerator.h"
|
|
#include "MetasoundLog.h"
|
|
#include "MetasoundOperatorBuilderSettings.h"
|
|
#include "MetasoundOperatorSettings.h"
|
|
#include "MetasoundParameterTransmitter.h"
|
|
#include "MetasoundPrimitives.h"
|
|
#include "MetasoundReceiveNode.h"
|
|
#include "MetasoundSettings.h"
|
|
#include "MetasoundTrace.h"
|
|
#include "MetasoundTrigger.h"
|
|
#include "MetasoundUObjectRegistry.h"
|
|
#include "UObject/ObjectSaveContext.h"
|
|
#include "UObject/ScriptInterface.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(MetasoundSource)
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
#include "EdGraph/EdGraph.h"
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
#define LOCTEXT_NAMESPACE "MetaSound"
|
|
|
|
namespace Metasound
|
|
{
|
|
namespace SourcePrivate
|
|
{
|
|
Frontend::FMetaSoundAssetRegistrationOptions GetInitRegistrationOptions()
|
|
{
|
|
Frontend::FMetaSoundAssetRegistrationOptions RegOptions;
|
|
RegOptions.bForceReregister = false;
|
|
if (const UMetaSoundSettings* Settings = GetDefault<UMetaSoundSettings>())
|
|
{
|
|
RegOptions.bAutoUpdateLogWarningOnDroppedConnection = Settings->bAutoUpdateLogWarningOnDroppedConnection;
|
|
}
|
|
|
|
return RegOptions;
|
|
}
|
|
|
|
class FParameterRouter
|
|
{
|
|
struct FQueueState
|
|
{
|
|
TWeakPtr<TSpscQueue<FMetaSoundParameterTransmitter::FParameter>> DataChannel;
|
|
bool bWriterAvailable = true;
|
|
};
|
|
|
|
public:
|
|
|
|
using FAudioDeviceIDAndInstanceID = TTuple<Audio::DeviceID, uint64>;
|
|
|
|
TSharedPtr<TSpscQueue<FMetaSoundParameterTransmitter::FParameter>> FindOrCreateDataChannelForReader(Audio::DeviceID InDeviceID, uint64 InstanceID)
|
|
{
|
|
constexpr bool bIsForWriter = false;
|
|
return FindOrCreateDataChannel(InDeviceID, InstanceID, bIsForWriter);
|
|
}
|
|
|
|
TSharedPtr<TSpscQueue<FMetaSoundParameterTransmitter::FParameter>> FindOrCreateDataChannelForWriter(Audio::DeviceID InDeviceID, uint64 InstanceID)
|
|
{
|
|
constexpr bool bIsForWriter = true;
|
|
return FindOrCreateDataChannel(InDeviceID, InstanceID, bIsForWriter);
|
|
}
|
|
|
|
private:
|
|
|
|
TSharedPtr<TSpscQueue<FMetaSoundParameterTransmitter::FParameter>> FindOrCreateDataChannel(Audio::DeviceID InDeviceID, uint64 InstanceID, bool bIsForWriter)
|
|
{
|
|
FScopeLock Lock(&DataChannelMapCS);
|
|
|
|
FAudioDeviceIDAndInstanceID Key = {InDeviceID, InstanceID};
|
|
const bool bIsForReader = !bIsForWriter;
|
|
|
|
if (FQueueState* State = DataChannels.Find(Key))
|
|
{
|
|
// Allow multiple readers to be returned because FMetaSoundGenerators are recreated when they come out of virtualization.
|
|
// Only allow a single writer to be returned because FMetaSoundParameterTransmitters are only created once
|
|
const bool bIsAvailable = bIsForReader || (State->bWriterAvailable && bIsForWriter);
|
|
if (bIsAvailable)
|
|
{
|
|
TSharedPtr<TSpscQueue<FMetaSoundParameterTransmitter::FParameter>> Channel = State->DataChannel.Pin();
|
|
if (Channel.IsValid())
|
|
{
|
|
if (bIsForWriter)
|
|
{
|
|
State->bWriterAvailable = false;
|
|
}
|
|
return Channel;
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<TSpscQueue<FMetaSoundParameterTransmitter::FParameter>> NewChannel = MakeShared<TSpscQueue<FMetaSoundParameterTransmitter::FParameter>>();
|
|
|
|
FQueueState NewState;
|
|
NewState.DataChannel = NewChannel;
|
|
if (bIsForWriter)
|
|
{
|
|
NewState.bWriterAvailable = false;
|
|
}
|
|
|
|
DataChannels.Add(Key, NewState);
|
|
return NewChannel;
|
|
}
|
|
|
|
FCriticalSection DataChannelMapCS;
|
|
TSortedMap<FAudioDeviceIDAndInstanceID, FQueueState> DataChannels;
|
|
};
|
|
} // namespace SourcePrivate
|
|
} // namespace Metasound
|
|
|
|
|
|
UMetaSoundSource::UMetaSoundSource(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
, FMetasoundAssetBase()
|
|
{
|
|
bProcedural = true;
|
|
bRequiresStopFade = true;
|
|
NumChannels = 1;
|
|
|
|
// todo: ensure that we have a method so that the audio engine can be authoritative over the sample rate the UMetaSoundSource runs at.
|
|
SampleRate = 48000.f;
|
|
}
|
|
|
|
const UClass& UMetaSoundSource::GetBaseMetaSoundUClass() const
|
|
{
|
|
return *UMetaSoundSource::StaticClass();
|
|
}
|
|
|
|
const FMetasoundFrontendDocument& UMetaSoundSource::GetDocument() const
|
|
{
|
|
return RootMetasoundDocument;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void UMetaSoundSource::PostEditUndo()
|
|
{
|
|
Super::PostEditUndo();
|
|
Metasound::FMetaSoundEngineAssetHelper::PostEditUndo(*this);
|
|
}
|
|
|
|
void UMetaSoundSource::PostDuplicate(EDuplicateMode::Type InDuplicateMode)
|
|
{
|
|
Super::PostDuplicate(InDuplicateMode);
|
|
|
|
// Guid is reset as asset may share implementation from
|
|
// asset duplicated from but should not be registered as such.
|
|
if (InDuplicateMode == EDuplicateMode::Normal)
|
|
{
|
|
AssetClassID = FGuid::NewGuid();
|
|
Metasound::Frontend::FRenameRootGraphClass::Generate(GetDocumentHandle(), AssetClassID);
|
|
}
|
|
}
|
|
|
|
void UMetaSoundSource::PostEditChangeProperty(FPropertyChangedEvent& InEvent)
|
|
{
|
|
Super::PostEditChangeProperty(InEvent);
|
|
|
|
if (InEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UMetaSoundSource, OutputFormat))
|
|
{
|
|
PostEditChangeOutputFormat();
|
|
}
|
|
}
|
|
|
|
void UMetaSoundSource::PostEditChangeOutputFormat()
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
UMetaSoundSourceBuilder* SourceBuilder = UMetaSoundBuilderSubsystem::GetConstChecked().AttachSourceBuilderToAsset(this);
|
|
EMetaSoundBuilderResult Result;
|
|
SourceBuilder->SetFormat(OutputFormat, Result);
|
|
if (Result == EMetaSoundBuilderResult::Succeeded)
|
|
{
|
|
// Update the data in this UMetaSoundSource to reflect what is in the metasound document.
|
|
ConformObjectDataToInterfaces();
|
|
|
|
// Use the editor form of register to ensure other editors'
|
|
// MetaSounds are auto-updated if they are referencing this graph.
|
|
if (Graph)
|
|
{
|
|
Graph->RegisterGraphWithFrontend();
|
|
}
|
|
MarkMetasoundDocumentDirty();
|
|
}
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
bool UMetaSoundSource::ConformObjectDataToInterfaces()
|
|
{
|
|
using namespace Metasound::Engine;
|
|
using namespace Metasound::Frontend;
|
|
|
|
bool bDidAlterObjectData = false;
|
|
|
|
// Update the OutputFormat and NumChannels to match the audio format interface
|
|
// on the root document.
|
|
const FOutputAudioFormatInfoMap& FormatInfo = GetOutputAudioFormatInfo();
|
|
for (const FOutputAudioFormatInfoPair& Pair : FormatInfo)
|
|
{
|
|
if (RootMetasoundDocument.Interfaces.Contains(Pair.Value.InterfaceVersion))
|
|
{
|
|
if ((OutputFormat != Pair.Key) || (NumChannels != Pair.Value.OutputVertexChannelOrder.Num()))
|
|
{
|
|
OutputFormat = Pair.Key;
|
|
NumChannels = Pair.Value.OutputVertexChannelOrder.Num();
|
|
bDidAlterObjectData = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bDidAlterObjectData;
|
|
}
|
|
|
|
void UMetaSoundSource::BeginDestroy()
|
|
{
|
|
UnregisterGraphWithFrontend();
|
|
Super::BeginDestroy();
|
|
}
|
|
|
|
void UMetaSoundSource::PreSave(FObjectPreSaveContext InSaveContext)
|
|
{
|
|
Super::PreSave(InSaveContext);
|
|
Metasound::FMetaSoundEngineAssetHelper::PreSaveAsset(*this, InSaveContext);
|
|
}
|
|
|
|
void UMetaSoundSource::Serialize(FArchive& InArchive)
|
|
{
|
|
Super::Serialize(InArchive);
|
|
Metasound::FMetaSoundEngineAssetHelper::SerializeToArchive(*this, InArchive);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void UMetaSoundSource::SetReferencedAssetClasses(TSet<Metasound::Frontend::IMetaSoundAssetManager::FAssetInfo>&& InAssetClasses)
|
|
{
|
|
Metasound::FMetaSoundEngineAssetHelper::SetReferencedAssetClasses(*this, MoveTemp(InAssetClasses));
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
TArray<FMetasoundAssetBase*> UMetaSoundSource::GetReferencedAssets()
|
|
{
|
|
return Metasound::FMetaSoundEngineAssetHelper::GetReferencedAssets(*this);
|
|
}
|
|
|
|
const TSet<FSoftObjectPath>& UMetaSoundSource::GetAsyncReferencedAssetClassPaths() const
|
|
{
|
|
return ReferenceAssetClassCache;
|
|
}
|
|
|
|
void UMetaSoundSource::OnAsyncReferencedAssetsLoaded(const TArray<FMetasoundAssetBase*>& InAsyncReferences)
|
|
{
|
|
Metasound::FMetaSoundEngineAssetHelper::OnAsyncReferencedAssetsLoaded(*this, InAsyncReferences);
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
|
|
UEdGraph* UMetaSoundSource::GetGraph()
|
|
{
|
|
return Graph;
|
|
}
|
|
|
|
const UEdGraph* UMetaSoundSource::GetGraph() const
|
|
{
|
|
return Graph;
|
|
}
|
|
|
|
UEdGraph& UMetaSoundSource::GetGraphChecked()
|
|
{
|
|
check(Graph);
|
|
return *Graph;
|
|
}
|
|
|
|
const UEdGraph& UMetaSoundSource::GetGraphChecked() const
|
|
{
|
|
check(Graph);
|
|
return *Graph;
|
|
}
|
|
|
|
FText UMetaSoundSource::GetDisplayName() const
|
|
{
|
|
FString TypeName = UMetaSoundSource::StaticClass()->GetName();
|
|
return FMetasoundAssetBase::GetDisplayName(MoveTemp(TypeName));
|
|
}
|
|
|
|
|
|
void UMetaSoundSource::SetRegistryAssetClassInfo(const Metasound::Frontend::FNodeClassInfo& InNodeInfo)
|
|
{
|
|
Metasound::FMetaSoundEngineAssetHelper::SetMetaSoundRegistryAssetClassInfo(*this, InNodeInfo);
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
void UMetaSoundSource::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
Metasound::FMetaSoundEngineAssetHelper::PostLoad(*this);
|
|
|
|
Duration = GetDuration();
|
|
bLooping = IsLooping();
|
|
}
|
|
|
|
void UMetaSoundSource::InitParameters(TArray<FAudioParameter>& ParametersToInit, FName InFeatureName)
|
|
{
|
|
using namespace Metasound::SourcePrivate;
|
|
|
|
METASOUND_LLM_SCOPE;
|
|
|
|
// Have to call register vs a simple get as the source may have yet to start playing/has not been registered
|
|
// via InitResources. If it has, this call is fast and returns the already cached RuntimeData.
|
|
RegisterGraphWithFrontend(GetInitRegistrationOptions());
|
|
const FRuntimeData& RuntimeData = GetRuntimeData();
|
|
const TArray<FMetasoundFrontendClassInput>& PublicInputs = RuntimeData.PublicInputs;
|
|
|
|
TMap<FName, FMetasoundFrontendVertex> PublicInputMap;
|
|
Algo::Transform(PublicInputs, PublicInputMap, [](const FMetasoundFrontendClassInput& Input)
|
|
{
|
|
return TPair<FName, FMetasoundFrontendVertex>(Input.Name, Input);
|
|
});
|
|
|
|
// Removes values that are not explicitly defined by the ParamType and returns
|
|
// whether or not the parameter is a valid input and should be included.
|
|
auto Sanitize = [&PublicInputMap](FAudioParameter& Parameter) -> bool
|
|
{
|
|
const FMetasoundFrontendVertex* Input = PublicInputMap.Find(Parameter.ParamName);
|
|
if (!Input)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const bool bIsMatchingType = Parameter.TypeName.IsNone() || (Parameter.TypeName == Input->TypeName);
|
|
if (!bIsMatchingType)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch (Parameter.ParamType)
|
|
{
|
|
case EAudioParameterType::Trigger:
|
|
{
|
|
Parameter = FAudioParameter(Parameter.ParamName, EAudioParameterType::Trigger);
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::Boolean:
|
|
{
|
|
Parameter = FAudioParameter(Parameter.ParamName, Parameter.BoolParam);
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::BooleanArray:
|
|
{
|
|
TArray<bool> TempArray = Parameter.ArrayBoolParam;
|
|
Parameter = FAudioParameter(Parameter.ParamName, MoveTemp(TempArray));
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::Float:
|
|
{
|
|
Parameter = FAudioParameter(Parameter.ParamName, Parameter.FloatParam);
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::FloatArray:
|
|
{
|
|
TArray<float> TempArray = Parameter.ArrayFloatParam;
|
|
Parameter = FAudioParameter(Parameter.ParamName, MoveTemp(TempArray));
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::Integer:
|
|
{
|
|
Parameter = FAudioParameter(Parameter.ParamName, Parameter.IntParam);
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::IntegerArray:
|
|
{
|
|
TArray<int32> TempArray = Parameter.ArrayIntParam;
|
|
Parameter = FAudioParameter(Parameter.ParamName, MoveTemp(TempArray));
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::Object:
|
|
{
|
|
Parameter = FAudioParameter(Parameter.ParamName, Parameter.ObjectParam);
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::ObjectArray:
|
|
{
|
|
TArray<UObject*> TempArray = Parameter.ArrayObjectParam;
|
|
Parameter = FAudioParameter(Parameter.ParamName, MoveTemp(TempArray));
|
|
}
|
|
break;
|
|
|
|
|
|
case EAudioParameterType::String:
|
|
{
|
|
Parameter = FAudioParameter(Parameter.ParamName, Parameter.StringParam);
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::StringArray:
|
|
{
|
|
TArray<FString> TempArray = Parameter.ArrayStringParam;
|
|
Parameter = FAudioParameter(Parameter.ParamName, MoveTemp(TempArray));
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::None:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
auto ConstructProxies = [this](FAudioParameter& OutParamToInit)
|
|
{
|
|
using namespace Metasound;
|
|
using namespace Metasound::Frontend;
|
|
|
|
switch (OutParamToInit.ParamType)
|
|
{
|
|
case EAudioParameterType::Object:
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
if (IDataTypeRegistry::Get().GetDataTypeInfo(OutParamToInit.ObjectParam, DataTypeInfo))
|
|
{
|
|
TSharedPtr<Audio::IProxyData> ProxyPtr = IDataTypeRegistry::Get().CreateProxyFromUObject(DataTypeInfo.DataTypeName, OutParamToInit.ObjectParam);
|
|
OutParamToInit.ObjectProxies.Emplace(MoveTemp(ProxyPtr));
|
|
|
|
// Null out param as it is no longer needed (nor desired to be accessed once passed to the Audio Thread)
|
|
OutParamToInit.ObjectParam = nullptr;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::ObjectArray:
|
|
{
|
|
for (TObjectPtr<UObject>& Object : OutParamToInit.ArrayObjectParam)
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
if (IDataTypeRegistry::Get().GetDataTypeInfo(Object, DataTypeInfo))
|
|
{
|
|
TSharedPtr<Audio::IProxyData> ProxyPtr = IDataTypeRegistry::Get().CreateProxyFromUObject(DataTypeInfo.DataTypeName, Object);
|
|
OutParamToInit.ObjectProxies.Emplace(MoveTemp(ProxyPtr));
|
|
}
|
|
}
|
|
// Reset param array as it is no longer needed (nor desired to be accessed once passed to the Audio Thread).
|
|
// All object manipulation hereafter should be done via proxies
|
|
OutParamToInit.ArrayObjectParam.Reset();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
|
|
for (int32 i = ParametersToInit.Num() - 1; i >= 0; --i)
|
|
{
|
|
FAudioParameter& Parameter = ParametersToInit[i];
|
|
|
|
#if !NO_LOGGING
|
|
// For logging in case of failure
|
|
const FString AssetName = GetName();
|
|
#endif // !NO_LOGGING
|
|
|
|
if (Sanitize(Parameter))
|
|
{
|
|
if (IsParameterValid(Parameter, PublicInputMap))
|
|
{
|
|
ConstructProxies(Parameter);
|
|
}
|
|
else
|
|
{
|
|
#if !NO_LOGGING
|
|
if (::Metasound::MetaSoundParameterEnableWarningOnIgnoredParameterCVar)
|
|
{
|
|
UE_LOG(LogMetaSound, Warning, TEXT("Failed to set invalid parameter '%s' in asset '%s': Either does not exist or is unsupported type"), *Parameter.ParamName.ToString(), *AssetName);
|
|
}
|
|
#endif // !NO_LOGGING
|
|
constexpr bool bAllowShrinking = false;
|
|
ParametersToInit.RemoveAtSwap(i, 1, bAllowShrinking);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
constexpr bool bAllowShrinking = false;
|
|
ParametersToInit.RemoveAtSwap(i, 1, bAllowShrinking);
|
|
|
|
#if !NO_LOGGING
|
|
if (::Metasound::MetaSoundParameterEnableWarningOnIgnoredParameterCVar)
|
|
{
|
|
UE_LOG(LogMetaSound, Warning, TEXT("Failed to set parameter '%s' in asset '%s': No name specified, no transmittable input found, or type mismatch."), *Parameter.ParamName.ToString(), *AssetName);
|
|
}
|
|
#endif // !NO_LOGGING
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMetaSoundSource::InitResources()
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
using namespace Metasound::SourcePrivate;
|
|
|
|
METASOUND_LLM_SCOPE;
|
|
METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(UMetaSoundSource::InitResources);
|
|
|
|
RegisterGraphWithFrontend(GetInitRegistrationOptions());
|
|
}
|
|
|
|
Metasound::Frontend::FNodeClassInfo UMetaSoundSource::GetAssetClassInfo() const
|
|
{
|
|
return { GetDocumentChecked().RootGraph, FSoftObjectPath(this) };
|
|
}
|
|
|
|
bool UMetaSoundSource::IsPlayable() const
|
|
{
|
|
// todo: cache off whether this metasound is buildable to an operator.
|
|
return true;
|
|
}
|
|
|
|
bool UMetaSoundSource::SupportsSubtitles() const
|
|
{
|
|
return Super::SupportsSubtitles();
|
|
}
|
|
|
|
float UMetaSoundSource::GetDuration() const
|
|
{
|
|
// This is an unfortunate function required by logic in determining what sounds can be potentially
|
|
// culled (in this case prematurally). MetaSound OneShots are stopped either by internally logic that
|
|
// triggers OnFinished, or if an external system requests the sound to be stopped. Setting the duration
|
|
// as a "close to" maximum length without being considered looping avoids the MetaSound from being
|
|
// culled inappropriately.
|
|
return IsOneShot() ? INDEFINITELY_LOOPING_DURATION - 1.0f : INDEFINITELY_LOOPING_DURATION;
|
|
}
|
|
|
|
bool UMetaSoundSource::ImplementsParameterInterface(Audio::FParameterInterfacePtr InInterface) const
|
|
{
|
|
const FMetasoundFrontendVersion Version { InInterface->GetName(), { InInterface->GetVersion().Major, InInterface->GetVersion().Minor } };
|
|
return GetDocumentChecked().Interfaces.Contains(Version);
|
|
}
|
|
|
|
ISoundGeneratorPtr UMetaSoundSource::CreateSoundGenerator(const FSoundGeneratorInitParams& InParams, TArray<FAudioParameter>&& InDefaultParameters)
|
|
{
|
|
using namespace Metasound;
|
|
using namespace Metasound::Frontend;
|
|
using namespace Metasound::Engine;
|
|
using namespace Metasound::SourcePrivate;
|
|
|
|
METASOUND_LLM_SCOPE;
|
|
METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(UMetaSoundSource::CreateSoundGenerator);
|
|
|
|
SampleRate = InParams.SampleRate;
|
|
FOperatorSettings InSettings = GetOperatorSettings(static_cast<FSampleRate>(SampleRate));
|
|
FMetasoundEnvironment Environment = CreateEnvironment(InParams);
|
|
FParameterRouter& Router = GetParameterRouter();
|
|
TSharedPtr<TSpscQueue<FMetaSoundParameterTransmitter::FParameter>> DataChannel = Router.FindOrCreateDataChannelForReader(InParams.AudioDeviceID, InParams.InstanceID);
|
|
|
|
TSharedPtr<const IGraph, ESPMode::ThreadSafe> MetasoundGraph = GetRuntimeData().Graph;
|
|
if (!MetasoundGraph.IsValid())
|
|
{
|
|
return ISoundGeneratorPtr(nullptr);
|
|
}
|
|
|
|
FOperatorBuilderSettings BuilderSettings = FOperatorBuilderSettings::GetDefaultSettings();
|
|
|
|
// Graph analyzer currently only enabled for preview sounds (but can theoretically be supported for all sounds)
|
|
BuilderSettings.bPopulateInternalDataReferences = InParams.bIsPreviewSound;
|
|
|
|
constexpr bool bBuildSynchronous = false;
|
|
FMetasoundGeneratorInitParams InitParams =
|
|
{
|
|
InSettings,
|
|
MoveTemp(BuilderSettings),
|
|
MetasoundGraph,
|
|
Environment,
|
|
GetName(),
|
|
GetOutputAudioChannelOrder(),
|
|
MoveTemp(InDefaultParameters),
|
|
bBuildSynchronous,
|
|
DataChannel,
|
|
DynamicTransactor
|
|
};
|
|
|
|
TSharedPtr<FMetasoundGenerator> Generator = MakeShared<FMetasoundGenerator>(MoveTemp(InitParams));
|
|
TrackGenerator(InParams.AudioComponentId, Generator);
|
|
|
|
return ISoundGeneratorPtr(Generator);
|
|
}
|
|
|
|
void UMetaSoundSource::OnEndGenerate(ISoundGeneratorPtr Generator)
|
|
{
|
|
using namespace Metasound;
|
|
ForgetGenerator(Generator);
|
|
}
|
|
|
|
bool UMetaSoundSource::GetAllDefaultParameters(TArray<FAudioParameter>& OutParameters) const
|
|
{
|
|
using namespace Metasound;
|
|
using namespace Metasound::Frontend;
|
|
using namespace Metasound::Engine;
|
|
|
|
// TODO: Make this use the cached runtime data's input copy. This call can become expensive if called repeatedly.
|
|
TArray<FMetasoundFrontendClassInput> PublicInputs = GetPublicClassInputs();
|
|
for(const FMetasoundFrontendClassInput& Input : PublicInputs)
|
|
{
|
|
FAudioParameter Params;
|
|
Params.ParamName = Input.Name;
|
|
Params.TypeName = Input.TypeName;
|
|
|
|
switch (Input.DefaultLiteral.GetType())
|
|
{
|
|
case EMetasoundFrontendLiteralType::Boolean:
|
|
{
|
|
static const FName TriggerName = "Trigger";
|
|
if (Params.TypeName == TriggerName)
|
|
{
|
|
Params.ParamType = EAudioParameterType::Trigger;
|
|
}
|
|
else
|
|
{
|
|
Params.ParamType = EAudioParameterType::Boolean;
|
|
}
|
|
|
|
ensure(Input.DefaultLiteral.TryGet(Params.BoolParam));
|
|
}
|
|
break;
|
|
|
|
case EMetasoundFrontendLiteralType::BooleanArray:
|
|
{
|
|
Params.ParamType = EAudioParameterType::BooleanArray;
|
|
ensure(Input.DefaultLiteral.TryGet(Params.ArrayBoolParam));
|
|
}
|
|
break;
|
|
|
|
case EMetasoundFrontendLiteralType::Integer:
|
|
{
|
|
Params.ParamType = EAudioParameterType::Integer;
|
|
ensure(Input.DefaultLiteral.TryGet(Params.IntParam));
|
|
}
|
|
break;
|
|
|
|
case EMetasoundFrontendLiteralType::IntegerArray:
|
|
{
|
|
Params.ParamType = EAudioParameterType::IntegerArray;
|
|
ensure(Input.DefaultLiteral.TryGet(Params.ArrayIntParam));
|
|
}
|
|
break;
|
|
|
|
case EMetasoundFrontendLiteralType::Float:
|
|
{
|
|
Params.ParamType = EAudioParameterType::Float;
|
|
ensure(Input.DefaultLiteral.TryGet(Params.FloatParam));
|
|
}
|
|
break;
|
|
|
|
case EMetasoundFrontendLiteralType::FloatArray:
|
|
{
|
|
Params.ParamType = EAudioParameterType::FloatArray;
|
|
ensure(Input.DefaultLiteral.TryGet(Params.ArrayFloatParam));
|
|
}
|
|
break;
|
|
|
|
case EMetasoundFrontendLiteralType::String:
|
|
{
|
|
Params.ParamType = EAudioParameterType::String;
|
|
ensure(Input.DefaultLiteral.TryGet(Params.StringParam));
|
|
}
|
|
break;
|
|
|
|
case EMetasoundFrontendLiteralType::StringArray:
|
|
{
|
|
Params.ParamType = EAudioParameterType::StringArray;
|
|
ensure(Input.DefaultLiteral.TryGet(Params.ArrayStringParam));
|
|
}
|
|
break;
|
|
|
|
case EMetasoundFrontendLiteralType::UObject:
|
|
{
|
|
Params.ParamType = EAudioParameterType::Object;
|
|
UObject* Object = nullptr;
|
|
ensure(Input.DefaultLiteral.TryGet(Object));
|
|
Params.ObjectParam = Object;
|
|
}
|
|
break;
|
|
|
|
case EMetasoundFrontendLiteralType::UObjectArray:
|
|
{
|
|
Params.ParamType = EAudioParameterType::ObjectArray;
|
|
ensure(Input.DefaultLiteral.TryGet(MutableView(Params.ArrayObjectParam)));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (Params.ParamType != EAudioParameterType::None)
|
|
{
|
|
OutParameters.Add(Params);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool UMetaSoundSource::IsParameterValid(const FAudioParameter& InParameter) const
|
|
{
|
|
TMap<FName, FMetasoundFrontendVertex> InputNameTypeMap;
|
|
Algo::Transform(GetDocumentChecked().RootGraph.Interface.Inputs, InputNameTypeMap, [] (const FMetasoundFrontendClassInput& Input)
|
|
{
|
|
return TPair<FName, FMetasoundFrontendVertex>(Input.Name, Input);
|
|
});
|
|
return IsParameterValid(InParameter, InputNameTypeMap);
|
|
}
|
|
|
|
bool UMetaSoundSource::IsParameterValid(const FAudioParameter& InParameter, const TMap<FName, FMetasoundFrontendVertex>& InInputNameVertexMap) const
|
|
{
|
|
using namespace Metasound;
|
|
using namespace Metasound::Frontend;
|
|
|
|
if (InParameter.ParamName.IsNone())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FMetasoundFrontendVertex* Vertex = InInputNameVertexMap.Find(InParameter.ParamName);
|
|
if (!Vertex)
|
|
{
|
|
return false;
|
|
}
|
|
const FName& TypeName = Vertex->TypeName;
|
|
|
|
bool bIsValid = false;
|
|
switch (InParameter.ParamType)
|
|
{
|
|
case EAudioParameterType::Trigger:
|
|
case EAudioParameterType::Boolean:
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
bIsValid = IDataTypeRegistry::Get().GetDataTypeInfo(TypeName, DataTypeInfo);
|
|
bIsValid &= DataTypeInfo.bIsBoolParsable;
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::BooleanArray:
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
bIsValid = IDataTypeRegistry::Get().GetDataTypeInfo(TypeName, DataTypeInfo);
|
|
bIsValid &= DataTypeInfo.bIsBoolArrayParsable;
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::Float:
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
bIsValid = IDataTypeRegistry::Get().GetDataTypeInfo(TypeName, DataTypeInfo);
|
|
bIsValid &= DataTypeInfo.bIsFloatParsable;
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::FloatArray:
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
bIsValid = IDataTypeRegistry::Get().GetDataTypeInfo(TypeName, DataTypeInfo);
|
|
bIsValid &= DataTypeInfo.bIsFloatArrayParsable;
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::Integer:
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
bIsValid = IDataTypeRegistry::Get().GetDataTypeInfo(TypeName, DataTypeInfo);
|
|
bIsValid &= DataTypeInfo.bIsIntParsable;
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::IntegerArray:
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
bIsValid = IDataTypeRegistry::Get().GetDataTypeInfo(TypeName, DataTypeInfo);
|
|
bIsValid &= DataTypeInfo.bIsIntArrayParsable;
|
|
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::Object:
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
bIsValid = IDataTypeRegistry::Get().GetDataTypeInfo(InParameter.ObjectParam, DataTypeInfo);
|
|
bIsValid &= DataTypeInfo.bIsProxyParsable || DataTypeInfo.bIsUniquePtrProxyParsable_DEPRECATED;
|
|
bIsValid &= DataTypeInfo.DataTypeName == TypeName;
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::ObjectArray:
|
|
{
|
|
bIsValid = true;
|
|
|
|
const FName ElementTypeName = CreateElementTypeNameFromArrayTypeName(TypeName);
|
|
for (UObject* Object : InParameter.ArrayObjectParam)
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
bIsValid = IDataTypeRegistry::Get().GetDataTypeInfo(Object, DataTypeInfo);
|
|
bIsValid &= DataTypeInfo.bIsProxyParsable || DataTypeInfo.bIsUniquePtrProxyParsable_DEPRECATED;
|
|
bIsValid &= DataTypeInfo.DataTypeName == ElementTypeName;
|
|
|
|
if (!bIsValid)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::String:
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
bIsValid = IDataTypeRegistry::Get().GetDataTypeInfo(TypeName, DataTypeInfo);
|
|
bIsValid &= DataTypeInfo.bIsStringParsable;
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::StringArray:
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
bIsValid = IDataTypeRegistry::Get().GetDataTypeInfo(TypeName, DataTypeInfo);
|
|
bIsValid &= DataTypeInfo.bIsStringArrayParsable;
|
|
}
|
|
break;
|
|
|
|
case EAudioParameterType::NoneArray:
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
bIsValid = IDataTypeRegistry::Get().GetDataTypeInfo(TypeName, DataTypeInfo);
|
|
bIsValid &= DataTypeInfo.bIsDefaultArrayParsable;
|
|
}
|
|
case EAudioParameterType::None:
|
|
default:
|
|
{
|
|
FDataTypeRegistryInfo DataTypeInfo;
|
|
bIsValid = IDataTypeRegistry::Get().GetDataTypeInfo(TypeName, DataTypeInfo);
|
|
bIsValid &= DataTypeInfo.bIsDefaultParsable;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return bIsValid;
|
|
}
|
|
|
|
bool UMetaSoundSource::IsLooping() const
|
|
{
|
|
return !IsOneShot();
|
|
}
|
|
|
|
bool UMetaSoundSource::IsOneShot() const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
// If the metasound source implements the one-shot interface, then it's a one-shot metasound
|
|
return IsInterfaceDeclared(SourceOneShotInterface::GetVersion());
|
|
}
|
|
|
|
TSharedPtr<Audio::IParameterTransmitter> UMetaSoundSource::CreateParameterTransmitter(Audio::FParameterTransmitterInitParams&& InParams) const
|
|
{
|
|
using namespace Metasound;
|
|
using namespace Metasound::SourcePrivate;
|
|
|
|
METASOUND_LLM_SCOPE;
|
|
|
|
const FRuntimeData& RuntimeData = GetRuntimeData();
|
|
|
|
TArray<FName> ValidParameters;
|
|
Algo::Transform(RuntimeData.TransmittableInputs, ValidParameters, [](const FMetasoundFrontendClassInput& InputVertex) { return InputVertex.Name; });
|
|
FParameterRouter& Router = GetParameterRouter();
|
|
TSharedPtr<TSpscQueue<FMetaSoundParameterTransmitter::FParameter>> DataChannel = Router.FindOrCreateDataChannelForWriter(InParams.AudioDeviceID, InParams.InstanceID);
|
|
|
|
Metasound::FMetaSoundParameterTransmitter::FInitParams InitParams(GetOperatorSettings(InParams.SampleRate), InParams.InstanceID, MoveTemp(InParams.DefaultParams), MoveTemp(ValidParameters), DataChannel);
|
|
InitParams.DebugMetaSoundName = GetFName();
|
|
|
|
return MakeShared<Metasound::FMetaSoundParameterTransmitter>(MoveTemp(InitParams));
|
|
}
|
|
|
|
Metasound::FOperatorSettings UMetaSoundSource::GetOperatorSettings(Metasound::FSampleRate InSampleRate) const
|
|
{
|
|
const float BlockRate = Metasound::Frontend::GetDefaultBlockRate();
|
|
return Metasound::FOperatorSettings(InSampleRate, BlockRate);
|
|
}
|
|
|
|
Metasound::FMetasoundEnvironment UMetaSoundSource::CreateEnvironment() const
|
|
{
|
|
using namespace Metasound;
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundEnvironment Environment;
|
|
Environment.SetValue<uint32>(SourceInterface::Environment::SoundUniqueID, GetUniqueID());
|
|
|
|
return Environment;
|
|
}
|
|
|
|
Metasound::FMetasoundEnvironment UMetaSoundSource::CreateEnvironment(const FSoundGeneratorInitParams& InParams) const
|
|
{
|
|
using namespace Metasound;
|
|
using namespace Metasound::Engine;
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundEnvironment Environment = CreateEnvironment();
|
|
Environment.SetValue<bool>(SourceInterface::Environment::IsPreview, InParams.bIsPreviewSound);
|
|
Environment.SetValue<uint64>(SourceInterface::Environment::TransmitterID, InParams.InstanceID);
|
|
Environment.SetValue<Audio::FDeviceId>(SourceInterface::Environment::DeviceID, InParams.AudioDeviceID);
|
|
Environment.SetValue<int32>(SourceInterface::Environment::AudioMixerNumOutputFrames, InParams.AudioMixerNumOutputFrames);
|
|
|
|
#if WITH_METASOUND_DEBUG_ENVIRONMENT
|
|
Environment.SetValue<FString>(SourceInterface::Environment::GraphName, GetFullName());
|
|
#endif // WITH_METASOUND_DEBUG_ENVIRONMENT
|
|
|
|
return Environment;
|
|
}
|
|
|
|
Metasound::FMetasoundEnvironment UMetaSoundSource::CreateEnvironment(const Audio::FParameterTransmitterInitParams& InParams) const
|
|
{
|
|
using namespace Metasound;
|
|
using namespace Metasound::Engine;
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundEnvironment Environment = CreateEnvironment();
|
|
Environment.SetValue<uint64>(SourceInterface::Environment::TransmitterID, InParams.InstanceID);
|
|
|
|
return Environment;
|
|
}
|
|
|
|
const TArray<Metasound::FVertexName>& UMetaSoundSource::GetOutputAudioChannelOrder() const
|
|
{
|
|
using namespace Metasound::Engine;
|
|
using namespace Metasound::Frontend;
|
|
|
|
if (const FOutputAudioFormatInfo* FormatInfo = GetOutputAudioFormatInfo().Find(OutputFormat))
|
|
{
|
|
return FormatInfo->OutputVertexChannelOrder;
|
|
}
|
|
else
|
|
{
|
|
// Unhandled audio format. Need to update audio output format vertex key map.
|
|
checkNoEntry();
|
|
static const TArray<Metasound::FVertexName> Empty;
|
|
return Empty;
|
|
}
|
|
}
|
|
|
|
void UMetaSoundSource::TrackGenerator(uint64 Id, TSharedPtr<Metasound::FMetasoundGenerator> Generator)
|
|
{
|
|
FScopeLock GeneratorMapLock(&GeneratorMapCriticalSection);
|
|
Generators.Add(Id, Generator);
|
|
OnGeneratorInstanceCreated.Broadcast(Id, Generator);
|
|
}
|
|
|
|
void UMetaSoundSource::ForgetGenerator(ISoundGeneratorPtr Generator)
|
|
{
|
|
using namespace Metasound;
|
|
FMetasoundGenerator* AsMetasoundGenerator = static_cast<FMetasoundGenerator*>(Generator.Get());
|
|
FScopeLock GeneratorMapLock(&GeneratorMapCriticalSection);
|
|
for (auto It = Generators.begin(); It != Generators.end(); ++It)
|
|
{
|
|
if ((*It).Value.HasSameObject(AsMetasoundGenerator))
|
|
{
|
|
OnGeneratorInstanceDestroyed.Broadcast((*It).Key, StaticCastSharedPtr<Metasound::FMetasoundGenerator>(Generator));
|
|
Generators.Remove((*It).Key);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
TWeakPtr<Metasound::FMetasoundGenerator> UMetaSoundSource::GetGeneratorForAudioComponent(uint64 ComponentId) const
|
|
{
|
|
using namespace Metasound;
|
|
FScopeLock GeneratorMapLock(&GeneratorMapCriticalSection);
|
|
const TWeakPtr<FMetasoundGenerator>* Result = Generators.Find(ComponentId);
|
|
if (!Result)
|
|
{
|
|
return TWeakPtr<FMetasoundGenerator>(nullptr);
|
|
}
|
|
return *Result;
|
|
}
|
|
|
|
Metasound::SourcePrivate::FParameterRouter& UMetaSoundSource::GetParameterRouter()
|
|
{
|
|
using namespace Metasound::SourcePrivate;
|
|
|
|
static FParameterRouter Router;
|
|
return Router;
|
|
}
|
|
|
|
TSharedPtr<Metasound::DynamicGraph::FDynamicOperatorTransactor> UMetaSoundSource::EnableDynamicGenerators(bool bInIsEnabled)
|
|
{
|
|
using namespace Metasound;
|
|
using namespace Metasound::DynamicGraph;
|
|
|
|
if (bInIsEnabled && (!DynamicTransactor.IsValid()))
|
|
{
|
|
TSharedPtr<IGraph> CurrentGraph = GetRuntimeData().Graph;
|
|
if (CurrentGraph.IsValid())
|
|
{
|
|
DynamicTransactor = MakeShared<FDynamicOperatorTransactor>(*CurrentGraph);
|
|
}
|
|
else
|
|
{
|
|
DynamicTransactor = MakeShared<FDynamicOperatorTransactor>();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DynamicTransactor.Reset();
|
|
}
|
|
|
|
return DynamicTransactor;
|
|
}
|
|
|
|
TSharedPtr<Metasound::DynamicGraph::FDynamicOperatorTransactor> UMetaSoundSource::GetDynamicGeneratorTransactor() const
|
|
{
|
|
return DynamicTransactor;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE // MetaSound
|