Skip to content

Commit

Permalink
Merge pull request #47 from matthewscharles/StereoTools
Browse files Browse the repository at this point in the history
Stereo tools
  • Loading branch information
matthewscharles authored Dec 7, 2024
2 parents 88263ec + 6a2d10e commit 9cec973
Show file tree
Hide file tree
Showing 10 changed files with 1,467 additions and 536 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This repository contains a collection of custom nodes for Unreal Engine's MetaSo
![Screenshot of a selection of custom nodes in Metasound, as listed in the table below.](./docs/svg/_nodes.svg)

## Branches
There are currently fourteen nodes available in the *MetaSoundBranches* plugin for testing, with several more in development:
There are currently sixteen nodes available in the *MetaSoundBranches* plugin for testing, with several more in development:

| Node | Category | Description |
|-------------------------------------|-----------------------|---------------------------------------------------------------------------------------------------------------------|
Expand All @@ -18,7 +18,9 @@ There are currently fourteen nodes available in the *MetaSoundBranches* plugin f
| `Shift Register` | Modulation | An eight-stage shift register for floats. |
| `Slew (audio)` | Filters | A slew limiter to smooth out the rise and fall times of an audio signal. |
| `Slew (float)` | Filters | A slew limiter to smooth out the rise and fall times of an audio signal. |
| `Stereo Balance` | Spatialization | Adjusts the balance of a stereo signal. |
| `Stereo Crossfade` | Envelopes | A crossfader for stereo signals. |
| `Stereo Gain` | Mix | Simple gain for a stereo signal. |
| `Stereo Inverter` | Spatialization | Invert and/or swap stereo channels. |
| `Stereo Width` | Spatialization | Stereo width adjustment (0-200%), using mid-side processing. |
| `Tuning` | Tuning | Quantize a float value to a custom 12-note tuning, with adjustment in cents per-note. |
Expand All @@ -28,7 +30,7 @@ Upon installing the plugin, these items will appear in the sub-category `Branche
## Installation

### Downloadable binaries
- Download for Windows and Mac here: [0.1.0-alpha](https://github.com/matthewscharles/metasound-plugins/releases/tag/0.1.0)
- Download for Windows and Mac here: [💾 0.1.0-alpha](https://github.com/matthewscharles/metasound-plugins/releases/tag/0.1.0)
- Extract the contents of the zip file into the plugins folder of your project or engine as preferred.

### Building from source
Expand Down
171 changes: 171 additions & 0 deletions Source/MetasoundBranches/Private/MetasoundStereoBalanceNode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#include "MetasoundBranches/Public/MetasoundStereoBalanceNode.h"
#include "MetasoundExecutableOperator.h" // TExecutableOperator class
#include "MetasoundPrimitives.h" // ReadRef and WriteRef descriptions for bool, int32, float, and string
#include "MetasoundNodeRegistrationMacro.h" // METASOUND_LOCTEXT and METASOUND_REGISTER_NODE macros
#include "MetasoundStandardNodesNames.h" // StandardNodes namespace
#include "MetasoundFacade.h" // FNodeFacade class, eliminates the need for a fair amount of boilerplate code
#include "MetasoundParamHelper.h" // METASOUND_PARAM and METASOUND_GET_PARAM family of macros
#include "Math/UnrealMathUtility.h" // For FMath functions

#define LOCTEXT_NAMESPACE "MetasoundStandardNodes_BalanceNode"

namespace Metasound
{
namespace BalanceNodeNames
{
METASOUND_PARAM(InputLeftSignal, "In L", "Left channel of the stereo input signal.");
METASOUND_PARAM(InputRightSignal, "In R", "Right channel of the stereo input signal.");
METASOUND_PARAM(InputBalance, "Balance", "Balance control ranging from -1.0 (full left) to 1.0 (full right).");

METASOUND_PARAM(OutputLeftSignal, "Out L", "Left channel of the adjusted stereo output signal.");
METASOUND_PARAM(OutputRightSignal, "Out R", "Right channel of the adjusted stereo output signal.");
}

class FBalanceOperator : public TExecutableOperator<FBalanceOperator>
{
public:
FBalanceOperator(
const FOperatorSettings& InSettings,
const FAudioBufferReadRef& InLeftSignal,
const FAudioBufferReadRef& InRightSignal,
const FFloatReadRef& InBalance)
: InputLeftSignal(InLeftSignal)
, InputRightSignal(InRightSignal)
, InputBalance(InBalance)
, OutputLeftSignal(FAudioBufferWriteRef::CreateNew(InSettings))
, OutputRightSignal(FAudioBufferWriteRef::CreateNew(InSettings))
{
}

static const FVertexInterface& DeclareVertexInterface()
{
using namespace BalanceNodeNames;

static const FVertexInterface Interface(
FInputVertexInterface(
TInputDataVertexModel<FAudioBuffer>(METASOUND_GET_PARAM_NAME_AND_METADATA(InputLeftSignal)),
TInputDataVertexModel<FAudioBuffer>(METASOUND_GET_PARAM_NAME_AND_METADATA(InputRightSignal)),
TInputDataVertexModel<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(InputBalance), 0.0f) // Default balance is centered
),
FOutputVertexInterface(
TOutputDataVertexModel<FAudioBuffer>(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputLeftSignal)),
TOutputDataVertexModel<FAudioBuffer>(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputRightSignal))
)
);

return Interface;
}

static const FNodeClassMetadata& GetNodeInfo()
{
auto CreateNodeClassMetadata = []() -> FNodeClassMetadata
{
FVertexInterface NodeInterface = DeclareVertexInterface();

FNodeClassMetadata Metadata;

Metadata.ClassName = { StandardNodes::Namespace, TEXT("Stereo Balance"), StandardNodes::AudioVariant };
Metadata.MajorVersion = 1;
Metadata.MinorVersion = 0;
Metadata.DisplayName = METASOUND_LOCTEXT("StereoGainNodeDisplayName", "Stereo Balance");
Metadata.Description = METASOUND_LOCTEXT("StereoGainNodeDesc", "Adjusts the balance of a stereo signal.");
Metadata.Author = "Charles Matthews";
Metadata.PromptIfMissing = PluginNodeMissingPrompt;
Metadata.DefaultInterface = NodeInterface;
Metadata.CategoryHierarchy = { METASOUND_LOCTEXT("Custom", "Branches") };
Metadata.Keywords = TArray<FText>();

return Metadata;
};

static const FNodeClassMetadata Metadata = CreateNodeClassMetadata();
return Metadata;
}

virtual FDataReferenceCollection GetInputs() const override
{
using namespace BalanceNodeNames;

FDataReferenceCollection InputDataReferences;

InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(InputLeftSignal), InputLeftSignal);
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(InputRightSignal), InputRightSignal);
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(InputBalance), InputBalance);

return InputDataReferences;
}

virtual FDataReferenceCollection GetOutputs() const override
{
using namespace BalanceNodeNames;

FDataReferenceCollection OutputDataReferences;

OutputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(OutputLeftSignal), OutputLeftSignal);
OutputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(OutputRightSignal), OutputRightSignal);

return OutputDataReferences;
}

static TUniquePtr<IOperator> CreateOperator(const FCreateOperatorParams& InParams, FBuildErrorArray& OutErrors)
{
using namespace BalanceNodeNames;

const Metasound::FDataReferenceCollection& InputCollection = InParams.InputDataReferences;
const Metasound::FInputVertexInterface& InputInterface = DeclareVertexInterface().GetInputInterface();

TDataReadReference<FAudioBuffer> InputLeftSignal = InputCollection.GetDataReadReferenceOrConstructWithVertexDefault<FAudioBuffer>(InputInterface, METASOUND_GET_PARAM_NAME(InputLeftSignal), InParams.OperatorSettings);
TDataReadReference<FAudioBuffer> InputRightSignal = InputCollection.GetDataReadReferenceOrConstructWithVertexDefault<FAudioBuffer>(InputInterface, METASOUND_GET_PARAM_NAME(InputRightSignal), InParams.OperatorSettings);
TDataReadReference<float> InputBalance = InputCollection.GetDataReadReferenceOrConstructWithVertexDefault<float>(InputInterface, METASOUND_GET_PARAM_NAME(InputBalance), InParams.OperatorSettings);

return MakeUnique<FBalanceOperator>(InParams.OperatorSettings, InputLeftSignal, InputRightSignal, InputBalance);
}

void Execute()
{
int32 NumFrames = InputLeftSignal->Num();

const float* LeftData = InputLeftSignal->GetData();
const float* RightData = InputRightSignal->GetData();
float* OutputLeftData = OutputLeftSignal->GetData();
float* OutputRightData = OutputRightSignal->GetData();

float Balance = FMath::Clamp(*InputBalance, -1.0f, 1.0f);

float Angle = (Balance + 1.0f) * (PI / 4.0f);

float LeftGain = FMath::Cos(Angle);
float RightGain = FMath::Sin(Angle);

for (int32 i = 0; i < NumFrames; ++i)
{
OutputLeftData[i] = LeftData[i] * LeftGain;
OutputRightData[i] = RightData[i] * RightGain;
}
}

private:

// Inputs
FAudioBufferReadRef InputLeftSignal;
FAudioBufferReadRef InputRightSignal;
FFloatReadRef InputBalance;

// Outputs
FAudioBufferWriteRef OutputLeftSignal;
FAudioBufferWriteRef OutputRightSignal;
};

class FBalanceNode : public FNodeFacade
{
public:
FBalanceNode(const FNodeInitData& InitData)
: FNodeFacade(InitData.InstanceName, InitData.InstanceID, TFacadeOperatorClass<FBalanceOperator>())
{
}
};

METASOUND_REGISTER_NODE(FBalanceNode);
}

#undef LOCTEXT_NAMESPACE
160 changes: 160 additions & 0 deletions Source/MetasoundBranches/Private/MetasoundStereoGainNode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#include "MetasoundBranches/Public/MetasoundStereoGainNode.h"
#include "MetasoundExecutableOperator.h" // TExecutableOperator class
#include "MetasoundPrimitives.h" // ReadRef and WriteRef descriptions for bool, int32, float, and string
#include "MetasoundNodeRegistrationMacro.h" // METASOUND_LOCTEXT and METASOUND_REGISTER_NODE macros
#include "MetasoundStandardNodesNames.h" // StandardNodes namespace
#include "MetasoundFacade.h" // FNodeFacade class
#include "MetasoundParamHelper.h" // METASOUND_PARAM and METASOUND_GET_PARAM family of macros

#define LOCTEXT_NAMESPACE "MetasoundStandardNodes_StereoGain"

namespace Metasound
{
namespace StereoGainNodeNames
{
METASOUND_PARAM(InputLeftSignal, "In L", "Left channel audio input.");
METASOUND_PARAM(InputRightSignal, "In R", "Right channel audio input.");
METASOUND_PARAM(InputGain, "Gain (Lin)", "Gain control (0.0 to 1.0).");

METASOUND_PARAM(OutputLeftSignal, "Out L", "Left output channel.");
METASOUND_PARAM(OutputRightSignal, "Out R", "Right output channel.");
}

class FStereoGainOperator : public TExecutableOperator<FStereoGainOperator>
{
public:
FStereoGainOperator(
const FOperatorSettings& InSettings,
const FAudioBufferReadRef& InLeftSignal,
const FAudioBufferReadRef& InRightSignal,
const FFloatReadRef& InGain)
: InputLeftSignal(InLeftSignal)
, InputRightSignal(InRightSignal)
, InputGain(InGain)
, OutputLeftSignal(FAudioBufferWriteRef::CreateNew(InSettings))
, OutputRightSignal(FAudioBufferWriteRef::CreateNew(InSettings))
{
}

static const FVertexInterface& DeclareVertexInterface()
{
using namespace StereoGainNodeNames;

static const FVertexInterface Interface(
FInputVertexInterface(
TInputDataVertexModel<FAudioBuffer>(METASOUND_GET_PARAM_NAME_AND_METADATA(InputLeftSignal)),
TInputDataVertexModel<FAudioBuffer>(METASOUND_GET_PARAM_NAME_AND_METADATA(InputRightSignal)),
TInputDataVertexModel<float>(METASOUND_GET_PARAM_NAME_AND_METADATA(InputGain), 1.0f)
),
FOutputVertexInterface(
TOutputDataVertexModel<FAudioBuffer>(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputLeftSignal)),
TOutputDataVertexModel<FAudioBuffer>(METASOUND_GET_PARAM_NAME_AND_METADATA(OutputRightSignal))
)
);

return Interface;
}

static const FNodeClassMetadata& GetNodeInfo()
{
auto CreateNodeClassMetadata = []() -> FNodeClassMetadata
{
FVertexInterface NodeInterface = DeclareVertexInterface();

FNodeClassMetadata Metadata;
Metadata.ClassName = { StandardNodes::Namespace, TEXT("Stereo Gain"), StandardNodes::AudioVariant };
Metadata.MajorVersion = 1;
Metadata.MinorVersion = 0;
Metadata.DisplayName = METASOUND_LOCTEXT("StereoGainNodeDisplayName", "Stereo Gain");
Metadata.Description = METASOUND_LOCTEXT("StereoGainNodeDesc", "Scale a stereo input to a gain value.");
Metadata.Author = "Charles Matthews";
Metadata.PromptIfMissing = PluginNodeMissingPrompt;
Metadata.DefaultInterface = NodeInterface;
Metadata.CategoryHierarchy = { METASOUND_LOCTEXT("Custom", "Branches") };
Metadata.Keywords = TArray<FText>();

return Metadata;
};

static const FNodeClassMetadata Metadata = CreateNodeClassMetadata();
return Metadata;
}

virtual FDataReferenceCollection GetInputs() const override
{
using namespace StereoGainNodeNames;

FDataReferenceCollection InputDataReferences;
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(InputLeftSignal), InputLeftSignal);
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(InputRightSignal), InputRightSignal);
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(InputGain), InputGain);

return InputDataReferences;
}

virtual FDataReferenceCollection GetOutputs() const override
{
using namespace StereoGainNodeNames;

FDataReferenceCollection OutputDataReferences;
OutputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(OutputLeftSignal), OutputLeftSignal);
OutputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(OutputRightSignal), OutputRightSignal);
return OutputDataReferences;
}

static TUniquePtr<IOperator> CreateOperator(const FCreateOperatorParams& InParams, FBuildErrorArray& OutErrors)
{
using namespace StereoGainNodeNames;

const Metasound::FDataReferenceCollection& InputCollection = InParams.InputDataReferences;
const Metasound::FInputVertexInterface& InputInterface = DeclareVertexInterface().GetInputInterface();

TDataReadReference<FAudioBuffer> InputLeftSignal = InputCollection.GetDataReadReferenceOrConstructWithVertexDefault<FAudioBuffer>(InputInterface, METASOUND_GET_PARAM_NAME(InputLeftSignal), InParams.OperatorSettings);
TDataReadReference<FAudioBuffer> InputRightSignal = InputCollection.GetDataReadReferenceOrConstructWithVertexDefault<FAudioBuffer>(InputInterface, METASOUND_GET_PARAM_NAME(InputRightSignal), InParams.OperatorSettings);
TDataReadReference<float> Gain = InputCollection.GetDataReadReferenceOrConstructWithVertexDefault<float>(InputInterface, METASOUND_GET_PARAM_NAME(InputGain), InParams.OperatorSettings);

return MakeUnique<FStereoGainOperator>(InParams.OperatorSettings, InputLeftSignal, InputRightSignal, Gain);
}

void Execute()
{
const int32 NumFrames = InputLeftSignal->Num();

const float* LeftData = InputLeftSignal->GetData();
const float* RightData = InputRightSignal->GetData();
float* OutputLeftData = OutputLeftSignal->GetData();
float* OutputRightData = OutputRightSignal->GetData();

const float GainVal = *InputGain;

for (int32 i = 0; i < NumFrames; ++i)
{
OutputLeftData[i] = LeftData[i] * GainVal;
OutputRightData[i] = RightData[i] * GainVal;
}
}

private:
// Inputs
FAudioBufferReadRef InputLeftSignal;
FAudioBufferReadRef InputRightSignal;
FFloatReadRef InputGain;

// Outputs
FAudioBufferWriteRef OutputLeftSignal;
FAudioBufferWriteRef OutputRightSignal;
};

class FStereoGainNode : public FNodeFacade
{
public:
FStereoGainNode(const FNodeInitData& InitData)
: FNodeFacade(InitData.InstanceName, InitData.InstanceID, TFacadeOperatorClass<FStereoGainOperator>())
{
}
};

METASOUND_REGISTER_NODE(FStereoGainNode);
}

#undef LOCTEXT_NAMESPACE
13 changes: 13 additions & 0 deletions Source/MetasoundBranches/Public/MetasoundStereoBalanceNode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include "Metasound.h"
#include "MetasoundNode.h"

namespace MetasoundBranches
{
class FMetasoundStereoBalanceNode : public Metasound::FNode
{
public:
FMetasoundStereoBalanceNode();
};
}
13 changes: 13 additions & 0 deletions Source/MetasoundBranches/Public/MetasoundStereoGainNode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include "Metasound.h"
#include "MetasoundNode.h"

namespace MetasoundBranches
{
class FMetasoundStereoGainNode : public Metasound::FNode
{
public:
FMetasoundStereoGainNode();
};
}
Loading

0 comments on commit 9cec973

Please sign in to comment.