Back to main page...

MIDIbox NG

User Manual --- .NGR Script

MIDIbox NG provides a (primitive) script language which allows to change values of control elements, to send free definable MIDI events, to output special text strings on LCD and to send debug messages to the MIOS Terminal. It even supports (limited) flowcontrol with if/elsif/else statements.

The idea for this script feature came up with the request to run a sequence of value changes (with delays in between) during power-on, resp. after a .NGC file has been loaded. Over the time it has been enhanced by additional commands to cover other usecases, and meanwhile we even have some kind of flow control!

On a MBHP_CORE_STM32F4, the .NGR script will loaded from SD Card and then compiled into RAM to allow realtime execution of the commands within less than 1 mS! :-)
The MBHP_CORE_LPC17 module only allows direct execution from SD Card, which is much slower and therefore only allows sporadic interactive operations like special button functions (typically it will take 5..50 mS on this core!).

The .NGR file has the same name like the .NGC file. After power-on MIDIbox NG will load DEFAULT.NGC, and therefore also DEFAULT.NGR (if available).

Usecases

Here a list of intended usecases:

  • set values of control elements (LEDs, Buttons, LED Rings, Encoders, etc...) during startup with optional delays (for having some "blink" ;-).
    E.g. a nice sequence of patterns for LEDs and LED Rings by pushing a button to surprise your friends.
  • send an informative message to the MIOS Studio terminal.
    E.g. to debug your .NGC setup (or the .NGR execution flow).
  • set a control element value depending on conditions, which are derived from other control elements.
    E.g. to implement complex selections (if BUTTON:1 and BUTTON:2 is pressed the same time, do something special...)
  • send a fully customizable MIDI messages, even multiple ones triggered by the same event.
    E.g. to select different patches on synthesizers which are connected to different MIDI ports and listen to different MIDI channels.
  • MBHP_CORE_STM32F4 only (direct execution from RAM): advanced event processing in realtime

If you know other interesting usecases which are not listed here, please let me know! :-)

Calling the .NGR script

The .NGR script can be called with following methods:

  • it's started automatically with ^section == 0 after the corresponding .NGC file has been loaded. If you want to prevent the execution, please write following lines at the beginning of the .NGR file:
    if ^section == 0
      exit
    endif
    
  • it can be started from a Meta event which is defined in the .NGC file, e.g.:
    # start script with ^section == 1
    EVENT_BUTTON id=1  type=Meta  meta=RunSection:1  button_mode=OnOnly
    
    Some configuration examples can be found under runscr1.ngc (which calls runscr1.ngr) and runscr2.ngc (which calls runscr2.ngr)
  • it can be started from the MIOS Terminal with run <section> <value>
    The ^section variable will contain the value of <section>
    And the ^value variable will contain <value>
    Example:
    run 1 127
    

Commands

Here a list of available commands:
Command Description
LCD <string> Prints an immediate message on screen while it's executed.
All string control formats and directives are supported (see description of EVENT_* label parameter), which especially means that it's possible to clear the screen, and to jump to different cursor positions at different displays. %d etc. will print the current ^value
Examples:
# clear screen
LCD "%C"

# print message on first line of first LCD
LCD "@(1:1:1)Hello World!"

# print message on second line of first LCD
LCD "@(1:1:2)Second Line"

# print message on first line of second LCD
LCD "@(2:1:1)Second Display"
LOG <string> Sends a message to the MIOS Terminal.
Currently formatted messages are not supported, only static string (could change in future). Example:
LOG "Hello World!"
SEND <midi-event> Sends a MIDI event. The event definition typically contains:
  • the MIDI event type (NoteOff, NoteOn, PolyPressure, CC, ProgramChange, Aftertouch, Pitchbend, NRPN, SysEx)
  • the MIDI port (USB1..USB4, OUT1..OUT4, OSC1..OSC4)
  • the MIDI channel (1..16)
  • one or more values depending on the event type
Examples:
#    type   port chn key  value
send NoteOn USB1   1  36  ^value
send NoteOn USB1   1  36  0

#    type         port chn key value
send Polypressure USB1   1  36 ^value

#    type port chn key  value
send CC   USB1   1  16  ^value

#    type          port chn value
send ProgramChange USB1   1 ^value

#    type       port chn value
send Aftertouch USB1   1 ^value

#    type      port chn value
send PitchBend USB1   1 ^value

#    type port chn number value
send NRPN USB1   1 0x0123 ^value

#    type  port stream
send SysEx USB1 0xf0 0x11 0x22 0x33 0x44 ^value 0xf7
SEND_SEQ <delay> <length> <type> <port> <chn> <key|cc> <velocity|value> A very powerful command which allows to sequence MIDI events, e.g. to play tunes or even to create a simple step sequencer!
Please find examples under:
  • seq1.ngr/seq1.ngc: a 16 step note sequencer controlled from 16 rotary encoders
  • seq2.ngr/seq1.ngc: a 8 step note sequencer controlled from 16 rotary encoders (upper row: notes, lower row: velocity
  • seq3.ngr/seq1.ngc: a 16 step CC sequencer controlled from 16 rotary encoders
EXEC_META <meta-event> [<value>] Executes a meta event with an optional value.
A list of available meta events can be found in the .NGC chapter. Examples:
if ^section == 0
  # wait until all values have been scanned (consider 2 seconds startup delay + a little bit margin)
  delay_ms 2500
  
  # now capture the AINSER values
  exec_meta RetrieveAinserValues

  # and dump out values
  exec_meta DumpSnapshot
endif
or
if ^section == 1
  exec_meta SetSnapshot 1
  exec_meta DumpSnapshot
endif
TRIGGER <id> Triggers a control element with the given <id>, so that it sends out it's MIDI event and prints it's label on LCD.
Example:
# send the MIDI event of the first three pots connected to the AINSER module
trigger AINSER:1
trigger AINSER:2
trigger AINSER:3
SET <id> <value>
SET ^<variable> <value>
Similar to TRIGGER, but it also changes the value of the control element.
Example:
# turn on the first 3 LEDs connected to the DOUT module:
set LED:1 127
set LED:2 127
set LED:3 127
The SET command also allows to change the variables: ^section, ^value, ^bank (will change the EVENT banks). And it allows to change the global variables ^dev, ^pat, ^bnk, ^ins, ^chn.
Examples:
# turn on the first 3 LEDs connected to the DOUT module:
set ^section 1
set ^value 2
set ^bank 3
set ^pat ENC:1
CHANGE <id> <1|0> Similar to SET, but won't trigger a MIDI event.
Example:
change BUTTON:1 42
will change the button value to 42, but the MIDI event which has been specified for this button won't be generated.
SET_RGB <id> <red>:<green><blue> Similar to the rgb parameter in the .NGC file, this command allows to change the brightness levels for the three LED layers of a LED matrix. All three values range from 0..15.
Example:
# it's assumed, that a DOUT_MATRIX is configured for all three layers,
# and that led_emu_id_offset is set to 1001, so that the LEDs are
# individually accessible via LED:1001, LED:1002, ...

# set the RGB levels for multicolour LEDs connected to a DOUT_MATRIX

# Red
set_rgb LED:1001 15:0:0

# Green
set_rgb LED:1002 0:15:0

# Blue
set_rgb LED:1003 0:0:15

# Yellow
set_rgb LED:1004 15:15:0

# Cyan
set_rgb LED:1005 0:15:15

# Magenta
set_rgb LED:1006 15:0:15

# Orange
set_rgb LED:1007 15:8:0

# White
set_rgb LED:1008 15:15:15

# now turn on the LEDs by setting the maximum value
set LED:1001 127
set LED:1002 127
set LED:1003 127
set LED:1004 127
set LED:1005 127
set LED:1006 127
set LED:1007 127
set LED:1008 127
SET_HSV <id> <h>:<s><v> Sets the hue/saturation/value colour code of a WS2812 based RGBLED. H ranges from 0..359 (grad); saturation and value (brightness) range from 0..100 (percentage)
Example:
# red
set_hsv RGBLED:1 0:100:25

# green
set_hsv RGBLED:2 120:100:25

# blue
set_hsv RGBLED:3 240:100:25
SET_LOCK <id> <1|0> Allows to lock/unlock the MIDI receiver of an EVENT.
Examples: with
      set_lock ENC:1 1
the encoder with hw_id=1 won't change its value on incoming MIDI events anymore (it still can be changed manually when it's moved).
With:
      set_lock ENC:1 0
the encoder will be unlocked, so that it will change the value on a matching incoming MIDI event again (default).
SET_ACTIVE <id> <1|0> Can be alternatively used instead of the bank mechanism to activate/deactivate events, e.g. if the same events should be activated in multiple banks under certain conditions. A usage example can be found under:
SET_NO_DUMP <id> <1|0> Allows to change the "no_dump" flag which specifies if an EVENT_xxx should be sent during a DumpSnapshot. This feature can be used to handle different snapshot setups.
SET_MIN <id> <1|0>
SET_MAX <id> <1|0>
Sets the minimal/maximal value of an event (in the .NGR file specified with range=<min>:<max>.
Example:
# let the button toggle between 63 and 65
set_min BUTTON:1 63
set_max BUTTON:1 65
DELAY_MS <ms> Delays the execution of the script for the given number of milliseconds.
Example:
set LED:1 127
delay_ms 100
set LED:1 0

set LED:2 127
delay_ms 100
set LED:2 0

set LED:3 127
delay_ms 100
set LED:3 0
SET_KB_TRANSPOSE <KB:1|2> <transpose-value> Sets the keyboard transpose value. Example:
set_kb_transpose KB:1 ^value
will change the transpose according to a given ^value, e.g. from an rotary encoder. See also kb_6.ngr/kb_6.ngc
SET_KB_VELOCITY_MAP <KB:1|2> <velocity-map> Sets the keyboard transpose value. Example:
set_kb_velocity_map KB:1 3
will change the velocity map according to a given value (here: 3), e.g. triggered by a button. See also kb_6.ngr/kb_6.ngc
LOAD <setup> Switch to another setup (.NGC, .NGS, .NGR, ... files)
IF
ELSIF
ELSE
ENDIF
See special flow control chapter below.
EXIT Stops the execution of the .NGR script. Example:
# don't do anything during startup
if ^section == 0
  exit
endif

Expressions

All values specified with the SET/SEND/IF/etc... command are handled as expressions which can be:

  • a constant decimal value from -16384..16384
  • a constant hexadecimal value from 0x0..0x3fff
  • a constant octal value from 0..037777
  • a control element hw_id, e.g. BUTTON:1, ENC:1, AINSER:1, etc... (see next section about IDs)
  • a control element id, e.g. (id)BUTTON:1, (id)ENC:1, (id)AINSER:1, etc... (see next section about IDs)
  • ^section: this variable can be passed from the Meta event or "run" terminal command. It's 0 when a .NGC file.
  • ^value: the item value when the script is called from a Meta event.
  • ^bank: the current bank

More ^ variables can be implemented on request.

IDs

Whenever a control element is addressed with it's id (e.g. BUTTON:1, LED:1, AINSER:1, ENC:1 (see also .NGC chapter), the parser will search for a matching hardware id (hw_id) by default.

This means for example, that a

set LED:1 127
command will change the value of the first LED connected to the first pin of a DOUT shift register (or emulated LED matrix LED if emu_led_id_offset is used in the DOUT_MATRIX configuration).

If no EVENT_LED has been defined in the .NGC file, the hardware will be directly accessed via a virtual event like known from fwd_id.

If multiple EVENT_LED are assigned to the same hw_id, but to different banks, only the EVENT item of the currently selected bank will be accessed.

Sometimes you want to access an element based on it's id independent from the current bank selection. Or the specified id doesn't match with the assigned hw_id. For such cases, it's possible to cast this type with the (id):

# set EVENT_LED id=1 directly without considering the current bank selection
set (id)LED:1 127

For completeness reasons, it's also possible to cast (hw_id), although this type is used by default:

# set EVENT_LED id=??? hw_id=1
set (hw_id)LED:1 127

# is doing the same like:
set LED:1 127

The casting method is also useful if you want to read a value of a control element independent from the bank selection, such as:

if (id)BUTTON:20 != 0
  log "BUTTON:20 is pressed"
endif

Operations

Some basic mathematical operations are supported. They have to be surrounded with square-brackets ([...]).

Syntax: [<left-operand><operator><right-operand>]

Examples:

  • set LED:2000 [LED:2000 + 1]
    will increment the value stored in LED:2000
  • set LED:2000 [LED:2000 - 1]
    will decrement the value stored in LED:2000

Note that nested operations are supported as well, such as:

  • send CC USB1 1 [LED:2000 + [LED:2001 + [LED:2002 + LED:2003]]]

More examples can be found in runscr5.ngc and runscr5.ngr

Support operators:

  • +: addition
  • -: substraction
  • *: multiplication
  • /: divide
  • %: modulo
  • &: logical AND
  • |: logical OR
  • ^: logical XOR

Flow Control

With the IF/ELSIF/ELSE/ENDIF commands it's possible to define condition under which blocks of commands are executed. Nested IF conditions are allowed, the max. depth is 16!

A condition consists of a left and right value + a condition in the middle:

  • <left expression> <condition <right expression>

    Examples:

    if ^section == 1
    
      log "Section is 1"
    endif
    
    or:
    if ^section == 1
      # initial patches for my synths:
      send CC OUT1 1 0 0
      send ProgramChange OUT1 1 0
    
      send CC OUT1 2 0 0
      send ProgramChange OUT1 2 0
    
      send CC OUT1 3 0 0
      send ProgramChange OUT1 3 0
    
      send CC OUT2 1 0 0
      send ProgramChange OUT2 1 0
    
      exit
    endif
    
    
    if ^section == 2
      # alternative patches for my synths:
      send CC OUT1 1 0 0
      send ProgramChange OUT1 1 21
    
      send CC OUT1 2 0 0
      send ProgramChange OUT1 2 1
    
      send CC OUT1 3 0 0
      send ProgramChange OUT1 3 75
    
      send CC OUT2 1 0 0
      send ProgramChange OUT2 1 0x10
    
      exit
    endif
    
    or:
    if ^section == 1
      if BUTTON:1 > 0
        if BUTTON:2 > 0
          log "BUTTON:1 and BUTTON:2 are pressed"
        endif
      endif
    endif
    

    Supported conditions are: ==, !=, <, <=, >, >=.
    Logical combinations are not supported.

    Loops (resp. restarting)

    Loops are not directly supported. There is neither a "goto"/"jump" command, nor a "do" or "while" loop, to keep the parser algorithm simple.

    But with following trick it's possible to restart the .NGR script with the RunSection:<section> meta event. As you can see, this even allows to start the .NGR file with a different ^section value. Usage example:

    # don't do anything during startup & patch load
    if ^section == 0
      exit
    endif
    
    # loop this section as long as BUTTON:2000 is active
    # Note that this button has been configured for toggle mode!
    if ^section == 1
      log "Section 1 called"
    
      # do this
    
      # and that
    
      # maybe with some delays:
      delay_ms 100
    
      # and finally:
      if (id)BUTTON:2000 != 0
        LCD "@(1:1:2)loop running "
        log "Retriggering section 1"
        exec_meta RunSection:1
      else
        LCD "@(1:1:2)loop finished"
        log "Finished."
      endif
      exit
    endif
    

    The files can be found under runscr3.ngc and runscr3.ngr.

    For the case, that you (unintentionally) created an endless loop, it's possible to stop the execution with the "runstop" command in the MIOS Terminal. In addition, it's possible to generate a RunStop meta event with a dedicated button.

    Limitations

    The capabilities are still limited, mainly caused by RAM size limitations of the MBHP_CORE_LPC17. Following limitations have to be considered:

    • please don't expect a script language which is so powerful like for example eLua! Although it would be possible to run eLua on a LPC1769, the available RAM isn't sufficient to satisfy the needs for MIDIbox NG in parallel!
    • It's currently not possible to declare and set customized variables. Limited capabilities could be available in future, but they will consume some RAM!
    • It's not possible to combine conditions in an IF statement (e.g. with && or ||) because this would blow up the parser too much. If this is desired, use nested IF statements on an AND condition, or multiple IF statements on an OR condition.


    Last update: 2023-11-04

    Copyright © 1998-2023, Thorsten Klose. All rights reserved.