2021-08-11 15:03:33 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "Internationalization/Text.h"
# include "MetasoundExecutableOperator.h"
# include "MetasoundEnumRegistrationMacro.h"
# include "MetasoundNodeRegistrationMacro.h"
# include "MetasoundPrimitives.h"
# include "MetasoundStandardNodesNames.h"
# include "MetasoundAudioBuffer.h"
# include "MetasoundStandardNodesCategories.h"
# include "MetasoundFacade.h"
# include "MetasoundParamHelper.h"
# include "DSP/Dsp.h"
# include "DSP/DynamicsProcessor.h"
# define LOCTEXT_NAMESPACE "MetasoundStandardNodes_LimiterNode"
namespace Metasound
{
/* Mid-Side Encoder */
namespace LimiterVertexNames
{
METASOUND_PARAM ( InputAudio , " Audio " , " Incoming audio signal to compress. " ) ;
2022-10-19 22:39:29 -04:00
METASOUND_PARAM ( InputInGainDb , " Input Gain dB " , " Gain to apply to the input before limiting, in decibels. Maximum 100 dB. " ) ;
2021-08-11 15:03:33 -04:00
METASOUND_PARAM ( InputThresholdDb , " Threshold dB " , " Amplitude threshold above which gain will be reduced. " ) ;
METASOUND_PARAM ( InputReleaseTime , " Release Time " , " How long it takes for audio below the threshold to return to its original volume level. " ) ;
METASOUND_PARAM ( InputKneeMode , " Knee " , " Whether the limiter uses a hard or soft knee. " ) ;
METASOUND_PARAM ( OutputAudio , " Audio " , " The output audio signal. " ) ;
}
enum class EKneeMode
{
Hard = 0 ,
Soft ,
} ;
DECLARE_METASOUND_ENUM ( EKneeMode , EKneeMode : : Hard , METASOUNDSTANDARDNODES_API ,
FEnumKneeMode , FEnumKneeModeInfo , FKneeModeReadRef , FEnumKneeModeWriteRef ) ;
DEFINE_METASOUND_ENUM_BEGIN ( EKneeMode , FEnumKneeMode , " KneeMode " )
2022-02-10 18:36:47 -05:00
DEFINE_METASOUND_ENUM_ENTRY ( EKneeMode : : Hard , " KneeModeHardDescription " , " Hard " , " KneeModeHardDescriptionTT " , " Only audio strictly above the threshold is affected by the limiter. " ) ,
DEFINE_METASOUND_ENUM_ENTRY ( EKneeMode : : Soft , " KneeModeSoftDescription " , " Soft " , " KneeModeSoftDescriptionTT " , " Limiter activates more smoothly near the threshold. " ) ,
2021-08-11 15:03:33 -04:00
DEFINE_METASOUND_ENUM_END ( )
// Operator Class
class FLimiterOperator : public TExecutableOperator < FLimiterOperator >
{
public :
static constexpr float HardKneeBandwitdh = 0.0f ;
static constexpr float SoftKneeBandwitdh = 10.0f ;
2022-10-19 22:39:29 -04:00
static constexpr float MaxInputGain = 100.0f ;
2021-08-11 15:03:33 -04:00
2023-03-02 14:40:35 -05:00
FLimiterOperator ( const FCreateOperatorParams & InParams ,
2021-08-11 15:03:33 -04:00
const FAudioBufferReadRef & InAudio ,
const FFloatReadRef & InGainDb ,
const FFloatReadRef & InThresholdDb ,
const FTimeReadRef & InReleaseTime ,
const FKneeModeReadRef & InKneeMode )
: AudioInput ( InAudio )
, InGainDbInput ( InGainDb )
, ThresholdDbInput ( InThresholdDb )
, ReleaseTimeInput ( InReleaseTime )
, KneeModeInput ( InKneeMode )
2023-03-02 14:40:35 -05:00
, AudioOutput ( FAudioBufferWriteRef : : CreateNew ( InParams . OperatorSettings ) )
2021-08-11 15:03:33 -04:00
, Limiter ( )
, PrevInGainDb ( * InGainDb )
, PrevThresholdDb ( * InThresholdDb )
, PrevReleaseTime ( FMath : : Max ( FTime : : ToMilliseconds ( * InReleaseTime ) , 0.0 ) )
{
2023-03-02 14:40:35 -05:00
Reset ( InParams ) ;
2021-08-11 15:03:33 -04:00
}
static const FNodeClassMetadata & GetNodeInfo ( )
{
auto CreateNodeClassMetadata = [ ] ( ) - > FNodeClassMetadata
{
FVertexInterface NodeInterface = DeclareVertexInterface ( ) ;
FNodeClassMetadata Metadata
{
FNodeClassName { StandardNodes : : Namespace , TEXT ( " Limiter " ) , StandardNodes : : AudioVariant } ,
1 , // Major Version
0 , // Minor Version
2022-02-10 18:36:47 -05:00
METASOUND_LOCTEXT ( " LimiterDisplayName " , " Limiter " ) ,
METASOUND_LOCTEXT ( " LimiterDesc " , " Prevents a signal from going above a given threshold. " ) ,
2021-08-11 15:03:33 -04:00
PluginAuthor ,
PluginNodeMissingPrompt ,
NodeInterface ,
{ NodeCategories : : Dynamics } ,
{ } ,
FNodeDisplayStyle { }
} ;
return Metadata ;
} ;
static const FNodeClassMetadata Metadata = CreateNodeClassMetadata ( ) ;
return Metadata ;
}
static const FVertexInterface & DeclareVertexInterface ( )
{
using namespace LimiterVertexNames ;
static const FVertexInterface Interface (
FInputVertexInterface (
2022-03-31 16:49:59 -04:00
TInputDataVertex < FAudioBuffer > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( InputAudio ) ) ,
TInputDataVertex < float > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( InputInGainDb ) , 0.0f ) ,
TInputDataVertex < float > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( InputThresholdDb ) , 0.0f ) ,
TInputDataVertex < FTime > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( InputReleaseTime ) , 0.1f ) ,
2023-05-25 18:13:32 -04:00
TInputDataVertex < FEnumKneeMode > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( InputKneeMode ) , ( int32 ) EKneeMode : : Hard )
2021-08-11 15:03:33 -04:00
) ,
FOutputVertexInterface (
2022-03-31 16:49:59 -04:00
TOutputDataVertex < FAudioBuffer > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( OutputAudio ) )
2021-08-11 15:03:33 -04:00
)
) ;
return Interface ;
}
2023-05-22 13:28:27 -04:00
virtual void BindInputs ( FInputVertexInterfaceData & InOutVertexData ) override
2021-08-11 15:03:33 -04:00
{
using namespace LimiterVertexNames ;
2023-05-22 13:28:27 -04:00
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( InputAudio ) , AudioInput ) ;
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( InputInGainDb ) , InGainDbInput ) ;
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( InputThresholdDb ) , ThresholdDbInput ) ;
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( InputReleaseTime ) , ReleaseTimeInput ) ;
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( InputKneeMode ) , KneeModeInput ) ;
}
2021-08-11 15:03:33 -04:00
2023-05-22 13:28:27 -04:00
virtual void BindOutputs ( FOutputVertexInterfaceData & InOutVertexData ) override
{
using namespace LimiterVertexNames ;
2021-08-11 15:03:33 -04:00
2023-05-22 13:28:27 -04:00
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( OutputAudio ) , AudioOutput ) ;
}
virtual FDataReferenceCollection GetInputs ( ) const override
{
// This should never be called. Bind(...) is called instead. This method
// exists as a stop-gap until the API can be deprecated and removed.
checkNoEntry ( ) ;
return { } ;
2021-08-11 15:03:33 -04:00
}
virtual FDataReferenceCollection GetOutputs ( ) const override
{
2023-05-22 13:28:27 -04:00
// This should never be called. Bind(...) is called instead. This method
// exists as a stop-gap until the API can be deprecated and removed.
checkNoEntry ( ) ;
return { } ;
2021-08-11 15:03:33 -04:00
}
static TUniquePtr < IOperator > CreateOperator ( const FCreateOperatorParams & InParams , FBuildErrorArray & OutErrors )
{
using namespace LimiterVertexNames ;
const FDataReferenceCollection & Inputs = InParams . InputDataReferences ;
const FInputVertexInterface & InputInterface = DeclareVertexInterface ( ) . GetInputInterface ( ) ;
FAudioBufferReadRef AudioIn = Inputs . GetDataReadReferenceOrConstruct < FAudioBuffer > ( METASOUND_GET_PARAM_NAME ( InputAudio ) , InParams . OperatorSettings ) ;
FFloatReadRef InGainDbIn = Inputs . GetDataReadReferenceOrConstructWithVertexDefault < float > ( InputInterface , METASOUND_GET_PARAM_NAME ( InputInGainDb ) , InParams . OperatorSettings ) ;
FFloatReadRef ThresholdDbIn = Inputs . GetDataReadReferenceOrConstructWithVertexDefault < float > ( InputInterface , METASOUND_GET_PARAM_NAME ( InputThresholdDb ) , InParams . OperatorSettings ) ;
FTimeReadRef ReleaseTimeIn = Inputs . GetDataReadReferenceOrConstructWithVertexDefault < FTime > ( InputInterface , METASOUND_GET_PARAM_NAME ( InputReleaseTime ) , InParams . OperatorSettings ) ;
2023-05-25 18:13:32 -04:00
FKneeModeReadRef KneeModeIn = Inputs . GetDataReadReferenceOrConstructWithVertexDefault < FEnumKneeMode > ( InputInterface , METASOUND_GET_PARAM_NAME ( InputKneeMode ) , InParams . OperatorSettings ) ;
2021-08-11 15:03:33 -04:00
2023-03-02 14:40:35 -05:00
return MakeUnique < FLimiterOperator > ( InParams , AudioIn , InGainDbIn , ThresholdDbIn , ReleaseTimeIn , KneeModeIn ) ;
2021-08-11 15:03:33 -04:00
}
2023-03-02 14:40:35 -05:00
void Reset ( const IOperator : : FResetParams & InParams )
2021-08-11 15:03:33 -04:00
{
2023-03-02 14:40:35 -05:00
AudioOutput - > Zero ( ) ;
const float ClampedInGainDb = FMath : : Min ( * InGainDbInput , MaxInputGain ) ;
const float ClampedReleaseTime = FMath : : Max ( FTime : : ToMilliseconds ( * ReleaseTimeInput ) , 0.0 ) ;
Limiter . Init ( InParams . OperatorSettings . GetSampleRate ( ) , 1 ) ;
Limiter . SetProcessingMode ( Audio : : EDynamicsProcessingMode : : Limiter ) ;
Limiter . SetInputGain ( ClampedInGainDb ) ;
Limiter . SetThreshold ( * ThresholdDbInput ) ;
Limiter . SetAttackTime ( 0.0f ) ;
Limiter . SetReleaseTime ( ClampedReleaseTime ) ;
Limiter . SetPeakMode ( Audio : : EPeakMode : : Peak ) ;
2021-08-11 15:03:33 -04:00
switch ( * KneeModeInput )
{
default :
case EKneeMode : : Hard :
Limiter . SetKneeBandwidth ( HardKneeBandwitdh ) ;
break ;
case EKneeMode : : Soft :
Limiter . SetKneeBandwidth ( SoftKneeBandwitdh ) ;
break ;
2023-03-02 14:40:35 -05:00
}
PrevInGainDb = ClampedInGainDb ;
PrevReleaseTime = ClampedReleaseTime ;
PrevThresholdDb = * ThresholdDbInput ;
PrevKneeMode = * KneeModeInput ;
}
void Execute ( )
{
/* Update parameters */
float ClampedInGainDb = FMath : : Min ( * InGainDbInput , MaxInputGain ) ;
if ( ! FMath : : IsNearlyEqual ( ClampedInGainDb , PrevInGainDb ) )
{
Limiter . SetInputGain ( ClampedInGainDb ) ;
PrevInGainDb = ClampedInGainDb ;
}
if ( ! FMath : : IsNearlyEqual ( * ThresholdDbInput , PrevThresholdDb ) )
{
Limiter . SetThreshold ( * ThresholdDbInput ) ;
PrevThresholdDb = * ThresholdDbInput ;
}
// Release time cannot be negative
double CurrRelease = FMath : : Max ( FTime : : ToMilliseconds ( * ReleaseTimeInput ) , 0.0f ) ;
if ( ! FMath : : IsNearlyEqual ( CurrRelease , PrevReleaseTime ) )
{
Limiter . SetReleaseTime ( CurrRelease ) ;
PrevReleaseTime = CurrRelease ;
}
if ( PrevKneeMode ! = * KneeModeInput )
{
switch ( * KneeModeInput )
{
default :
case EKneeMode : : Hard :
Limiter . SetKneeBandwidth ( HardKneeBandwitdh ) ;
break ;
case EKneeMode : : Soft :
Limiter . SetKneeBandwidth ( SoftKneeBandwitdh ) ;
break ;
}
PrevKneeMode = * KneeModeInput ;
}
2021-08-11 15:03:33 -04:00
Limiter . ProcessAudio ( AudioInput - > GetData ( ) , AudioInput - > Num ( ) , AudioOutput - > GetData ( ) ) ;
}
private :
FAudioBufferReadRef AudioInput ;
FFloatReadRef InGainDbInput ;
FFloatReadRef ThresholdDbInput ;
FTimeReadRef ReleaseTimeInput ;
FKneeModeReadRef KneeModeInput ;
FAudioBufferWriteRef AudioOutput ;
// Internal DSP Limiter
Audio : : FDynamicsProcessor Limiter ;
// Cached variables
float PrevInGainDb ;
float PrevThresholdDb ;
double PrevReleaseTime ;
2023-03-02 14:40:35 -05:00
EKneeMode PrevKneeMode ;
2021-08-11 15:03:33 -04:00
} ;
// Node Class
class FLimiterNode : public FNodeFacade
{
public :
FLimiterNode ( const FNodeInitData & InitData )
: FNodeFacade ( InitData . InstanceName , InitData . InstanceID , TFacadeOperatorClass < FLimiterOperator > ( ) )
{
}
} ;
// Register node
METASOUND_REGISTER_NODE ( FLimiterNode )
}
# undef LOCTEXT_NAMESPACE