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 ctl.tempo(140) yield ctl.seed(42, { once: true }) yield cast(synth, key, seq(4, '0,2,4,~'), ctx)}Helper functions
Two helper namespaces build these events for you — they are to transport/FX what
cast() is to sound and v.* is to visuals. Each returns a plain event object, so
yielding raw objects (the “raw form” shown throughout this page) remains equally valid.
ctl.*— transport / control:ctl.tempo(bpm, opts?),ctl.bpc(n, opts?),ctl.seed(n, opts?),ctl.eos(beat?).fx.*— master FX (distinct from per-machine insert FX likereverb()):fx.reverb(mix, opts?),fx.delay(time, feedback, opts?),fx.gain(g, opts?).
yield ctl.tempo(120, { once: true })yield ctl.bpc(8)yield ctl.seed(42, { once: true })yield ctl.eos(4)yield fx.reverb(0.3)yield fx.delay(0.375, 0.4, { beat: 2 })yield fx.gain(0.8)Common fields
| Field | Type | Description |
|---|---|---|
type | string | Identifies the event kind (required) |
once | boolean | When true, the event applies only on the first cycle after play() or swap(). Subsequent cycles ignore it. |
beat | number | Offset in beats within the cycle at which the event fires (FX events only, default 0). |
tempo — Change BPM
yield ctl.tempo(160)yield ctl.tempo(90, { once: true }) // set once at start
// raw form (equivalent)yield { type: 'tempo', bpm: 90, once: true }Takes effect at the next cycle boundary, so all notes in the current cycle still use the previous BPM.
| Field | Type | Description |
|---|---|---|
bpm | number | New beats-per-minute value (must be positive) |
bpc — Change beats per cycle
yield ctl.bpc(8)
// raw form (equivalent)yield { type: 'bpc', bpc: 8 }Changes how many beats make up one cycle.
| Field | Type | Description |
|---|---|---|
bpc | number | New cycle length in beats (positive integer) |
seed — Set random seed
yield ctl.seed(42, { once: true })
// raw form (equivalent)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.
| Field | Type | Description |
|---|---|---|
seed | number | Integer seed value. Any finite number is accepted. |
Reproducibility guarantee
When you call play() or swap():
- The seed resets to the value passed to
new Incanto({ seed })(default0). - On the first cycle,
ctx.rand()is derived from that initial seed. - When a
seedevent is processed, subsequent cycles use the new seed with a per-generator cycle counter that restarts from0on eachplay()/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 ctl.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) }}Random numbers
ctx.rand is the seeded generator. Call it for a float, or use .int for an
integer — both draw from the same reproducible stream.
ctx.rand() // float in [0, 1)ctx.rand(8) // float in [0, 8)ctx.rand(0.2, 0.8) // float in [0.2, 0.8)
ctx.rand.int(0, 3) // integer in [0, 3] — both ends inclusivectx.rand.int(1, 6) // dice roll: 1..6Handy for randomizing song development deterministically — e.g. pick a section or a transposition each cycle:
function* song(ctx) { yield ctl.seed(99, { once: true })
const keys = ['C4:minor', 'F4:dorian', 'A3:phrygian'] const key = scales(4, keys[ctx.rand.int(0, keys.length - 1)]) const cutoff = ctx.rand(400, 3000)
yield cast(vasynth({ cutoff }), key, seq(4, '0,2,4,~'), ctx)}| Call | Returns |
|---|---|
ctx.rand() | float [0, 1) |
ctx.rand(max) | float [0, max) |
ctx.rand(min, max) | float [min, max) |
ctx.rand.int(min, max) | integer [min, max] (inclusive) |
eos — Stop playback
yield ctl.eos() // stop at end of the cycleyield ctl.eos(4) // stop at beat 4
// raw form (equivalent)yield { type: 'eos', beat: 4 }Stops the transport. Without beat, playback ends at the cycle’s last beat.
| Field | Type | Description |
|---|---|---|
beat | number | Cycle beat offset at which to stop (default = end of cycle) |
fx_reverb — Master reverb mix
yield fx.reverb(0.3)yield fx.reverb(0.6, { beat: 2 }) // fade in at beat 2
// raw form (equivalent)yield { type: 'fx_reverb', mix: 0.6, beat: 2 }| Field | Type | Description |
|---|---|---|
mix | number | Wet/dry ratio: 0 = dry, 1 = full wet |
beat | number | Cycle beat offset for the change (default 0) |
fx_delay — Ping-pong delay
yield fx.delay(0.375, 0.4)
// raw form (equivalent)yield { type: 'fx_delay', time: 0.375, feedback: 0.4 }| Field | Type | Description |
|---|---|---|
time | number | Delay time in seconds |
feedback | number | Feedback amount: 0 = no repeats, <1 = decaying repeats |
beat | number | Cycle beat offset (default 0) |
fx_gain — Master output gain
yield fx.gain(0.8)
// raw form (equivalent)yield { type: 'fx_gain', gain: 0.8 }| Field | Type | Description |
|---|---|---|
gain | number | Output gain multiplier (1.0 = unity gain) |
beat | number | Cycle 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)}| Field | Type | Description |
|---|---|---|
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.