How to control modules with scripts


  • administrators

    A scripted interface for your instrument is pretty useless unless it can change the parameters or data of the modules in your patch. Basically there are two main concepts, which aim at different data types:

    1. Change a numerical parameter
    2. Change complex data (arrays of SliderPacks or Table curves)

    The first data type can be controlled using the internal parameter system of HISE while the second data type is controlled by connecting the scripting widgets directly to its target modules.

    Parameters in HISE

    All modules in HISE (modulators, synths, even another script processors) share a common parameter system that allows control over a parameter. This system is used for all changes to parameters, regardless if you drag a knob or recall a preset.
    Also this system can be used from within scripts. The rule is pretty simple:

    A parameter is always a float number and accessed by an zero-based index.

    The pseudo code for this operation looks like this:

    Module.setAttribute(1, 0.57); // sets the SECOND parameter to 0.57
    value = Module.getAttribute(0); // gets the FIRST parameter
    

    Changing parameters from a script

    In order to change a parameter, you'll need three things:

    1. The Module

    You need to tell the script which module it should change. This is achieved by creating a wrapper object for this module in your script's onInit-callback:

    // this stores a reference to modulator with the name "LFO Modulator 12"
    var lfo = Synth.getModulator("LFO Modulator 12");
    

    When you hit compile, it searches all sub-modules of the synth where your script is residing until it finds a module with the matching name. Be aware that this follows the principle of encapsulation: your script can't control modules that are added to another sound generator (unless its in a Container and the sound generator is a child synthesiser).

    Make sure every module has a unique ID (normally it creates a unique ID but collision can appear when pasting existing modules).

    For every module type there is a dedicated API call:

    var lfo = Synth.getModulator("LFO"); // Searches for modulators
    var transposer = Synth.getMidiProcessor("Transposer"); // searches for MIDI processors
    var sine = Synth.getChildSynth("Sine Generator"); // searches for child synthesisers
    var limiter = Synth.getEffect("Limiter"); // searches for effects
    

    Since this will be a very common operation, there are multiple shortcuts that saves you some typing:

    1. Click on a module to focus it (the second menu bar entry name should change to "Edit MODULE X").
      Then, press Ctrl + Shift + C (or use the menu Edit -> Create Script Variable Declaration to copy a script variable declaration into the clipboard. (The standard Ctrl + C shortcut copies the entire module.)
    2. You can right click on a header of a module (or in the Patch Browser) and select "Create Script Variable Declaration". This will copy the line into the clipboard so you can save some typing.

    After you used this function, you can simply paste the definition into your onInit callback. The variable name will be a valid JS identifier parsed from the module name (so "Lfo Modulator 12" will turn into lfoModulator12 )

    2. The Parameter Index

    Although it would be easier to identify the desired parameter via a string (eg. "Volume"), for performance reason it is better to access them with an index (you should be able to change parameters in MIDI callbacks without problems). A string comparison is a pretty heavy operation compared to a simple integer comparison.

    But there is no need for magic numbers, there are several ways to obtain the index:

    1. In the development tools area, there is a searchable list with all modules. You can right click on a module and a popup with all parameter names and their respective index should popup.
    2. Once you created a module wrapper, you can use the autocomplete menu to obtain the name (a module wrapper has all its parameter names as named constants).
    lfo.setAttribute(lfo.Frequency, 4.8);
    

    3. The Value

    While other parameter systems like VST automation are normalized (= in the range from 0.0 to 1.0), the parameters within HISE can be set directly: A threshhold value of -12.0 dB can be directly set with something like Limiter.setAttribute(Limiter.Threshhold, -12.0);

    This is pretty straight forward for sliders. For buttons, everything above 0.5 is considered true (on). For comboboxes (the popup selector boxes) the values are the index of the entry (but not zero based, because zero means no selection).
    Be aware that there is no range checking when setting parameters via script. This can be used for interesting experiments (you can eg. use a LFO for FM synthesis if you set its frequency in the audible range), but it can also create pretty weird behaviour if you call it with some funky values.

    Special Parameters

    There are some exception to that rule which will be listed here:

    • some parameters (eg. in the reverb module) that are displayed as percentage (0% ... 100%), which are internally handled as 0 ... 1.0.
    • the volume (Gain) of a synthesiser is set as linear gain value from 0.0 .. 1.0 with 0.5 being -6dB. You can use the API call Engine.getGainFactorFromDecibel() if you want to set the volume from a dB range knob.

    The API call

    If you have all three things gathered, changing the parameter can be achieved using this API call:

    Module.setAttribute(Module.ParameterName, value)
    

    The most prominent place to call this would be in the onControl callback of your script (but since this operation is trimmed for performance, its perfectly OK to call this also in an MIDI CC callback or another time-critical callback). This callback supplies you with two parameters: the changed widget and the new value of this widget. You can now use a big conditional chain to implement each widget's logic:

    function onControl(number, value)
    {
        if(number == widget1)
        {
            Module1.setAttribute(Module1.Parameter, value);
        }
        else if (number == widget2)
        {
            Module2.setAttribute(Module2.Parameter, value);
        }
    }
    

    The onControl callback is mainly called from the message thread without any time critical requirements. But be aware that when using a MIDI CC for automation of a parameter, it will be called in the audio thread, so don't spend too much time here.

    Example

    Add a Waveform Generator to your empty container. Then add a Script Processor to the Container's MIDI processor chain (this is important: if you add the ScriptProcessor to the Waveform Generator, it won't find itself, because it only looks in child synths).

    Use this script (Copy it in your clipboard, right click in your script editor and choose "Load script from clipboard"):

    // Creates a reference to our child synth.
    WaveformGenerator = Synth.getChildSynth("Waveform Generator");
    
    // Add a knob that will control the parameters
    Knob = Content.addKnob("Knob", 0, 0);
    
    // Change the knob to a random range
    Knob.setRange(-30.0, 40.0, 1.0);
    
    function onNoteOn() {}
    function onNoteOff() {}
    function onController() {}
    function onTimer() {}
    
    function onControl(number, value)
    {
        if(number == Knob)
        {
            // Set Left Pan
            WaveformGenerator.setAttribute(WaveformGenerator.Pan1, value);
    
            // Set the Right Pan
            WaveformGenerator.setAttribute(WaveformGenerator.Pan2, -value);        
        }
    }
    

    You should now see a knob on your scripts´s interface which changes the pan value of both waveforms.

    Changing script controls

    You can also change parameters of another ScriptProcessor. The procedure is exactly the same as with another modules. The index is the order of declaration (again zero-based). Let's assume this is our onInit callback:

    knob1 = Content.addKnob("knob1", 0, 0);
    button2 = Content.addButton("button2", 150, 0);
    

    Index 0wold now change the knob1 widget, and index 1 changes the state of the button button2. This trick is pretty useful if you want to control a MIDI processing script from your main interface. Since it is generally recommended to defer the main script and let smaller scripts do their little task, this is a nice way of adjusting their logic.

    This is not the only way of inter-script communication. For internal data sharing as well as more complex data communication, you can use the Globals-Object that grants its access to all scripts.

    The parameter connection wizard

    Since Build 631, there is a dialog window that assists you with connecting widgets to modules:

    1. Right click on a widget (the editor must be open to allow context menus on script controls)
    2. Choose "Connect to module parameter"
    3. Select the Module and the parameter that you want to control
    4. Press OK. All necessary scripting calls are inserted in the script.

    It won't work with external files tough (it results in duplicate code lines because it looks only the onInit callback and the onControl callback for existing lines)

    ParameterConnector.gif

    Controlling complex widgets

    This parameter system only works with single float numbers. But what if you want to control the more advanced widgets (SliderPacks / Tables of a module)? For this problem, there is a completely different approach:

    You can connect complex widgets to a processor to control it directly.

    This makes it completely obsolete to add any control callback logic to your script. Simply create a widget and connect it to a module that has a widget like this. When you change the script widget, the module will be automatically changed. Also, recalling user presets also changes the actual model.

    Right now there are three complex widget types that allow connection to a module:

    Widget Type Data Type Connectable modules
    SliderPack Array Harmonic Filter, Array Modulator
    AudioWaveform AudioFile Convolution Reverb, Looper, Audio File Envelope
    Table Curve / Function Velocity Modulator, LFO Modulator, ...

    Connecting the widgets to a module

    The most convenient way to connect a widget is using the Interface Designer.

    If you don't know how to use the Interface Designer, check out this chapter in the manual:
    Interface Designer Manual

    Follow these steps:

    1. Create a module with a complex widget (eg. a Velocity Modulator).
    2. Create a complex widget (in this case a Table)
    3. Open the Interface Designer for this widget
    4. The property ProcessorID should yield a popup with all available modules that can be connected to the widget. Select the module you want to control (make sure it's name is unique).
    5. Recompile the script to establish the connection.

    That's it. From now on, this widget is controlling the module directly (it should be the same as changing the widget on the actual module interface


Log in to reply

Looks like your connection to How to control modules with scripts was lost, please wait while we try to reconnect.