HISE Docs

Third Party C++ Nodes


If you compile your scriptnode dsp networks as dll it will create a C++ file for each network. HISE will then load this code as dynamic library and allows you to use the C++ version of your networks inside HISE (either as "frozen" DspNetwork inside a network or as HardcodedFX module ) in the module tree.

When you export your plugin, it will include these files into the C++ compilation so that you don't need to bother about shipping the dynamic library file alongside your plugin / app.

But it doesn't stop there. This system can also be used to add any arbitrary C++ DSP code as node and even lets you run HISE under your IDE's debugger (VS or XCode) while developing the C++ nodes.

In order to do so, you just need to add your files in the subfolder DspNetworks/ThirdParty , and then use the same workflow like when you're exporting DspNetworks. When creating your nodes, you need to follow these rules:

The API looks very similar to the classes that you use inside a snex_node . But the most simplest way to follow these rules is to use the tool function in the file menu Create Third Party C++ node template . This will create a file with your desired name and adds a C++ class that compiles as node with all required functions and metadata informations (it will also create the node_properties.json file with some default flags). From there you can start implementing your DSP algorithms (or write the glue code to embed third party algorithms from the src subfolder).

If you're on Windows, you can choose to close HISE after the dll compilation and run the dll in the VS debugger by double clicking on the solution file. On macOS it's a little bit more complicated because you have to select HISE as debug executable in the Xcode scheme editor.

Communication

There are multiple ways to handle the data communication between HISE and the C++ node:

Connection Data type Direction realtime safe
Parameter callbacks single double value HISE -> node yes
Complex Data objects float buffers bidirectional yes
Global Cable value single double value bidirectional yes
Global Cable data arbitrary JSON object bidirectional no

Parameter callbacks

Parameter callbacks are functions that are defined in your node and can be called with a single double precision float value. They are templated with the parameter index to avoid branching and are always called through this function of your node:

template  void setParameter(double value);

You need to register them with the expected range, ID and callback in the createParameters() callback:

void createParameters(ParameterDataList& data)
{
  {
    parameter::data p("MyParameter", { 0.0, 1.0 });
    registerCallback<0>(p);
    p.setDefaultValue(0.5);
    data.add(std::move(p));
  }
}

They will show up as knobs on the module interface and can be connected like any other parameter of a HISE module using the parameterId property (or via Module.setAttribute(Module.ParameterId, value) scripting method). Note that the direction of communication is only from HISE to your node - you cannot use this way for sending values back to HISE.

Complex Data objects

You can define the number of complex data objects for each node and then use the data that comes from HISE. This allows you to use

in your node. In order to use any of these objects, just change the value of the constant expression in your node class:

static constexpr int NumTables = 0;
static constexpr int NumSliderPacks = 2;
static constexpr int NumAudioFiles = 0;
static constexpr int NumFilters = 0;
static constexpr int NumDisplayBuffers = 0;

In this example the node will now tell HISE to create two SliderPacks and expose it to the node. This is done through the setExternalData callback which you can use to fetch the data.

ExternalData ed;
block sp1;
block sp2;

void setExternalData(const ExternalData& data, int index)
{
  // Copy a reference to the entire external data object
  ed = data;

  // Create references to the actual data
  if(index == 0)
    data.referBlockTo(sp1, 0);

  if (index == 1)
    data.referBlockTo(sp2, 0);
}

Now if you want to use the data somewhere in your node (eg. the process callbacks), it's recommended to use the data mutex so that there is no concurrent access between threads:

if(auto sl = DataTryReadLock(ed))
{
  // first slider value
  const float value = sp1[0];

  // send a message to flash the third slider
  ed.setDisplayedValue(3.0);
}

Global cable

If you need true bidirectional communication with an arbitrary data type, you can use the global cable system to receive and send any data from and to HISE. Note that within the global cable system there are two separate communication lines:

For a reference on how to use this, please take a look at the global_cable node reference.

UI Thread considerations

The juce::Timer (or juce::AsyncUpdater ) class require the message thread to run and be available and that is not the case in the DLL build, because the code in there runs in a different process and cannot access the UI thread of the main application. So if you try to use the JUCE classes for asynchronous communication on the UI thread, it will not work. This problem is only occuring when running the node inside HISE - as soon as you compile the plugin, the UI thread is available again.

So for the development phase I've added a small helper class that runs a thread inside the DLL process and basically replicates the functionality of the juce::Timer and juce::AsyncUpdater class. All you need to do is to subclass from these classes instead of the JUCE ones and it will take care of everything:

template  struct my_node: public juce::Timer // nope
{

};

template  struct my_node: public hise::DllTimer // this will work
{
    my_node()
    {
        startTimer(30);
    }

    void timerCallback() override { ... }
};

template  struct my_node: public juce::AsyncUpdater // nope
{

};

template  struct my_node: public hise::DllAsyncUpdater // this will work
{
    my_node()
    {
        
    }

    void process(ProcessData<2>& data) {
        triggerAsyncUpdate();
    }

    void handleAsyncUpdate() override {}
};

These are drop in replacements of the JUCE classes. Once you compile the plugin, it will automatically resort to using the default JUCE classes with the proper UI thread model.