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, covering the handlers, the values you can read, and every function the language knows.
You do not need to have programmed before. Most scripts are two or three lines, and the shipped examples 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 or you press Apply, and it runs per clip. It sweeps the clip and emits notes, and those notes are then real, saved notes that play and export like anything else. The script never runs during playback, so there is 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 |
| `on harmonic_chord` | once for every chord in the song's 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
The handler runs for every note the master plays. source_pitch is that note's
pitch, so adding twelve puts it an octave above, and emit_note places it at
the same spot for the same length at half the velocity, which keeps it behind
the master.
Placing notes
emit_note(pitch, beat, length, velocity)
pitch is a MIDI note number, where 60 is middle C and 12 is an octave. Anything outside the MIDI range is skipped.
beat is where the note starts, counted as described below. Beats start at 1.
length is optional, in beats, and defaults to one beat.
velocity is optional, from 0 to 1, and defaults to 0.8.
A note starting past the end of the clip is skipped, and one running past the
end is trimmed to fit. Call emit_note as many times as you like in one
handler. Twice stacks 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_beat is measured from the start of the clip, so
passing it straight to emit_note lines the note up with the master.
In on bar and on beat, the beat you give emit_note is measured from the
start of the current bar. So this lays a note on that bar's downbeat, four
beats long:
# A held root under every bar.
on bar
emit_note(48, 1, 4)
end
In on init, beats are measured from the start of the clip.
For a steady pulse inside the bar, loop inside on bar with a running beat
counter. 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 for empty, 1 for a note every beat |
| `source_low` | the lowest pitch the master plays |
| `source_high` | the highest pitch the master plays |
| `mix_density` | how busy the rest of the mix is, on the same scale |
| `mix_low` | the lowest pitch any other track plays |
| `mix_high` | the highest pitch any other track 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 progression has a chord at this spot |
| `key` | the song's key as text, for example `"C Major"` |
| `whole` `half` `quarter` `eighth` `sixteenth` | note lengths in beats, so durations read musically |
Available only inside on source_note, describing 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 is 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, and the console
tells you which line.
Holding a pad across a repeated note
on source_note runs once for every master note, so when the master holds a
note by repeating it, three quarter-note C's in a row, a plain pad would
retrigger three times. Two functions 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 note that is not repeated, source_run_length() is just that note's own
length and is_run_start() is true, so the same line handles runs and single
notes without a branch on the duration.
A different note ends a run, and by default so does any gap, since only
touching notes join. If the master plays the pitch staccato, short notes
separated by short rests, pass a max_gap in beats to bridge those gaps so the
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.
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 does not replace them, so the pad sits on the generated track alongside the master. That is why the examples drop the velocity, to tuck it behind. To hear only the pad, mute the master track.
Available only inside on source_chord and on harmonic_chord, describing the
chord being handled:
| Value | Meaning |
|---|---|
| `chord_root` | the chord's root, as a pitch |
| `chord_quality` | the chord's flavour as text, such as `"major"` or `"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 it lasts, in beats |
Functions
Notes and output
| Function | What it does |
|---|---|
| `emit_note(pitch, beat, length?, velocity?)` | place a note, described above |
| `mute(on)` | mute or unmute this track, the only way to mute a generated track |
| `log(value)` | write a line to the console, handy for peeking at a value |
Fitting into your mix
A script can see the other tracks, not just its master, so it can add to the mix instead of piling onto a spot that is already taken.
| Function | What it does |
|---|---|
| `place_open(pitch)` | the same note, moved whole octaves into a register your mix leaves open |
| `mix_crowded(pitch)` | true when other tracks are already busy around this pitch |
| `mix_busy_at(beat)` | true when another track already plays on this beat |
place_open makes the decision for you. mix_crowded and mix_busy_at hand
you the same information so you can decide yourself. Drums do not count as
pitches, so they never make a register look crowded, but they do count for
mix_busy_at, because that one is about rhythm.
# A shimmer an octave up, but only where the mix has room for it.
on source_note
var pitch = place_open(source_pitch + 12)
emit_note(pitch, source_beat, source_length, source_velocity * 0.5)
end
# Fill the beats nothing else is playing on.
on beat
if not mix_busy_at(beat_index)
emit_note(72, beat_index, 1)
end
end
Numbers
| Function | What it does |
|---|---|
| `random()` | a number from 0 to 1, reproducible |
| `min(a, b)`, `max(a, b)` | the smaller or larger of two numbers |
| `clamp(value, low, high)` | keep a value within a range |
| `round(x)`, `floor(x)`, `ceil(x)` | round to nearest, down, or up |
| `abs(x)` | distance from zero |
| `sin(x)`, `cos(x)` | smooth, wavy motion, for a slow swell |
Chords
Inside on source_chord and on harmonic_chord.
| Function | What it does |
|---|---|
| `chord_tone(n)` | a note from the chord: 1 is the root, 3 the third, 5 the fifth, 7 the seventh |
| `arp_tone(step, octaves)` | walks the chord's notes for an arpeggio, climbing through octaves |
Harmony
Available anywhere.
| Function | What it does |
|---|---|
| `nearest_harmony_tone(pitch)` | nudges a pitch to the closest note of the chord playing here |
Scale and key
The song must have a key.
| Function | What it does |
|---|---|
| `scale_degree(n)` | a note from the key's scale: 1 is the home note |
| `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 and feel
| Function | What it does |
|---|---|
| `subdivide(n)` | the length of one of n equal parts of a beat |
| `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 for a less mechanical feel |
| `chance(p)` | true that fraction of the time, so `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 and end. Conditions use the usual comparisons and
combine with and, or and not, with true and false as the yes and 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. Add as i to
number each pass, starting at zero, so every repeat can differ. That 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 does not apply. A script is the only way to silence it.
on init
mute(true)
end
When something is wrong
The editor checks the script as you type. A moment after you stop, the console shows how many notes it would produce, or the problem and the line it is on. Click the error and the cursor jumps there, switching handler first if the error is in another one.
A broken script never half-fills the track. If it cannot run, the track comes out empty and unmuted, and the reason is in the console, so a typo is always visible rather than silent. A couple of forgiving touches keep small slips from becoming errors: dividing by zero gives zero rather than failing, and a pitch or beat outside the clip is skipped.
A few things to know
random() is reproducible. It is seeded from the track, so the random part
lands the same way every time the track recomputes and your part does not
reshuffle itself behind your back.
There is no remainder operator and no custom functions. Keep scripts to the words on this page.
A script can read the song's chords, whether played or from the progression, and its key. The built-in generators, counterpoint and full pad voicing among them, are not available as functions.
The shipped examples
Pick one when you add the track, then change it in the editor.
Octave shimmer is a soft layer an octave above each master note. Open
shimmer is the same idea moved to wherever your mix still has room, one line
different from Octave shimmer, so comparing the two shows exactly what
place_open does. Fifth above is a fuller companion line a fifth up.
Octave stack places one and two octaves above for a wide, airy doubling.
Delayed echo is a softer fifth one beat later, a simple call and response.
See generated tracks for how to add one, choose its master, and audition it.