Skip to content

Commit 5c75c1b

Browse files
committed
Boilerplate code for new metasound node
1 parent af2500c commit 5c75c1b

File tree

6 files changed

+292
-26
lines changed

6 files changed

+292
-26
lines changed

Sfizz4Unreal.uplugin

+19-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
22
"FileVersion": 3,
33
"Version": 1,
4-
"VersionName": "1.0",
4+
"VersionName": "0.1",
55
"FriendlyName": "Sfizz4Unreal",
66
"Description": "MIDI rendering in Metasounds through Sfizz ",
7-
"Category": "Other",
7+
"Category": "Audio",
88
"CreatedBy": "Amir Ben-Kiki",
9-
"CreatedByURL": "",
10-
"DocsURL": "",
9+
"CreatedByURL": "https://github.com/Amir-BK",
10+
"DocsURL": "https://github.com/Amir-BK/Sfizz4Unreal",
1111
"MarketplaceURL": "",
1212
"SupportURL": "",
1313
"CanContainContent": true,
@@ -18,7 +18,21 @@
1818
{
1919
"Name": "Sfizz4Unreal",
2020
"Type": "Runtime",
21-
"LoadingPhase": "Default"
21+
"LoadingPhase": "PreDefault"
22+
}
23+
],
24+
"Plugins": [
25+
{
26+
"Name": "Synthesis",
27+
"Enabled": true
28+
},
29+
{
30+
"Name": "Harmonix",
31+
"Enabled": true
32+
},
33+
{
34+
"Name": "Metasound",
35+
"Enabled": true
2236
}
2337
]
2438
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// Copyright Epic Games, Inc. All Rights Reserved.
2+
3+
#include "CoreMinimal.h"
4+
#include "MetasoundExecutableOperator.h"
5+
#include "MetasoundFacade.h"
6+
#include "MetasoundNodeInterface.h"
7+
#include "MetasoundParamHelper.h"
8+
#include "MetasoundSampleCounter.h"
9+
#include "MetasoundStandardNodesCategories.h"
10+
#include "MetasoundVertex.h"
11+
12+
#include "HarmonixMetasound/DataTypes/MidiStream.h"
13+
#include "HarmonixMetasound/DataTypes/MusicTransport.h"
14+
//#include "MidiStreamTrackIsolatorNode.h"
15+
16+
#include "SfizzSynthNode.h"
17+
//#include "MidiTrackIsolator.h"
18+
19+
#define LOCTEXT_NAMESPACE "Sfizz4Unreal_SfizzSyntNode"
20+
21+
namespace Sfizz4Unreal::SfizzSynthNode
22+
{
23+
using namespace Metasound;
24+
using namespace HarmonixMetasound;
25+
26+
const FNodeClassName& GetClassName()
27+
{
28+
static FNodeClassName ClassName
29+
{
30+
"Sfizz",
31+
"SfizzSynth",
32+
""
33+
};
34+
return ClassName;
35+
}
36+
37+
int32 GetCurrentMajorVersion()
38+
{
39+
return 1;
40+
}
41+
42+
namespace Inputs
43+
{
44+
DEFINE_INPUT_METASOUND_PARAM(Enable, "Enable", "Enable");
45+
DEFINE_INPUT_METASOUND_PARAM(MidiStream, "MidiStream", "MidiStream");
46+
DEFINE_INPUT_METASOUND_PARAM(MinTrackIndex, "Track Index", "Track");
47+
DEFINE_INPUT_METASOUND_PARAM(MaxTrackIndex, "Channel Index", "Channel");
48+
DEFINE_INPUT_METASOUND_PARAM(MidiDeviceName, "Midi Device Name", "The name of the midi input device we want to receive with this node")
49+
//DEFINE_INPUT_METASOUND_PARAM(IncludeConductorTrack, "Include Conductor Track", "Enable to include the conductor track (AKA track 0)");
50+
}
51+
52+
namespace Outputs
53+
{
54+
DEFINE_OUTPUT_METASOUND_PARAM(MidiStream, "MidiStream", "MidiStream");
55+
}
56+
57+
class FSfizzSynthMetasoundOperator final : public TExecutableOperator<FSfizzSynthMetasoundOperator>
58+
{
59+
public:
60+
static const FNodeClassMetadata& GetNodeInfo()
61+
{
62+
auto InitNodeInfo = []() -> FNodeClassMetadata
63+
{
64+
FNodeClassMetadata Info;
65+
Info.ClassName = GetClassName();
66+
Info.MajorVersion = 1;
67+
Info.MinorVersion = 0;
68+
Info.DisplayName = INVTEXT("Sfizz Sampler");
69+
Info.Description = INVTEXT("Renders MIDI Stream to audio data using Sfizz");
70+
Info.Author = PluginAuthor;
71+
Info.PromptIfMissing = PluginNodeMissingPrompt;
72+
Info.DefaultInterface = GetVertexInterface();
73+
Info.CategoryHierarchy = { INVTEXT("Sfizz"), NodeCategories::Music };
74+
return Info;
75+
};
76+
77+
static const FNodeClassMetadata Info = InitNodeInfo();
78+
79+
return Info;
80+
}
81+
82+
static const FVertexInterface& GetVertexInterface()
83+
{
84+
static const FVertexInterface Interface(
85+
FInputVertexInterface(
86+
TInputDataVertex<bool>(METASOUND_GET_PARAM_NAME_AND_METADATA(Inputs::Enable), true),
87+
TInputDataVertex<FMidiStream>(METASOUND_GET_PARAM_NAME_AND_METADATA(Inputs::MidiStream)),
88+
TInputDataVertex<int32>(METASOUND_GET_PARAM_NAME_AND_METADATA(Inputs::MinTrackIndex), 0),
89+
TInputDataVertex<int32>(METASOUND_GET_PARAM_NAME_AND_METADATA(Inputs::MaxTrackIndex), 0),
90+
TInputDataVertex<FString>(METASOUND_GET_PARAM_NAME_AND_METADATA(Inputs::MidiDeviceName))
91+
//TInputDataVertex<bool>(METASOUND_GET_PARAM_NAME_AND_METADATA(Inputs::IncludeConductorTrack), false)
92+
),
93+
FOutputVertexInterface(
94+
TOutputDataVertex<FMidiStream>(METASOUND_GET_PARAM_NAME_AND_METADATA(Outputs::MidiStream))
95+
)
96+
);
97+
98+
return Interface;
99+
}
100+
101+
struct FInputs
102+
{
103+
FBoolReadRef Enabled;
104+
FMidiStreamReadRef MidiStream;
105+
FInt32ReadRef MinTrackIndex;
106+
FInt32ReadRef MaxTrackIndex;
107+
FStringReadRef MidiDeviceName;
108+
//FBoolReadRef IncludeConductorTrack;
109+
};
110+
111+
struct FOutputs
112+
{
113+
FMidiStreamWriteRef MidiStream;
114+
};
115+
116+
static TUniquePtr<IOperator> CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults)
117+
{
118+
const FInputVertexInterfaceData& InputData = InParams.InputData;
119+
120+
FInputs Inputs
121+
{
122+
InputData.GetOrCreateDefaultDataReadReference<bool>(Inputs::EnableName, InParams.OperatorSettings),
123+
InputData.GetOrConstructDataReadReference<FMidiStream>(Inputs::MidiStreamName),
124+
InputData.GetOrCreateDefaultDataReadReference<int32>(Inputs::MinTrackIndexName, InParams.OperatorSettings),
125+
InputData.GetOrCreateDefaultDataReadReference<int32>(Inputs::MaxTrackIndexName, InParams.OperatorSettings),
126+
InputData.GetOrCreateDefaultDataReadReference<FString>(Inputs::MidiDeviceNameName, InParams.OperatorSettings)
127+
//InputData.GetOrCreateDefaultDataReadReference<bool>(Inputs::IncludeConductorTrackName, InParams.OperatorSettings)
128+
};
129+
130+
FOutputs Outputs
131+
{
132+
FMidiStreamWriteRef::CreateNew()
133+
};
134+
135+
return MakeUnique<FSfizzSynthMetasoundOperator>(InParams, MoveTemp(Inputs), MoveTemp(Outputs));
136+
}
137+
138+
FSfizzSynthMetasoundOperator(const FBuildOperatorParams& InParams, FInputs&& InInputs, FOutputs&& InOutputs)
139+
: Inputs(MoveTemp(InInputs))
140+
, Outputs(MoveTemp(InOutputs))
141+
{
142+
Reset(InParams);
143+
}
144+
145+
virtual void BindInputs(FInputVertexInterfaceData& InVertexData) override
146+
{
147+
InVertexData.BindReadVertex(Inputs::EnableName, Inputs.Enabled);
148+
InVertexData.BindReadVertex(Inputs::MidiStreamName, Inputs.MidiStream);
149+
InVertexData.BindReadVertex(Inputs::MinTrackIndexName, Inputs.MinTrackIndex);
150+
InVertexData.BindReadVertex(Inputs::MaxTrackIndexName, Inputs.MaxTrackIndex);
151+
InVertexData.BindReadVertex(Inputs::MidiDeviceNameName, Inputs.MidiDeviceName);
152+
//InVertexData.BindReadVertex(Inputs::IncludeConductorTrackName, Inputs.IncludeConductorTrack);
153+
}
154+
155+
virtual void BindOutputs(FOutputVertexInterfaceData& InVertexData) override
156+
{
157+
InVertexData.BindReadVertex(Outputs::MidiStreamName, Outputs.MidiStream);
158+
}
159+
160+
void Reset(const FResetParams&)
161+
{
162+
163+
}
164+
165+
166+
//destructor
167+
virtual ~FSfizzSynthMetasoundOperator()
168+
{
169+
UE_LOG(LogTemp, Log, TEXT("Sfizz Synth Node Destructor"));
170+
171+
}
172+
173+
174+
void Execute()
175+
{
176+
//Filter.SetFilterValues(*Inputs.MinTrackIndex, *Inputs.MaxTrackIndex, false);
177+
178+
Outputs.MidiStream->PrepareBlock();
179+
180+
if (*Inputs.Enabled)
181+
{
182+
//stream current tick?
183+
//int32 CurrentTick = Inputs.MidiStream->GetClock()->GetCurrentMidiTick();
184+
//Inputs.MidiStream->Add
185+
186+
PendingMessages.Empty();
187+
//Filter.Process(*Inputs.MidiStream, *Outputs.MidiStream);
188+
}
189+
}
190+
private:
191+
FInputs Inputs;
192+
FOutputs Outputs;
193+
194+
FDelegateHandle RawEventDelegateHandle;
195+
int32 TickOffset = 0; //in theory we can start when the stream tick is different from the device tick, we'll see
196+
TArray<TTuple<int32, FMidiMsg>> PendingMessages;
197+
//unDAWMetasounds::TrackIsolatorOP::FMidiTrackIsolator Filter;
198+
};
199+
200+
class FSfizzSynthNode final : public FNodeFacade
201+
{
202+
public:
203+
explicit FSfizzSynthNode(const FNodeInitData& InInitData)
204+
: FNodeFacade(InInitData.InstanceName, InInitData.InstanceID, TFacadeOperatorClass<FSfizzSynthMetasoundOperator>())
205+
{}
206+
virtual ~FSfizzSynthNode() override = default;
207+
};
208+
209+
METASOUND_REGISTER_NODE(FSfizzSynthNode)
210+
}
211+
212+
#undef LOCTEXT_NAMESPACE // "HarmonixMetaSound"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright Epic Games, Inc. All Rights Reserved.
2+
3+
#pragma once
4+
5+
#include "MetasoundNodeInterface.h"
6+
7+
#include "HarmonixMetasound/Common.h"
8+
9+
namespace Sfizz4Unreal::SfizzSynthNode
10+
{
11+
SFIZZ4UNREAL_API const Metasound::FNodeClassName& GetClassName();
12+
SFIZZ4UNREAL_API int32 GetCurrentMajorVersion();
13+
14+
//namespace Inputs
15+
//{
16+
// DECLARE_METASOUND_PARAM_ALIAS(Enable);
17+
// DECLARE_METASOUND_PARAM_ALIAS(MidiStream);
18+
// DECLARE_METASOUND_PARAM_EXTERN(MinTrackIndex);
19+
// DECLARE_METASOUND_PARAM_EXTERN(MaxTrackIndex);
20+
// DECLARE_METASOUND_PARAM_EXTERN(IncludeConductorTrack);
21+
//}
22+
23+
//namespace Outputs
24+
//{
25+
// DECLARE_METASOUND_PARAM_ALIAS(MidiStream);
26+
//}
27+
}

Source/Sfizz4Unreal/Private/SfizzSynthComponent.cpp

+11-9
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ USfizzSynthComponent::~USfizzSynthComponent()
1313
{
1414
sfizz_free(SfizzSynth);
1515
SfizzSynth = nullptr;
16-
DecodedChannelsStartPtr[0] = nullptr;
17-
DecodedChannelsStartPtr[1] = nullptr;
16+
DeinterleavedBuffer[0] = nullptr;
17+
DeinterleavedBuffer[1] = nullptr;
1818
DecodedAudioDataBuffer.clear();
1919

2020
}
@@ -26,8 +26,8 @@ void USfizzSynthComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
2626

2727

2828
sfizz_free(SfizzSynth);
29-
DecodedChannelsStartPtr[0] = nullptr;
30-
DecodedChannelsStartPtr[1] = nullptr;
29+
DeinterleavedBuffer[0] = nullptr;
30+
DeinterleavedBuffer[1] = nullptr;
3131
DecodedAudioDataBuffer.clear();
3232

3333
}
@@ -40,9 +40,9 @@ bool USfizzSynthComponent::Init(int32& SampleRate)
4040
sfizz_set_sample_rate(SfizzSynth, SampleRate);
4141
sfizz_set_samples_per_block(SfizzSynth, 1024);
4242
DecodedAudioDataBuffer.resize(1024);
43-
DecodedChannelsStartPtr.resize(2);
44-
DecodedChannelsStartPtr[0] = DecodedAudioDataBuffer.data();
45-
DecodedChannelsStartPtr[1] = DecodedAudioDataBuffer.data() + 512;
43+
DeinterleavedBuffer.resize(2);
44+
DeinterleavedBuffer[0] = DecodedAudioDataBuffer.data();
45+
DeinterleavedBuffer[1] = DecodedAudioDataBuffer.data() + 512;
4646
//sfizz_set_num_voices(synth, 64);
4747
FString PathToSanitize = SfzAssetPath;
4848
PathToSanitize.TrimQuotesInline();
@@ -66,9 +66,11 @@ int32 USfizzSynthComponent::OnGenerateAudio(float* OutAudio, int32 NumSamples)
6666
return 0;
6767
}
6868

69-
sfizz_render_block(SfizzSynth, DecodedChannelsStartPtr.data(), 2, 512);
69+
//we give SFZ a pointer to the head of the deinterleaved array
70+
sfizz_render_block(SfizzSynth, DeinterleavedBuffer.data(), 2, 512);
7071

71-
Audio::BufferInterleave2ChannelFast(DecodedChannelsStartPtr[0], DecodedChannelsStartPtr[1], OutAudio, NumSamples / 2);
72+
//unreal audio system expects interleaved audio for multichannel
73+
Audio::BufferInterleave2ChannelFast(DeinterleavedBuffer[0], DeinterleavedBuffer[1], OutAudio, NumSamples / 2);
7274

7375
return NumSamples;
7476
}

Source/Sfizz4Unreal/Public/SfizzSynthComponent.h

+13-12
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,6 @@ class SFIZZ4UNREAL_API USfizzSynthComponent : public USynthComponent
1414
{
1515
GENERATED_BODY()
1616

17-
sfizz_synth_t* SfizzSynth;
18-
19-
~USfizzSynthComponent();
20-
21-
22-
std::vector<float> DecodedAudioDataBuffer;
23-
std::vector<float*> DecodedChannelsStartPtr;
24-
25-
//the local path for the SFZ sample bank, if you intend to use it in a packaged game make sure this is a local path and the the SFZ file and its samples are packaged
26-
UPROPERTY(EditAnywhere, Category = "Sfizz")
27-
FString SfzAssetPath;
28-
2917
public:
3018

3119
void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
@@ -42,5 +30,18 @@ class SFIZZ4UNREAL_API USfizzSynthComponent : public USynthComponent
4230
UFUNCTION(BlueprintCallable)
4331
void SendNoteOff(int32 NoteNumber, int32 Velocity);
4432

33+
~USfizzSynthComponent();
34+
35+
//the local path for the SFZ sample bank, if you intend to use it in a packaged game make sure this is a local path and the the SFZ file and its samples are packaged
36+
UPROPERTY(EditAnywhere, Category = "Sfizz")
37+
FString SfzAssetPath;
38+
39+
protected:
40+
sfizz_synth_t* SfizzSynth;
41+
42+
std::vector<float> DecodedAudioDataBuffer;
43+
std::vector<float*> DeinterleavedBuffer;
44+
45+
4546

4647
};

Source/Sfizz4Unreal/Sfizz4Unreal.Build.cs

+10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public class Sfizz4Unreal : ModuleRules
88
public Sfizz4Unreal(ReadOnlyTargetRules Target) : base(Target)
99
{
1010
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
11+
IWYUSupport = IWYUSupport.None;
1112
PublicIncludePaths.Add(Path.Combine(PluginDirectory, "Sfizz"));
1213

1314

@@ -62,6 +63,15 @@ public Sfizz4Unreal(ReadOnlyTargetRules Target) : base(Target)
6263
"AudioMixer",
6364
"AudioMixerCore",
6465
"SignalProcessing",
66+
"MetasoundStandardNodes",
67+
"HarmonixDsp",
68+
"HarmonixMidi",
69+
"Harmonix",
70+
"HarmonixMetasound",
71+
"MetasoundEngine",
72+
"MetasoundGraphCore",
73+
"MetasoundFrontend",
74+
"MetasoundGenerator",
6575
// ... add private dependencies that you statically link with here ...
6676
}
6777
);

0 commit comments

Comments
 (0)