2021-04-04 23:08:06 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "MetasoundNodeRegistrationMacro.h"
# include "MetasoundAudioBuffer.h"
# include "CoreMinimal.h"
2021-09-13 14:13:39 -04:00
# include "DSP/BufferVectorOperations.h"
2022-06-15 01:43:38 -04:00
# include "DSP/FloatArrayMath.h"
2021-09-13 14:13:39 -04:00
# include "Internationalization/Text.h"
2021-04-04 23:08:06 -04:00
# include "MetasoundExecutableOperator.h"
2021-09-13 14:13:39 -04:00
# include "MetasoundFacade.h"
# include "MetasoundParamHelper.h"
2021-04-04 23:08:06 -04:00
# include "MetasoundPrimitives.h"
2021-08-09 15:08:37 -04:00
# include "MetasoundStandardNodesCategories.h"
2021-04-04 23:08:06 -04:00
# include "MetasoundStandardNodesNames.h"
# include "MetasoundTrigger.h"
2021-09-13 14:13:39 -04:00
# include "MetasoundVertex.h"
2021-04-04 23:08:06 -04:00
# define LOCTEXT_NAMESPACE "MetasoundStandardNodes_CrossfadeNode"
# define REGISTER_CROSSFADE_NODE(DataType, Number) \
using FCrossfadeNode # # DataType # # _ # # Number = TCrossfadeNode < DataType , Number > ; \
METASOUND_REGISTER_NODE ( FCrossfadeNode # # DataType # # _ # # Number ) \
namespace Metasound
{
namespace CrossfadeVertexNames
{
2022-03-17 13:14:50 -04:00
METASOUND_PARAM ( InputCrossfadeValue , " Crossfade Value " , " Crossfade value to crossfade across inputs. Output will be the float value between adjacent whole number values. " )
2023-01-04 15:45:18 -05:00
METASOUND_PARAM ( OutputTrigger , " Out " , " Output value. " )
2021-04-04 23:08:06 -04:00
2021-09-13 14:13:39 -04:00
const FVertexName GetInputName ( uint32 InIndex )
2021-04-04 23:08:06 -04:00
{
2021-09-13 14:13:39 -04:00
return * FString : : Format ( TEXT ( " In {0} " ) , { InIndex } ) ;
2021-04-04 23:08:06 -04:00
}
const FText GetInputDescription ( uint32 InIndex )
{
2022-02-10 18:36:47 -05:00
return METASOUND_LOCTEXT_FORMAT ( " CrossfadeInputDesc " , " Cross fade {0} input. " , InIndex ) ;
2021-04-04 23:08:06 -04:00
}
2022-03-17 13:14:50 -04:00
const FText GetInputDisplayName ( uint32 InIndex )
2021-04-04 23:08:06 -04:00
{
2022-03-17 13:14:50 -04:00
return METASOUND_LOCTEXT_FORMAT ( " CrossfadeInputDisplayName " , " In {0} " , InIndex ) ;
2021-04-04 23:08:06 -04:00
}
}
template < typename ValueType , uint32 NumInputs >
class TCrossfadeHelper
{
} ;
// Partial specialization for float
template < uint32 NumInputs >
class TCrossfadeHelper < float , NumInputs >
{
public :
TCrossfadeHelper ( int32 NumFramesPerBlock ) { }
void GetCrossfadeOutput ( int32 IndexA , int32 IndexB , float Alpha , const TArray < FFloatReadRef > & InCurrentValues , float & OutValue )
{
const FFloatReadRef & InA = InCurrentValues [ IndexA ] ;
const FFloatReadRef & InB = InCurrentValues [ IndexB ] ;
OutValue = FMath : : Lerp ( * InA , * InB , Alpha ) ;
}
} ;
// Partial specialization for FAudioBuffer
template < uint32 NumInputs >
class TCrossfadeHelper < FAudioBuffer , NumInputs >
{
public :
TCrossfadeHelper ( int32 InNumFramesPerBlock )
: NumFramesPerBlock ( InNumFramesPerBlock )
{
PrevGains . AddZeroed ( NumInputs ) ;
CurrentGains . AddZeroed ( NumInputs ) ;
NeedsMixing . AddZeroed ( NumInputs ) ;
}
void GetCrossfadeOutput ( int32 IndexA , int32 IndexB , float Alpha , const TArray < FAudioBufferReadRef > & InAudioBuffersValues , FAudioBuffer & OutAudioBuffer )
{
// Determine the gains
for ( int32 i = 0 ; i < NumInputs ; + + i )
{
if ( i = = IndexA )
{
CurrentGains [ i ] = 1.0f - Alpha ;
NeedsMixing [ i ] = true ;
}
else if ( i = = IndexB )
{
CurrentGains [ i ] = Alpha ;
NeedsMixing [ i ] = true ;
}
else
{
CurrentGains [ i ] = 0.0f ;
// If we were already at 0.0f, don't need to do any mixing!
if ( PrevGains [ i ] = = 0.0f )
{
NeedsMixing [ i ] = false ;
}
}
}
// Zero the output buffer so we can mix into it
OutAudioBuffer . Zero ( ) ;
2022-04-04 13:09:43 -04:00
TArrayView < float > OutAudioBufferView ( OutAudioBuffer . GetData ( ) , OutAudioBuffer . Num ( ) ) ;
2021-04-04 23:08:06 -04:00
// Now write to the scratch buffers w/ fade buffer fast given the new inputs
for ( int32 i = 0 ; i < NumInputs ; + + i )
{
// Only need to do anything on an input if either curr or prev is non-zero
if ( NeedsMixing [ i ] )
{
// Copy the input to the output
const FAudioBufferReadRef & InBuff = InAudioBuffersValues [ i ] ;
2022-04-04 13:09:43 -04:00
TArrayView < const float > BufferView ( ( * InBuff ) . GetData ( ) , NumFramesPerBlock ) ;
2021-04-04 23:08:06 -04:00
const float * BufferPtr = ( * InBuff ) . GetData ( ) ;
// mix in and fade to the target gain values
2022-04-04 13:09:43 -04:00
Audio : : ArrayMixIn ( BufferView , OutAudioBufferView , PrevGains [ i ] , CurrentGains [ i ] ) ;
2021-04-04 23:08:06 -04:00
}
}
// Copy the CurrentGains to PrevGains
PrevGains = CurrentGains ;
}
private :
int32 NumFramesPerBlock = 0 ;
TArray < float > PrevGains ;
TArray < float > CurrentGains ;
TArray < bool > NeedsMixing ;
} ;
template < typename ValueType , uint32 NumInputs >
class TCrossfadeOperator : public TExecutableOperator < TCrossfadeOperator < ValueType , NumInputs > >
{
public :
static const FVertexInterface & GetVertexInterface ( )
{
using namespace CrossfadeVertexNames ;
auto CreateDefaultInterface = [ ] ( ) - > FVertexInterface
{
FInputVertexInterface InputInterface ;
2022-03-31 16:49:59 -04:00
InputInterface . Add ( TInputDataVertex < float > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( InputCrossfadeValue ) ) ) ;
2021-04-04 23:08:06 -04:00
for ( uint32 i = 0 ; i < NumInputs ; + + i )
{
2022-03-17 13:14:50 -04:00
const FDataVertexMetadata InputMetadata
{
GetInputDescription ( i ) ,
GetInputDisplayName ( i )
} ;
2022-03-31 16:49:59 -04:00
InputInterface . Add ( TInputDataVertex < ValueType > ( GetInputName ( i ) , InputMetadata ) ) ;
2021-04-04 23:08:06 -04:00
}
FOutputVertexInterface OutputInterface ;
2022-03-31 16:49:59 -04:00
OutputInterface . Add ( TOutputDataVertex < ValueType > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( OutputTrigger ) ) ) ;
2021-04-04 23:08:06 -04:00
return FVertexInterface ( InputInterface , OutputInterface ) ;
} ;
static const FVertexInterface DefaultInterface = CreateDefaultInterface ( ) ;
return DefaultInterface ;
}
static const FNodeClassMetadata & GetNodeInfo ( )
{
auto CreateNodeClassMetadata = [ ] ( ) - > FNodeClassMetadata
{
FName DataTypeName = GetMetasoundDataTypeName < ValueType > ( ) ;
FName OperatorName = * FString : : Printf ( TEXT ( " Trigger Route (%s, %d) " ) , * DataTypeName . ToString ( ) , NumInputs ) ;
2022-02-10 18:36:47 -05:00
FText NodeDisplayName = METASOUND_LOCTEXT_FORMAT ( " CrossfadeDisplayNamePattern " , " Crossfade ({0}, {1}) " , GetMetasoundDataTypeDisplayText < ValueType > ( ) , NumInputs ) ;
const FText NodeDescription = METASOUND_LOCTEXT ( " CrossfadeDescription " , " Crossfades inputs to outputs. " ) ;
2021-04-04 23:08:06 -04:00
FVertexInterface NodeInterface = GetVertexInterface ( ) ;
FNodeClassMetadata Metadata
{
2021-08-09 15:08:37 -04:00
FNodeClassName { " Crossfade " , OperatorName , DataTypeName } ,
2021-04-04 23:08:06 -04:00
1 , // Major Version
0 , // Minor Version
NodeDisplayName ,
NodeDescription ,
PluginAuthor ,
PluginNodeMissingPrompt ,
NodeInterface ,
2021-08-09 15:08:37 -04:00
{ NodeCategories : : Envelopes } ,
{ } ,
FNodeDisplayStyle ( )
2021-04-04 23:08:06 -04:00
} ;
return Metadata ;
} ;
static const FNodeClassMetadata Metadata = CreateNodeClassMetadata ( ) ;
return Metadata ;
}
static TUniquePtr < IOperator > CreateOperator ( const FCreateOperatorParams & InParams , TArray < TUniquePtr < IOperatorBuildError > > & OutErrors )
{
using namespace CrossfadeVertexNames ;
const FInputVertexInterface & InputInterface = InParams . Node . GetVertexInterface ( ) . GetInputInterface ( ) ;
const FDataReferenceCollection & InputCollection = InParams . InputDataReferences ;
2022-03-17 13:14:50 -04:00
FFloatReadRef CrossfadeValue = InputCollection . GetDataReadReferenceOrConstructWithVertexDefault < float > ( InputInterface , METASOUND_GET_PARAM_NAME ( InputCrossfadeValue ) , InParams . OperatorSettings ) ;
2021-04-04 23:08:06 -04:00
TArray < TDataReadReference < ValueType > > InputValues ;
for ( uint32 i = 0 ; i < NumInputs ; + + i )
{
InputValues . Add ( InputCollection . GetDataReadReferenceOrConstructWithVertexDefault < ValueType > ( InputInterface , GetInputName ( i ) , InParams . OperatorSettings ) ) ;
}
return MakeUnique < TCrossfadeOperator < ValueType , NumInputs > > ( InParams . OperatorSettings , CrossfadeValue , MoveTemp ( InputValues ) ) ;
}
TCrossfadeOperator ( const FOperatorSettings & InSettings , const FFloatReadRef & InCrossfadeValue , TArray < TDataReadReference < ValueType > > & & InInputValues )
: CrossfadeValue ( InCrossfadeValue )
, InputValues ( MoveTemp ( InInputValues ) )
, OutputValue ( TDataWriteReferenceFactory < ValueType > : : CreateAny ( InSettings ) )
, Crossfader ( InSettings . GetNumFramesPerBlock ( ) )
{
PerformCrossfadeOutput ( ) ;
}
virtual ~ TCrossfadeOperator ( ) = default ;
2023-05-22 13:28:27 -04:00
virtual void BindInputs ( FInputVertexInterfaceData & InOutVertexData ) override
2021-04-04 23:08:06 -04:00
{
using namespace CrossfadeVertexNames ;
2023-05-22 13:28:27 -04:00
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( InputCrossfadeValue ) , CrossfadeValue ) ;
2021-04-04 23:08:06 -04:00
for ( uint32 i = 0 ; i < NumInputs ; + + i )
{
2023-05-22 13:28:27 -04:00
InOutVertexData . BindReadVertex ( GetInputName ( i ) , InputValues [ i ] ) ;
2021-04-04 23:08:06 -04:00
}
2023-05-22 13:28:27 -04:00
}
virtual void BindOutputs ( FOutputVertexInterfaceData & InOutVertexData ) override
{
using namespace CrossfadeVertexNames ;
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( OutputTrigger ) , OutputValue ) ;
}
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-04-04 23:08:06 -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-04-04 23:08:06 -04:00
}
void PerformCrossfadeOutput ( )
{
// Clamp the cross fade value based on the number of inputs
float CurrentCrossfadeValue = FMath : : Clamp ( * CrossfadeValue , 0.0f , ( float ) ( NumInputs - 1 ) ) ;
// Only update the cross fade state if anything has changed
if ( ! FMath : : IsNearlyEqual ( CurrentCrossfadeValue , PrevCrossfadeValue ) )
{
PrevCrossfadeValue = CurrentCrossfadeValue ;
IndexA = ( int32 ) FMath : : Floor ( CurrentCrossfadeValue ) ;
2021-07-12 17:41:09 -04:00
IndexB = FMath : : Clamp ( IndexA + 1 , 0.0f , ( float ) ( NumInputs - 1 ) ) ;
2021-04-04 23:08:06 -04:00
Alpha = CurrentCrossfadeValue - ( float ) IndexA ;
}
// Need to call this each block in case inputs have changed
Crossfader . GetCrossfadeOutput ( IndexA , IndexB , Alpha , InputValues , * OutputValue ) ;
}
2023-03-02 14:40:35 -05:00
void Reset ( const IOperator : : FResetParams & InParams )
{
PerformCrossfadeOutput ( ) ;
}
2021-04-04 23:08:06 -04:00
void Execute ( )
{
PerformCrossfadeOutput ( ) ;
}
private :
FFloatReadRef CrossfadeValue ;
TArray < TDataReadReference < ValueType > > InputValues ;
TDataWriteReference < ValueType > OutputValue ;
float PrevCrossfadeValue = - 1.0f ;
int32 IndexA = 0 ;
int32 IndexB = 0 ;
float Alpha = 0.0f ;
TCrossfadeHelper < ValueType , NumInputs > Crossfader ;
} ;
template < typename ValueType , uint32 NumInputs >
class TCrossfadeNode : public FNodeFacade
{
public :
/**
* Constructor used by the Metasound Frontend .
*/
TCrossfadeNode ( const FNodeInitData & InInitData )
: FNodeFacade ( InInitData . InstanceName , InInitData . InstanceID , TFacadeOperatorClass < TCrossfadeOperator < ValueType , NumInputs > > ( ) )
{ }
virtual ~ TCrossfadeNode ( ) = default ;
} ;
REGISTER_CROSSFADE_NODE ( float , 2 ) ;
REGISTER_CROSSFADE_NODE ( float , 3 ) ;
REGISTER_CROSSFADE_NODE ( float , 4 ) ;
REGISTER_CROSSFADE_NODE ( float , 5 ) ;
REGISTER_CROSSFADE_NODE ( float , 6 ) ;
REGISTER_CROSSFADE_NODE ( float , 7 ) ;
REGISTER_CROSSFADE_NODE ( float , 8 ) ;
REGISTER_CROSSFADE_NODE ( FAudioBuffer , 2 ) ;
REGISTER_CROSSFADE_NODE ( FAudioBuffer , 3 ) ;
REGISTER_CROSSFADE_NODE ( FAudioBuffer , 4 ) ;
REGISTER_CROSSFADE_NODE ( FAudioBuffer , 5 ) ;
REGISTER_CROSSFADE_NODE ( FAudioBuffer , 6 ) ;
REGISTER_CROSSFADE_NODE ( FAudioBuffer , 7 ) ;
REGISTER_CROSSFADE_NODE ( FAudioBuffer , 8 ) ;
}
# undef LOCTEXT_NAMESPACE