// Copyright Epic Games, Inc. All Rights Reserved. #include "MetasoundCatMixerNode.h" #include "MetasoundChannelAgnosticType.h" #include "MetasoundExecutableOperator.h" #include "MetasoundFacade.h" #include "MetasoundNodeRegistrationMacro.h" #include "MetasoundPrimitives.h" #include "MetasoundStandardNodesCategories.h" #include "MetasoundVertex.h" #include "Algo/MaxElement.h" #include "DSP/FloatArrayMath.h" #include "TypeFamily/ChannelTypeFamily.h" #include "MetasoundCatBreakNode.h" #include "ChannelAgnostic/ChannelAgnosticTypeUtils.h" #define LOCTEXT_NAMESPACE "MetasoundStandardNodes_CatMixerNode" namespace Metasound { namespace CatMixerPrivate { METASOUND_PARAM(OutputCat, "Cat Out", "Channel Agnostic Output"); class FCatMixerOperatorData final : public TOperatorData { public: static const FLazyName OperatorDataTypeName; FCatMixerOperatorData(const FName InToMixType, const int32 InNumInputs, const EMetasoundMixerFormatChoosingMethod InFormatChosingMethod, const EMetasoundChannelMapMonoUpmixMethod InUpMixMethod, const EMetasoundCatCastingMethod InCastingMethod ) : ToMixType(InToMixType), NumInputs(InNumInputs), FormatChoosingMethod(InFormatChosingMethod), ChannelMapMonoUpmixMethod(InUpMixMethod), CatCastingMethod(InCastingMethod) {} FName ToMixType; int32 NumInputs = 1; EMetasoundMixerFormatChoosingMethod FormatChoosingMethod; EMetasoundChannelMapMonoUpmixMethod ChannelMapMonoUpmixMethod; EMetasoundCatCastingMethod CatCastingMethod; }; // Linkage. const FLazyName FCatMixerOperatorData::OperatorDataTypeName = TEXT("FCatMixerOperatorData"); const FLazyName CatInputBaseName{"In"}; const FLazyName GainInputBaseName{ "Gain" }; static FName MakeCatInputVertexName(const int32 InIndex) { FName Name = CatInputBaseName; Name.SetNumber(InIndex); return Name; } static FName MakeGainInputVertexName(const int32 InIndex) { FName Name = GainInputBaseName; Name.SetNumber(InIndex); return Name; } static FInputDataVertex MakeInputCatVertex(const int32 InIndex, const FName InName, const FString& InFriendlyName) { const FName InputName = MakeCatInputVertexName(InIndex); const FText InputDisplayName = METASOUND_LOCTEXT_FORMAT("In_DisplayName", "{0}", FText::FromString(FString::Printf(TEXT("%s %d"), *InName.ToString(), InIndex))); return TInputDataVertex{InputName, FDataVertexMetadata{FText::FromString(*InFriendlyName), InputDisplayName}}; } static FInputDataVertex MakeInputGainVertex(const int32 InIndex, const FName InName, const FString& InFriendlyName) { const FName InputName = MakeGainInputVertexName(InIndex); const FText InputDisplayName = METASOUND_LOCTEXT_FORMAT("In_DisplayName", "{0}", FText::FromString(FString::Printf(TEXT("%s %d"), *InName.ToString(), InIndex))); return TInputDataVertex{InputName, FDataVertexMetadata{ FText::FromString(*InFriendlyName), InputDisplayName }, 1.0f}; } FVertexInterface GetVertexInterface(const int32 InNumInputs) { FInputVertexInterface InputInterface; for (int32 i = 0; i < InNumInputs; i++) { InputInterface.Add(MakeInputCatVertex(i,CatInputBaseName, TEXT("Cat Input") )); InputInterface.Add(MakeInputGainVertex(i, GainInputBaseName, TEXT("Gain Input"))); } FOutputVertexInterface OutputInterface; OutputInterface.Add(TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputCat))); return FVertexInterface { MoveTemp(InputInterface), MoveTemp(OutputInterface) }; } static const Audio::FChannelTypeFamily* FindHighestInputType(const TArray& Inputs) { if (const FChannelAgnosticTypeReadRef* Found = Algo::MaxElementBy(Inputs, [](const FChannelAgnosticTypeReadRef& i) { return i->NumChannels(); }) ) { return &(*Found)->GetType(); } return nullptr; } static const Audio::FChannelTypeFamily* FindLowestInputType(const TArray& Inputs) { if (const FChannelAgnosticTypeReadRef* Found = Algo::MinElementBy(Inputs, [](const FChannelAgnosticTypeReadRef& i) { return i->NumChannels(); }) ) { return &(*Found)->GetType(); } return nullptr; } static const Audio::FChannelTypeFamily* ChooseOutputFormat( const TArray& InOutputs, const Audio::IChannelTypeRegistry& InRegistry, const FCatMixerOperatorData& InOperatorData) { using enum EMetasoundMixerFormatChoosingMethod; switch (InOperatorData.FormatChoosingMethod) { case MetasoundOutput: // TODO. case HighestInput: return FindHighestInputType(InOutputs); case LowestInput: return FindLowestInputType(InOutputs); case Custom: return InRegistry.FindChannel(InOperatorData.ToMixType); default: checkNoEntry(); } return nullptr; } static const Audio::FChannelTypeFamily* ResolveOutputFormat( const TArray& InOutputs, const Audio::IChannelTypeRegistry& InRegistry, const FCatMixerOperatorData& InOperatorData) { if (const Audio::FChannelTypeFamily* Found = ChooseOutputFormat(InOutputs, InRegistry, InOperatorData)) { return InRegistry.FindConcreteChannel(Found->GetName()); } return nullptr; } } class FCatMixerOperator final : public TExecutableOperator { public: FCatMixerOperator(TArray&& InGains, TArray&& InInputCat, FChannelAgnosticTypeWriteRef&& InOutputCat, const FOperatorSettings& InSettings, const CatMixerPrivate::FCatMixerOperatorData& InOpData) : Gains(MoveTemp(InGains)) , Inputs(MoveTemp(InInputCat)) , Outputs(MoveTemp(InOutputCat)) , Settings(InSettings) , TranscodeScratch(InSettings, Outputs->GetType().GetName()) { PrevGains.Reset(Gains.Num()); for (FFloatReadRef& Gain : Gains) { PrevGains.Add(*Gain); } for (FChannelAgnosticTypeReadRef& Input : Inputs) { Transcoders.Emplace(Input->GetType().GetTranscoder( { .ToType = Outputs->GetType(), .TranscodeMethod = static_cast(InOpData.CatCastingMethod), .MixMethod = static_cast(InOpData.CatCastingMethod) })); } } virtual ~FCatMixerOperator() override = default; static const FNodeClassMetadata& GetNodeInfo() { static const FNodeClassMetadata Metadata = CreateNodeClassMetadata ( FName(TEXT("CAT Mixer Node")), METASOUND_LOCTEXT("Metasound_CatMixerNodeDisplayName", "CAT Mixer Node"), METASOUND_LOCTEXT("Metasound_CatMixerNodeDescription", "CAT Mixer Node"), CatMixerPrivate::GetVertexInterface(1) ); return Metadata; } static TUniquePtr CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) { using namespace CatMixerPrivate; const FCatMixerOperatorData* OperatorData = CastOperatorData(InParams.Node.GetOperatorData().Get()); if (!OperatorData) { return MakeUnique(); } const Audio::FChannelTypeFamily* ConcreteToType = Audio::GetChannelRegistry().FindConcreteChannel(OperatorData->ToMixType); if (!ConcreteToType) { return MakeUnique(); } // inputs. TArray Inputs; TArray Gains; for (int32 i = 0; i < OperatorData->NumInputs; ++i) { Inputs.Emplace(InParams.InputData.GetOrCreateDefaultDataReadReference(MakeCatInputVertexName(i), InParams.OperatorSettings)); Gains.Emplace(InParams.InputData.GetOrCreateDefaultDataReadReference(MakeGainInputVertexName(i), InParams.OperatorSettings)); } const Audio::FChannelTypeFamily* Resolved = ResolveOutputFormat(Inputs,Audio::GetChannelRegistry(), *OperatorData); if (!Resolved) { return MakeUnique(); } // Make the node. return MakeUnique( MoveTemp(Gains), MoveTemp(Inputs), FChannelAgnosticTypeWriteRef::CreateNew(InParams.OperatorSettings, Resolved->GetName()), InParams.OperatorSettings, *OperatorData ); } virtual void BindInputs(FInputVertexInterfaceData& InOutVertexData) override { using namespace CatMixerPrivate; for (int32 i = 0; i < Inputs.Num(); i++) { InOutVertexData.BindReadVertex(MakeCatInputVertexName(i), Inputs[i]); InOutVertexData.BindReadVertex(MakeGainInputVertexName(i), Gains[i]); } } virtual void BindOutputs(FOutputVertexInterfaceData& InOutVertexData) override { using namespace CatMixerPrivate; InOutVertexData.BindWriteVertex(METASOUND_GET_PARAM_NAME(OutputCat), Outputs); } void Reset(const FResetParams&) { Outputs->Zero(); for (int32 i = 0; i < Inputs.Num(); i++) { PrevGains[i] = 0.f; } } static void MixInDiscreteCat(const FChannelAgnosticType& InSrc, FChannelAgnosticType& InDst, const float InStartGain, const float InEndGain) { // these are the same format now as they've been transcoded. check(InSrc.NumChannels() == InDst.NumChannels()); check(InSrc.IsA(InDst.GetType().GetName())); const int32 NumChannels = InSrc.NumChannels(); for (int32 i = 0; i < NumChannels; ++i) { TArrayView Dst = InDst.GetChannel(i); TArrayView Src = InSrc.GetChannel(i); Audio::ArrayMixIn(Src, Dst, InStartGain, InEndGain); } } void Execute() { using namespace Audio; // Always zero the output to start. Outputs->Zero(); const int32 NumFrames = Settings.GetNumFramesPerBlock(); for (int32 i = 0; i < Inputs.Num(); i++) { const FChannelAgnosticTypeReadRef& Input = Inputs[i]; const float PrevGainValue = PrevGains[i]; const float GainValue = *Gains[i]; { // Transcode channel into scratch buffer. TStackArrayOfPointers Src = MakeMultiMonoPointersFromView(Input->GetRawMultiMono(), NumFrames, Input->NumChannels()); TStackArrayOfPointers Dst = MakeMultiMonoPointersFromView(TranscodeScratch.GetRawMultiMono(), NumFrames, Outputs->NumChannels()); Transcoders[i](Src, Dst, NumFrames); } // Mix scratch buffer into results. (lerp previous gain and current to avoid zippering). MixInDiscreteCat(TranscodeScratch, *Outputs , PrevGainValue, GainValue); PrevGains[i] = GainValue; } } private: TArray Gains; TArray PrevGains; TArray Inputs; FChannelAgnosticTypeWriteRef Outputs; FOperatorSettings Settings; TArray Transcoders; FChannelAgnosticType TranscodeScratch; static FNodeClassMetadata CreateNodeClassMetadata(const FName& InOperatorName, const FText& InDisplayName, const FText& InDescription, const FVertexInterface& InDefaultInterface) { FNodeClassMetadata Metadata { FNodeClassName { "CatAudioMixer", InOperatorName, FName() }, 1, // Major Version 0, // Minor Version InDisplayName, InDescription, PluginAuthor, PluginNodeMissingPrompt, InDefaultInterface, { NodeCategories::Mix }, { METASOUND_LOCTEXT("Metasound_AudioMixerKeyword", "Mixer") }, FNodeDisplayStyle{} }; return Metadata; } }; // class FCatMixerOperator using FCatMixerNode = TNodeFacade; // register node config. METASOUND_REGISTER_NODE_AND_CONFIGURATION(FCatMixerNode, FMetaSoundCatMixingNodeConfiguration); } // namespace Metasound TInstancedStruct FMetaSoundCatMixingNodeConfiguration::OverrideDefaultInterface(const FMetasoundFrontendClass& InNodeClass) const { using namespace Metasound; using namespace CatMixerPrivate; return TInstancedStruct::Make(FMetasoundFrontendClassInterface::GenerateClassInterface(GetVertexInterface(NumInputs))); } TSharedPtr FMetaSoundCatMixingNodeConfiguration::GetOperatorData() const { using namespace Metasound::CatMixerPrivate; return MakeShared(CustomMixFormat, NumInputs, FormatChoosingMethod, ChannelMapMonoUpmixMethod, CatCastingMethod); } #undef LOCTEXT_NAMESPACE // "MetasoundStandardNodes_CatMixerNode"