Documentation
The language
The syntax and semantics, without the music theory. The lessons teach the language by writing music in it; this is the reference — what each mark means and what the engine does with it. Every example below is executed by the test suite.
Every construct, one page each
The clock
bpm nTempoHow fast a quarter note goes. One UNIT is a sixteenth, so one second holds bpm/15 units.meter n · unit 1/nMeter and unitHow long a measure is: `meter` counts beats, `unit` says what a beat is written as.key <root> <quality> · modeKey and modeWhat the scale degrees mean. Change the key and every degree moves with it.
Sounds
Duration
Naming
Name ::= …RulesGive a figure a name. A rule that is defined and never stated sounds nothing.Name[a:b]SlicesA python slice over the ITEMS a reference plays — `Row[:4]`, `Row[2:]`, `Row[::-1]`.Name \i xOverridesState a rule with one item replaced. The index is ZERO-BASED and counts what the reference PLAYS.Cell ::= $1 … · Cell(x)Cells and holesA rule with a hole. `$1` is the first argument; a multi-item argument SPREADS and the cell TILES to cover it.
Transforms
Syntax and semantics. No music is taught here — for that, the lessons. Everything below is a statement about what the engine does, and every example is executed by language-doc.test.ts, so a claim on this page is either true or a failing test.
The in-product help command (HELP_TEXT in src/core/kernel.ts) is the quick reference. This is the long one, and it covers three things help does not: the document model, item overrides, and cells with holes.
1The document
A project is ONE document. There are no blocks and no play keyword.
- An unindented expression line is a playable segment.
- Indented lines continue the segment above them.
- A line containing
::=is a rule definition. - A line whose first word is a command word is a command.
%starts a comment, to end of line.%%or more is a zero-duration grid symbol, not a comment.
Blank lines are spacing, any number of them.
The only automatic composite is the whole document's: a file that states no expression sounds the LAST rule it defines. Definitions elsewhere sound nothing of their own.
2Commands
One per line.
| command | effect |
|---|---|
bpm <n> | tempo in QUARTER notes per minute. Range 5–240, fractions allowed. Default 30. |
meter <n> | beats in a measure |
unit <v> | the note value a beat gets (unit 1/4, unit 8) |
key <root>[oct] [maj|min|harm|mel] | the key degrees resolve against |
mode <diatonic|chromatic|midi> | how bare numbers are read |
axis <degree|first> | where ^ mirrors. Default 1, the tonal centre |
inst <sym> = <target> | alias a symbol to a sound |
assert <expr> == <expr> | fail the line unless both sides play the same events |
rules / reset / help | list, clear, document |
bpm counts QUARTER notes and one unit is a SIXTEENTH, so a unit is 15/bpm seconds and four units make a beat.
3Sounds
Notes are lowercase scientific pitch: c4 is middle C. Accidentals suffix the letter, and double accidentals are allowed: f#3, eb5, c##2, gbb3.
Degrees are bare numbers, resolved against the current key and mode. They auto-wrap over the whole integer line, so 8 is the octave above 1 and 0 is the seventh below it.
- Octave prefixes curry:
'3is up one,''3up two,.5down one. - Accidental suffixes inflect chromatically:
6#,7b. m60is absolute MIDI in any mode.
Rests are -. A run of dashes is still ONE rest; -*4 is the canonical spelling of four units of silence.
Relative pitch counts from the last written pitch: i<n> in semitones, s<n> in scale steps. It is threaded left to right.
Anchors set what relatives count from WITHOUT sounding: !c4 has zero duration. No mark may follow one.
Chords are one token, <root><Quality>[$voicing], sounding together in one slot. Roots may be absolute (cMaj) or degrees (1Maj, 5Dom7), and octave prefixes and accidentals compose (.1Maj, 4#Min).
Qualities compose: a base (Maj Min Dim Aug Dom Sus2 Sus4), an extension (6 7 9 11 13), and alterations (b5 #5 b9 #9 #11 b13).
Dia builds the stack FROM the scale, so the key decides the quality.
A $ voicing lists members bottom to top; a member the chord lacks is an error, never a silent no-op.
4Duration marks
| mark | effect |
|---|---|
_ | double, or multiply by n — c4 _3 |
+ | add one unit, or n — c4 +3 holds four units |
/ | halve, or divide by n — c4 /3 |
* | repeat the previous item n times |
The duration of an item is
dur = (1 + Σ '+') × ∏('_') ÷ ∏('/')* groups its copies, so a mark after it covers the whole group: kick*4 /2 is four kicks inside two units.
5Transforms
A mark after an item transforms it. After a rule reference the transform covers the WHOLE expansion.
| mark | effect |
|---|---|
~ | retrograde — time reversed, durations kept |
^ | inversion — pitch mirrored about the axis |
@n | transpose by n semitones, signed |
@~n | transpose by n steps of the current key |
They are component-wise and order-insensitive among themselves, but the engine reflects BEFORE transposing, so a ^ at a use site negates a fold's @k. Chromatic folds do not commute with @~ marks (stepShift(x+k,n) ≠ stepShift(x,n)+k), which is why a fold whose uses carry @~ is refused.
Nested ^ cancel. Drums and rests are unchanged by ^ and @.
6Rules
NAME ::= body. Uppercase names are rules; everything else is a terminal.
=separates alternatives, and extra alternatives may go on their own lines starting with=.- EBNF postfix:
&zero-or-more,?a timed optional slot,( )groups. One-or-more isx x&.*and+are duration marks, not repetition. Name#npins a reference to the n-th derivation instead of ranging over all of them.
A rule the document never states sounds nothing.
7References: slices and overrides
A […] suffix slices the result python-style, over ITEMS not tokens.
A reference may override one item with \<index> <replacement>. The index is ZERO-BASED — \0 is the first item — and it counts what the reference PLAYS, not its body's tokens, so it survives a later pass factoring that body into references of its own.
The replacement is ONE element. Brace it if it carries a mark of its own, or the mark is read as the reference's:
Slices and overrides differ in what counts as an item: in a SLICE a marked nested reference counts as one item.
An override must come BEFORE any mark, and putting it after fails silently. Row@2 \1 {7 _4} does not transpose and does not override — it gives back the bare rule, with no error. Both marks are discarded. The form that works puts the override inside what the mark covers:
The two contexts fail differently, which is worth knowing. As a STATEMENT, Row@2 \1 {7 _4} sounds the bare rule and says nothing. Inside an assert the same text is rejected — unknown symbol(s): \1 — so a claim pinned by an assert catches what a statement swallows. That is the general case for preferring an assert over a sentence.
8Simultaneity
{A, B} sounds its fields together. Fields are separated by ,.
Direction toggles and relative pitch are inherited by {} fields at entry; a chord passes on its first field's pitch.
9Bars
| … | stretches what is between two bars to exactly ONE measure of the current time signature. | a b | c d e | is two equal measures with different subdivisions.
Bars scope per source line, and a : directly after | ends the run.
A barred body cannot be referred to in part. A slice drops the bars, so R[1:3] of a barred R sounds its items at consecutive positions, measure-aligned slices included.
10Cells and holes
A rule may take arguments. $1 is a hole the argument fills; a bare reference spreads and tiles over the argument.
Slicing a call-bodied rule returns everything — a known sharp edge.
11What "the same figure" means
Exactly one mark's worth of difference, and nothing else:
- the same figure TRANSPOSED —
@k,@~k - the same figure at a constant DURATION RATIO —
_n,/n, composed as_3 /2 - each PRODUCTION a generative rule states
Two spellings that play the same (time, midi, dur) events are the same music; assert is how the document says so, and the engine checks it.
Generated from docs/language.md. The pattern blocks are run by language-doc.test.ts.