Skip to content

How to Add an Arpeggiator

Wire up the Stl.Steparp system to add a step arpeggiator and sequencer with configurable step tables, timing, and per-step modulation.

Prerequisites

  • A working IVLS product with at least one flow and Stl.PlayEvent
  • Familiarity with flows and voice fields
  • UI controls for step table editing (optional, but needed for user interaction)

Steps

1. Add Stl.Steparp.Bundle to IVLS_NODES

The bundle includes all required steparp nodes: Stl.Steparp.Core (timing and helpers), Stl.Steparp.Data (parameter storage), Stl.Steparp.TableData (step table storage), Stl.Steparp.SnapWait (grid snap), Stl.Steparp.Arp (arpeggiator engine), Stl.Steparp.Seq (sequencer engine), and the step notify relay nodes.

define IVLS_NODES := ..., Stl.Steparp.Bundle, ...

2. Create arp and seq flows

Register separate flows for the arpeggiator and sequencer. Each flow starts with Stl.Steparp.SnapWait (which delays the note until the next grid boundary when snap-to-grid is enabled), followed by either Stl.Steparp.Arp or Stl.Steparp.Seq, then moves to your sound output flow:

node MyProduct.Flows:
    cb Flows:
        define FLOWS += my.arp_flow, my.seq_flow, my.sound_flow

        { Arpeggiator flow }
        ivls.register_node(my.arp_flow, Stl.Steparp.SnapWait)
        ivls.register_node(my.arp_flow, Stl.Steparp.Arp)
        ivls.register_move(my.arp_flow, my.sound_flow)

        { Sequencer flow }
        ivls.register_node(my.seq_flow, Stl.Steparp.SnapWait)
        ivls.register_node(my.seq_flow, Stl.Steparp.Seq)
        ivls.register_move(my.seq_flow, my.sound_flow)

        { Sound output flow }
        ivls.register_node(my.sound_flow, Stl.PlayEvent)
end node

3. Add a Divert node to route based on steparp mode

The steparp system stores its mode in steparp[thread].mode. Use a Divert node to send voices to the correct flow:

node MyProduct.Divert.StepArp:
    cb NoteOn:
        if my.arp_enabled = TRUE
            if steparp[thread].mode = steparp.arp_mode.ARP
                ivls.reflow_voice(self, my.arp_flow)
            else
                ivls.reflow_voice(self, my.seq_flow)
            end if
        else
            ivls.reflow_voice(self, my.sound_flow)
        end if

        ivls.pass()
end node

Register the divert node in your base flow:

ivls.register_node(my.base_flow, MyProduct.Divert.StepArp)

4. Set up engine box parameters for step table data

The steparp system uses two engine box data layers:

  • steparp -- global parameters (mode, speed, swing, steps, note order, loop mode, table enables)
  • steparp_t -- per-step table data (trigger, volume, pan, length, pitch, wave, modulation)

Both are initialized automatically by Stl.Steparp.Data and Stl.Steparp.TableData (included in the bundle). The key parameters are:

Parameter Values Purpose
steparp[thread].mode steparp.arp_mode.ARP, steparp.arp_mode.SEQ Arpeggiator or sequencer mode
steparp[thread].sync TRUE / FALSE Sync to host tempo or free-running
steparp[thread].sync_speed ticks.sync.* constants Step duration when synced
steparp[thread].free_speed 10--1000 Step duration in ms when free
steparp[thread].seq_steps 2--32 Number of active steps
steparp[thread].note_order steparp.note_order.* Note ordering (Played, Up, Down, etc.)
steparp[thread].octaves -4 to 4 Octave range for arpeggiation
steparp[thread].loop_mode steparp.loop_end.* Loop, Stop, or Hold
steparp[thread].swing 0--127 Swing amount

Per-step table values are accessed as steparp_t[thread].trigger[step], steparp_t[thread].volume[step], etc.

5. Wire step callbacks (optional)

If you need to react to each arp or seq step (for example, to update a UI step indicator), implement the step callback nodes:

node MyProduct.StepIndicator:
    cb ArpStepOn:
        { Fired when the arpeggiator triggers a new step }
        declare step := Voice[self].step_arp.table_idx
        my.update_step_display(step)

    cb SeqStepOn:
        { Fired when the sequencer triggers a new step }
        declare step := Voice[self].step_arp.table_idx
        my.update_step_display(step)
end node

Register this node in IVLS_NODES so its callbacks are included in the callback chain:

define IVLS_NODES := ..., MyProduct.StepIndicator, ...

6. Control arp/seq state

To stop the arpeggiator or sequencer programmatically (for example, when switching sounds), use the state functions:

{ Stop the arpeggiator on a thread }
stl.arp.state(thread, OFF)

{ Stop the sequencer on a thread }
stl.seq.state(thread, OFF)

Trigger modes

Each step in the table has a trigger mode that controls its behavior:

Mode Constant Behavior
Off steparp.trigger_mode.OFF No note plays on this step
Retrigger steparp.trigger_mode.RETRIGGER A new note is triggered
Tie steparp.trigger_mode.TIE The previous note continues, pitch-shifted to the new step's note

Verify

  1. Enable arpeggiator mode, hold a chord, and verify that notes cycle through the held keys
  2. Switch to sequencer mode -- held notes should play the step pitch pattern
  3. Change note_order -- verify that Up, Down, Up-Down patterns work correctly
  4. Adjust seq_steps -- the pattern length should change in real time
  5. Enable tempo sync and change sync_speed -- step rate should follow the DAW tempo
  6. Set some steps to TIE -- verify that the previous note glides to the new pitch without retriggering
  7. Set some steps to OFF -- verify silence on those steps

Further reading

  • Guide: Step Arpeggiator for the conceptual architecture
  • Guide: Engine Boxes for understanding the steparp and steparp_t data layers
  • Guide: Flows for how ivls.register_move chains flows together