Skip to content

Scripting Best Practices

christophhart edited this page Mar 26, 2016 · 1 revision

Scripting Best Practices

When designing interfaces, these tricks might help you speed up your workflow or your scripts.

Interface design

Use a script as main interface

In order to use a script as main interface for the instrument, you'll need to call these methods:

Content.setWidth(width);
Content.setHeight(height);
Synth.addToFront(true);

This script will now be the main interface, which will appear as plugin UI in the finished product.

Separate MIDI processing logic from Interface scripts

Although both things can be achieved with the same module (the Script Processor), it can be considered as good practice to keep these two things separated. The reason is (apart from the usual encapsulation benefits) a huge performance increase if you defer the interface script.

Deferring a script means running all callbacks that are triggered by MIDI messages or the timer on the message thread instead of the audio thread. It comes with some trade-offs (you can't change the midi message with a deferred callback or rely on the accuracy of the timer), but since it is not running on the audio thread, you can do (almost) whatever you want in any of the callbacks without having to risk a drop out (in the worst case, the interface will get laggy).

Deferring a script is simple: just call

Synth.deferCallbacks(true);

and from then, the callbacks will stay away from the audio thread.

These are some things that heavily rely on the deferred callback feature:

  • setting text values of labels according to MIDI input
  • using the timer for animation purposes
  • heavy calculations from MIDI input

But what if you want to control the behaviour of a MIDI processing script with your interface? Theoretically it would be simpler to combine interface and MIDI processing in this case. But I would suggest using two Script Processors and communicate between them using the standard setAttribute way:

// ====================================================================== Interface Script
interfaceSlider = Content.addKnob("interfaceSlider", 0, 0);

MIDIProcessor = Synth.getMidiProcessor("MIDI Processor");

Synth.deferCallbacks(true);

function onControl(number, value)
{
	MIDIProcessor.setAttribute(0, value);
}

// ====================================================================== MIDI Processor Script

processorSlider = Content.addKnob("processorSlider", 11, 5);

function onNoteOn()
{
	Message.setVelocity(processorSlider.getValue() * 127);
}

dragging the knob in the interface script will change the knob in the MIDI Processor script (setAttribute uses the definition order to find the right interface widget from the supplied parameter index)

Write helper functions for your widgets

Almost every plugin has a limited set of widgets which all share the same appearance. By using a helper function that creates a widget and set the properties, you can use a one liner to create your actual elements:

function createButton(id, x,y, text)
{
    // Create a button and stores it to the temporary variable 'b'
    var b = Content.addButton(id, x, y);
    
    // Use the arguments to set properties differently
    b.set("text", text);
    
    // Some random constant properties for every widget
    b.set("saveInPreset", false);
    b.set("visible", false);
    
    return b;
}

button1 = createButton("button1", 0, 0, "Test 1");
button2 = createButton("button2", 0, 0, "Test 2");

Use external files for interface definitions

Interface definitions can be a pretty boring script with lots of redundancy. While the HISE scripting editor features some nice tools for developing, there are many text editors out there with far more advanced editing features. Using a text editor (eg. Sublime Text 3) for these files can speed up the repetitive work.

Use a hidden SliderPack for data you want to store / restore

Since the user preset system only saves and restores widgets, every data you want to store must be saved in a interface element (even if you actually don't want to display that information). Whenever a user preset should contain more than simple numbers (for example an array), the most convenient way to achieve this is to use a SliderPack and hide it by default (you can even add a debugging option that shows the slider pack if you want to check what's going on).

Writing MIDI processing scripts

These best practices mainly aim at writing the fastest possible code.

Use MidiList objects as often as possible

I added a custom data type, the MidiList which is a fixed array with length 128 that contains integer values and some handy methods for the most common tasks (searching for a value, setting all values, get the min and max value and so on) without having to manually iterate over the array.

Whenever you want to store polyphonic data (for example the last velocity for each note number), using a MidiList brings both speed increase as well as clarity (because most tasks can be done with a simple one liner).

Avoid local variables in MIDI callbacks

If you want to store a calculation result before using it as argument (to avoid spaghetti code), I would recommend to define a temporary variable in the onInit callback. Creating a variable in the callback brings some unpredictable results because it needs to allocate data).