K

Sequence and arp

Drive notes from the transport.

To fire notes in time, gate a Clock off the host playhead. update(ctx.ppqPosition()) returns true on the block where a new step begins.

private clock: Clock = new Clock(0.25); // one step per sixteenth
private pattern: StaticArray<bool> = new StaticArray<bool>(32);
private len: i32 = 0;
private cur: i32 = -1;

Bake a Euclidean pattern with euclid(pulses, steps, rotation, out) (only when its settings change, never per sample), then read the current step on each tick:

process(ctx: Ctx): void {
  this.len = euclid(4, 16, 0, this.pattern); // 4 hits across 16 steps
  if (ctx.isPlaying() && this.clock.update(ctx.ppqPosition())) {
    if (this.cur >= 0) { ctx.noteOff(0, this.cur); this.cur = -1; }
    const idx = this.clock.step() % this.len;
    if (this.pattern[idx]) { ctx.noteOn(0, 36, 110); this.cur = 36; }
  }
}

For an arpeggiator, collect held notes and step through them instead:

private arp: Arpeggiator = new Arpeggiator(16);

// in process: feed it MIDI, then step on the clock
if (ev.isNoteOn()) this.arp.noteOn(ev.note);
else if (ev.isNoteOff()) this.arp.noteOff(ev.note);

if (ctx.isPlaying() && this.clock.update(ctx.ppqPosition())) {
  const n = this.arp.next(); // -1 when nothing is held
  if (n >= 0) ctx.noteOn(0, n, 100);
}

setPattern(ArpPattern.UpDown) and setOctaves(n) shape the run. For a mono voice, MonoNoteStack(maxNotes) tracks held notes and reports the one to sound with current(NotePriority.Highest). Construct Clock, Arpeggiator, and MonoNoteStack in prepare.

See Harmony and Sync to tempo and key.