// Copyright Epic Games, Inc. All Rights Reserved. #include "MetasoundAudioFormats.h" #include "CoreMinimal.h" #include "MetasoundArrayNodesRegistration.h" #include "MetasoundAudioBuffer.h" #include "MetasoundDataReference.h" #include "MetasoundDataTypeRegistrationMacro.h" #include "MetasoundInputNode.h" #include "MetasoundLiteral.h" #include "MetasoundOutputNode.h" #include "MetasoundVertex.h" #define LOCTEXT_NAMESPACE "MetasoundStandardNodes_AudioFormats" namespace Metasound { /* FMultichannelAudioFormat */ FMultichannelAudioFormat::FMultichannelAudioFormat() : NumChannels(0) { } FMultichannelAudioFormat::FMultichannelAudioFormat(int32 InNumFrames, int32 InNumChannels) : NumChannels(InNumChannels) { NumChannels = FMath::Max(0, NumChannels); InNumFrames = FMath::Max(0, InNumFrames); for (int32 i = 0; i < NumChannels; i++) { FAudioBufferWriteRef Audio = FAudioBufferWriteRef::CreateNew(InNumFrames); Audio->Zero(); WritableBufferStorage.Add(Audio); ReadableBufferStorage.Add(Audio); } WritableBuffers = WritableBufferStorage; ReadableBuffers = ReadableBufferStorage; } FMultichannelAudioFormat::FMultichannelAudioFormat(const FOperatorSettings& InSettings, int32 InNumChannels) : FMultichannelAudioFormat(InSettings.GetNumFramesPerBlock(), InNumChannels) {} FMultichannelAudioFormat::FMultichannelAudioFormat(TArrayView InWriteRefs) : NumChannels(InWriteRefs.Num()) { if (NumChannels > 0) { const int32 NumFrames = InWriteRefs[0]->Num(); for (const FAudioBufferWriteRef& Ref : InWriteRefs) { checkf(NumFrames == Ref->Num(), TEXT("All buffers must have same number of frames (%d != %d)"), NumFrames, Ref->Num()); WritableBufferStorage.Add(Ref); ReadableBufferStorage.Add(Ref); } WritableBuffers = WritableBufferStorage; ReadableBuffers = ReadableBufferStorage; } } FMultichannelAudioFormat::FMultichannelAudioFormat(TArrayView InReadRefs) : NumChannels(InReadRefs.Num()) { if (NumChannels > 0) { const int32 NumFrames = InReadRefs[0]->Num(); for (const FAudioBufferReadRef& Ref : InReadRefs) { checkf(NumFrames == Ref->Num(), TEXT("All buffers must have same number of frames (%d != %d)"), NumFrames, Ref->Num()); WritableBufferStorage.Add(WriteCast(Ref)); ReadableBufferStorage.Add(Ref); } WritableBuffers = WritableBufferStorage; ReadableBuffers = ReadableBufferStorage; } } // Special vertex keys for stereo input/output nodes. namespace StereoAudioFormatVertexKeys { METASOUND_PARAM(LeftChannelVertex, "Left", "Left channel audio output.") METASOUND_PARAM(RightChannelVertex, "Right", "Right channel audio output.") } // Specialization of TOutputNode to support direct connection // of audio buffers to left/right inputs. template<> class METASOUNDSTANDARDNODES_API TOutputNode : public FNode { // FOutputOperator primarly used to report inputs and outputs. Has no execute function. class FOutputOperator : public FNoOpOperator { public: FOutputOperator(const FVertexName& InOutputName, TDataReadReference InLeft, TDataReadReference InRight, TDataReadReference InStereo) : OutputName(InOutputName) , Left(InLeft) , Right(InRight) , Stereo(InStereo) { } virtual ~FOutputOperator() {} virtual void BindInputs(FInputVertexInterfaceData& InOutVertexData) override { using namespace StereoAudioFormatVertexKeys; InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(LeftChannelVertex), Left); InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(RightChannelVertex), Right); } virtual void BindOutputs(FOutputVertexInterfaceData& InOutVertexData) override { InOutVertexData.BindReadVertex(OutputName, Stereo); } private: FVertexName OutputName; TDataReadReference Left; TDataReadReference Right; TDataReadReference Stereo; }; class FOutputOperatorFactory : public IOperatorFactory { public: FOutputOperatorFactory(const FVertexName& InOutputName) : OutputName(InOutputName) { } TUniquePtr CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) override { using namespace StereoAudioFormatVertexKeys; // Construct stereo from left and right audio buffers. TDataReadReference Left = InParams.InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(LeftChannelVertex), InParams.OperatorSettings); TDataReadReference Right = InParams.InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(RightChannelVertex), InParams.OperatorSettings); TDataReadReference Stereo = TDataReadReferenceFactory::CreateExplicitArgs(InParams.OperatorSettings, WriteCast(Left), WriteCast(Right)); return MakeUnique(OutputName, Left, Right, Stereo); } private: FVertexName OutputName; }; static FVertexInterface CreateVertexInterface(const FVertexName& InVertexName) { using namespace StereoAudioFormatVertexKeys; return FVertexInterface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(LeftChannelVertex)), TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(RightChannelVertex)) ), FOutputVertexInterface( TOutputDataVertex(InVertexName, FDataVertexMetadata{METASOUND_LOCTEXT("Metasound_StereoOutputVertexDescription", "Stereo Output.")}) ) ); } public: TOutputNode(const FVertexName& InInstanceName, const FGuid& InInstanceID, const FVertexName& InVertexName) : FNode(InInstanceName, InInstanceID, CreateNodeClassMetadata(InVertexName)) , VertexInterface(CreateVertexInterface(InVertexName)) , Factory(MakeShared(InVertexName)) { } static FNodeClassMetadata CreateNodeClassMetadata(const FVertexName& InOutputName) { FNodeClassMetadata Info; Info.ClassName = { "Output", GetMetasoundDataTypeName(), "" }; Info.MajorVersion = 1; Info.MinorVersion = 0; Info.DisplayName = METASOUND_LOCTEXT_FORMAT("Metasound_OutputNodeDisplayNameFormat", "Output {0}", GetMetasoundDataTypeDisplayText()); Info.Description = METASOUND_LOCTEXT("Metasound_OutputNodeDescription", "Output from the parent Metasound graph."); Info.Author = PluginAuthor; Info.PromptIfMissing = PluginNodeMissingPrompt; Info.DefaultInterface = CreateVertexInterface(InOutputName); Info.bDeprecated = true; return Info; }; const FVertexInterface& GetVertexInterface() const override { return VertexInterface; } bool SetVertexInterface(const FVertexInterface& InInterface) override { return VertexInterface == InInterface; } bool IsVertexInterfaceSupported(const FVertexInterface& InInterface) const { return VertexInterface == InInterface; } virtual TSharedRef GetDefaultOperatorFactory() const override { return Factory; } private: FVertexInterface VertexInterface; TSharedRef Factory; }; // Input node specialization to expose left/right audio buffers template<> class METASOUNDSTANDARDNODES_API TInputNode : public FInputNode { // Noop operator. Used to return inputs / outputs for debugging. class FInputOperator : public FNoOpOperator { public: FInputOperator(const FVertexName& InInputName, TDataReadReference InLeft, TDataReadReference InRight, TDataReadReference InStereo) : InputName(InInputName) , Left(InLeft) , Right(InRight) , Stereo(InStereo) { } virtual ~FInputOperator() {} virtual void BindInputs(FInputVertexInterfaceData& InOutVertexData) override { InOutVertexData.BindReadVertex(InputName, Stereo); } virtual void BindOutputs(FOutputVertexInterfaceData& InOutVertexData) override { using namespace StereoAudioFormatVertexKeys; InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(LeftChannelVertex), Left); InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(RightChannelVertex), Right); } private: FVertexName InputName; TDataReadReference Left; TDataReadReference Right; TDataReadReference Stereo; }; class FInputOperatorFactory : public IOperatorFactory { public: FInputOperatorFactory(const FVertexName& InInputName) : InputName(InInputName) { } TUniquePtr CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) override { // Split a stereo signal into left/right. TDataReadReference Stereo = InParams.InputData.GetOrCreateDefaultDataReadReference(InputName, InParams.OperatorSettings); TDataReadReference Left = Stereo->GetLeft(); TDataReadReference Right = Stereo->GetRight(); return MakeUnique(InputName, Left, Right, Stereo); } private: FVertexName InputName; }; static FVertexInterface CreateVertexInterface(const FVertexName& InVertexName) { using namespace StereoAudioFormatVertexKeys; return FVertexInterface( FInputVertexInterface( TInputDataVertex(InVertexName, FDataVertexMetadata{METASOUND_LOCTEXT("Metasound_StereoInputVertexDescription", "Stereo Input.")}) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(LeftChannelVertex)), TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(RightChannelVertex)) ) ); } public: static constexpr bool bCanRegister = true; TInputNode(FInputNodeConstructorParams&& InParams) : FInputNode(MoveTemp(InParams), GetMetasoundDataTypeName(), EVertexAccessType::Reference, MakeShared(InParams.VertexName)) { } explicit TInputNode(const FVertexName& InVertexName, FNodeData InNodeData, TSharedRef InClassMetadata) : FInputNode(MakeShared(InVertexName), MoveTemp(InNodeData), MoveTemp(InClassMetadata)) { } static FNodeClassMetadata CreateNodeClassMetadata(const FVertexName& InInputName) { FNodeClassMetadata Info; Info.ClassName = { "Input", GetMetasoundDataTypeName(), "" }; Info.MajorVersion = 1; Info.MinorVersion = 0; Info.DisplayName = METASOUND_LOCTEXT_FORMAT("Metasound_InputNodeDisplayNameFormat", "Input {0}", GetMetasoundDataTypeDisplayText()); Info.Description = METASOUND_LOCTEXT("Metasound_InputNodeDescription", "Input from the parent Metasound graph."); Info.Author = PluginAuthor; Info.PromptIfMissing = PluginNodeMissingPrompt; Info.DefaultInterface = CreateVertexInterface(InInputName); Info.bDeprecated = true; return Info; }; }; // TOutputNode<> specialization for FMonoAudioFormat. Allows an audio buffer // to be directly connected to an mono audio output. template<> class METASOUNDSTANDARDNODES_API TOutputNode : public FNode { class FOutputOperator : public FNoOpOperator { public: FOutputOperator(const FVertexName& InOutputName, TDataReadReference InCenter, TDataReadReference InMono) : OutputName(InOutputName) , Center(InCenter) , Mono(InMono) { } virtual ~FOutputOperator() {} virtual void BindInputs(FInputVertexInterfaceData& InOutVertexData) override { InOutVertexData.BindReadVertex(OutputName, Center); } virtual void BindOutputs(FOutputVertexInterfaceData& InOutVertexData) override { InOutVertexData.BindReadVertex(OutputName, Mono); } virtual FExecuteFunction GetExecuteFunction() override { return nullptr; } virtual FResetFunction GetResetFunction() override { return nullptr; } private: FVertexName OutputName; TDataReadReference Center; TDataReadReference Mono; }; class FOutputOperatorFactory : public IOperatorFactory { public: FOutputOperatorFactory(const FVertexName& InOutputName) : OutputName(InOutputName) { } TUniquePtr CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) override { TDataReadReference Center = InParams.InputData.GetOrCreateDefaultDataReadReference(OutputName, InParams.OperatorSettings); TDataReadReference Mono = TDataReadReferenceFactory::CreateExplicitArgs(InParams.OperatorSettings, WriteCast(Center)); return MakeUnique(OutputName, Center, Mono); } private: FVertexName OutputName; }; static FVertexInterface CreateVertexInterface(const FVertexName& InVertexName) { return FVertexInterface( FInputVertexInterface( TInputDataVertex(InVertexName, FDataVertexMetadata{METASOUND_LOCTEXT("Metasound_CenterMonoOutputVertexDescription", "Center channel audio output.")}) ), FOutputVertexInterface( TOutputDataVertex(InVertexName, FDataVertexMetadata{METASOUND_LOCTEXT("Metasound_MonoOutputVertexDescription", "Mono Output.")}) ) ); } public: TOutputNode(const FVertexName& InInstanceName, const FGuid& InInstanceID, const FVertexName& InVertexName) : FNode(InInstanceName, InInstanceID, CreateNodeClassMetadata(InVertexName)) , VertexInterface(CreateVertexInterface(InVertexName)) , Factory(MakeShared(InVertexName)) { } static FNodeClassMetadata CreateNodeClassMetadata(const FVertexName& InOutputName) { FNodeClassMetadata Info; Info.ClassName = { "Output", GetMetasoundDataTypeName(), "" }; Info.MajorVersion = 1; Info.MinorVersion = 0; Info.DisplayName = METASOUND_LOCTEXT_FORMAT("Metasound_OutputNodeDisplayNameFormat", "Output {0}", GetMetasoundDataTypeDisplayText()); Info.Description = METASOUND_LOCTEXT("Metasound_OutputNodeDescription", "Output from the parent Metasound graph."); Info.Author = PluginAuthor; Info.PromptIfMissing = PluginNodeMissingPrompt; Info.DefaultInterface = CreateVertexInterface(InOutputName); Info.bDeprecated = true; return Info; }; const FVertexInterface& GetVertexInterface() const override { return VertexInterface; } bool SetVertexInterface(const FVertexInterface& InInterface) override { return VertexInterface == InInterface; } bool IsVertexInterfaceSupported(const FVertexInterface& InInterface) const { return VertexInterface == InInterface; } TSharedRef GetDefaultOperatorFactory() const override { return Factory; } private: FVertexInterface VertexInterface; TSharedRef Factory; }; // TInputNode<> specializastion for FMonoAudioFormat. Allows an input mono audio // format to be exposed as a single buffer. template<> class METASOUNDSTANDARDNODES_API TInputNode : public FInputNode { class FInputOperator : public FNoOpOperator { public: FInputOperator(const FVertexName& InInputName, TDataReadReference InCenter, TDataReadReference InMono) : InputName(InInputName) , Center(InCenter) , Mono(InMono) { } virtual ~FInputOperator() {} virtual void BindInputs(FInputVertexInterfaceData& InOutVertexData) override { InOutVertexData.BindReadVertex(InputName, Mono); } virtual void BindOutputs(FOutputVertexInterfaceData& InOutVertexData) override { InOutVertexData.BindReadVertex(InputName, Center); } virtual FExecuteFunction GetExecuteFunction() override { return nullptr; } virtual FResetFunction GetResetFunction() override { return nullptr; } private: FVertexName InputName; TDataReadReference Center; TDataReadReference Mono; }; class FInputOperatorFactory : public IOperatorFactory { public: FInputOperatorFactory(const FVertexName& InInputName) : InputName(InInputName) { } TUniquePtr CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) override { TDataReadReference Mono = InParams.InputData.GetOrCreateDefaultDataReadReference(InputName, InParams.OperatorSettings); TDataReadReference Center = Mono->GetCenter(); return MakeUnique(InputName, Center, Mono); } private: FVertexName InputName; }; static FVertexInterface CreateVertexInterface(const FVertexName& InVertexName) { return FVertexInterface( FInputVertexInterface( TInputDataVertex(InVertexName, FDataVertexMetadata{METASOUND_LOCTEXT("Metasound_MonoInputVertexDescription", "Mono Input.")}) ), FOutputVertexInterface( TOutputDataVertex(InVertexName, FDataVertexMetadata{METASOUND_LOCTEXT("Metasound_CenterMonoInputVertexDescription", "Center channel audio output.")}) ) ); } public: static constexpr bool bCanRegister = true; TInputNode(FInputNodeConstructorParams&& InParams) : FInputNode(MoveTemp(InParams), GetMetasoundDataTypeName(), EVertexAccessType::Reference, MakeShared(InParams.VertexName)) { } explicit TInputNode(const FVertexName& InVertexName, FNodeData InNodeData, TSharedRef InClassMetadata) : FInputNode(MakeShared(InVertexName), MoveTemp(InNodeData), MoveTemp(InClassMetadata)) { } static FNodeClassMetadata CreateNodeClassMetadata(const FVertexName& InInputName) { FNodeClassMetadata Info; Info.ClassName = { "Input", GetMetasoundDataTypeName(), "" }; Info.MajorVersion = 1; Info.MinorVersion = 0; Info.DisplayName = METASOUND_LOCTEXT_FORMAT("Metasound_InputNodeDisplayNameFormat", "Input {0}", GetMetasoundDataTypeDisplayText()); Info.Description = METASOUND_LOCTEXT("Metasound_InputNodeDescription", "Input from the parent Metasound graph."); Info.Author = PluginAuthor; Info.PromptIfMissing = PluginNodeMissingPrompt; Info.DefaultInterface = CreateVertexInterface(InInputName); Info.bDeprecated = true; return Info; }; }; // Disable arrays of audio formats. template<> struct TEnableArrayNodes { static constexpr bool Value = false; }; template<> struct TEnableArrayNodes { static constexpr bool Value = false; }; // Disable transmission of audio formats template<> struct TEnableTransmissionNodeRegistration { static constexpr bool Value = false; }; template<> struct TEnableTransmissionNodeRegistration { static constexpr bool Value = false; }; // Disable auto converts using audio format constructors template struct TEnableAutoConverterNodeRegistration { static constexpr bool Value = !std::is_arithmetic::value; }; template struct TEnableAutoConverterNodeRegistration { static constexpr bool Value = !std::is_arithmetic::value; }; // Disable arrays of audio formats template<> struct TEnableAutoArrayTypeRegistration { static constexpr bool Value = false; }; template<> struct TEnableAutoArrayTypeRegistration { static constexpr bool Value = false; }; // Disable constructor inputs/outputs template<> struct TEnableConstructorVertex { static constexpr bool Value = false; }; // Disable constructor inputs/outputs template<> struct TEnableConstructorVertex { static constexpr bool Value = false; }; } // Data type registration has to happen after TInputNode<> and TOutputNode<> specializations // so that the registration macro has access to the specializations. REGISTER_METASOUND_DATATYPE(Metasound::FMonoAudioFormat, "Audio:Mono"); REGISTER_METASOUND_DATATYPE(Metasound::FStereoAudioFormat, "Audio:Stereo"); //REGISTER_METASOUND_DATATYPE(Metasound::FMultichannelAudioFormat, "Audio:Multichannel", ELiteralType::Integer); #undef LOCTEXT_NAMESPACE // MetasoundStandardNodes