Mini-notation
Mini-notation is a terse, string-based DSL for describing rhythmic patterns. It comes from the TidalCycles family and is embedded inside Akkado alongside the regular DSP-graph syntax.
The basics
A pattern is a typed string literal whose content is a space-separated list of events that evenly fill one cycle (by default, one bar). The prefix tells the compiler what kind of pattern you mean — n for notes, s for samples, c for chords, v for raw values.
n"c4 e4 g4 b4" // four notes, one per beat
n"c4 [e4 g4] b4" // subdivide: e4+g4 share beat 2
n"c4 ~ e4 ~" // ~ is a rest
n"c4*4" // repeat c4 four times in the slot
n"<c4 e4 g4>" // one note per cycle, rotating Combining with signals
Pattern literals coerce to a frequency signal at any signal slot, so you pass them straight into an oscillator’s pitch argument.
n"c4 e4 g4 b4"
|> saw(@.freq)
|> lp(@, 1200)
|> out(@) Lead with the pattern literal, then pipe it into a waveform builtin (saw, sqr, tri, …). @ is the placeholder — same role as % — and @.freq reads the per-event frequency from the pattern record. When the pattern advances, the oscillator’s frequency updates on the beat. Because of hot-swap, the phase keeps going across pitch changes.
For envelope-triggered notes, reach for @.gate so the envelope fires on each pattern event (and stays silent on ~ rests) rather than running an independent clock that can drift:
n"c4 e4 ~ g4"
|> saw(@.freq) * ar(@.gate, 0.01, 0.2)
|> out(@) Inline modifiers
Mini-notation supports a handful of inline modifiers on each event:
x*3— speed: playxthree times inside the slot (event duration shrinks to fit).x/3— slow: stretchxto take 3× its slot length.x@3— weight:xtakes 3× the slot length of unweighted siblings.x!3— replicate: expand to three copies ofxas separate slots in the parent sequence (!alone defaults to 2).x?— playxwith 50% probability (x?0.3for 30%).[a b]— sub-sequence:athenbinside the slot.<a b c>— alternate: one element per cycle, cycling through.[a, b]— stack:aandbplay simultaneously (comma only works inside[…]or<…>).
Per-event params
Each pattern event is a record. You’ve already seen @.freq and @.gate; the same @ also exposes vel, note, dur, phase, sample_id, and a handful of others — the full table lives in Records → Pattern events.
Two of those fields have inline-suffix shortcuts you can write directly inside the pattern string:
c4:0.8— set velocity on a pitch or chord atom (@.velbecomes 0.8 for that event; range 0–1).bd:2— pick a sample variant (@.sample_iddistinguishesbdfrombd:2). Integer index after the colon, sample atoms only.
n"c4 e4:0.4 g4 b4:0.9"
|> saw(@.freq) * ar(@.gate, 0.01, 0.2) * @.vel
|> out(@) When you want richer per-event modulation than velocity, multiply in a parallel pattern at the same length — every slot in a pattern is itself a signal:
n"c4 e4 g4 b4"
|> saw(@.freq) * ar(@.gate, 0.01, 0.2)
|> lp(@, v"400 1200 800 2400")
|> out(@) For per-event params that don’t have an inline suffix (filter cutoff, send level, custom controls), use record-suffix syntax (n"c4{cutoff:0.3}") or the bend() / aftertouch() / dur() transforms — both covered in Pattern Modulation.
Example: a riff with a filter sweep
n"<c4 eb4> g4 [bb4 c5] a4"
|> sqr(@.freq) * ar(@.gate, 0.01, 0.25)
|> lp(@, 400 + ar(@.gate, 0.01, 0.25) * 3000)
|> @ * 0.25
|> reverb(@, 0.3)
|> out(@) @.gate triggers the envelope on every pattern event — including the <c4 eb4> alternation and the [bb4 c5] sub-sequence — so the amp and filter envelopes follow the actual note rate, not a fixed clock.