// Copyright Epic Games, Inc. All Rights Reserved. #include "MetasoundEnvelopeFollowerNode.h" #include "Internationalization/Text.h" #include "MetasoundAudioBuffer.h" #include "MetasoundExecutableOperator.h" #include "MetasoundFacade.h" #include "MetasoundEnumRegistrationMacro.h" #include "MetasoundNodeRegistrationMacro.h" #include "MetasoundDataTypeRegistrationMacro.h" #include "MetasoundOperatorSettings.h" #include "MetasoundPrimitives.h" #include "MetasoundStandardNodesNames.h" #include "MetasoundTrigger.h" #include "MetasoundTime.h" #include "MetasoundVertex.h" #include "MetasoundParamHelper.h" #include "DSP/EnvelopeFollower.h" #define LOCTEXT_NAMESPACE "MetasoundStandardNodes_EnvelopeFollower" namespace Metasound { namespace EnvelopeFollower { static const TCHAR* InParamNameAudioInput = TEXT("In"); static const TCHAR* InParamNameAttackTime = TEXT("Attack Time"); static const TCHAR* InParamNameReleaseTime = TEXT("Release Time"); static const TCHAR* InParamNameFollowMode = TEXT("Peak Mode"); static const TCHAR* OutParamNameEnvelope = TEXT("Envelope"); } METASOUND_PARAM(OutputAudioEnvelope, "Audio Envelope", "The output envelope value of the audio signal (audio rate)."); enum class EEnvelopeFollowMode { MeanSquared = 0, RootMeanSquared, Peak }; DECLARE_METASOUND_ENUM(EEnvelopeFollowMode, EEnvelopeFollowMode::MeanSquared, METASOUNDSTANDARDNODES_API, FEnumEnvelopeFollowMode, FEnumEnvelopeFollowModeInfo, FEnvelopeFollowModeReadRef, FEnumEnvelopeFollowModeWriteRef); DEFINE_METASOUND_ENUM_BEGIN(EEnvelopeFollowMode, FEnumEnvelopeFollowMode, "EnvelopeFollowMode") DEFINE_METASOUND_ENUM_ENTRY(EEnvelopeFollowMode::MeanSquared, LOCTEXT("EnvelopeFollowModeMSDescription", "MS"), LOCTEXT("EnvelopeFollowModeMSDescriptionTT", "Envelope follows a running Mean Squared of the audio signal.")), DEFINE_METASOUND_ENUM_ENTRY(EEnvelopeFollowMode::RootMeanSquared, LOCTEXT("EnvelopeFollowModeRMSDescription", "RMS"), LOCTEXT("EnvelopeFollowModeRMSDescriptionTT", "Envelope follows a running Root Mean Squared of the audio signal.")), DEFINE_METASOUND_ENUM_ENTRY(EEnvelopeFollowMode::Peak, LOCTEXT("EnvelopeFollowModePeakDescription", "Peak"), LOCTEXT("EnvelopeFollowModePeakDescriptionTT", "Envelope follows the peaks in the audio signal.")), DEFINE_METASOUND_ENUM_END() class FEnvelopeFollowerOperator : public TExecutableOperator { public: static const FNodeClassMetadata& GetNodeInfo(); static const FVertexInterface& GetVertexInterface(); static TUniquePtr CreateOperator(const FCreateOperatorParams& InParams, FBuildErrorArray& OutErrors); FEnvelopeFollowerOperator(const FOperatorSettings& InSettings, const FAudioBufferReadRef& InAudioInput, const FTimeReadRef& InAttackTime, const FTimeReadRef& InReleaseTime, const FEnvelopeFollowModeReadRef& InEnvelopeMode); virtual FDataReferenceCollection GetInputs() const override; virtual FDataReferenceCollection GetOutputs() const override; void Execute(); private: // The input audio buffer FAudioBufferReadRef AudioInput; // The amount of attack time FTimeReadRef AttackTimeInput; // The amount of release time FTimeReadRef ReleaseTimeInput; // The Envelope-Following method FEnvelopeFollowModeReadRef FollowModeInput; // The envelope outputs FFloatWriteRef EnvelopeFloatOutput; FAudioBufferWriteRef EnvelopeAudioOutput; // The envelope follower DSP object Audio::FEnvelopeFollower EnvelopeFollower; double PrevAttackTime = 0.0; double PrevReleaseTime = 0.0; EEnvelopeFollowMode PrevFollowMode = EEnvelopeFollowMode::Peak; }; FEnvelopeFollowerOperator::FEnvelopeFollowerOperator(const FOperatorSettings& InSettings, const FAudioBufferReadRef& InAudioInput, const FTimeReadRef& InAttackTime, const FTimeReadRef& InReleaseTime, const FEnvelopeFollowModeReadRef& InEnvelopeMode) : AudioInput(InAudioInput) , AttackTimeInput(InAttackTime) , ReleaseTimeInput(InReleaseTime) , FollowModeInput(InEnvelopeMode) , EnvelopeFloatOutput(FFloatWriteRef::CreateNew()) , EnvelopeAudioOutput(FAudioBufferWriteRef::CreateNew(InSettings)) { PrevAttackTime = FMath::Max(FTime::ToMilliseconds(*AttackTimeInput), 0.0); PrevReleaseTime = FMath::Max(FTime::ToMilliseconds(*ReleaseTimeInput), 0.0); EnvelopeFollower.Init(InSettings.GetSampleRate(), PrevAttackTime, PrevReleaseTime); } FDataReferenceCollection FEnvelopeFollowerOperator::GetInputs() const { FDataReferenceCollection InputDataReferences; InputDataReferences.AddDataReadReference(EnvelopeFollower::InParamNameAudioInput, AudioInput); InputDataReferences.AddDataReadReference(EnvelopeFollower::InParamNameAttackTime, AttackTimeInput); InputDataReferences.AddDataReadReference(EnvelopeFollower::InParamNameReleaseTime, ReleaseTimeInput); InputDataReferences.AddDataReadReference(EnvelopeFollower::InParamNameFollowMode, FollowModeInput); return InputDataReferences; } FDataReferenceCollection FEnvelopeFollowerOperator::GetOutputs() const { FDataReferenceCollection OutputDataReferences; OutputDataReferences.AddDataReadReference(EnvelopeFollower::OutParamNameEnvelope, EnvelopeFloatOutput); OutputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(OutputAudioEnvelope), EnvelopeAudioOutput); return OutputDataReferences; } void FEnvelopeFollowerOperator::Execute() { // Check for any input changes double CurrentAttackTime = FMath::Max(FTime::ToMilliseconds(*AttackTimeInput), 0.0); if (!FMath::IsNearlyEqual(CurrentAttackTime, PrevAttackTime)) { PrevAttackTime = CurrentAttackTime; EnvelopeFollower.SetAttackTime(CurrentAttackTime); } double CurrentReleaseTime = FMath::Max(FTime::ToMilliseconds(*ReleaseTimeInput), 0.0); if (!FMath::IsNearlyEqual(CurrentReleaseTime, PrevReleaseTime)) { PrevReleaseTime = CurrentReleaseTime; EnvelopeFollower.SetReleaseTime(CurrentReleaseTime); } if (PrevFollowMode != *FollowModeInput) { PrevFollowMode = *FollowModeInput; switch (PrevFollowMode) { case EEnvelopeFollowMode::MeanSquared: default: EnvelopeFollower.SetMode(Audio::EPeakMode::Type::MeanSquared); break; case EEnvelopeFollowMode::RootMeanSquared: EnvelopeFollower.SetMode(Audio::EPeakMode::Type::RootMeanSquared); break; case EEnvelopeFollowMode::Peak: EnvelopeFollower.SetMode(Audio::EPeakMode::Type::Peak); break; } } // Process the audio through the envelope follower EnvelopeFollower.ProcessAudio(AudioInput->GetData(), EnvelopeAudioOutput->GetData(), AudioInput->Num()); *EnvelopeFloatOutput = EnvelopeFollower.GetCurrentValue(); } const FVertexInterface& FEnvelopeFollowerOperator::GetVertexInterface() { static const FVertexInterface Interface( FInputVertexInterface( TInputDataVertexModel(EnvelopeFollower::InParamNameAudioInput, LOCTEXT("AudioInputToolTT", "Audio input.")), TInputDataVertexModel(EnvelopeFollower::InParamNameAttackTime, LOCTEXT("AttackTimeTT", "The attack time of the envelope follower."), 0.01f), TInputDataVertexModel(EnvelopeFollower::InParamNameReleaseTime, LOCTEXT("ReleaseTimeTT", "The release time of the envelope follower."), 0.1f), TInputDataVertexModel(EnvelopeFollower::InParamNameFollowMode, LOCTEXT("FollowModeTT", "The following-method of the envelope follower.")) ), FOutputVertexInterface( TOutputDataVertexModel(EnvelopeFollower::OutParamNameEnvelope, LOCTEXT("EnvelopeFollowerOutputTT", "The output envelope value of the audio signal.")), TOutputDataVertexModel(METASOUND_GET_PARAM_NAME_AND_TT(OutputAudioEnvelope)) ) ); return Interface; } const FNodeClassMetadata& FEnvelopeFollowerOperator::GetNodeInfo() { auto InitNodeInfo = []() -> FNodeClassMetadata { FNodeClassMetadata Info; Info.ClassName = { StandardNodes::Namespace, TEXT("Envelope Follower"), TEXT("") }; Info.MajorVersion = 1; Info.MinorVersion = 3; Info.DisplayName = LOCTEXT("Metasound_EnvelopeFollowerDisplayName", "Envelope Follower"); Info.Description = LOCTEXT("Metasound_EnvelopeFollowerDescription", "Outputs an envelope from an input audio signal."); Info.Author = PluginAuthor; Info.PromptIfMissing = PluginNodeMissingPrompt; Info.DefaultInterface = GetVertexInterface(); return Info; }; static const FNodeClassMetadata Info = InitNodeInfo(); return Info; } TUniquePtr FEnvelopeFollowerOperator::CreateOperator(const FCreateOperatorParams& InParams, FBuildErrorArray& OutErrors) { const FEnvelopeFollowerNode& EnvelopeFollowerNode = static_cast(InParams.Node); const FDataReferenceCollection& InputCollection = InParams.InputDataReferences; const FInputVertexInterface& InputInterface = GetVertexInterface().GetInputInterface(); FAudioBufferReadRef AudioIn = InputCollection.GetDataReadReferenceOrConstruct(EnvelopeFollower::InParamNameAudioInput, InParams.OperatorSettings); FTimeReadRef AttackTime = InputCollection.GetDataReadReferenceOrConstructWithVertexDefault(InputInterface, EnvelopeFollower::InParamNameAttackTime, InParams.OperatorSettings); FTimeReadRef ReleaseTime = InputCollection.GetDataReadReferenceOrConstructWithVertexDefault(InputInterface, EnvelopeFollower::InParamNameReleaseTime, InParams.OperatorSettings); FEnvelopeFollowModeReadRef EnvelopeModeIn = InputCollection.GetDataReadReferenceOrConstruct(EnvelopeFollower::InParamNameFollowMode); return MakeUnique(InParams.OperatorSettings, AudioIn, AttackTime, ReleaseTime, EnvelopeModeIn); } FEnvelopeFollowerNode::FEnvelopeFollowerNode(const FNodeInitData& InitData) : FNodeFacade(InitData.InstanceName, InitData.InstanceID, TFacadeOperatorClass()) { } METASOUND_REGISTER_NODE(FEnvelopeFollowerNode) } #undef LOCTEXT_NAMESPACE