Stl.Steparp¶
Stl.Steparp is the IVLS step arpeggiator and sequencer. It operates in two modes — ARP (note reordering across held voices) and SEQ (pitch offsets from a table) — and is structurally identical across all products, with skinning and flow routing as the only product-specific additions.
Why This Exists¶
Building an arpeggiator and sequencer from scratch in every product duplicates large amounts of timing, voice-management, and sync logic. Stl.Steparp provides a complete, tested implementation that plugs into the IVLS voice pipeline. Products route incoming voices to arp or seq flows and implement display callbacks; the engine does the rest.
Key Concepts¶
Bundle Composition¶
| Node | Role |
|---|---|
Stl.Steparp.Core |
Shared timing, modbit, and duration helpers |
Stl.Steparp.Arp |
Arpeggiator playback engine |
Stl.Steparp.Seq |
Sequencer playback engine |
Stl.Steparp.Sys.NotifySeqStep |
Relay: fires SeqStepOn/SeqStepOff callbacks |
Stl.Steparp.Sys.NotifyArpStep |
Relay: fires ArpStepOn/ArpStepOff callbacks |
ARP vs. SEQ Mode¶
Products route the incoming voice based on current mode:
if steparp[thread].mode = steparp.arp_mode.ARP
ivls.reflow_voice(self, plc.arp_flow)
else
ivls.reflow_voice(self, plc.seq_flow)
end ifBoth flows converge at the product's play event node.
Per-Step Table Data¶
Per-step data is stored in steparp_t[thread]:
| Table | Purpose |
|---|---|
trigger[step] |
OFF / RETRIGGER / TIE |
volume[step] |
Per-step velocity/volume (0–127) |
pan[step] |
Per-step pan offset |
length[step] |
Per-step gate length factor |
pitch[step] |
SEQ mode pitch offset from base note (semitones) |
wave[step] |
Per-step waveform selection |
modulation[step] |
Per-step modulation output for Modrix routing |
Timing¶
Step length is computed from two sources depending on steparp[thread].sync:
function steparp.calculate_step_length(thread) -> len
len := BOOL.CHOOSE(
steparp[thread].sync,
ticks.sync[steparp[thread].sync_speed], // sync to tempo
steparp[thread].free_speed * 1000 // free: ms to microseconds
)
end functionSwing is applied as an alternating signed offset per step parity — up to one-third of the step length.
ARP Mode¶
Stl.Steparp.Arp maintains sorted lists of active voices per thread. On NoteOn, the voice is appended and sorted lists are rebuilt. If the arpeggiator is not already running on this thread, a while loop launches inline. Note ordering modes (PLAYED, ASC, DEC, ASCDEC, RANDOM, etc.) are resolved each iteration, making parameter changes take effect in real-time.
Trigger modes:
- RETRIGGER — previous voice released; new voice fired each step
- TIE — same voice continues; only modbits (volume, pan, tune) are updated
- OFF — previous voice released; no new voice
For TIE steps, the tune modbit is updated to reflect the pitch difference between current and previous note, producing smooth pitch changes without retriggering.
Loop end modes: Loop (restart from 0), Stop (release on last step), Hold (hold last note; loop continues empty iterations).
SEQ Mode¶
In SEQ mode, each triggered voice runs its own looped playback context advancing through the step table independently of which notes are held. The pitch table offset is applied as a tune modbit relative to the original played note. The sequencer uses the same trigger modes, loop-end constants, and swing logic as the arpeggiator.
Step Callbacks: SeqStepOn/Off, ArpStepOn/Off¶
Both modes spawn a lightweight notify voice on each step routed through a dedicated flow. The relay nodes fire overridable product callbacks:
// Stl.Steparp.Sys.NotifySeqStep:
cb NoteOn: __RUN_CB__(SeqStepOn)
cb NoteOff: __RUN_CB__(SeqStepOff)The notify voice carries Voice[vo].step_arp.table_idx so the callback knows which step fired. Products use these callbacks to update display widgets, drive external state, or write per-step modulation output.
Modulation Table Output to Modrix¶
Per-step volume and pan are applied as modbits on the triggered voice. For TIE steps these modbits persist — only their values are updated, producing smooth per-step modulation without retriggering. Polycore adds a Polycore.StepArpMod node that reads steparp_t[layer].modulation[step_idx] and forwards it to modrix as a bipolar modulation source.
Connections to Other Parts of IVLS¶
Stl.Steparp voices terminate at stl-play-event, which is the terminal node for both arp and seq flows. Per-step modbits use the same vmc modbit infrastructure as fade modulation. Display updates route through ui-routines sub-groups to keep display writes safe on the audio thread.
Patterns and Caveats¶
- The arpeggiator's
whileloop runs asynchronously in a taskfunc. Once started, it runs until all active voices on that thread are released. Do not add blocking operations inside step callbacks. - Mode switching (
ARPvs.SEQ) takes effect immediately — a new note after a mode change enters the newly active flow. Notes already in flight continue in their current mode. stl.arp.buffer_stale[thread] := TRUErestarts the sequence when a new NoteOn arrives while the arpeggiator is stopped inHoldmode.- Display writes (step highlight, active step counter) must go through UI Routine sub-groups, not directly from the step callback, to avoid audio thread violations.
Related¶
- stl-play-event — terminal node for both arp and seq flows
- vmc — per-step modbits use the VMC modbit infrastructure
- modrix — per-step modulation table output feeds Modrix as a modulator source