2021-04-01 18:38:40 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "CoreMinimal.h"
# include "DSP/MidiNoteQuantizer.h"
# include "Internationalization/Text.h"
# include "MetasoundFacade.h"
# include "MetasoundPrimitives.h"
# include "MetasoundAudioBuffer.h"
# include "MetasoundParamHelper.h"
# include "MetasoundOperatorSettings.h"
# include "MetasoundStandardNodesNames.h"
# include "MetasoundExecutableOperator.h"
# include "MetasoundNodeRegistrationMacro.h"
# include "MetasoundStandardNodesCategories.h"
# include "MetasoundDataTypeRegistrationMacro.h"
2021-04-07 02:57:54 -04:00
# define LOCTEXT_NAMESPACE "MetasoundStandardNodes_MidiNoteQuantizerNode"
2021-04-01 18:38:40 -04:00
namespace Metasound
{
// forward declarations
// ...
# pragma region Parameter Names
namespace MidiNoteQuantizerParameterNames
{
// inputs
METASOUND_PARAM ( ParamNoteIn , " Note In " , " Midi Note to quantize " ) ;
2023-08-04 16:08:17 -04:00
METASOUND_PARAM ( ParamRootNote , " Root Note " , " Midi note to treat as the Root (e.g. where 0.0 = C, 1.0 = Db/C#, etc). Values are clamped to positive values. " ) ;
2023-05-25 18:45:39 -04:00
METASOUND_PARAM ( ParamScaleDegrees , " Scale Degrees " , " Set of notes in ascending order, represeting half steps starting at 0.0f meaning the Root Note. Scale degrees should be the notes in a scale that not including the octave. Can be the output of the Scale To Note Array node. " ) ;
METASOUND_PARAM ( ParamScaleRange , " Scale Range In " , " The number of semitones in the scale. E.g. a regular diatonic scale will be 12 semitones. Exotic scales could be something else. " ) ;
2021-04-01 18:38:40 -04:00
// outputs
METASOUND_PARAM ( ParamNoteOutput , " Note Out " , " Quantized Note " ) ;
} // namespace MidiNoteQuantizerParameterNames
using namespace MidiNoteQuantizerParameterNames ;
# pragma endregion // Parameter Names
# pragma region Operator Declaration
class FMidiNoteQuantizerOperator : public TExecutableOperator < FMidiNoteQuantizerOperator >
{
public :
using FArrayScaleDegreeReadRef = TDataReadReference < TArray < float > > ;
using ScaleDegreeArrayType = TArray < float > ;
// ctor
FMidiNoteQuantizerOperator (
2023-03-02 14:40:35 -05:00
const FCreateOperatorParams & InParams
2021-04-01 18:38:40 -04:00
, const FFloatReadRef & InMidiNoteIn
, const FFloatReadRef & InRootNote
, const FArrayScaleDegreeReadRef & InScale
2023-05-25 18:45:39 -04:00
, const FFloatReadRef & InScaleRange
2021-04-01 18:38:40 -04:00
) ;
// node interface
static const FNodeClassMetadata & GetNodeInfo ( ) ;
static FVertexInterface DeclareVertexInterface ( ) ;
static TUniquePtr < IOperator > CreateOperator ( const FCreateOperatorParams & InParams , FBuildErrorArray & OutErrors ) ;
2023-05-22 13:28:27 -04:00
virtual void BindInputs ( FInputVertexInterfaceData & InOutVertexData ) override ;
virtual void BindOutputs ( FOutputVertexInterfaceData & InOutVertexData ) override ;
2021-04-01 18:38:40 -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-04-01 18:38:40 -04:00
void Execute ( ) ;
private : // members
// input pins
FFloatReadRef MidiNoteIn ;
FFloatReadRef RootNote ;
FArrayScaleDegreeReadRef Scale ;
2023-05-25 18:45:39 -04:00
FFloatReadRef ScaleRange ;
2021-04-01 18:38:40 -04:00
// output pins
FFloatWriteRef MidiNoteOut ;
// cached values
float PreviousNoteIn = TNumericLimits < float > : : Lowest ( ) ;
float PreviousRoot = - 1.0f ;
TArray < float > PreviousScale ;
float PreviousNoteOut = 0.0f ;
2023-05-25 18:45:39 -04:00
float PreviousScaleRange = 12.0f ;
2021-04-01 18:38:40 -04:00
2023-03-02 14:40:35 -05:00
static constexpr float MaxNote = 1e12 f ;
static constexpr float MinNote = - 1e12 f ;
static constexpr float MaxRoot = 1e12 f ;
static constexpr float MinRoot = 0.f ;
2021-04-01 18:38:40 -04:00
} ; // class FMidiNoteQuantizerOperator
# pragma endregion // Operator Declaration
# pragma region Operator Implementation
// ctor
FMidiNoteQuantizerOperator : : FMidiNoteQuantizerOperator (
2023-03-02 14:40:35 -05:00
const FCreateOperatorParams & InParams
2021-04-01 18:38:40 -04:00
, const FFloatReadRef & InMidiNoteIn
, const FFloatReadRef & InRootNote
, const FArrayScaleDegreeReadRef & InScale
2023-05-25 18:45:39 -04:00
, const FFloatReadRef & InScaleRange
)
2021-04-01 18:38:40 -04:00
: MidiNoteIn ( InMidiNoteIn )
, RootNote ( InRootNote )
, Scale ( InScale )
2023-05-25 18:45:39 -04:00
, ScaleRange ( InScaleRange )
2021-04-01 18:38:40 -04:00
, MidiNoteOut ( FFloatWriteRef : : CreateNew ( ) )
{
2023-03-02 14:40:35 -05:00
Reset ( InParams ) ;
2021-04-01 18:38:40 -04:00
}
const FNodeClassMetadata & FMidiNoteQuantizerOperator : : GetNodeInfo ( )
{
auto InitNodeInfo = [ ] ( ) - > FNodeClassMetadata
{
FNodeClassMetadata Info ;
2021-08-09 15:08:37 -04:00
Info . ClassName = { StandardNodes : : Namespace , TEXT ( " MIDI Note Quantizer " ) , StandardNodes : : AudioVariant } ;
2021-04-01 18:38:40 -04:00
Info . MajorVersion = 1 ;
Info . MinorVersion = 0 ;
2022-02-10 18:36:47 -05:00
Info . DisplayName = METASOUND_LOCTEXT ( " Metasound_MIDI_Note_Quantizer_NodeDisplayName " , " MIDI Note Quantizer " ) ;
Info . Description = METASOUND_LOCTEXT ( " MidiNoteQuantizer_NodeDescription " , " Quantizes a MIDI note to the nearset note that matches provided criteria " ) ;
2021-04-01 18:38:40 -04:00
Info . Author = PluginAuthor ;
Info . PromptIfMissing = PluginNodeMissingPrompt ;
Info . DefaultInterface = DeclareVertexInterface ( ) ;
2021-08-09 15:08:37 -04:00
Info . CategoryHierarchy . Emplace ( NodeCategories : : Music ) ;
2022-08-17 16:42:56 -04:00
Info . Keywords . Add ( METASOUND_LOCTEXT ( " QuantizerPitchKeyword " , " Pitch " ) ) ;
2021-04-01 18:38:40 -04:00
return Info ;
} ;
static const FNodeClassMetadata Info = InitNodeInfo ( ) ;
return Info ;
}
FVertexInterface FMidiNoteQuantizerOperator : : DeclareVertexInterface ( )
{
static const FVertexInterface Interface (
FInputVertexInterface (
2022-03-31 16:49:59 -04:00
TInputDataVertex < float > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( ParamNoteIn ) , 60.0f ) ,
TInputDataVertex < float > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( ParamRootNote ) , 0.0f ) ,
2023-05-25 18:45:39 -04:00
TInputDataVertex < float > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( ParamScaleRange ) , 12.0f ) ,
2022-03-31 16:49:59 -04:00
TInputDataVertex < ScaleDegreeArrayType > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( ParamScaleDegrees ) )
2021-04-01 18:38:40 -04:00
) ,
FOutputVertexInterface (
2022-03-31 16:49:59 -04:00
TOutputDataVertex < float > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( ParamNoteOutput ) )
2021-04-01 18:38:40 -04:00
)
) ;
return Interface ;
}
TUniquePtr < IOperator > FMidiNoteQuantizerOperator : : CreateOperator ( const FCreateOperatorParams & InParams , FBuildErrorArray & OutErrors )
{
const FDataReferenceCollection & InputDataRefs = InParams . InputDataReferences ;
2023-05-25 18:45:39 -04:00
const FInputVertexInterface & InputInterface = InParams . Node . GetVertexInterface ( ) . GetInputInterface ( ) ;
2021-04-01 18:38:40 -04:00
// inputs
2023-05-25 18:45:39 -04:00
FFloatReadRef MidiNoteIn = InputDataRefs . GetDataReadReferenceOrConstructWithVertexDefault < float > ( InputInterface , METASOUND_GET_PARAM_NAME ( ParamNoteIn ) , InParams . OperatorSettings ) ;
FFloatReadRef RootNoteIn = InputDataRefs . GetDataReadReferenceOrConstructWithVertexDefault < float > ( InputInterface , METASOUND_GET_PARAM_NAME ( ParamRootNote ) , InParams . OperatorSettings ) ;
2021-04-01 18:38:40 -04:00
FArrayScaleDegreeReadRef InScaleArray = InParams . InputDataReferences . GetDataReadReferenceOrConstruct < ScaleDegreeArrayType > ( METASOUND_GET_PARAM_NAME ( ParamScaleDegrees ) ) ;
2023-05-25 18:45:39 -04:00
FFloatReadRef ScaleRangeIn = InputDataRefs . GetDataReadReferenceOrConstructWithVertexDefault < float > ( InputInterface , METASOUND_GET_PARAM_NAME ( ParamScaleRange ) , InParams . OperatorSettings ) ;
2021-04-01 18:38:40 -04:00
return MakeUnique < FMidiNoteQuantizerOperator > (
2023-03-02 14:40:35 -05:00
InParams
2021-04-01 18:38:40 -04:00
, MidiNoteIn
, RootNoteIn
, InScaleArray
2023-05-25 18:45:39 -04:00
, ScaleRangeIn
2021-04-01 18:38:40 -04:00
) ;
}
2023-05-22 13:28:27 -04:00
void FMidiNoteQuantizerOperator : : BindInputs ( FInputVertexInterfaceData & InOutVertexData )
{
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( ParamNoteIn ) , MidiNoteIn ) ;
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( ParamRootNote ) , RootNote ) ;
2023-05-25 18:45:39 -04:00
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( ParamScaleRange ) , ScaleRange ) ;
2023-05-22 13:28:27 -04:00
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( ParamScaleDegrees ) , Scale ) ;
}
void FMidiNoteQuantizerOperator : : BindOutputs ( FOutputVertexInterfaceData & InOutVertexData )
{
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( ParamNoteOutput ) , MidiNoteOut ) ;
}
2021-04-01 18:38:40 -04:00
FDataReferenceCollection FMidiNoteQuantizerOperator : : GetInputs ( ) 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-04-01 18:38:40 -04:00
}
FDataReferenceCollection FMidiNoteQuantizerOperator : : 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-04-01 18:38:40 -04:00
}
2023-03-02 14:40:35 -05:00
void FMidiNoteQuantizerOperator : : Reset ( const IOperator : : FResetParams & InParams )
{
PreviousNoteIn = FMath : : Clamp ( * MidiNoteIn , MinNote , MaxNote ) ;
PreviousRoot = FMath : : Clamp ( * RootNote , MinRoot , MaxRoot ) ;
PreviousScale = * Scale ;
2023-05-25 18:45:39 -04:00
PreviousScaleRange = FMath : : Max ( * ScaleRange , 1.0f ) ;
2023-03-02 14:40:35 -05:00
2023-05-30 17:07:23 -04:00
if ( PreviousScale . IsEmpty ( ) )
{
PreviousNoteOut = PreviousNoteIn ;
}
else
{
PreviousScale . Sort ( ) ;
PreviousNoteOut = Audio : : FMidiNoteQuantizer : : QuantizeMidiNote ( PreviousNoteIn , PreviousRoot , PreviousScale , PreviousScaleRange ) ;
}
2023-03-02 14:40:35 -05:00
* MidiNoteOut = PreviousNoteOut ;
}
2021-04-01 18:38:40 -04:00
void FMidiNoteQuantizerOperator : : Execute ( )
{
if ( ( * Scale ) . IsEmpty ( ) )
{
* MidiNoteOut = * MidiNoteIn ;
2021-04-06 23:20:55 -04:00
return ;
2021-04-01 18:38:40 -04:00
}
// calculate new output and cache values if needed
2023-03-02 14:40:35 -05:00
const float CurrentNote = FMath : : Clamp ( * MidiNoteIn , MinNote , MaxNote ) ;
const float CurrentRoot = FMath : : Clamp ( * RootNote , MinRoot , MaxRoot ) ;
2023-05-25 18:45:39 -04:00
const float CurrentScaleRange = FMath : : Max ( * ScaleRange , 1.0f ) ; ;
2023-03-02 14:40:35 -05:00
if ( ! FMath : : IsNearlyEqual ( PreviousNoteIn , CurrentNote )
2021-04-01 18:38:40 -04:00
| | ! FMath : : IsNearlyEqual ( PreviousRoot , CurrentRoot )
| | PreviousScale ! = * Scale
2023-05-25 18:45:39 -04:00
| | ! FMath : : IsNearlyEqual ( PreviousScaleRange , CurrentScaleRange )
2021-04-01 18:38:40 -04:00
)
{
// cache values
2023-03-02 14:40:35 -05:00
PreviousNoteIn = CurrentNote ;
PreviousRoot = CurrentRoot ;
2021-04-01 18:38:40 -04:00
PreviousScale = * Scale ;
2023-05-25 18:45:39 -04:00
PreviousScaleRange = CurrentScaleRange ;
2021-04-01 18:38:40 -04:00
PreviousScale . Sort ( ) ;
2023-05-25 18:45:39 -04:00
PreviousNoteOut = Audio : : FMidiNoteQuantizer : : QuantizeMidiNote ( PreviousNoteIn , PreviousRoot , PreviousScale , PreviousScaleRange ) ;
2021-04-01 18:38:40 -04:00
}
// set the output value
* MidiNoteOut = PreviousNoteOut ;
}
# pragma endregion // Operator Implementation
# pragma region Node Declaration
class METASOUNDSTANDARDNODES_API FMidiNoteQuantizerNode : public FNodeFacade
{
public :
// public node api needs to define two conversion constructors:
// (1: from FString)
2021-09-13 14:13:39 -04:00
FMidiNoteQuantizerNode ( const FVertexName & InInstanceName , const FGuid & InInstanceID )
2021-04-01 18:38:40 -04:00
: FNodeFacade ( InInstanceName , InInstanceID , TFacadeOperatorClass < FMidiNoteQuantizerOperator > ( ) )
{ }
// (2: From an NodeInitData struct)
FMidiNoteQuantizerNode ( const FNodeInitData & InInitData )
: FMidiNoteQuantizerNode ( InInitData . InstanceName , InInitData . InstanceID )
{ }
} ;
# pragma endregion // Node Declaration
# pragma region Node Registration
METASOUND_REGISTER_NODE ( FMidiNoteQuantizerNode ) ;
# pragma endregion // Node Registration
} // namespace Metasound
2022-03-31 16:49:59 -04:00
# undef LOCTEXT_NAMESPACE //MetasoundBasicFilterNodes