Solved Finding Sample Rate, and Other C++ scriptnode questions
-
There are a few things that have been tripping me up. which are missing from the default c++ external 3rd party node template.
I've been trying to discover:
- What is the correct method to find the sample rate ?
- What is the best practice to get data out of a c++ node and into the main hise interface script?
- Are there are any tools already in place for time based effects (think LFO / Envelope)?
- It is possible (to easily) change the size of a frame and process very specific frame lengths (eg. in 0.4s frames)?
To have these things written up for reference, or included in a template would be incredibly useful!
I would be really appreciative if anyone knows the answers to any of these already because I've been making a mess trying to figure it out : )
-
-
For reference, here is my current template.
// ==================================| 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 NodeName: public data::base { // Metadata Definitions ------------------------------------------------------------------------ SNEX_NODE(NodeName); struct MetadataClass { SN_NODE_ID("NodeName"); }; // 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 = 0; static constexpr int NumFilters = 0; static constexpr int NumDisplayBuffers = 0; // Scriptnode Callbacks ------------------------------------------------------------------------ // User Controllable Variables float GAIN = 1.0f; // Internal Variables double sr = 0.0; // Sample rate, initialized here and set properly in the prepare function // Initialise the processing specs here void prepare(PrepareSpecs specs) { } void reset() { } void handleHiseEvent(HiseEvent& e) { } template <typename T> void process(T& data) { static constexpr int NumChannels = getFixChannelAmount(); // Cast the dynamic channel data to a fixed channel amount auto& fixData = data.template as<ProcessData<NumChannels>>(); // Create a FrameProcessor object auto fd = fixData.toFrameData(); while(fd.next()) { // Forward to frame processing processFrame(fd.toSpan()); } } float processSampleL(float s) { float shapedSample = s * GAIN; return shapedSample; } float processSampleR(float s) { float shapedSample = s * GAIN; return shapedSample; } template <typename T> void processFrame(T& data) { // Assuming `data` is a contiguous block with interleaved channels const size_t numChannels = 2; // We only have two channels const size_t numSamples = data.size() / numChannels; // Process each channel differently for (size_t channel = 0; channel < numChannels; ++channel) { for (size_t i = 0; i < numSamples; ++i) { size_t index = i * numChannels + channel; if (channel == 0) { // Process left channel data[index] = processSampleL(data[index]); } else if (channel == 1) { // Process right channel data[index] = processSampleR(data[index]); //processSampleR(data[index]); } } } } int handleModulation(double& value) { return 0; } void setExternalData(const ExternalData& data, int index) { } // Parameter Functions ------------------------------------------------------------------------- template <int P> void setParameter(double v) { if (P == 0) { // Update the GAIN parameter GAIN = static_cast<float>(v); } if (P == 1) { } } void createParameters(ParameterDataList& data) { { parameter::data p("GAIN", { 0.0, 1.0 }); registerCallback<0>(p); p.setDefaultValue(1.0); data.add(std::move(p)); } { parameter::data p("ParameterName", { 0.0, 1.0 }); registerCallback<0>(p); p.setDefaultValue(0.5); data.add(std::move(p)); } } }; }
-
@griffinboy
bump (srry) -
What is the correct method to find the sample rate ?
Grab it in the
prepare
callback (just as in SNEX). You get aPrepareSpecs
object that contains all processing information (channel count, sample rate, max blocksize, etc).What is the best practice to get data out of a c++ node and into the main hise interface script?
Depends on the data type, but it's way more complicated than it should be :)
Are there are any tools already in place for time based effects (think LFO / Envelope)?
No, but you can use every scriptnode node as C++ class as a member of your node class, eg.
envelope::ahdsr
. That's basically how the C++ generator works, but you can obviously use them yourself too. If you do so, you need to forward all callbacks to the member, just like you would with any external C++ DSP code.It is possible (to easily) change the size of a frame and process very specific frame lengths (eg. in 0.4s frames)?
Similar to the last answer, the tools for slicing up buffers that you have in scriptnode are also available as C++ templates, so
wrap::fix_block<ahdsr::envelope, 32>
. Prepare for waves of weird template compilation errors if you go down that route, but that's the tradeoff for getting a fast end product :) -
Thank you for your answers I appreciate it.
What is the best practice to get data out of a c++ node and into the main hise interface script?
Unfortunately this is one is quite important.
My requirement is that main interface script in my project needs to be able to grab one specific variable out of my node.
My first idea was to turn the node into a modulator and then pass the value to a global cable in scriptnode. But I've been having issues with compiled networks not really working as they should with global cable.
I'm not sure if there are any alternatives to this. -
@griffinboy said in Finding Sample Rate, and Other C++ scriptnode questions:
My first idea was to turn the node into a modulator and then pass the value to a global cable in scriptnode.
Actually that's what I would recommend too in that use case:
- Create a C++ node
my_node
with a modulation output - Load the
project.my_node
in a DSP network and connect the mod output to aglobal_cable
with a fixed ID - Export the DSP network
- Use the global cable in your interface script.
I'll do a quick test to see where it gets stuck, but that should theoretically work.
- Create a C++ node
-
Just checked the procedure and it works fine. You have to create the global routing manager and the cable you connect to in the interface script before you load the hardcoded master FX (because the DLL cannot create anything because of DLL heap memory issues) and that might become a problem if you load a real project but the basic functionality works.
-
Thanks I'll try it out!
I was previously having issues where when I compile the FX vst the cable would no longer function, but I shall see
-