2021-06-22 14:45:41 -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 "DSP/Dsp.h"
|
|
|
|
|
#include "MetasoundStandardNodesCategories.h"
|
|
|
|
|
#include "MetasoundFacade.h"
|
|
|
|
|
#include "MetasoundParamHelper.h"
|
|
|
|
|
#include "DSP/FloatArrayMath.h"
|
|
|
|
|
#include "DSP/BitCrusher.h"
|
|
|
|
|
|
|
|
|
|
#define LOCTEXT_NAMESPACE "MetasoundStandardNodes_BitcrusherNode"
|
|
|
|
|
|
|
|
|
|
namespace Metasound
|
|
|
|
|
{
|
|
|
|
|
/* Mid-Side Encoder */
|
|
|
|
|
namespace BitcrusherVertexNames
|
|
|
|
|
{
|
|
|
|
|
METASOUND_PARAM(InputAudio, "Audio", "Incoming audio signal to bitcrush.");
|
|
|
|
|
METASOUND_PARAM(InputSampleRate, "Sample Rate", "The sampling frequency to downsample the audio to.");
|
|
|
|
|
METASOUND_PARAM(InputBitDepth, "Bit Depth", "The bit resolution to reduce the audio to.");
|
|
|
|
|
|
|
|
|
|
METASOUND_PARAM(OutputAudio, "Audio", "The bitcrushed audio signal.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Operator Class
|
|
|
|
|
class FBitcrusherOperator : public TExecutableOperator<FBitcrusherOperator>
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
|
2023-10-13 19:29:51 -04:00
|
|
|
FBitcrusherOperator(const FBuildOperatorParams& InParams,
|
2021-06-22 14:45:41 -04:00
|
|
|
const FAudioBufferReadRef& InAudio,
|
|
|
|
|
const FFloatReadRef& InCrushedSampleRate,
|
|
|
|
|
const FFloatReadRef& InCrushedBitDepth)
|
|
|
|
|
: AudioInput(InAudio)
|
2023-03-02 14:40:35 -05:00
|
|
|
, AudioOutput(FAudioBufferWriteRef::CreateNew(InParams.OperatorSettings))
|
2021-06-22 14:45:41 -04:00
|
|
|
, CrushedSampleRate(InCrushedSampleRate)
|
|
|
|
|
, CrushedBitDepth(InCrushedBitDepth)
|
2023-03-02 14:40:35 -05:00
|
|
|
, MaxSampleRate(InParams.OperatorSettings.GetSampleRate())
|
2021-06-22 14:45:41 -04:00
|
|
|
{
|
2023-03-02 14:40:35 -05:00
|
|
|
Reset(InParams);
|
2021-06-22 14:45:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const FNodeClassMetadata& GetNodeInfo()
|
|
|
|
|
{
|
|
|
|
|
auto CreateNodeClassMetadata = []() -> FNodeClassMetadata
|
|
|
|
|
{
|
|
|
|
|
FVertexInterface NodeInterface = DeclareVertexInterface();
|
|
|
|
|
|
|
|
|
|
FNodeClassMetadata Metadata
|
|
|
|
|
{
|
2021-08-09 15:08:37 -04:00
|
|
|
FNodeClassName { StandardNodes::Namespace, "Bitcrusher", StandardNodes::AudioVariant },
|
2021-06-22 14:45:41 -04:00
|
|
|
1, // Major Version
|
|
|
|
|
0, // Minor Version
|
2022-02-10 18:36:47 -05:00
|
|
|
METASOUND_LOCTEXT("BitcrusherDisplayName", "Bitcrusher"),
|
|
|
|
|
METASOUND_LOCTEXT("BitcrusherDesc", "Downsamples and lowers the bit-depth of an incoming audio signal."),
|
2021-06-22 14:45:41 -04:00
|
|
|
PluginAuthor,
|
|
|
|
|
PluginNodeMissingPrompt,
|
|
|
|
|
NodeInterface,
|
2021-08-09 15:08:37 -04:00
|
|
|
{ NodeCategories::Filters },
|
|
|
|
|
{ },
|
|
|
|
|
FNodeDisplayStyle()
|
2021-06-22 14:45:41 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return Metadata;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const FNodeClassMetadata Metadata = CreateNodeClassMetadata();
|
|
|
|
|
return Metadata;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const FVertexInterface& DeclareVertexInterface()
|
|
|
|
|
{
|
|
|
|
|
using namespace BitcrusherVertexNames;
|
|
|
|
|
|
|
|
|
|
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(InputSampleRate), 8000.0f),
|
|
|
|
|
TInputDataVertex<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(InputBitDepth), 8.0f)
|
2021-06-22 14:45:41 -04:00
|
|
|
),
|
|
|
|
|
FOutputVertexInterface(
|
2022-03-31 16:49:59 -04:00
|
|
|
TOutputDataVertex<FAudioBuffer>(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputAudio))
|
2021-06-22 14:45:41 -04:00
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return Interface;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-22 13:28:27 -04:00
|
|
|
|
|
|
|
|
virtual void BindInputs(FInputVertexInterfaceData& InOutVertexData) override
|
2021-06-22 14:45:41 -04:00
|
|
|
{
|
|
|
|
|
using namespace BitcrusherVertexNames;
|
|
|
|
|
|
2023-05-22 13:28:27 -04:00
|
|
|
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InputAudio), AudioInput);
|
|
|
|
|
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InputSampleRate), CrushedSampleRate);
|
|
|
|
|
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(InputBitDepth), CrushedBitDepth);
|
|
|
|
|
}
|
2021-06-22 14:45:41 -04:00
|
|
|
|
2023-05-22 13:28:27 -04:00
|
|
|
virtual void BindOutputs(FOutputVertexInterfaceData& InOutVertexData) override
|
|
|
|
|
{
|
|
|
|
|
using namespace BitcrusherVertexNames;
|
2021-06-22 14:45:41 -04:00
|
|
|
|
2023-05-22 13:28:27 -04:00
|
|
|
InOutVertexData.BindReadVertex(METASOUND_GET_PARAM_NAME(OutputAudio), AudioOutput);
|
|
|
|
|
}
|
2021-06-22 14:45:41 -04:00
|
|
|
|
2023-05-22 13:28:27 -04:00
|
|
|
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-06-22 14:45:41 -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-06-22 14:45:41 -04:00
|
|
|
}
|
|
|
|
|
|
2023-10-13 19:29:51 -04:00
|
|
|
static TUniquePtr<IOperator> CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults)
|
2021-06-22 14:45:41 -04:00
|
|
|
{
|
|
|
|
|
using namespace BitcrusherVertexNames;
|
2023-10-13 19:29:51 -04:00
|
|
|
const FInputVertexInterfaceData& InputData = InParams.InputData;
|
2021-06-22 14:45:41 -04:00
|
|
|
|
2023-10-13 19:29:51 -04:00
|
|
|
FAudioBufferReadRef AudioIn = InputData.GetOrConstructDataReadReference<FAudioBuffer>(METASOUND_GET_PARAM_NAME(InputAudio), InParams.OperatorSettings);
|
|
|
|
|
FFloatReadRef SampleRateIn = InputData.GetOrCreateDefaultDataReadReference<float>(METASOUND_GET_PARAM_NAME(InputSampleRate), InParams.OperatorSettings);
|
|
|
|
|
FFloatReadRef BitDepthIn = InputData.GetOrCreateDefaultDataReadReference<float>(METASOUND_GET_PARAM_NAME(InputBitDepth), InParams.OperatorSettings);
|
2021-06-22 14:45:41 -04:00
|
|
|
|
2023-03-02 14:40:35 -05:00
|
|
|
return MakeUnique<FBitcrusherOperator>(InParams, AudioIn, SampleRateIn, BitDepthIn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Reset(const IOperator::FResetParams& InParams)
|
|
|
|
|
{
|
|
|
|
|
AudioOutput->Zero();
|
|
|
|
|
|
|
|
|
|
// Passing in 1 for NumChannels because the node takes one audio input
|
|
|
|
|
Bitcrusher.Init(InParams.OperatorSettings.GetSampleRate(), 1);
|
|
|
|
|
|
|
|
|
|
const float CurSampleRate = FMath::Clamp(*CrushedSampleRate, 1.0f, MaxSampleRate);
|
|
|
|
|
const float CurBitDepth = FMath::Clamp(*CrushedBitDepth, 1.0f, Bitcrusher.GetMaxBitDepth());
|
|
|
|
|
|
|
|
|
|
Bitcrusher.SetSampleRateCrush(CurSampleRate);
|
|
|
|
|
Bitcrusher.SetBitDepthCrush(CurBitDepth);
|
|
|
|
|
|
|
|
|
|
PrevSampleRate = CurSampleRate;
|
|
|
|
|
PrevBitDepth = CurBitDepth;
|
2021-06-22 14:45:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Execute()
|
|
|
|
|
{
|
|
|
|
|
const int32 NumFrames = AudioInput->Num();
|
|
|
|
|
if (NumFrames != AudioOutput->Num())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-06 19:46:23 -04:00
|
|
|
const float CurrSampleRate = FMath::Clamp(*CrushedSampleRate, 1.0f, MaxSampleRate);
|
2021-06-22 14:45:41 -04:00
|
|
|
if (!FMath::IsNearlyEqual(PrevSampleRate, CurrSampleRate))
|
|
|
|
|
{
|
|
|
|
|
Bitcrusher.SetSampleRateCrush(CurrSampleRate);
|
|
|
|
|
PrevSampleRate = CurrSampleRate;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-06 19:46:23 -04:00
|
|
|
const float CurrBitDepth = FMath::Clamp(*CrushedBitDepth, 1.0f, Bitcrusher.GetMaxBitDepth());
|
2021-06-22 14:45:41 -04:00
|
|
|
if (!FMath::IsNearlyEqual(PrevBitDepth, CurrBitDepth))
|
|
|
|
|
{
|
|
|
|
|
Bitcrusher.SetBitDepthCrush(CurrBitDepth);
|
|
|
|
|
PrevBitDepth = CurrBitDepth;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Bitcrusher.ProcessAudio(AudioInput->GetData(), NumFrames, AudioOutput->GetData());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
|
|
// Audio input and output
|
|
|
|
|
FAudioBufferReadRef AudioInput;
|
|
|
|
|
FAudioBufferWriteRef AudioOutput;
|
|
|
|
|
|
|
|
|
|
// User-set sample rate the audio will be converted to
|
|
|
|
|
FFloatReadRef CrushedSampleRate;
|
|
|
|
|
// User-set bit depth for each sample
|
|
|
|
|
FFloatReadRef CrushedBitDepth;
|
|
|
|
|
|
|
|
|
|
// Internal DSP bitcrusher
|
|
|
|
|
Audio::FBitCrusher Bitcrusher;
|
|
|
|
|
|
|
|
|
|
// Cached input parameters
|
|
|
|
|
float PrevSampleRate;
|
|
|
|
|
float PrevBitDepth;
|
2022-10-06 19:46:23 -04:00
|
|
|
const float MaxSampleRate;
|
2021-06-22 14:45:41 -04:00
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Node Class
|
|
|
|
|
class FBitcrusherNode : public FNodeFacade
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
FBitcrusherNode(const FNodeInitData& InitData)
|
|
|
|
|
: FNodeFacade(InitData.InstanceName, InitData.InstanceID, TFacadeOperatorClass<FBitcrusherOperator>())
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Register node
|
|
|
|
|
METASOUND_REGISTER_NODE(FBitcrusherNode)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|