2021-03-18 20:04:51 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "Internationalization/Text.h"
# include "MetasoundExecutableOperator.h"
# include "MetasoundNodeRegistrationMacro.h"
# include "MetasoundDataTypeRegistrationMacro.h"
# include "MetasoundPrimitives.h"
# include "MetasoundStandardNodesNames.h"
# include "MetasoundTrigger.h"
# include "MetasoundTime.h"
# include "MetasoundAudioBuffer.h"
# include "DSP/BufferVectorOperations.h"
# include "DSP/Delay.h"
# include "DSP/Dsp.h"
2021-04-03 18:40:14 -04:00
# include "MetasoundFacade.h"
# include "MetasoundStandardNodesCategories.h"
2021-09-13 14:13:39 -04:00
# include "MetasoundVertex.h"
2021-03-18 20:04:51 -04:00
2021-04-03 18:40:14 -04:00
# define LOCTEXT_NAMESPACE "MetasoundStandardNodes_ITDPannerNode"
2021-03-18 20:04:51 -04:00
namespace Metasound
{
namespace ITDPannerVertexNames
{
2021-09-13 14:13:39 -04:00
const FVertexName & GetInputAudioName ( )
2021-03-18 20:04:51 -04:00
{
2021-09-13 14:13:39 -04:00
static FVertexName Name = TEXT ( " In " ) ;
2021-03-18 20:04:51 -04:00
return Name ;
}
const FText & GetInputAudioDescription ( )
{
2022-02-10 18:36:47 -05:00
static FText Desc = METASOUND_LOCTEXT ( " ITDPannerNodeInDesc " , " The input audio to spatialize. " ) ;
2021-03-18 20:04:51 -04:00
return Desc ;
}
2021-09-13 14:13:39 -04:00
const FVertexName & GetInputPanAngleName ( )
2021-03-18 20:04:51 -04:00
{
2021-09-13 14:13:39 -04:00
static FVertexName Name = TEXT ( " Angle " ) ;
2021-03-18 20:04:51 -04:00
return Name ;
}
const FText & GetInputPanAngleDescription ( )
{
2022-02-10 18:36:47 -05:00
static FText Desc = METASOUND_LOCTEXT ( " ITDPannerNodePanAngleDesc " , " The sound source angle in degrees. 90 degrees is in front, 0 degrees is to the right, 270 degrees is behind, 180 degrees is to the left. " ) ;
2021-03-18 20:04:51 -04:00
return Desc ;
}
2021-09-13 14:13:39 -04:00
const FVertexName & GetInputDistanceFactorName ( )
2021-03-18 20:04:51 -04:00
{
2021-09-13 14:13:39 -04:00
static FVertexName Name = TEXT ( " Distance Factor " ) ;
2021-03-18 20:04:51 -04:00
return Name ;
}
const FText & GetInputDistanceFactorDescription ( )
{
2022-02-10 18:36:47 -05:00
static FText Desc = METASOUND_LOCTEXT ( " ITDPannerNodeDistanceFactorDesc " , " The normalized distance factor (0.0 to 1.0) to use for ILD (Inter-aural level difference) calculations. 0.0 is near, 1.0 is far. The further away something is the less there is a difference in levels (gain) between the ears. " ) ;
2021-03-18 20:04:51 -04:00
return Desc ;
}
2021-09-13 14:13:39 -04:00
const FVertexName & GetInputHeadWidthName ( )
2021-03-18 20:04:51 -04:00
{
2021-09-13 14:13:39 -04:00
static FVertexName Name = TEXT ( " Head Width " ) ;
2021-03-18 20:04:51 -04:00
return Name ;
}
const FText & GetInputHeadWidthDescription ( )
{
2022-02-10 18:36:47 -05:00
static FText Desc = METASOUND_LOCTEXT ( " ITDPannerNodeHeadWidthDesc " , " The width of the listener head to use for ITD calculations in centimeters. " ) ;
2021-03-18 20:04:51 -04:00
return Desc ;
}
2021-09-13 14:13:39 -04:00
const FVertexName & GetOutputAudioLeftName ( )
2021-03-18 20:04:51 -04:00
{
2021-09-13 14:13:39 -04:00
static FVertexName Name = TEXT ( " Out Left " ) ;
2021-03-18 20:04:51 -04:00
return Name ;
}
const FText & GetOutputAudioLeftDescription ( )
{
2022-02-10 18:36:47 -05:00
static FText Desc = METASOUND_LOCTEXT ( " ITDPannerNodeOutputLeftDescription " , " Left channel audio output. " ) ;
2021-03-18 20:04:51 -04:00
return Desc ;
}
2021-09-13 14:13:39 -04:00
const FVertexName & GetOutputAudioRightName ( )
2021-03-18 20:04:51 -04:00
{
2021-09-13 14:13:39 -04:00
static FVertexName Name = TEXT ( " Out Right " ) ;
2021-03-18 20:04:51 -04:00
return Name ;
}
const FText & GetOutputAudioRightDescription ( )
{
2022-02-10 18:36:47 -05:00
static FText Desc = METASOUND_LOCTEXT ( " ITDPannerNodeOutputRightDescription " , " Right channel audio output. " ) ;
2021-03-18 20:04:51 -04:00
return Desc ;
}
}
class FITDPannerOperator : public TExecutableOperator < FITDPannerOperator >
{
public :
static const FNodeClassMetadata & GetNodeInfo ( ) ;
static const FVertexInterface & GetVertexInterface ( ) ;
static TUniquePtr < IOperator > CreateOperator ( const FCreateOperatorParams & InParams , FBuildErrorArray & OutErrors ) ;
FITDPannerOperator ( const FOperatorSettings & InSettings ,
const FAudioBufferReadRef & InAudioInput ,
const FFloatReadRef & InPanningAngle ,
const FFloatReadRef & InDistanceFactor ,
const FFloatReadRef & InHeadWidth ) ;
virtual FDataReferenceCollection GetInputs ( ) const override ;
virtual FDataReferenceCollection GetOutputs ( ) const override ;
void Execute ( ) ;
private :
2021-03-24 16:29:25 -04:00
void UpdateParams ( bool bIsInit ) ;
2021-03-18 20:04:51 -04:00
FAudioBufferReadRef AudioInput ;
FFloatReadRef PanningAngle ;
FFloatReadRef DistanceFactor ;
FFloatReadRef HeadWidth ;
FAudioBufferWriteRef AudioLeftOutput ;
FAudioBufferWriteRef AudioRightOutput ;
float CurrAngle = 0.0f ;
float CurrX = 0.0f ;
float CurrY = 0.0f ;
float CurrDistanceFactor = 0.0f ;
float CurrHeadWidth = 0.0f ;
float CurrLeftGain = 0.0f ;
float CurrRightGain = 0.0f ;
float CurrLeftDelay = 0.0f ;
float CurrRightDelay = 0.0f ;
float PrevLeftGain = 0.0f ;
float PrevRightGain = 0.0f ;
Audio : : FDelay LeftDelay ;
Audio : : FDelay RightDelay ;
} ;
FITDPannerOperator : : FITDPannerOperator ( const FOperatorSettings & InSettings ,
const FAudioBufferReadRef & InAudioInput ,
const FFloatReadRef & InPanningAngle ,
const FFloatReadRef & InDistanceFactor ,
const FFloatReadRef & InHeadWidth )
: AudioInput ( InAudioInput )
, PanningAngle ( InPanningAngle )
, DistanceFactor ( InDistanceFactor )
, HeadWidth ( InHeadWidth )
, AudioLeftOutput ( FAudioBufferWriteRef : : CreateNew ( InSettings ) )
, AudioRightOutput ( FAudioBufferWriteRef : : CreateNew ( InSettings ) )
{
LeftDelay . Init ( InSettings . GetSampleRate ( ) , 0.5f ) ;
RightDelay . Init ( InSettings . GetSampleRate ( ) , 0.5f ) ;
const float EaseFactor = Audio : : FExponentialEase : : GetFactorForTau ( 0.1f , InSettings . GetSampleRate ( ) ) ;
LeftDelay . SetEaseFactor ( EaseFactor ) ;
RightDelay . SetEaseFactor ( EaseFactor ) ;
CurrAngle = FMath : : Clamp ( * PanningAngle , 0.0f , 360.0f ) ;
CurrDistanceFactor = FMath : : Clamp ( * DistanceFactor , 0.0f , 1.0f ) ;
CurrHeadWidth = FMath : : Max ( * InHeadWidth , 0.0f ) ;
2021-03-24 16:29:25 -04:00
UpdateParams ( true ) ;
2021-03-18 20:04:51 -04:00
PrevLeftGain = CurrLeftGain ;
PrevRightGain = CurrRightGain ;
}
FDataReferenceCollection FITDPannerOperator : : GetInputs ( ) const
{
using namespace ITDPannerVertexNames ;
FDataReferenceCollection InputDataReferences ;
InputDataReferences . AddDataReadReference ( GetInputAudioName ( ) , AudioInput ) ;
InputDataReferences . AddDataReadReference ( GetInputPanAngleName ( ) , PanningAngle ) ;
InputDataReferences . AddDataReadReference ( GetInputDistanceFactorName ( ) , DistanceFactor ) ;
InputDataReferences . AddDataReadReference ( GetInputHeadWidthName ( ) , HeadWidth ) ;
return InputDataReferences ;
}
FDataReferenceCollection FITDPannerOperator : : GetOutputs ( ) const
{
using namespace ITDPannerVertexNames ;
FDataReferenceCollection OutputDataReferences ;
OutputDataReferences . AddDataReadReference ( GetOutputAudioLeftName ( ) , AudioLeftOutput ) ;
OutputDataReferences . AddDataReadReference ( GetOutputAudioRightName ( ) , AudioRightOutput ) ;
return OutputDataReferences ;
}
2021-03-24 16:29:25 -04:00
void FITDPannerOperator : : UpdateParams ( bool bIsInit )
2021-03-18 20:04:51 -04:00
{
// ****************
// Update the x-y values
const float CurrRadians = ( CurrAngle / 360.0f ) * 2.0f * PI ;
FMath : : SinCos ( & CurrY , & CurrX , CurrRadians ) ;
// ****************
// Update ILD gains
const float HeadRadiusMeters = 0.005f * CurrHeadWidth ; // (InHeadWidth / 100.0f) / 2.0f;
// InX is -1.0 to 1.0, so get it in 0.0 to 1.0 (i.e. hard left, hard right)
const float Fraction = ( CurrX + 1.0f ) * 0.5f ;
// Feed the linear pan value into a equal power equation
float PanLeft ;
float PanRight ;
FMath : : SinCos ( & PanRight , & PanLeft , 0.5f * PI * Fraction ) ;
// If distance factor is 1.0 (i.e. far away) this will have equal gain, if distance factor is 0.0 it will be normal equal power pan.
CurrLeftGain = FMath : : Lerp ( PanLeft , 0.5f , CurrDistanceFactor ) ;
CurrRightGain = FMath : : Lerp ( PanRight , 0.5f , CurrDistanceFactor ) ;
// *********************
// Update the ITD delays
// Use pythagorean theorem to get distances
const float DistToLeftEar = FMath : : Sqrt ( ( CurrY * CurrY ) + FMath : : Square ( HeadRadiusMeters + CurrX ) ) ;
const float DistToRightEar = FMath : : Sqrt ( ( CurrY * CurrY ) + FMath : : Square ( HeadRadiusMeters - CurrX ) ) ;
// Compute delta time based on speed of sound
constexpr float SpeedOfSound = 343.0f ;
const float DeltaTimeSeconds = ( DistToLeftEar - DistToRightEar ) / SpeedOfSound ;
if ( DeltaTimeSeconds > 0.0f )
{
2021-03-24 16:29:25 -04:00
LeftDelay . SetEasedDelayMsec ( 1000.0f * DeltaTimeSeconds , bIsInit ) ;
RightDelay . SetEasedDelayMsec ( 0.0f , bIsInit ) ;
2021-03-18 20:04:51 -04:00
}
else
{
2021-03-24 16:29:25 -04:00
LeftDelay . SetEasedDelayMsec ( 0.0f , bIsInit ) ;
RightDelay . SetEasedDelayMsec ( - 1000.0f * DeltaTimeSeconds , bIsInit ) ;
2021-03-18 20:04:51 -04:00
}
}
void FITDPannerOperator : : Execute ( )
{
2021-03-24 16:29:25 -04:00
float NewHeadWidth = FMath : : Max ( * HeadWidth , 0.0f ) ;
float NewAngle = FMath : : Clamp ( * PanningAngle , 0.0f , 360.0f ) ;
float NewDistanceFactor = FMath : : Clamp ( * DistanceFactor , 0.0f , 1.0f ) ;
2021-03-18 20:04:51 -04:00
2021-03-24 16:29:25 -04:00
if ( ! FMath : : IsNearlyEqual ( NewAngle , CurrHeadWidth ) | |
! FMath : : IsNearlyEqual ( NewDistanceFactor , CurrAngle ) | |
! FMath : : IsNearlyEqual ( NewHeadWidth , CurrDistanceFactor ) )
{
CurrHeadWidth = NewHeadWidth ;
CurrAngle = NewAngle ;
CurrDistanceFactor = NewDistanceFactor ;
UpdateParams ( false ) ;
2021-03-18 20:04:51 -04:00
}
const float * InputBufferPtr = AudioInput - > GetData ( ) ;
int32 InputSampleCount = AudioInput - > Num ( ) ;
float * OutputLeftBufferPtr = AudioLeftOutput - > GetData ( ) ;
float * OutputRightBufferPtr = AudioRightOutput - > GetData ( ) ;
// Feed the input audio into the left and right delays
for ( int32 i = 0 ; i < InputSampleCount ; + + i )
{
OutputLeftBufferPtr [ i ] = LeftDelay . ProcessAudioSample ( InputBufferPtr [ i ] ) ;
OutputRightBufferPtr [ i ] = RightDelay . ProcessAudioSample ( InputBufferPtr [ i ] ) ;
}
// Now apply the panning
if ( FMath : : IsNearlyEqual ( PrevLeftGain , CurrLeftDelay ) )
{
Audio : : MultiplyBufferByConstantInPlace ( OutputLeftBufferPtr , InputSampleCount , PrevLeftGain ) ;
Audio : : MultiplyBufferByConstantInPlace ( OutputRightBufferPtr , InputSampleCount , PrevRightGain ) ;
}
else
{
Audio : : FadeBufferFast ( OutputLeftBufferPtr , InputSampleCount , PrevLeftGain , CurrLeftGain ) ;
Audio : : FadeBufferFast ( OutputRightBufferPtr , InputSampleCount , PrevRightGain , CurrRightGain ) ;
PrevLeftGain = CurrLeftGain ;
PrevRightGain = CurrRightGain ;
}
}
const FVertexInterface & FITDPannerOperator : : GetVertexInterface ( )
{
using namespace ITDPannerVertexNames ;
static const FVertexInterface Interface (
FInputVertexInterface (
TInputDataVertexModel < FAudioBuffer > ( GetInputAudioName ( ) , GetInputAudioDescription ( ) ) ,
2021-03-24 16:29:25 -04:00
TInputDataVertexModel < float > ( GetInputPanAngleName ( ) , GetInputPanAngleDescription ( ) , 90.0f ) ,
2021-03-18 20:04:51 -04:00
TInputDataVertexModel < float > ( GetInputDistanceFactorName ( ) , GetInputDistanceFactorDescription ( ) , 0.0f ) ,
TInputDataVertexModel < float > ( GetInputHeadWidthName ( ) , GetInputHeadWidthDescription ( ) , 34.0f )
) ,
FOutputVertexInterface (
TOutputDataVertexModel < FAudioBuffer > ( GetOutputAudioLeftName ( ) , GetOutputAudioLeftDescription ( ) ) ,
TOutputDataVertexModel < FAudioBuffer > ( GetOutputAudioRightName ( ) , GetOutputAudioRightDescription ( ) )
)
) ;
return Interface ;
}
const FNodeClassMetadata & FITDPannerOperator : : GetNodeInfo ( )
{
auto InitNodeInfo = [ ] ( ) - > FNodeClassMetadata
{
FNodeClassMetadata Info ;
2021-08-09 15:08:37 -04:00
Info . ClassName = { StandardNodes : : Namespace , TEXT ( " ITD Panner " ) , TEXT ( " " ) } ;
2021-03-18 20:04:51 -04:00
Info . MajorVersion = 1 ;
Info . MinorVersion = 0 ;
2022-02-10 18:36:47 -05:00
Info . DisplayName = METASOUND_LOCTEXT ( " Metasound_ITDPannerDisplayName " , " ITD Panner " ) ;
Info . Description = METASOUND_LOCTEXT ( " Metasound_ITDPannerNodeDescription " , " Pans an input audio signal using an inter-aural time delay method. " ) ;
2021-03-18 20:04:51 -04:00
Info . Author = PluginAuthor ;
Info . PromptIfMissing = PluginNodeMissingPrompt ;
Info . DefaultInterface = GetVertexInterface ( ) ;
2021-08-09 15:08:37 -04:00
Info . CategoryHierarchy . Emplace ( NodeCategories : : Spatialization ) ;
2021-03-18 20:04:51 -04:00
return Info ;
} ;
static const FNodeClassMetadata Info = InitNodeInfo ( ) ;
return Info ;
}
TUniquePtr < IOperator > FITDPannerOperator : : CreateOperator ( const FCreateOperatorParams & InParams , FBuildErrorArray & OutErrors )
{
const FDataReferenceCollection & InputCollection = InParams . InputDataReferences ;
const FInputVertexInterface & InputInterface = GetVertexInterface ( ) . GetInputInterface ( ) ;
using namespace ITDPannerVertexNames ;
FAudioBufferReadRef AudioIn = InputCollection . GetDataReadReferenceOrConstruct < FAudioBuffer > ( GetInputAudioName ( ) , InParams . OperatorSettings ) ;
2021-03-23 22:43:28 -04:00
FFloatReadRef PanningAngle = InputCollection . GetDataReadReferenceOrConstructWithVertexDefault < float > ( InputInterface , GetInputPanAngleName ( ) , InParams . OperatorSettings ) ;
FFloatReadRef DistanceFactor = InputCollection . GetDataReadReferenceOrConstructWithVertexDefault < float > ( InputInterface , GetInputDistanceFactorName ( ) , InParams . OperatorSettings ) ;
FFloatReadRef HeadWidth = InputCollection . GetDataReadReferenceOrConstructWithVertexDefault < float > ( InputInterface , GetInputHeadWidthName ( ) , InParams . OperatorSettings ) ;
2021-03-18 20:04:51 -04:00
return MakeUnique < FITDPannerOperator > ( InParams . OperatorSettings , AudioIn , PanningAngle , DistanceFactor , HeadWidth ) ;
}
2021-04-03 18:40:14 -04:00
class FITDPannerNode : public FNodeFacade
2021-03-18 20:04:51 -04:00
{
2021-04-03 18:40:14 -04:00
public :
/**
* Constructor used by the Metasound Frontend .
*/
FITDPannerNode ( const FNodeInitData & InitData )
: FNodeFacade ( InitData . InstanceName , InitData . InstanceID , TFacadeOperatorClass < FITDPannerOperator > ( ) )
{
}
} ;
2021-03-18 20:04:51 -04:00
METASOUND_REGISTER_NODE ( FITDPannerNode )
}
# undef LOCTEXT_NAMESPACE