Timers and Envelopes
Timers
Section titled “Timers”What a Timer Is
Section titled “What a Timer Is”A timer is a script-generated event that fires at a future time. Unlike note or controller events, timers originate inside a script — not from incoming MIDI. They are the mechanism for anything that needs to happen on a schedule: note delays, arpeggiator steps, sequencer pulses, LFO ticks.
One-Shot vs. Recurring
Section titled “One-Shot vs. Recurring”A timer can be configured to fire once, or to repeat at a fixed interval until explicitly cancelled. Recurring timers are the natural fit for periodic tasks (arp, sequencer); one-shot timers suit delayed actions (echo, staccato shortening).
Creating and Cancelling Timers
Section titled “Creating and Cancelling Timers”-- One-shot: fires once after 0.5 secondslocal id = sys_create_timer_event(0, 0.5, 1)
-- Recurring: fires indefinitely every quarter notelocal id = sys_create_timer_event(0, sys_get_quarter_note_length(), nil)
-- Cancel an indefinite timersys_dispose_event(id)Timers arrive as events inside onProcessEvents. Handle them by checking
event.type:
function onProcessEvents(blockLen, events) for i, event in ipairs(events) do if event.type == sys_event_types.timer then -- event.id is the same id returned by sys_create_timer_event end endendPractical Patterns
Section titled “Practical Patterns”Tempo-Synced Pulse
Section titled “Tempo-Synced Pulse”A recurring timer fires on every beat and emits a note. The timer is started once at top level (when the script loads):
-- BEGIN_INFO_BLOCKscriptInfo = { scope = 'event-processor', parameters = {} }-- END_INFO_BLOCK
local pulseTimerId = sys_create_timer_event(0, sys_get_quarter_note_length(), nil)
function onProcessEvents(blockLen, events) for i, event in ipairs(events) do if event.type == sys_event_types.timer and event.id == pulseTimerId then sys_create_note_event(event.offset, 1, 36, 100, 0.05) -- short kick pulse end endendMIDI File Playback
Section titled “MIDI File Playback”For playing back a MIDI file continuously, use onProcessBlock rather than
timers — see Examples for a complete
implementation.
Envelopes (Custom Controls)
Section titled “Envelopes (Custom Controls)”What a Custom Control Is
Section titled “What a Custom Control Is”A custom control is a piecewise-linear envelope that is attached to a note event and modulates a target parameter over the note’s lifetime. You build the envelope by inserting individual breakpoints — each one a time/value coordinate. The engine draws a straight ramp between consecutive breakpoints.
Custom control events are created by the script and forwarded to the engine alongside the linked note. They are not received as incoming events.
Targets
Section titled “Targets”Each breakpoint specifies a target UID — the destination being modulated. There
are two built-in targets available from the sys_default_target_uids global:
| Key | Target |
|---|---|
sourceVolume | Volume of the PCM source |
sourceTune | Tuning of the PCM source |
For other modulation destinations, pass the component UID of a script modulator configured in the layer. This lets you drive filter cutoff, send levels, or any other addressable parameter.
Adding Breakpoints
Section titled “Adding Breakpoints”Each call to sys_create_custom_control_event inserts one breakpoint:
sys_create_custom_control_event(offset, linkedEventId, targetUid, value)offset— block-relative time of the breakpoint (same coordinate asevent.offset)linkedEventId— the note event this envelope belongs totargetUid— what to modulatevalue— the envelope value at this point in time
The envelope implicitly starts at its default value (1.0 for sourceVolume, 0.0
for sourceTune) at the note’s start. Breakpoints then define how the value
evolves.
Example: Note with a Fast Fade-Out
Section titled “Example: Note with a Fast Fade-Out”function onProcessEvents(blockLen, events) for i, event in ipairs(events) do if event.type == sys_event_types.noteOn then -- Create a 0.5-second note local id = sys_create_note_event( event.offset, event.channel, event.note, event.velocity, 0.5)
-- Add a breakpoint at the note's end: ramp volume from 1 → 0 sys_create_custom_control_event( event.offset + 0.5, id, sys_default_target_uids.sourceVolume, 0)
sys_dispose_event(event.id)
elseif event.type == sys_event_types.noteOff then sys_dispose_event(event.id) end endendExample: Multi-Segment Shape
Section titled “Example: Multi-Segment Shape”Call sys_create_custom_control_event multiple times on the same note to build
a more complex shape:
local id = sys_create_note_event( event.offset, event.channel, event.note, event.velocity, 2.0)
-- Attack: ramp up to 1.2 in 0.05s (overshoot)sys_create_custom_control_event(event.offset + 0.05, id, sys_default_target_uids.sourceVolume, 1.2)-- Decay: settle to 0.8 by 0.2ssys_create_custom_control_event(event.offset + 0.2, id, sys_default_target_uids.sourceVolume, 0.8)-- Release: fade to 0 at note endsys_create_custom_control_event(event.offset + 2.0, id, sys_default_target_uids.sourceVolume, 0)Multiple Envelopes on One Note
Section titled “Multiple Envelopes on One Note”You can attach envelopes to multiple targets on the same note by calling
sys_create_custom_control_event with different targetUid values. Each target
is evaluated independently:
-- Volume fadesys_create_custom_control_event(event.offset + 1.0, id, sys_default_target_uids.sourceVolume, 0)
-- Pitch slide (e.g. +2 semitones by note end)sys_create_custom_control_event(event.offset + 1.0, id, sys_default_target_uids.sourceTune, 2)