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

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.

running…edit it — it re-runs as you type

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.

running…edit it — it re-runs as you type

2Commands

One per line.

commandeffect
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 / helplist, 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.

running…edit it — it re-runs as you type

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: '3 is up one, ''3 up two, .5 down one.
  • Accidental suffixes inflect chromatically: 6#, 7b.
  • m60 is absolute MIDI in any mode.
running…edit it — it re-runs as you type

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.

running…edit it — it re-runs as you type

Anchors set what relatives count from WITHOUT sounding: !c4 has zero duration. No mark may follow one.

running…edit it — it re-runs as you type

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.

running…edit it — it re-runs as you type

A $ voicing lists members bottom to top; a member the chord lacks is an error, never a silent no-op.


4Duration marks

markeffect
_double, or multiply by nc4 _3
+add one unit, or nc4 +3 holds four units
/halve, or divide by nc4 /3
*repeat the previous item n times

The duration of an item is

dur = (1 + Σ '+') × ∏('_') ÷ ∏('/')
running…edit it — it re-runs as you type

* 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.

markeffect
~retrograde — time reversed, durations kept
^inversion — pitch mirrored about the axis
@ntranspose by n semitones, signed
@~ntranspose by n steps of the current key
running…edit it — it re-runs as you type

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 is x x&. * and + are duration marks, not repetition.
  • Name#n pins a reference to the n-th derivation instead of ranging over all of them.
running…edit it — it re-runs as you type

A rule the document never states sounds nothing.


7References: slices and overrides

A […] suffix slices the result python-style, over ITEMS not tokens.

running…edit it — it re-runs as you type

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.

running…edit it — it re-runs as you type

The replacement is ONE element. Brace it if it carries a mark of its own, or the mark is read as the reference's:

running…edit it — it re-runs as you type

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:

running…edit it — it re-runs as you type

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 ,.

running…edit it — it re-runs as you type

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.

running…edit it — it re-runs as you type

10Cells and holes

A rule may take arguments. $1 is a hole the argument fills; a bare reference spreads and tiles over the argument.

running…edit it — it re-runs as you type

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.

What the app reports. That it was opened, how long it stayed open, which lessons were read, whether an import decomposed, and errors — with a random device id, never a document, never a file. Off in Settings.

v0.1.16 · 79cbbf7

Documentation · Pattern Engine