Skip to content

Examples and Patterns

Each example below is a complete, ready-to-copy script focused on one task. Read the walkthrough in Your First Script before starting here if you haven’t already.


Shifts every note by a fixed semitone interval. The simplest possible RT script.


Emits one or more additional notes at fixed intervals alongside each incoming note. All copies share the same duration as the original.


Applies a curve to incoming note velocities — useful for softening a hard- playing keyboard or adding expressiveness to a controller with a shallow range.


Detects when a new note overlaps an already-held note and tags the new note with a legato flag. A downstream articulation script can read the tag and select a legato sample articulation.


Intercepts CC 64 (sustain pedal) and holds notes that would otherwise end while the pedal is down, releasing them only when the pedal is lifted.


Delays every incoming note by a fixed time offset, creating a single echo. Chain two instances for a multi-tap delay.


When a closed hi-hat note arrives, silences any currently playing open hi-hat voices. Models the physical behaviour of a real hi-hat pedal.


A recurring timer drives a step sequencer. On each step it emits a note and sends the current step index to the UI via cross-script messaging, so a UI script can move a playhead indicator.


Plays back a MIDI file continuously using onProcessBlock. A script-level IsPlaying parameter acts as the transport control. Each block, the script queries the MIDI file for events that fall within the current time window and emits them into the engine.

The playback position is tracked as a file-absolute time in seconds. A negative value means stopped; zero or positive means playing. onProcessBlock receives the block duration directly as blockLen, so no delta tracking is needed. Looping is handled by wrapping the position when it exceeds the track duration.

-- BEGIN_INFO_BLOCK
scriptInfo = {
scope = 'event-processor',
parameters = {
IsPlaying = {
type = 'onOff',
defaultValue = 0,
},
}
}
-- END_INFO_BLOCK
local tracks = sys_load_midi_file("my_sequence.mid")
local track = tracks[1].trackHandle
local trackLen = tracks[1].duration
local playbackPos = -1 -- < 0: stopped
function onProcessBlock(blockLen)
local playing = scriptInfo.parameters.IsPlaying.value == 1
if playing and playbackPos < 0 then
playbackPos = 0 -- transition: stopped → playing
elseif not playing then
playbackPos = -1
return
end
-- Emit note events for this block's window
local notes = sys_get_midi_note_events(track, playbackPos, playbackPos + blockLen)
for i, note in ipairs(notes) do
sys_create_note_event(
note.seconds - playbackPos, -- convert to block-relative offset
note.channel, note.note, note.velo, note.duration)
end
-- Emit controller events
local ccs = sys_get_midi_controller_events(track, playbackPos, playbackPos + blockLen)
for i, cc in ipairs(ccs) do
sys_create_control_event(
cc.seconds - playbackPos,
cc.channel, cc.controller, cc.value)
end
-- Advance and loop
playbackPos = playbackPos + blockLen
if playbackPos >= trackLen then
playbackPos = playbackPos - trackLen
end
end

Bind the IsPlaying parameter to a toggle button in the UI to start and stop playback. To play back a different track, change the index in tracks[1] to the desired track number.