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"
2022-03-17 13:14:50 -04:00
# include "MetasoundParamHelper.h"
2021-03-18 20:04:51 -04:00
# 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"
2022-06-15 01:43:38 -04:00
# include "DSP/FloatArrayMath.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
{
2022-03-17 13:14:50 -04:00
METASOUND_PARAM ( InputAudio , " In " , " The input audio to spatialize. " )
METASOUND_PARAM ( InputPanAngle , " Angle " , " 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. " )
METASOUND_PARAM ( InputDistanceFactor , " Distance Factor " , " 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. " )
METASOUND_PARAM ( InputHeadWidth , " Head Width " , " The width of the listener head to use for ITD calculations in centimeters. " )
METASOUND_PARAM ( OutputAudioLeft , " Out Left " , " Left channel audio output. " )
METASOUND_PARAM ( OutputAudioRight , " Out Right " , " Right channel audio output. " )
2021-03-18 20:04:51 -04:00
}
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 ) ;
2023-05-22 13:28:27 -04:00
virtual void BindInputs ( FInputVertexInterfaceData & InOutVertexData ) override ;
virtual void BindOutputs ( FOutputVertexInterfaceData & InOutVertexData ) override ;
2021-03-18 20:04:51 -04:00
virtual FDataReferenceCollection GetInputs ( ) const override ;
virtual FDataReferenceCollection GetOutputs ( ) const override ;
2023-03-02 14:40:35 -05:00
void Reset ( const IOperator : : FResetParams & InParams ) ;
2021-03-18 20:04:51 -04:00
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 ;
}
2023-05-22 13:28:27 -04:00
void FITDPannerOperator : : BindInputs ( FInputVertexInterfaceData & InOutVertexData )
2021-03-18 20:04:51 -04:00
{
using namespace ITDPannerVertexNames ;
2023-05-22 13:28:27 -04:00
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( InputAudio ) , AudioInput ) ;
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( InputPanAngle ) , PanningAngle ) ;
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( InputDistanceFactor ) , DistanceFactor ) ;
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( InputHeadWidth ) , HeadWidth ) ;
}
2021-03-18 20:04:51 -04:00
2023-05-22 13:28:27 -04:00
void FITDPannerOperator : : BindOutputs ( FOutputVertexInterfaceData & InOutVertexData )
{
using namespace ITDPannerVertexNames ;
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( OutputAudioLeft ) , AudioLeftOutput ) ;
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( OutputAudioRight ) , AudioRightOutput ) ;
}
FDataReferenceCollection FITDPannerOperator : : GetInputs ( ) const
{
// 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-03-18 20:04:51 -04:00
}
FDataReferenceCollection FITDPannerOperator : : GetOutputs ( ) const
{
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-03-18 20:04:51 -04:00
}
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
}
}
2023-03-02 14:40:35 -05:00
void FITDPannerOperator : : Reset ( const IOperator : : FResetParams & InParams )
{
AudioLeftOutput - > Zero ( ) ;
AudioRightOutput - > Zero ( ) ;
LeftDelay . Reset ( ) ;
RightDelay . Reset ( ) ;
CurrAngle = FMath : : Clamp ( * PanningAngle , 0.0f , 360.0f ) ;
CurrDistanceFactor = FMath : : Clamp ( * DistanceFactor , 0.0f , 1.0f ) ;
CurrHeadWidth = FMath : : Max ( * HeadWidth , 0.0f ) ;
UpdateParams ( true ) ;
PrevLeftGain = CurrLeftGain ;
PrevRightGain = CurrRightGain ;
}
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 ( ) ;
2022-04-04 13:09:43 -04:00
TArrayView < float > OutputLeftBufferView ( AudioLeftOutput - > GetData ( ) , InputSampleCount ) ;
TArrayView < float > OutputRightBufferView ( AudioRightOutput - > GetData ( ) , InputSampleCount ) ;
2021-03-18 20:04:51 -04:00
// 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 ) )
{
2022-04-04 13:09:43 -04:00
Audio : : ArrayMultiplyByConstantInPlace ( OutputLeftBufferView , PrevLeftGain ) ;
Audio : : ArrayMultiplyByConstantInPlace ( OutputRightBufferView , PrevRightGain ) ;
2021-03-18 20:04:51 -04:00
}
else
{
2022-04-04 13:09:43 -04:00
Audio : : ArrayFade ( OutputLeftBufferView , PrevLeftGain , CurrLeftGain ) ;
Audio : : ArrayFade ( OutputRightBufferView , PrevRightGain , CurrRightGain ) ;
2021-03-18 20:04:51 -04:00
PrevLeftGain = CurrLeftGain ;
PrevRightGain = CurrRightGain ;
}
}
const FVertexInterface & FITDPannerOperator : : GetVertexInterface ( )
{
using namespace ITDPannerVertexNames ;
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 ( InputPanAngle ) , 90.0f ) ,
TInputDataVertex < float > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( InputDistanceFactor ) , 0.0f ) ,
TInputDataVertex < float > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( InputHeadWidth ) , 34.0f )
2021-03-18 20:04:51 -04:00
) ,
FOutputVertexInterface (
2022-03-31 16:49:59 -04:00
TOutputDataVertex < FAudioBuffer > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( OutputAudioLeft ) ) ,
TOutputDataVertex < FAudioBuffer > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( OutputAudioRight ) )
2021-03-18 20:04:51 -04:00
)
) ;
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 ) ;
2022-08-17 16:42:56 -04:00
Info . Keywords = { METASOUND_LOCTEXT ( " ITDBinauralKeyword " , " Binaural " ) , METASOUND_LOCTEXT ( " ITDInterauralKeyword " , " Interaural Time Delay " ) } ;
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 ;
2022-03-17 13:14:50 -04:00
FAudioBufferReadRef AudioIn = InputCollection . GetDataReadReferenceOrConstruct < FAudioBuffer > ( METASOUND_GET_PARAM_NAME ( InputAudio ) , InParams . OperatorSettings ) ;
FFloatReadRef PanningAngle = InputCollection . GetDataReadReferenceOrConstructWithVertexDefault < float > ( InputInterface , METASOUND_GET_PARAM_NAME ( InputPanAngle ) , InParams . OperatorSettings ) ;
FFloatReadRef DistanceFactor = InputCollection . GetDataReadReferenceOrConstructWithVertexDefault < float > ( InputInterface , METASOUND_GET_PARAM_NAME ( InputDistanceFactor ) , InParams . OperatorSettings ) ;
FFloatReadRef HeadWidth = InputCollection . GetDataReadReferenceOrConstructWithVertexDefault < float > ( InputInterface , METASOUND_GET_PARAM_NAME ( InputHeadWidth ) , 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