Core Concepts
Events
Section titled “Events”Scripts receive four event types: noteOn, noteOff, controller, and
timer. The first two map directly to traditional MIDI — a script sees a
noteOn when a key is pressed and a noteOff when it is released. This means a
script can react to either edge independently, which is necessary for things
like sustain pedal logic, legato detection, or choking voices on release.
Controller events (CC, pitch bend, aftertouch) represent instantaneous state changes with no duration.
Timer events are generated by the script itself on a schedule you define.
The Internal Length Model
Section titled “The Internal Length Model”Internally, the engine represents notes as single events with a duration rather than as on/off pairs. This is worth knowing because it shapes how emitted notes work: when a script plays a note it specifies a length in seconds rather than scheduling a separate note-off. A note played from a live keyboard has infinite length until the release arrives. The details are covered in the event type reference; for now, just know the distinction exists between what comes in (noteOn/noteOff) and what goes out (a length-bearing note event).
The Queue Model
Section titled “The Queue Model”Every script processor owns a queue of pending events. At the start of each
render block, incoming events are placed into this queue. When onProcessEvents
returns, everything still in the queue is forwarded to the next processor.
This has a simple but important consequence: forwarding is the default. A script that does nothing to an event will pass it through automatically. To actively do something, a script either creates new events or removes existing ones.
Blocking events
Section titled “Blocking events”To prevent an event from reaching the next processor, call sys_dispose_event
with its ID:
sys_dispose_event(event.id) -- remove one eventsys_dispose_all_events() -- remove everything currently in the queueA script that calls sys_dispose_all_events() at the top of onProcessEvents
acts as a complete MIDI silencer — nothing passes through unless the script
explicitly creates new events of its own.
Creating events
Section titled “Creating events”sys_create_note_event, sys_create_control_event, sys_create_timer_event,
and sys_create_script_event add new events to the queue. They are forwarded
alongside any events that were not disposed.
Transforming events
Section titled “Transforming events”To change an event’s properties (pitch, velocity, etc.), dispose the original and create a replacement with the modified values. The new event enters the queue at the same logical time offset as the original.
The Script Info Header
Section titled “The Script Info Header”Every script must begin with a script info block — a Lua table called
scriptInfo enclosed between two mandatory comment markers:
-- BEGIN_INFO_BLOCK
scriptInfo = { scope = 'event-processor', parameters = { -- optional parameter definitions }}
-- END_INFO_BLOCKThe markers are required and must contain nothing but the scriptInfo
definition. scope must always be 'event-processor'.
Script Parameters
Section titled “Script Parameters”The parameters table lets a script declare engine parameters it owns. These
behave like any other engine parameter: they can be bound to UI controls, saved
with the patch, and automated by the host. They also give the script a way to
receive per-instance configuration — a harmonizer can expose a Transpose
interval parameter, a delay can expose a Time parameter in milliseconds, and
so on.
parameters = { Transpose = { type = 'interval', minValue = -24, maxValue = 24, defaultValue = 0, },}A parameter’s current value is readable directly from the definition — but only inside a callback (not at top-level script load time):
function onProcessEvents(blockLen, events) for i, event in ipairs(events) do if event.type == sys_event_types.noteOn then local shifted = event.note + scriptInfo.parameters.Transpose.value end endendThe .value shorthand is the most convenient way to read an own parameter
inside a callback. When you need the UID — for instance to pass to
sys_subscribe_to_parameter or sys_set_parameter_value — use
sys_get_own_parameter_uid("Name").
To read any parameter’s value outside a callback — at top-level script load
time, for instance — use sys_get_parameter_value. It has the opposite
restriction from .value: valid only at top level, never inside a callback.
Its use is discouraged in general; sys_subscribe_to_parameter is almost
always the better choice, since its return value already gives you the current
value and its callback keeps you in sync with future changes.
See the API Reference for the full list of supported parameter types.
Script Isolation
Section titled “Script Isolation”Each script processor has a completely isolated Lua environment. Scripts do not share globals, upvalues, or any state. The only ways to communicate across scripts are:
- Script events — create a new event carrying an arbitrary payload; downstream scripts receive it through the normal event flow (see Script Events)
- Cross-script messages — send Lua values to a named channel; UI scripts can subscribe to these (see Cross-Script Messaging)
Note that cross-script messages cannot be used for RT-to-RT communication — RT processors execute in a fixed order and message delivery does not fit that model. Use script events for passing information from one RT script to the next.
Execution Order
Section titled “Execution Order”Script processors execute in the order they appear in the layer’s processor chain. The first processor sees the raw incoming events; each subsequent processor sees only what the previous one forwarded. Order matters — place processors that filter or reshape events before the ones that depend on the result.