@HISEnberg @ulrik Yes, but please be aware that I am only a amateur. I hope that people who are really familiar with it will intervene here if I have described something incorrectly or badly.
SNEX nodes and C++ nodes look very similar and work almost the same. If you create a C++ template under ‘Tools -> Create C++ third party node template’ and view the C++ file in an editor, you will see that the basic functions correspond to those of the SNEX node. I have essentially just copied the code from the SNEX node into the respective functions of the C++ node.
When doing this, you must pay attention to the following:
- The variables declared in the function header must be named as they are used (for example, sometimes data and sometimes just d)
- The conversion of the data to FrameData in the process function must happen as it is already in the C++ template (I don't know why).
Specific C++ node specifications must then be made. In the ‘Metadata Definitions’ at the top, static constexpr int NumAudioFiles = 1 must be set, as we want to play an audio file. In addition, the ‘Play’ knob must be created in the createParameters function at the bottom.
Once all this is done, the node can be compiled via ‘Export -> Compile DSP networks as dll’. After restarting Hise, the SNEX node in ‘Script FX1’ can be replaced by the C++ node. The entire Scriptnode can then be compiled.
Here is my C++ Node version of the SNEX simple_player:
// ==================================| Third Party Node Template |==================================
#pragma once
#include <JuceHeader.h>
namespace project
{
using namespace juce;
using namespace hise;
using namespace scriptnode;
// ==========================| The node class with all required callbacks |==========================
template <int NV> struct s_player: public data::base
{
// Metadata Definitions ------------------------------------------------------------------------
SNEX_NODE(s_player);
struct MetadataClass
{
SN_NODE_ID("s_player");
};
// set to true if you want this node to have a modulation dragger
static constexpr bool isModNode() { return false; };
static constexpr bool isPolyphonic() { return NV > 1; };
// set to true if your node produces a tail
static constexpr bool hasTail() { return false; };
// set to true if your doesn't generate sound from silence and can be suspended when the input signal is silent
static constexpr bool isSuspendedOnSilence() { return false; };
// Undefine this method if you want a dynamic channel count
static constexpr int getFixChannelAmount() { return 2; };
// Define the amount and types of external data slots you want to use
static constexpr int NumTables = 0;
static constexpr int NumSliderPacks = 0;
static constexpr int NumAudioFiles = 1;
static constexpr int NumFilters = 0;
static constexpr int NumDisplayBuffers = 0;
// Global Variables -------------------------------------------------------------------------
span<float, 8> dummy = { 0.0f };
ExternalData ed;
// this is an array of two dynamic float buffers that will
// hold the recorded data
span<dyn<float>, 2> stereoData;
int currentIndex = 0;
int isPlaying = 0;
// Scriptnode Callbacks ------------------------------------------------------------------------
void prepare(PrepareSpecs specs){}
void reset(){}
void handleHiseEvent(HiseEvent& e){}
template <typename T> void processFrame(T& data)
{
// No interpolation necessary, just copy the
// recorded buffer
data[0] = stereoData[0][currentIndex];
data[1] = stereoData[1][currentIndex];
currentIndex++;
if(currentIndex >= ed.numSamples)
currentIndex = 0;
}
template <typename T> void process(T& data)
{
// We need to make sure that the audio render thread
// isn't processing this function while the external data slot
// is modified, so we use a DataReadLock with a try read access
DataReadLock sl(ed, true);
// check if the data lock was grabbed successfully, the playback
// is enabled and the data slot is not empty
if(sl.isLocked() && isPlaying && ed.numSamples > 0)
{
static constexpr int NumChannels = getFixChannelAmount();
// Cast the dynamic channel data to a fixed channel amount
auto& fixData = data.template as<ProcessData<NumChannels>>();
auto fd = fixData.toFrameData();
while(fd.next())
processFrame(fd.toSpan());
// set the display position
ed.setDisplayedValue((double)currentIndex);
}
}
int handleModulation(double& value)
{
return 0;
}
void setExternalData(const ExternalData& d, int index)
{
ed = d;
if(ed.numSamples > 0)
{
// refer the stereo data object to the audio buffer
ed.referBlockTo(stereoData[0], 0);
ed.referBlockTo(stereoData[1], Math.min(d.numChannels-1, 1));
}
else
{
// route it to a dummy buffer if the slot is empty
// (that's not required but otherwise the stereo data
// might point to a dangling buffer)
stereoData[0].referTo(dummy, 8, 0);
stereoData[1].referTo(dummy, 8, 0);
}
}
// Parameter Functions -------------------------------------------------------------------------
template <int P> void setParameter(double v)
{
auto shouldBePlaying = v > 0.5;
if(isPlaying != shouldBePlaying)
{
isPlaying = shouldBePlaying;
currentIndex = 0;
}
}
void createParameters(ParameterDataList& data)
{
{
// Create a parameter like this
parameter::data p("Play", { 0.0, 1.0});
// The template parameter (<0>) will be forwarded to setParameter<P>()
registerCallback<0>(p);
p.setDefaultValue(0);
data.add(std::move(p));
}
}
};
}