Skip to content

Meta Events

Meta events are plain objects you yield from the song generator alongside patterns. The engine intercepts them at the cycle boundary and applies their effect.

function* song(ctx) {
yield { type: 'tempo', bpm: 140 }
yield { type: 'seed', seed: 42, once: true }
yield cast(synth, key, seq(4, '0,2,4,~'), ctx)
}

Common fields

FieldTypeDescription
typestringIdentifies the event kind (required)
oncebooleanWhen true, the event applies only on the first cycle after play() or swap(). Subsequent cycles ignore it.
beatnumberOffset in beats within the cycle at which the event fires (FX events only, default 0).

tempo — Change BPM

yield { type: 'tempo', bpm: 160 }
yield { type: 'tempo', bpm: 90, once: true } // set once at start

Takes effect at the next cycle boundary, so all notes in the current cycle still use the previous BPM.

FieldTypeDescription
bpmnumberNew beats-per-minute value (must be positive)

bpc — Change beats per cycle

yield { type: 'bpc', bpc: 8 }

Changes how many beats make up one cycle.

FieldTypeDescription
bpcnumberNew cycle length in beats (positive integer)

seed — Set random seed

yield { type: 'seed', seed: 42, once: true }

Sets the random seed used to generate ctx.rand() values. With the same seed, the same sequence of ctx.rand() calls produces the same values — play → stop → play always sounds identical.

The seed takes effect starting from the next cycle. Use once: true so the seed is re-established after each swap() without overriding changes made later in the song.

FieldTypeDescription
seednumberInteger seed value. Any finite number is accepted.

Reproducibility guarantee

When you call play() or swap():

  1. The seed resets to the value passed to new Incanto({ seed }) (default 0).
  2. On the first cycle, ctx.rand() is derived from that initial seed.
  3. When a seed event is processed, subsequent cycles use the new seed with a per-generator cycle counter that restarts from 0 on each play() / swap().

This means two songs with the same seed event and the same generator always produce the same ctx.rand() sequence from cycle 1 onwards.

Example — reproducible variation

function* song(ctx) {
yield { type: 'seed', seed: 1337, once: true }
const key = scales(4, 'A3:minor')
const synth = vasynth({ wave: 'sawtooth', gain: 0.5 })
while (true) {
// ctx.rand() returns a deterministic value in [0, 1)
const octave = ctx.rand() > 0.5 ? 1 : 0
yield cast(synth, key, seq(4, `${octave},2,4,~`), ctx)
}
}

fx_reverb — Master reverb mix

yield { type: 'fx_reverb', mix: 0.3 }
yield { type: 'fx_reverb', mix: 0.6, beat: 2 } // fade in at beat 2
FieldTypeDescription
mixnumberWet/dry ratio: 0 = dry, 1 = full wet
beatnumberCycle beat offset for the change (default 0)

fx_delay — Ping-pong delay

yield { type: 'fx_delay', time: 0.375, feedback: 0.4 }
FieldTypeDescription
timenumberDelay time in seconds
feedbacknumberFeedback amount: 0 = no repeats, <1 = decaying repeats
beatnumberCycle beat offset (default 0)

fx_gain — Master output gain

yield { type: 'fx_gain', gain: 0.8 }
FieldTypeDescription
gainnumberOutput gain multiplier (1.0 = unity gain)
beatnumberCycle beat offset (default 0)

swap — Switch active deck (Dual Deck mode)

When the editor is in Dual Deck mode, yielding a swap event at a specific cycle transitions playback to the other deck at the next cycle boundary.

function* song(ctx) {
// switch to deck B after 4 cycles
if (ctx.cycle === 4) yield { type: 'swap', target: 'B' }
yield cast(synth, key, seq(4, '0,2,4,~'), ctx)
}
FieldTypeDescription
target'A' | 'B'Deck to switch to. Omit to swap to the opposite deck.

The swap takes effect at the next cycle boundary (same as Mod-Enter Apply). If the editor is in Single Deck mode or the target deck has no code, the event is ignored.


User-defined events

Any object with a type string and no machine key is treated as a meta event and passed to the onMeta callback you provide when constructing Incanto. This lets you synchronise lyrics, chord labels, lighting cues, or any external state with the music.

yield { type: 'lyrics', text: 'verse 1', beat: 0 }
yield { type: 'section', name: 'chorus' }

The engine delivers user-defined events via setTimeout at the correct audio time so they stay in sync with the notes.