Stl.RoundRobins¶
Stl.RoundRobins implements multi-dimensional round-robin sample selection using a pre-seeded shuffle table. It is a NotePass node — it runs after voice metadata fields are assigned but before play_note() fires — and writes the selected RR position to Voice[self].rr.
Why This Exists¶
Playing the same sample repeatedly on consecutive notes of the same pitch is the machine-gun effect. Round-robins solve this by cycling through a set of different samples for each pitch. A naive sequential cycle is still detectable. Stl.RoundRobins uses a pre-seeded shuffle table that ensures positions are distributed without audible patterns, even across articulation and interval dimensions.
Key Concepts¶
Two-Phase Architecture¶
- Configuration (Init time) — products call
ivls.RoundRobins.register_field()to declare which voice fields form the dimensions of the RR space. Each dimension contributes a stride factor to a composite index. - Playback (NotePass time) —
query_rr()looks up the shuffle table at the composite index and current counter position, thenadvance_rr()increments the counter modulo 4096.
Field Registration¶
ivls.RoundRobins.register_field(Voice.field.note, 128)
ivls.RoundRobins.register_field(Voice.field.tky.trigger_rr_artic, NUM_ARTICS)
ivls.RoundRobins.register_field(Voice.field.tky.interval, 25)Each call appends the field and its upper bound to the dimension arrays, then rebuilds the stride factor array. The composite index is:
idx = field_0_value * 1
+ field_1_value * width_0
+ field_2_value * width_0 * width_1
+ ...Maximum combined dimension width is 100,000 entries. Register only the fields that genuinely distinguish sample pools — adding unnecessary dimensions wastes counter space and risks hitting the limit.
The get_max_rr Hook¶
Products must override this hook. The default returns 0, which disables RR entirely:
function ivls.RoundRobins.hooks.get_max_rr(vo) -> rr_max override
rr_max := TACT.META_I(Voice[vo].tky.trigger_rr_artic, NUM_RR)
end functionWhen max = 0, the node writes rr_pos = 0 and does not advance the counter — no RR for that voice.
The Shuffle Seed Table¶
The seed table is loaded from an NKA file at Init. Each row (up to 32 rows, one per possible RR count) contains a pre-shuffled sequence of 4,096 positions. On each note:
rr_pos := rr.main_seed[max - 1, rr.counter[rr_idx]]Transport Reset¶
All 100,000 counter slots are zeroed on transport start, ensuring playback always begins at a predictable RR position — important for scoring workflows where the same session must produce the same sample sequence on every playthrough.
RR Consolidation¶
Some articulations share sample pools. Map secondary artic IDs to a canonical parent so they share a counter:
tky.rr_consolidate[artic_id.STAC_SHORT] := artic_id.STAC
tky.rr_consolidate[artic_id.STAC_LONG] := artic_id.STACWithout consolidation, two artic spellings that share a pool would cycle independently and repeat samples in each sub-sequence.
Skip-Bad-RRs Pattern¶
Some specific sample combinations have known quality issues. A skip table lets the system transparently advance past bad positions:
tky.rr.skip[artic_id.SUS_NORMAL, note, 2] := TRUE // skip RR position 2 for this noteAfter query_rr() returns a position, callers check the skip table. If flagged, the counter advances one additional step. This is applied transparently.
Positioning in the Flow¶
Stl.RoundRobins runs as a NotePass node in the metadata-assignment phase. All voice fields used as RR dimensions must be fully assigned before this node runs:
TACT.Modify.EvalArtics ← sets tky.trigger_rr_artic
→ TACT.Modify.TriggerVolume
→ Stl.RoundRobins ← NotePass: reads artic + note, writes Voice[self].rr
→ Stl.Pedals
→ Tokyo.Play.StandardLegatoConnections to Other Parts of IVLS¶
Stl.RoundRobins reads tact articulation metadata (via the get_max_rr hook) and writes Voice[self].rr, which is consumed by stl-play-event in EventGroups for sample group selection. stl-legato crossfade nodes also read Voice[self].rr to select the correct interval sample group.
Patterns and Caveats¶
- The shuffle table is seeded offline and loaded from NKA. It is not generated at runtime, which guarantees consistent distribution across sessions.
- If
query_rr_idx()returns-1(a registered field value is out of its declared bounds), the position is left unchanged and an error is logged. Uninitialized voice fields reaching the RR node before metadata assignment completes are the usual cause. - Manual counter control via
ivls.RoundRobins.set_rr(vo, rr_pos)is available for snapshot restore or explicit reset logic.
Related¶
- stl-play-event — consumes
Voice[self].rrin EventGroups for group selection - tact — articulation metadata provides the
maxRR count via the get_max_rr hook - stl-legato — legato crossfade nodes also read
Voice[self].rr