Scripting generated tracks
A generated track writes its own part by following another track. A short script decides how — for every note the master plays, or every bar of the clip, you say what notes to place. This page is the full reference: the handlers, the values you get, and every function the language knows.
You don't need to have programmed before. Most scripts are two or three lines, and the shipped examples (Octave shimmer, Fifth above, …) are good starting points — open one and change a number.
How a script runs
A script runs at generation time — once whenever the master changes (you edit it, or you press Apply in the editor) — and it runs per clip. It sweeps the clip and emits notes; those notes are then real, persisted notes that play and export like anything else. The script never runs during playback, so there's no live timing to worry about.
A script is made of handlers — blocks that fire at different moments:
| Handler | Fires |
|---|---|
| `on init` | once, before everything else — set up values here |
| `on bar` | once for every bar in the clip |
| `on beat` | once for every beat in the clip |
| `on source_note` | once for every note the master plays, in time order |
| `on source_chord` | once for every chord *played* in the master (notes struck together) |
| `on harmonic_chord` | once for every chord in the song's chord progression |
A script can have any of these, in any order, and Apply always runs the whole script. Most parts only
need on source_note:
# A soft shimmer one octave above each note the master plays.
on source_note
emit_note(source_pitch + 12, source_beat, source_length, source_velocity * 0.5)
end
on source_note means "for every note the master plays, do this." source_pitch is that note's
pitch, so source_pitch + 12 is an octave above; emit_note places it at the same spot
(source_beat) for the same length (source_length), at half the velocity so it sits behind the
master.
Placing notes — `emit_note`
emit_note(pitch, beat, length, velocity)
- pitch — MIDI note number (60 is middle C, 12 is an octave). Anything outside 0–127 is skipped.
- beat — where the note starts (see Counting beats below). Beats start at 1.
- length (optional) — how long, in beats. Defaults to 1 beat.
- velocity (optional) — how hard, from 0 to 1. Defaults to 0.8.
A note that would start past the end of the clip is skipped; one that would run past the end is
trimmed to fit. You can call emit_note as many times as you like in one handler — call it twice to
stack an octave:
on source_note
emit_note(source_pitch + 12, source_beat, source_length, source_velocity * 0.5)
emit_note(source_pitch + 24, source_beat, source_length, source_velocity * 0.35)
end
Counting beats
There are four beats to the bar, and beat 1 is the downbeat. Where "beat 1" lands depends on the handler:
In
on source_note,source_beatis measured from the start of the clip — pass it straight toemit_noteand the note lines up with the master.In
on barandon beat, the beat you giveemit_noteis measured from the start of the current bar — soemit_note(48, 1, 4)lays a note on that bar's downbeat, four beats long:# A held root under every bar. on bar emit_note(48, 1, 4) endIn
on init, beats are measured from the start of the clip.
For a steady within-bar pulse, loop inside on bar with a running beat counter (see repeat below) —
that reads more clearly than on beat, which always positions from the bar's downbeat.
The values you can read
Available anywhere:
| Value | Meaning |
|---|---|
| `bar_index` | which bar is running, starting at 1 |
| `beat_index` | which beat is running, starting at 1 |
| `bar_count` | how many bars the clip has |
| `source_density` | how busy the master is, 0 (empty) to 1 (a note every beat) |
| `source_low` | the lowest pitch the master plays |
| `source_high` | the highest pitch the master plays |
| `is_downbeat` | true on beat 1 of a bar |
| `is_offbeat` | true between the beats — the syncopated spots |
| `position_in_bar` | how far through the bar you are, 0 to 1 |
| `has_harmony` | true when the chord progression has a chord at the current spot |
| `key` | the song's key as text, e.g. `"C Major"` |
| `whole` `half` `quarter` `eighth` `sixteenth` | note lengths in beats (4, 2, 1, 0.5, 0.25) so durations read musically |
Available only inside on source_note (they describe the master note being handled):
| Value | Meaning |
|---|---|
| `source_pitch` | that note's pitch |
| `source_beat` | where it sits, in beats from the start of the clip |
| `source_length` | how long it is, in beats |
| `source_velocity` | how hard it's played, 0 to 1 |
| `source_bar` | which bar it falls in, starting at 1 |
| `source_beat_in_bar` | its beat within that bar, starting at 1 |
Reading a source_* value outside on source_note is an error (the console tells you which line).
Holding a pad across a repeated note
on source_note runs once for every master note, so if the master holds a note by repeating it
(three quarter-note C's in a row), a naïve pad would retrigger three times. Two builtins fix that, both
usable only inside on source_note:
| Function | Meaning |
|---|---|
| `source_run_length(max_gap?)` | beats from this note through a run of the same repeated note |
| `is_run_start(max_gap?)` | `true` when this note begins such a run |
source_run_length() measures the whole stretch the same pitch keeps sounding, and is_run_start() is
true only on the first note of that stretch — so you place one long pad and skip the repeats:
# One sustained pad per run, however many times the master repeats the note.
on source_note
if is_run_start()
emit_note(source_pitch, source_beat, source_run_length(), source_velocity * 0.5)
end
end
For a single note that isn't repeated, source_run_length() is just that note's own length and
is_run_start() is true — so the same line plays runs and single notes correctly, no if/else on
the duration needed.
A different note ends a run. By default so does any gap (only touching notes join). If the master
plays the pitch staccato — 16th notes separated by 16th rests, say — pass a max_gap (in beats) to
bridge those gaps so the whole burst reads as one run. Use the same max_gap in both calls:
# Merge a staccato burst of the same note into one held pad (gaps up to a 16th are bridged).
on source_note
if is_run_start(sixteenth)
emit_note(source_pitch, source_beat, source_run_length(sixteenth), source_velocity * 0.5)
end
end
The master still plays its own notes underneath: the script only reads them, it doesn't replace
them. So the pad sits on this generated track alongside the master — that's why the examples drop the
velocity to 0.5, to tuck it behind. If you want only the pad, mute the master track.
Available only inside on source_chord and on harmonic_chord (they describe the chord being handled):
| Value | Meaning |
|---|---|
| `chord_root` | the chord's root — its base note, as a pitch |
| `chord_quality` | the chord's flavour as text: `"major"`, `"minor"`, `"dom7"` … |
| `chord_size` | how many notes the chord has (3 for a triad, 4 for a seventh) |
| `chord_beat` | where the chord starts, in beats |
| `chord_length` | how long the chord lasts, in beats |
Functions
Notes and output
| Function | What it does |
|---|---|
| `emit_note(pitch, beat, length?, velocity?)` | place a note (see above) |
| `mute(on)` | mute or unmute this track — the only way to mute a generated track |
| `log(value)` | write a line to the console below, handy for peeking at a value |
Numbers
| Function | What it does |
|---|---|
| `random()` | a number from 0 to 1 — reproducible (same result every time the track recomputes) |
| `min(a, b)` / `max(a, b)` | the smaller / larger of two numbers |
| `clamp(value, low, high)` | keep a value within a range |
| `round(x)` / `floor(x)` / `ceil(x)` | round to nearest / down / up |
| `abs(x)` | distance from zero (drops the sign) |
| `sin(x)` / `cos(x)` | smooth, wavy motion — e.g. a slow swell |
Chords (inside on source_chord / on harmonic_chord)
| Function | What it does |
|---|---|
| `chord_tone(n)` | a note from the chord: 1 = root, 3 = third, 5 = fifth, 7 = seventh |
| `arp_tone(step, octaves)` | walks the chord's notes for an arpeggio, climbing through octaves |
Harmony (anywhere)
| Function | What it does |
|---|---|
| `nearest_harmony_tone(pitch)` | nudges a pitch to the closest note of the chord playing here — voice a part to the progression |
Scale & key (the song must have a key)
| Function | What it does |
|---|---|
| `scale_degree(n)` | a note from the key's scale: 1 = the home note, 2 = the next, and so on |
| `in_key(pitch)` | true when a pitch belongs to the song's key |
| `snap_to_key(pitch)` | pulls a pitch to the nearest note in the key |
Rhythm & feel
| Function | What it does |
|---|---|
| `subdivide(n)` | the length of one of n equal parts of a beat — `subdivide(4)` is a 1/16 |
| `euclid(step, pulses, steps)` | an even rhythm: true when this step is one of the spread-out hits |
| `metric_accent(beat)` | how strong a beat position feels, 0 to 1 — downbeats heaviest, good for velocity |
| `humanize(value, amount)` | adds a little random wobble (±amount) for a less mechanical feel |
| `chance(p)` | true `p` of the time — `chance(0.25)` fires a quarter of the time |
| `pick(a, b, …)` | picks one of the given values at random |
Writing logic
You can name values, branch, and repeat.
Name a value with var, then reuse it (assign again with var to change it):
on source_note
var lift = 12
emit_note(source_pitch + lift, source_beat, source_length, source_velocity * 0.5)
end
Branch with if … else … end. Conditions use < <= > >= == != and combine with and, or,
not; true and false are the yes/no values:
# Only shadow the loud notes, and put quiet ones lower.
on source_note
if source_velocity > 0.7
emit_note(source_pitch + 12, source_beat, source_length, source_velocity * 0.5)
else
emit_note(source_pitch - 12, source_beat, source_length, source_velocity * 0.4)
end
end
Repeat a block a fixed number of times with repeat count … end. Add as i to number each pass
(0, 1, 2 …) so every repeat can differ — this is the key to arpeggios and subdivisions:
# Four on the floor: a note on every beat of every bar.
on bar
repeat 4 as i
emit_note(36, 1 + i, 1, 0.9)
end
end
Comments start with # and run to the end of the line.
Muting
A generated track owns its own output, so the mixer's mute is off-limits — a script is the only way to silence it:
on init
mute(true)
end
When something's wrong
The editor checks the script as you type. A moment after you stop, the console shows how many notes it would produce — or, if there's a mistake, the problem and the line it's on. Click the error and the cursor jumps straight there (if it's in another handler, the editor switches to that handler first).
A broken script never half-fills the track: if it can't run, the track comes out empty (and un-muted) and the reason is in the console — so a typo is always visible, never silent. A couple of forgiving touches keep small slips from being errors: dividing by zero gives 0 rather than failing, and a pitch or beat that falls outside the clip is simply skipped.
A few things to know
random()is reproducible. It's seeded from the track, so the "random" part lands the same way every time the track recomputes — your part doesn't reshuffle itself behind your back.- There's no
%(remainder) and no custom functions — keep scripts to the words on this page. - A script can read the song's chords (played or from the progression) and key — see the chord handlers and the chord/scale functions above — but the built-in generators (counterpoint, full pad voicing, …) as functions are not available yet.
The shipped examples
Pick one when you add the track, then tweak it in the editor:
- Octave shimmer — a soft layer an octave above each master note.
- Fifth above — a fuller companion line, a fifth (7 semitones) up.
- Octave stack — one and two octaves above, for a wide, airy doubling.
- Delayed echo — a softer fifth, one beat later, for a simple call-and-response.
See Generated tracks for how to add a generated track, choose its master, and audition it.