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.
Transposer
Section titled “Transposer”Shifts every note by a fixed semitone interval. The simplest possible RT script.
Harmonizer
Section titled “Harmonizer”Emits one or more additional notes at fixed intervals alongside each incoming note. All copies share the same duration as the original.
Velocity Remap
Section titled “Velocity Remap”Applies a curve to incoming note velocities — useful for softening a hard- playing keyboard or adding expressiveness to a controller with a shallow range.
Legato Detector
Section titled “Legato Detector”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.
Sustain Pedal Hold
Section titled “Sustain Pedal Hold”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.
MIDI Delay
Section titled “MIDI Delay”Delays every incoming note by a fixed time offset, creating a single echo. Chain two instances for a multi-tap delay.
Hi-Hat Choker
Section titled “Hi-Hat Choker”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.
Step Sequencer with UI Playhead
Section titled “Step Sequencer with UI Playhead”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.
MIDI File Playback
Section titled “MIDI File Playback”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].trackHandlelocal 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 endendBind 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.