// Copyright Epic Games, Inc. All Rights Reserved. #include "Internationalization/Text.h" #include "MetasoundExecutableOperator.h" #include "MetasoundNodeRegistrationMacro.h" #include "MetasoundDataTypeRegistrationMacro.h" #include "MetasoundPrimitives.h" #include "MetasoundParamHelper.h" #include "MetasoundStandardNodesNames.h" #include "MetasoundStandardNodesCategories.h" #include "MetasoundAudioBuffer.h" #include "DSP/LongDelayAPF.h" #define LOCTEXT_NAMESPACE "MetasoundStandardNodes_DiffuserNode" namespace Metasound { namespace DiffuserNode { METASOUND_PARAM(InputAudio, "Input Audio", "Incoming Audio"); METASOUND_PARAM(InputDiffusionDepth, "Depth", "1 - 5: The number of filters to use to diffuse the audio. Will not update while running."); METASOUND_PARAM(InputFeedbackGain, "Feedback", "0 - 1: the amount of feedback on each diffuser."); METASOUND_PARAM(OutputAudio, "Output Audio", "Diffuse Audio"); // APF Lengths, as a ratio of samplerate. //These were influenced by the APF lengths used in Flexiverb.cpp, except in ms instead of samples static const float APFLengthsMs[] = { 5.3125f, 1.1458f, 11.5833f, 9.1875f, 1.21f, 7.104167f }; constexpr int32 MaxNumAPFs = 6; // max number of samples the APFs will allocate for internal work buffers constexpr int32 MaxBufferLengthSamples = 240; } class METASOUNDSTANDARDNODES_API FDiffuserOperator : public TExecutableOperator { public: static const FNodeClassMetadata& GetNodeInfo() { auto InitNodeInfo = []() -> FNodeClassMetadata { FNodeClassMetadata Info; Info.ClassName = { StandardNodes::Namespace, TEXT("Diffuser"), StandardNodes::AudioVariant }; Info.MajorVersion = 1; Info.MinorVersion = 0; Info.DisplayName = METASOUND_LOCTEXT("Metasound_DiffuserDisplayName", "Diffuser"); Info.Description = METASOUND_LOCTEXT("Metasound_DiffuserNodeDescription", "Applies diffusion to incoming audio."); Info.Author = PluginAuthor; Info.PromptIfMissing = PluginNodeMissingPrompt; Info.DefaultInterface = GetVertexInterface(); Info.CategoryHierarchy = { NodeCategories::Delays }; return Info; }; static const FNodeClassMetadata Info = InitNodeInfo(); return Info; } static const FVertexInterface& GetVertexInterface() { using namespace DiffuserNode; static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(InputAudio)), TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(InputDiffusionDepth), 4), TInputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(InputFeedbackGain), 0.707f) ), FOutputVertexInterface( TOutputDataVertex(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputAudio)) ) ); return Interface; } static TUniquePtr CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) { using namespace DiffuserNode; const FInputVertexInterfaceData& InputData = InParams.InputData; FAudioBufferReadRef AudioIn = InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(InputAudio), InParams.OperatorSettings); FInt32ReadRef DepthIn = InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(InputDiffusionDepth), InParams.OperatorSettings); FFloatReadRef FeedbackIn = InputData.GetOrCreateDefaultDataReadReference(METASOUND_GET_PARAM_NAME(InputFeedbackGain), InParams.OperatorSettings); return MakeUnique(InParams.OperatorSettings, AudioIn, DepthIn, FeedbackIn); } FDiffuserOperator(const FOperatorSettings& InSettings, const FAudioBufferReadRef& InAudioInput, const FInt32ReadRef& InDepth, const FFloatReadRef& InFeedback) : AudioInput(InAudioInput) , Depth(InDepth) , Feedback(InFeedback) , AudioOutput(FAudioBufferWriteRef::CreateNew(InSettings)) { using namespace DiffuserNode; ClampedDepth = FMath::Clamp(*InDepth, 1, MaxNumAPFs); const float FeedbackGain = FMath::Clamp(*InFeedback, 0.0f, 1.0f - KINDA_SMALL_NUMBER); const float SampleRatio = InSettings.GetSampleRate() / 1000.f; Delays.Reset(ClampedDepth); for (int32 FilterIdx = 0; FilterIdx < ClampedDepth; ++FilterIdx) { // This is all because FLongDelayAPF has a TUniquePtr and can't be copied instantiated directly into an array TUniquePtr& NewDelay = GetDelayFromIndex(FilterIdx); NewDelay = MakeUnique(FeedbackGain, SampleRatio * APFLengthsMs[FilterIdx], MaxBufferLengthSamples); Delays.Add(NewDelay.Get()); } } virtual void BindInputs(FInputVertexInterfaceData& InOutVertexData) override { using namespace DiffuserNode; InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InputAudio), AudioInput); InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InputDiffusionDepth), Depth); InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InputFeedbackGain), Feedback); } virtual void BindOutputs(FOutputVertexInterfaceData& InOutVertexData) override { using namespace DiffuserNode; InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(OutputAudio), AudioOutput); } void Reset(const IOperator::FResetParams& InParams) { using namespace DiffuserNode; for (Audio::FLongDelayAPF* Delay : Delays) { Delay->Reset(); } AudioOutput->Zero(); } void Execute() { const int32 NumSamples = AudioInput->Num(); FMemory::Memcpy(AudioOutput->GetData(), AudioInput->GetData(), NumSamples * sizeof(float)); const float FeedbackClamped = FMath::Clamp(*Feedback, 0.0f, 1.0f - KINDA_SMALL_NUMBER); for (Audio::FLongDelayAPF* Delay : Delays) { Delay->SetG(FeedbackClamped); Delay->ProcessAudio(*AudioOutput); } } private: // The input audio buffer FAudioBufferReadRef AudioInput; // The amount that the wave is shaped FInt32ReadRef Depth; int32 ClampedDepth = 1; // DC offset to apply before gain FFloatReadRef Feedback; // The audio output FAudioBufferWriteRef AudioOutput; TArray Delays; // Can't hold the unique ptrs of this directly in an array because of hidden copy constructors TUniquePtr Delay_0; TUniquePtr Delay_1; TUniquePtr Delay_2; TUniquePtr Delay_3; TUniquePtr Delay_4; TUniquePtr Delay_5; FORCEINLINE TUniquePtr& GetDelayFromIndex(int32 Idx) { switch (Idx) { case 0: return Delay_0; case 1: return Delay_1; case 2: return Delay_2; case 3: return Delay_3; case 4: return Delay_4; case 5: return Delay_5; default: ensure(false); } return Delay_0; } }; using FDiffuserNode = TNodeFacade; METASOUND_REGISTER_NODE(FDiffuserNode) } #undef LOCTEXT_NAMESPACE