Skip to content

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 if

Both 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 function

Swing 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 while loop 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 (ARP vs. 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] := TRUE restarts the sequence when a new NoteOn arrives while the arpeggiator is stopped in Hold mode.
  • 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.
  • 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