oontz · research
The Song Is the Source
Notes toward an audio programming language in which a whole five-minute track is a few kilobytes of text you can read, grep, diff, fork, review, and commit — and which renders to the same audio, every time, on any machine.
§Abstract
Software acquired version control, code review, package registries, forks, diffs, issue trackers, and a culture of reading each other's work. Music acquired the WAV file. This asymmetry is not a matter of taste or tooling maturity; it is a direct consequence of the artifact each field chose to exchange. Programmers exchange source. Musicians exchange renders. Every downstream capability follows from that one choice.
This article argues that a specific, narrow, and achievable class of language — a declarative, deterministic, whole-song source format with independent runtimes — closes the gap, and that the design space for such languages has been repeatedly half-explored for sixty-five years without anyone landing squarely in the middle of it. We give the history, the semantics, the architecture, the metaphors, the economics, the failure modes, and a conformance checklist precise enough that a reader can build a rival implementation, or a successor language, without asking us anything.
The reference implementation is oontz: an
instrument in which kick x...x...x...x... is four on the floor,
one character changes the song, and the entire track — arrangement, notes,
automation, sound design, even its visuals — is a text document that two
independently written engines are held to render identically. It exists to
prove that none of what follows is speculative.
0How to read this
This is long on purpose. It is not a manual, a tutorial, or a marketing page; those exist elsewhere and are shorter. It is the document we wanted when we started and could not find: the complete reasoning behind a category of language, written down while the reasons are still fresh, in the hope that whoever builds the next one starts from here instead of from scratch.
Six parts, each readable alone.
- Part I — The problem. What is actually broken, why six thousand years of musical notation is the relevant precedent, what sixty-five years of computer music got right, and the specific hole none of it fills.
- Part II — The theory. The laws, the semantics, the models of time, the notation theory, and the metaphors. This is the part that transfers to a language that shares none of our syntax.
- Part III — The architecture. How it is actually built, with the numbers: the layer cake, the scheduler, the extension mechanism, the two-runtime parity discipline.
- Part IV — The consequences. What the design buys — version control, near-zero hosting economics, structural search, machine collaboration, pedagogy, preservation, accessibility.
- Part V — Build your own. Twelve decisions with tradeoffs, a conformance checklist, the anti-patterns, and the open research problems.
- Part VI — Appendices. Grammar, schema, the hash, glossary, bibliography, licence to reuse.
If you have thirty seconds, read the nine laws in §5. If you have ten minutes, read Part II. If you are here to build something, Part V is the payload and everything before it is the justification.
Calling this a programming language invites a fight about whether it is one. It is not Turing-complete, has no user-defined functions, no recursion, and no variables. We use the word anyway, in the sense that HTML, SQL, regular expressions, Makefiles, and chess notation are languages: a formal grammar with a defined meaning, written by humans, executed by machines, and argued about by both. §26 explains why declining Turing-completeness is a feature and not a shortfall — and what it costs.
The problem
Why the artifact you exchange determines everything you can do, and why music picked the wrong one twice.
1The artifact gap
Consider what you can do with a program you have been given, and what you can do with a song you have been given.
| You want to | Program | Recording |
|---|---|---|
| See how it was made | Read the source | Impossible |
| Change one detail | Edit a line | Re-record it |
| Compare two versions | diff | Listen twice, guess |
| Take one part | Copy the function | Stem separation, badly |
| Find all examples of X | grep | Impossible |
| Suggest a change to someone | Pull request | Send an email describing it |
| Know who wrote which part | git blame | Liner notes, if lucky |
| Have a machine improve it | Edit the text | Regenerate the whole thing |
| Store 10,000 of them | Megabytes | Gigabytes, and a bandwidth bill |
The right column is not a failure of effort. Producers are not less collaborative than programmers, and DAW vendors are not lazy. The right column is what falls out of exchanging a render instead of a source. A WAV file is the output of a process from which the process itself has been discarded. It is a photograph of a building, offered in place of the plans.
You cannot diff two mixes. You cannot fork someone's bassline. You cannot grep a record for "every track that goes into a breakdown at bar 64." You cannot open a song and see how it was made, because the thing that was shared with you was the cake, not the recipe.
1.1 The project file is not a source format
An obvious objection: DAWs do keep the process. An Ableton
.als, a Logic project, a Reaper .rpp — these describe
how the track was made. Why is that not source?
Because a source format has properties beyond "contains the information," and project files have almost none of them:
- Not portable. A project opens in one application, often one version of it. Source that requires a specific vendor's binary is not source; it is a save file.
- Not self-contained. It references samples, plugins, and licences that live elsewhere and rot. Open a ten-year-old project and you get a dialog listing what is missing. The song is gone even though the file is there.
- Not readable. Even the XML-based ones are machine records, not human documents: thousands of lines of GUID-keyed device state per track, none of it composed by a person or meant to be read by one.
- Not diffable. Two versions of a project differ in thousands of lines of positional noise. A diff is technically produced and tells you nothing.
- Not small. With its samples, a project is hundreds of megabytes. It cannot ride in a chat message, a git repo, a URL, or a model's context window.
- Not deterministic. Same project, different machine, different plugin versions, different render. Sometimes subtly, which is worse.
The distinction is old and well understood in software. A source file and a memory dump can encode the same computation; only one of them is source. The test is not "does the information survive" but "can a human read it, a machine execute it, and a third party reproduce it without your computer."
1.2 Why this matters more than it sounds
Consider what version control did to programming — not to the mechanics, but to the culture. Before it was universal, code was something you had; after, it became something you shared, reviewed, blamed, bisected, reverted, and learned from by reading. The tooling did not merely make existing practices convenient. It created practices that had not existed: the pull request, code review as a norm, the public portfolio of commits, the ability to learn a craft by reading thousands of examples of it done well.
None of that is available to musicians, and the reason is not cultural. It is that the artifact they exchange does not support it. Change the artifact and the practices become possible; whether they emerge is then a cultural question, which is a much better problem to have.
We are not claiming that text is better than audio, that producers should learn to program, or that DAWs are bad. We are claiming something narrower: for a large and growing class of electronic music, a complete song can be represented as a small, readable, deterministic text document, and doing so unlocks an entire category of capability that is otherwise unreachable. The recording still exists. It is now a build artifact.
2Six thousand years of text
Music-as-text is not a new idea; it is the default state of music for most of recorded history, briefly interrupted by the twentieth century. Understanding that interruption explains the shape of the problem.
2.1 Notation as lossy compression with a purpose
The oldest known written music is a Hurrian hymn on a clay tablet from Ugarit, roughly 1400 BCE: cuneiform naming intervals and, apparently, performance instructions. What matters is not its antiquity but its strategy. It does not attempt to record the sound. It records the decisions that produce the sound, in a notation that assumes an informed performer.
Every notation since has made the same trade, tuned differently:
| System | Era | Records | Assumes |
|---|---|---|---|
| Hurrian tablets | c. 1400 BCE | Intervals, tuning | A living tradition |
| Neumes | c. 9th c. | Melodic contour, direction | You know the chant already |
| Guidonian staff | c. 1025 | Absolute pitch on lines | You can read; rhythm is loose |
| Mensural notation | 13th–16th c. | Pitch and duration | A shared rhythmic vocabulary |
| Common practice staff | 17th c.– | Pitch, rhythm, dynamics, articulation | Trained performers, instrument conventions |
| Tablature | 16th c.– | Finger positions, not pitches | A specific instrument |
| Graphic / text scores | 20th c. | Intent, constraints, prose | An interpreting artist |
Two lessons transfer directly to language design.
First: notation is compression against an assumed decoder. Neumes are tiny because the singers already knew the melodies; they encode only the delta. Tablature is precise because it targets one instrument and refuses to be general. Every notation buys concision by assuming context, and the notation's reach is exactly the reach of that assumption. A language for techno can be dramatically smaller than a general music language because it may assume a kick drum, a 4/4 grid, and a sixteenth-note resolution. That is not a limitation smuggled in; it is the source of the concision.
Second: notation shapes what gets composed. Mensural notation made rhythmic polyphony thinkable: once durations were writable, composers wrote music that required them. The staff's five lines biased Western music toward pitch-set thinking. Tablature produced idiomatic guitar figures nobody would derive from a staff. This is the musical case of a general principle — the Sapir–Whorf question is contested for natural language and obviously true for notations, because a notation is not a description of thought but a workspace for it.
Whatever your notation makes cheap will get made. If probability is one character, people will write probabilistic music. If polymeter requires a declaration, almost nobody will use polymeter; if it falls out of writing a five-character pattern next to a sixteen-character one, it will appear everywhere. You are not designing a way to write music. You are designing which music is easy to think.
2.2 The interruption
In 1877 the phonograph made it possible, for the first time, to exchange the sound rather than the instructions. Within a century that became the default: the artifact of music was a recording, the score demoted to professional infrastructure, and — critically — an entire genre of music grew up that had no score at all. Electronic and sample-based music is composed in the studio, in the act of recording. There is no prior document. The render is the original and the only.
This is why the gap is worst precisely where the tooling is most digital. Classical music kept a portable text format for four hundred years. Techno, born digital, has never had one — the most computational music in history is the least readable.
2.3 The plain-text survivors
Three text formats from the pre-web era are worth studying closely, because they solved preservation problems that any new format will face.
ABC notation (Chris Walshaw, 1991) encodes folk tunes in ASCII: a header of metadata, then melody as letters with modifiers. It was designed to be emailable and typeable, and it has outlived nearly every application that ever read it. There are thousands of tools, hundreds of thousands of tunes, and a spec that has changed slowly and compatibly for three decades. Its lesson is not syntactic — its syntax is wrong for anything but monophonic folk melody — it is institutional: a versioned formal spec, a canonical corpus, and multiple independent implementations produce a format that outlives its authors' careers.
MML (Music Macro Language, from late-1970s Microsoft BASIC
and then a generation of Japanese home computers and chiptune trackers) encoded
notes as terse command strings: cdefgab for pitches, numbers for
lengths, o for octave, t for tempo. It is the direct
ancestor of every "pattern as string" system, and it demonstrated that a
notation optimized for typing speed develops a different aesthetic from
one optimized for reading — a tension we return to in §9.
Tracker formats (Ultimate Soundtracker, 1987, and the MOD/XM/IT lineage after it) are the most underrated ancestor of this work. A tracker module is a complete song in one file: patterns as a grid of rows and channels, an order list sequencing those patterns, instruments, and effects columns — plus the samples. That was a genuinely whole-song format two decades before anyone used the phrase "music as code." Two properties are worth stealing outright: the grid is the interface (a pattern is spatially legible; you see the groove), and an order list separates material from arrangement (write the drop once, play it four times). Two properties are worth refusing: the samples make it big and unportable, and the effect columns are a hex-coded per-vendor mess that only an expert can read.
3The computer-music lineage
Sixty-five years of research languages, each of which solved something real. A designer who has not read this history will re-derive its mistakes. What follows is deliberately opinionated: for each, what it proved and what it costs.
3.1 Music-N and the unit generator (1957)
Max Mathews wrote MUSIC at Bell Labs in 1957 and it played seventeen seconds of sound on an IBM 704. By MUSIC III (1960) he had invented the idea that still organizes the entire field: the unit generator. An oscillator, an envelope, a filter are boxes with inputs and outputs; a sound is a graph of them. And the second idea, equally durable: the split between the orchestra (instrument definitions) and the score (a list of timed events that play them).
Nearly every system since is a variation. Csound (Barry Vercoe, 1986) is Music-N with a bigger opcode library and forty years of momentum. Its orchestra/score split is exactly Mathews'. The unit generator survives in SuperCollider's UGens, in Max/MSP's objects, in Web Audio's nodes, in every modular synthesizer ever built.
What it proved: a signal graph is the right model for synthesis, and separating "how it sounds" from "when it happens" is the right first cut.
What it costs: the score is a list of events, so a song is long. Csound scores are generated, not written. The orchestra is a full programming problem, so the beginner's first sound is far away. The format describes signal flow superbly and song structure not at all.
3.2 MIDI (1983) — the standard that made the wrong thing standard
MIDI is the most successful music interchange format in history and a cautionary tale in equal measure. It standardized performance events: note on, note off, velocity, controller changes, on sixteen channels. It is universal, forty years old, still shipping, and it made the modern studio possible.
But a Standard MIDI File is a tape of gestures, not a document of decisions. It records that a note began at tick 1920 with velocity 96; it does not record that this is the second bar of the drop, that the bassline is the motif transposed, that the filter sweep is a build. It has no notion of sections, of song structure, of why. And crucially it says nothing about timbre: the same file is a piano on one machine and a marimba on another, which is the entire reason General MIDI existed and the entire reason it sounded bad.
What it proved: a small, stable, boring interchange format beats a beautiful one, and it wins for decades. Also: it is worth supporting. oontz emits Standard MIDI Files because a format that cannot leave for a DAW is a trap, however elegant.
What it costs: everything above the event layer. MIDI is the assembly language of music, and forty years of the industry treating it as the interchange layer is a large part of why no higher-level interchange layer exists.
3.3 The live-coding generation
From the mid-1990s, a distinct tradition: languages designed so that editing the program while it runs is the performance.
SuperCollider (James McCartney, 1996) made the decisive architectural move: split the language (sclang, where you write patterns and logic) from the audio server (scsynth, which knows only about synth graphs and messages). They talk over OSC. The consequence is that the language can crash, be replaced, or be driven by something else entirely without the audio stopping. This separation is correct and we adopt it in a different form (§16).
ChucK (Ge Wang and Perry Cook, 2003) contributed
strongly-timed programming: time is a first-class type, the program
advances it explicitly (1::second => now;), and concurrency is
sample-accurate. It is the cleanest treatment of time as a language construct
anyone has produced, and it deserves more influence than it has.
TidalCycles (Alex McLean, from 2009) is the most important recent contribution to notation. Its pattern is not a list but a function from a span of time to a set of events — a genuinely functional-reactive model in which a cycle is the unit and patterns compose algebraically. Its "mini-notation" packs rests, subdivision, repetition, alternation, polymeter and probability into a few characters. Tidal is embedded in Haskell, which is both its power (real function composition over patterns) and its ceiling (the audience is people who will learn Haskell).
Sonic Pi (Sam Aaron, 2012) is the philosophical outlier and, by reach, the most successful: it was designed to teach programming to children in classrooms, and it is ruthless about the first ten seconds. Two lines make a sound. It is joyful on purpose. Its lesson is not technical at all — it is that time-to-first-sound is a design parameter with more impact than any feature in your feature list.
Strudel (2022–) ports Tidal's semantics to JavaScript in the browser, with a strict parser → AST → interchangeable outputs pipeline. It proves the browser is a serious runtime for this work, and its architectural discipline — text parses to structure, structure feeds multiple outputs (WebAudio, MIDI, OSC) — is the correct shape.
Others worth knowing: Extempore and Impromptu (Andrew Sorensen) for live-coded systems programming with temporal recursion; Overtone (Clojure over SuperCollider); Gibber (browser, live-coded, notated visually); ORCA (Devine Lu Linvega) — a two-dimensional esolang where operators are letters on a grid and the program is visibly alive, the most radical rethink of the music-notation surface in twenty years; Alda (text-first score language, deliberately unglamorous); Faust (functional DSP that compiles to C++/WASM, the right answer for writing the DSP layer); Nyquist (Roger Dannenberg — functional, behavioural abstraction, sound as a first-class value); bytebeat (Viznut, 2011 — a single C expression over a sample counter produces music, the extreme point of the compression axis and a genuinely useful thought experiment about how little text a song requires).
3.4 The algorave context
Live coding grew a performance culture — TOPLAP (2004) and its manifesto's famous demand to "show us your screens," then the algorave movement from 2012 — in which the projected source code is part of the show. This matters here for a non-obvious reason: it established, in front of real audiences, that an audience will look at code while dancing to it and that the code being legible is part of the aesthetic. That is empirical evidence for a readability-first notation, gathered in the least academic setting imaginable.
4The hole in the middle
Place the prior art on two axes. The first: how much of a song does the artifact contain? — from a single sound, through a loop, to a whole arranged track. The second: who can read it? — from "specialist programmer" to "anyone."
Systems cluster in three corners and avoid the fourth.
- Deep but narrow. Csound, SuperCollider, Faust, ChucK describe sound with enormous precision and say nothing about song structure. A five-minute arranged track in any of them is a program you generate, not a document you read.
- Whole but unreadable. DAW projects, MIDI files, tracker modules contain the whole song and are machine records, vendor-locked, or sample-dependent.
- Readable but wrong genre. ABC and staff notation are beautifully readable and model an idiom — melody, harmony, an assumed acoustic performer — that has no vocabulary for a sidechained reese bass or a twenty-four-bar filter sweep.
- Live-coding notation — Tidal and Strudel — is the closest thing to the right notation, and its systems are explicitly designed around the performance rather than the document. A Tidal set is a session, not a track you can hand someone as a finished, arranged, five-minute piece of music.
The unoccupied quadrant has a precise specification:
A complete modern song as canonical text: human- and machine-editable, versionable, diffable, searchable, forkable with provenance, small enough to paste anywhere, and rendered identically by independent runtimes.
Every clause is load-bearing. Drop "complete" and you have Tidal. Drop "readable" and you have MIDI. Drop "identical runtimes" and you have a DAW project. Drop "small" and you have stems.
The rest of this document is what it takes to build in that quadrant.
The theory
The laws, the semantics, the models of time, and the notation theory. This is the part that transfers to a language sharing none of our syntax.
5Nine laws
Every non-trivial decision in the reference implementation traces to one of these. They are stated as laws rather than principles because in practice they function as constraints that veto designs, not as values that inspire them.
The text is not a description of the music, a project file for it, or a recipe you follow to recreate it. It is the music, in the same sense that an HTML file is the web page. Audio is a build artifact, produced on demand, discarded without loss. If your format needs the render to be kept alongside it, the format is incomplete.
The same source produces the same audio: on any machine, in any year, in any conforming runtime. Not "similar," not "musically equivalent" — sample-identical, up to the float behaviour of the host. Determinism is what makes the source authoritative; without it the render creeps back to being the real artifact, and every other property in this list quietly dies with it.
Any notation extension must pass the guessability test:
someone who has never programmed, shown one example, can guess what it does.
x... *4 passes. x? passes — "that hit… maybe."
Nested polymetric alternation with a Euclidean modifier does not, and stays
out. This law costs real expressive power and is worth it, because the audience
for a music language is not the audience for a programming language.
A loop is not a song. The format must carry arrangement, sections, transitions, automation, sound design and metadata, because the difference between a sketch and a track is exactly the material a loop-shaped format discards. This is the clause that separates this class of language from the entire live-coding tradition.
At least two independent implementations, held to identical musical output by an automated gate. Not for redundancy — as proof that the spec is the spec and not a description of one program's behaviour. A format with one implementation is that implementation's save file wearing a costume.
Everything that affects the sound is in the document. No preferences, no machine-local config, no "you had to have the plugin," no random seed living outside the text. If a knob matters, it is written down; if it is not written down, it does not matter.
From arriving to hearing something you made: ten seconds, no install, no account, no signup wall, no sample pack download. This is not marketing. It is the single highest-leverage design constraint in the entire system, and it kills entire architectures — see §13, where it eliminates server-side rendering outright.
A creative tool must not crash, and must not lie. An unknown effect is skipped and reported, not fatal. An unreachable API degrades to the offline page rather than a spinner. A pattern with a typo plays what it can. Losing someone's work is the one unforgivable outcome; a wrong sound is recoverable, a lost session is not.
Every capability — editing, generating, sharing, AI collaboration, testing, export — operates on the same document. There is no privileged internal representation that some features get and others do not. A feature that needs a side channel is a design error, discovered early and cheaply.
5.1 What the laws forbid
Laws are useful mainly for what they exclude. In our case they killed, at various points, all of the following — each of which was proposed, sounded reasonable, and is incompatible with the list above:
| Proposal | Killed by | Why |
|---|---|---|
| Sample library | 1, 2, 6 | The song stops being self-contained; the render depends on files |
| Server-side audio rendering | 7 | 200 ms round trip; you cannot play an instrument through it |
| VST/plugin hosting | 2, 5, 6 | Non-deterministic, non-portable, unimplementable in a second runtime |
| Global random seed | 2, 6 | Same source, different audio — the one thing that cannot happen |
| Live-coding evaluation semantics | 4 | Optimizes for the session; leaves no finished document |
| Turing-complete scripting | 2, 3 | Halting, unbounded cost in an audio callback, unreadable songs |
| Binary "compiled song" format | 1, 9 | Undiffable, ungreppable — the entire value proposition inverted |
| Account required to play | 7 | Ten seconds becomes ninety and most people never arrive |
6A song is a pure function
Here is the whole semantic model, and everything else in the architecture is a consequence of it.
state_at(bar) → the fully resolved state of every track at that absolute bar
render(state) → the samples for one bar
A song is a function from bar index to state, composed with a function from state to audio. Both are pure. That is the entire model.
6.1 Why purity is the right frame
The obvious way to build a sequencer is as a state machine: a playhead advances, events fire, handlers mutate a global mixer. Every DAW works this way internally, and it is the wrong model for a document, for four reasons that all follow from the same root.
- Scrubbing becomes a replay problem. With mutation, "what
does bar 96 sound like" is only answerable by simulating bars 0–95, because the
state at 96 is the accumulated result of everything before. With a pure
state_at, it is a function call. Scrubbing is setting an index. - Offline render can drift from live playback. Two code
paths — a real-time path and a bounce path — will differ. With one pure
function feeding both, they cannot: the offline render is
state_atasked for every bar in turn, through the identical renderer. This is a correctness property you get for free and can otherwise never fully test. - Automation stops being spooky. A filter sweep is not an
agent that mutates a global cutoff over time; it is interpolation inside
the answer to
state_at. Ask for bar 40 and the cutoff is already what it should be at bar 40. There is no ordering hazard, no "did the automation run before or after the section change," no residue. - Testing becomes trivial. Pure functions are testable by inspection. You can assert that bar 64 has a kick, that energy rises monotonically across a build, that two versions of a song differ in exactly one section — all without producing a single sample of audio.
This is denotational semantics applied to music: define what a program means as a mathematical object, and let the implementation be any machinery that computes it. The meaning of a song is a function from time to sound. Everything an engine does is an optimization of that function, and every optimization is checkable against it.
6.2 Sections hold snapshots, not diffs
Given a song made of sections — intro, build, drop, break — there are two ways to represent them. As diffs: "the drop is the build plus a second bass and minus the reverb." Or as full snapshots: each section carries the complete state of every track.
Diffs are tempting. They are smaller, they express the way sections are often made ("copy the build, add stuff"), and they feel more elegant. They are the wrong choice, and the reasoning generalizes far beyond music:
- Spooky action. With diffs, editing the build silently changes the drop. That is correct dependency tracking and it is not how anyone thinks about a song. A person who fixes the intro's hi-hat does not expect the outro to move.
- Resolution cost on every read.
state_atwould have to walk the chain from the beginning, which is exactly the replay problem purity was supposed to eliminate. - Reordering breaks. Move the break before the drop and a diff chain either becomes meaningless or requires a rebase. With snapshots, reordering is reordering a list.
- Diffs of diffs are unreadable. The version-control diff between two versions of a song is the feature. A textual diff of a delta-encoded structure is noise.
The cost is redundancy: eight sections that share a kick pattern store it eight times. At the scale of a song — kilobytes — that is free. This is a recurring theme: when the artifact is small, prefer the representation that matches how people think, not the one that saves bytes. The compression argument that would have won in 1987 loses now, and designers carrying instincts from the era of scarce storage will make this call wrong.
6.3 Referential transparency in music
One consequence deserves its own name. Because state_at is
pure, a section is a value. It can be copied, named, transposed,
compared for equality, hashed. Two sections are the same section if their
snapshots are equal — which gives you, for free:
- a fingerprint for a song (hash the resolved states) and therefore duplicate detection, similarity search, and cache keys;
- structural equality between arrangements, so "these two tracks share a drop" is a computation, not an opinion;
- memoization of rendering: identical bars render once;
- diffing at the level of meaning — "the break grew eight bars, the bass filter opened by 200 Hz" — rather than at the level of text.
7Models of musical time
The deepest design decision in an audio language, and the one most often made by accident, is what kind of thing time is. There are four viable answers and they produce completely different languages.
| Model | Time is | Exemplars | Strength | Cost |
|---|---|---|---|---|
| Event list | A sorted list of timestamped events | MIDI, Csound score | Universal, simple, exact | No structure; huge; unreadable |
| Cycle / pattern function | A function from a time span to the events in it | TidalCycles, Strudel | Composes algebraically; infinite; elegant | Cycles are eternal — arrangement is an afterthought |
| Imperative clock | A variable the program advances | ChucK, Sonic Pi, Extempore | Intuitive; sample-accurate; concurrent | Program state is not a document; not a portable artifact |
| Timeline of snapshots | An ordered list of sections, each a full state | Trackers, DAWs, oontz | Arrangement is native; scrub is O(1); readable | Less algebraic; sub-bar structure needs its own layer |
7.1 Why the timeline wins for whole songs
The cycle model is intellectually the most beautiful. A Tidal pattern is a function from a time span to events; patterns compose with combinators; everything is infinite and everything is algebra. If your goal is live-coded performance, it is the correct model and nothing else comes close.
But a song is not eternal. A song is 5 minutes 12 seconds long, has an intro, drops at 1:04, breaks at 2:30, and ends. The cycle model can express that — with conditionals on the cycle number, or by sequencing patterns — but it expresses it awkwardly, because arrangement is not what the model is about. Ask a Tidal user for the file that is their finished five-minute track and the question does not quite parse; that is not a criticism of Tidal, it is evidence about what its time model is for.
Conversely, the timeline model makes arrangement the primitive and makes sub-bar structure a secondary concern — which is precisely the right trade for a document format, because arrangement is what distinguishes a song from a loop, and the loop is the easy part.
7.2 The hybrid, and where the seam goes
The honest resolution is that you need both, at different scales, and the design question is where the seam goes. In our reference implementation:
macro time (bars, sections, arrangement) → timeline of snapshots
meso time (one bar, the pattern grid) → positional string, index = time
micro time (swing, humanize, slides) → offsets computed at render
Three scales, three representations, each chosen for what that scale needs: the timeline gives you O(1) scrubbing and legible arrangement; the pattern string gives you a groove you can see; the micro layer gives you the difference between a machine and a musician. Getting these boundaries right is the whole game. A language that uses one representation for all three either cannot express arrangement (cycles all the way up) or cannot express groove (snapshots all the way down).
7.3 Bars, not seconds
A subtle corollary: all internal time is measured in bars and steps, never seconds. Seconds are derived at render time from BPM. This means a tempo change is a one-token edit that re-times the entire song correctly and instantly, automation stays musically anchored, and the whole document is tempo-independent. Formats that bake absolute time — MIDI ticks with a tempo map, audio stems — pay for it forever.
8Determinism and the human
Law 2 demands identical output from identical source. Music demands variation, or it sounds like a machine. These look contradictory. Resolving the apparent contradiction is one of the more satisfying results in this design, and it generalizes to any deterministic system that needs to feel alive.
8.1 The trap: a global random seed
The obvious approach is a seeded PRNG: store the seed in the document, and the "random" variation is reproducible. This fails, and the failure is instructive. A PRNG is a stream: its nth value depends on how many times it has been called. So the moment anything upstream changes — a track is added, a section is reordered, one voice consumes one extra sample of noise, the render order changes — every subsequent draw shifts. The document is technically deterministic and practically chaotic: editing the intro changes the hi-hats in the outro. That violates the same principle that killed diff-encoded sections in §6.2, and for the same underlying reason: order-dependence is spooky action.
8.2 The fix: hash the coordinates
Replace the stream with a pure function of position. Each probabilistic decision is keyed by exactly the coordinates that identify it — which track, which bar, which step — and hashed:
maybe(track, bar, step):
s = "track|bar|step"
h = 0
for ch in s: h = (h * 31 + ord(ch)) & 0xFFFFFFFF
h ^= h >> 15
return (h % 1000) < 700 # a 70% chance
The properties this buys are worth enumerating, because each solves a specific real problem:
- Position-independent. No call ordering, no stream state. Adding a track cannot disturb another track's decisions, because the other track's key does not mention it.
- Random-access. Bar 400's hits are computable without
computing bars 0–399. This is what makes
state_atgenuinely O(1) even with probability in the patterns. - Locally varying, globally stable. The groove differs bar to
bar — a
?hat lands in bar 3 and not in bar 4 — while the song as a whole is byte-identical on every render. The listener hears a human; the diff sees a constant. - Portable across languages. The hash is deliberately the dumbest possible construction: multiply-add-xor over character codes, all in 32-bit integer arithmetic. No floats, no library, no locale, no platform-specific behaviour. Ten lines in any language, identical results — which is exactly why the JavaScript engine and the Python engine agree decision for decision.
Never use a stream where you can use a function of coordinates. This applies to procedural terrain, to sharded ID generation, to test fixtures, to any generated content that must be stable under edits elsewhere. The seeded-PRNG habit is one of the most common sources of "why did this unrelated thing change" in generative systems, and the fix is always the same: hash the address, do not advance a stream.
8.3 The three layers of "human"
Groove is not one thing. Three mechanisms, deliberately separate:
- Swing — a systematic delay on odd steps, expressed as a percentage of the step. Deterministic, global, musical, and the single most recognizable feel parameter in dance music. Not random at all.
- Humanize — a per-track jitter in milliseconds, drawn from a per-track generator, applied to hit positions. Small (single-digit milliseconds), and the thing that separates a drum machine from a drummer.
- Probability — the
?step: a hit that happens 70% of the time, decided by the coordinate hash. This is structural variation rather than timing variation, and it is why a sixteen-bar hat pattern does not become wallpaper.
Keeping them separate matters because they answer different musical questions ("what feel," "how tight," "how varied") and because a single "humanize" knob that mixes them is unusable — the reason so many drum machines have one and nobody turns it up.
8.4 Determinism has limits, and you should state them
Honesty about the boundary is part of the spec. Two engines, one written in Python with NumPy and one in JavaScript over Web Audio, will make identical musical decisions — every hit, every probabilistic step, every automation value — but floating-point scheduling in a browser audio graph may differ in the last bit. So the guarantee is stated in two tiers:
- Musical determinism (required of every conforming runtime): identical events, identical parameter values, identical structure.
- Bit determinism (required within a runtime): golden renders are byte-identical across changes, enforced by hashing the output of a fixed corpus.
Claiming bit-identity across runtimes would be a lie, and a spec that lies about its guarantees is worse than one with weaker guarantees. Say what you enforce; enforce what you say.
9Notation as interface
The pattern language is four characters and it is the most consequential design decision in the system.
kick X...x...X...x..x
hat ..x...x...x.x.x.
oh ......x.......x.
clap ....x.......x...
x is a hit. X is an accented hit. . is
a rest. - is a tie. ? is a maybe. That is the entire
core notation, and a person who has never seen it can read that block.
9.1 Iconicity: the notation looks like the thing
Peirce distinguished icons — signs that resemble what they denote —
from symbols, which are related to their object only by convention.
Most programming notation is symbolic: euclid(5, 16) means a
five-in-sixteen Euclidean rhythm only if you know the convention.
x..x..x..x..x... is iconic: the spacing on the page is the
spacing in time. The visual rhythm of the characters is the rhythm.
This has consequences beyond charm.
- You can see the groove without decoding it. Reading is a perceptual act, not an interpretive one. Sixteen characters is within the span of foveal vision at normal text size, so a bar is taken in with one fixation — the same reason a tracker grid works.
- Errors are visible. A pattern one character too long is obvious the way a misaligned column is obvious. Compare finding an off-by-one in a list of tuples.
- Alignment across tracks is free. Four patterns stacked in a monospace font form a grid; the relationship between the kick and the hat is spatial. This is a tracker's core insight, preserved without the tracker.
- Editing is direct manipulation. Changing a rest to a hit is typing one character in the position where you hear the change. The gulf between intention and action, in Norman's terms, is one keystroke wide.
9.2 The guessability test, applied
Law 3 says an extension must be guessable from one example. In practice this is a hard filter and it has rejected more proposals than it has passed. The scoreboard:
| Notation | Means | Verdict |
|---|---|---|
x . X - | hit, rest, accent, tie | In. Guessable from the shape |
x? | a hit, most of the time | In. "That hit… maybe" |
x..x *4 | that pattern, four times | In. Multiplication is universal |
| Uneven pattern lengths | polymeter, automatically | In. No syntax at all — 5 against 16 just works |
a1~ c2! | slide into a1, accent c2 | In. One glance plus one example |
[xx][x.] | subdivision groups | Deferred. Guessable, but interacts with length rules |
<x .> | alternate between cycles | Out. Requires knowing "cycle" is a concept |
x(3,8,2) | Euclidean with rotation | Out. Three unlabelled numbers; not guessable |
every 4 (fast 2) | conditional transformation | Out. This is programming, and that is a different product |
Note what the rejections have in common: each is more powerful than what was accepted. The test is not "is this good," it is "does this cost a reader more than it gives them." A language that accepts every good idea becomes a language that only its designers can read — a fate that has claimed a remarkable number of otherwise excellent music languages.
9.3 Polymeter for free — the case study in good design
The single most elegant thing in the notation is something nobody designed. Patterns may be any length, and each is divided evenly across the bar. Write a 16-step kick and a 5-step hat, and the hat cycles against the kick with a period of 80 steps — genuine polymeter, the device behind a great deal of the most interesting rhythmic music on earth, and it required zero syntax. It is not a feature; it is the absence of a restriction that a lazier implementation would have imposed by hardcoding sixteen.
Generalize the lesson: the best features are the constraints you declined to add. Before designing syntax for a capability, check whether removing an assumption gives it to you for nothing. This is the design-space equivalent of deleting code, and it has the same superior payoff profile.
9.4 Two representations, one document
The song exists in two forms, exactly as a program exists as source text and as an abstract syntax tree, and both are text.
The command log (.oontz) — the canonical
human source. The exact commands that make the instrument produce the song, one
per line, comments with #. This is what you type, what a recording
session records, and what git diffs beautifully:
# warehouse — driving techno, the default starting point
bpm 132
swing 6
kick X...x...X...x..x
hat ..x...x...x.x.x.
oh ......x.......x.
clap ....x.......x...
bass a1! . a1~ . c2 . a1 . g1! . a1~ . c2 . d2 .
filter bass lp 420 res 0.8
sidechain bass 0.7
Ten lines. 232 bytes. A complete driving techno loop.
The compiled timeline (.song, JSON) — the
intermediate representation: the whole arrangement as data, which the composer
emits and the players consume. Sections, orders, per-track state, automation,
metadata, provenance. A full five-minute composed track is 15–40 KB.
Why both? Because they serve different readers and neither can serve both. The log is for humans and for diffs — it is sequential, imperative, and minimal. The IR is for machines and for structure — it is declarative, complete, and addressable. Attempting one representation for both produces either a JSON blob nobody can hand-write or a command log that tools must re-simulate to understand. Keeping the seam explicit is the same discipline as source-versus-AST in a compiler, and it pays the same dividends.
9.5 Naming, and the cost of cleverness
A final notational point that is easy to underrate: the command names are
the words a musician already uses. bpm, swing,
kick, hat, clap, filter,
sidechain, reverb. Not tempo_set, not
sc.amount, not track[0].pattern =. The vocabulary of
the domain, used directly, with the argument order that reads as English:
filter bass lp 420 res 0.8.
This costs generality — kick is a track name and a voice name
and a word — and buys the thing that matters more: a person can write
the language before they have read the manual, because they are typing
words they already know in the order they already say them.
10Totality and failing open
A compiler that rejects a bad program is doing its job. An instrument that refuses to make a sound is broken. This difference is more profound than it first appears and it should change how you write nearly every error path.
10.1 The instrument is not a compiler
When a programmer's build fails, the programmer fixes it — the feedback loop is intact and the error message is the most valuable output. When a musician's instrument goes silent mid-idea, the idea is gone. Musical work is not recoverable state; it lives in a person's short-term memory and evaporates in seconds. A tool that interrupts to complain has destroyed something that a correct error message does not replace.
So the reference implementation is total in the mathematical sense wherever it can be: every input maps to some output, and there are almost no error paths that stop the music.
| Input | A compiler would | An instrument does |
|---|---|---|
| Unknown effect name | Error, halt | Skip that effect, keep the chain |
| Pattern with a stray character | Syntax error | Treat it as a rest; play the rest of the bar |
| BPM of 0, NaN, or 109 | Reject | Clamp to the legal range at assignment |
| Note token that is not a note | Error | Silence for that step; everything else plays |
| A voice that throws | Crash | Zero samples for that hit; the bar continues |
| Automation hook raising | Stop the clock | Reuse the previous bar; count a drop |
| API unreachable | Spinner forever | Local mode, with a line saying so |
| Unknown field in a document | Reject the file | Ignore it — forward compatibility |
| Unknown format version | — | Refuse loudly. The one hard error |
10.2 Failing open is not failing silent
The critical qualifier in Law 8. "Fail open" means the music continues; it does not mean the problem is concealed. A skipped effect prints a line. A clamped BPM shows the clamped value. Dropped bars are counted and the counter is visible. The distinction:
- Fail closed — stop everything. Correct for a compiler, catastrophic for an instrument.
- Fail silent — continue, say nothing. The worst of all worlds: the user's mental model diverges from reality and they debug the wrong thing for an hour.
- Fail open — continue, say what happened, let the human decide when to care. Correct for creative tools.
10.3 Clamp at the assignment, not at the use
One implementation detail worth stealing verbatim. Tempo is assigned from a dozen places: typed commands, keyboard shortcuts, song sections, the composer, automation, a loaded file, a shared URL. One bad value makes a bar zero samples long, which the audio callback then divides by.
The lazy-but-wrong fix is to validate at each of the dozen call sites. The right fix is to make the field a property that clamps on write. One guard, enforced by the type, unbypassable, and any future thirteenth caller inherits it. Push validation to the narrowest point through which every path flows. That point is nearly always an assignment, not a use, and finding it is usually the whole work of the bug fix.
10.4 Forward compatibility as a contract
The document carries format: "oontz-song-1". The rules, stated
as obligations on readers rather than as hopes:
- A reader must accept unknown extra fields and preserve them when re-serializing where it can. This is what lets the format grow without a flag day.
- A reader must refuse an unknown format identifier loudly. Guessing at a format you do not know is how silent corruption happens.
- A writer must not require fields that did not exist in the version it names. Defaults are part of the spec, not part of the implementation.
This is the discipline that let ABC notation survive thirty years and a hundred applications, and it costs nothing to adopt on day one and is nearly impossible to retrofit on day one thousand.
11Metaphors and analogies
Metaphors are not decoration in system design; they are compression for reasoning. The right one lets you predict how the system should behave in a case you have not considered. The wrong one makes you build the wrong thing confidently. Here are the ones we use, what each explains, and — importantly — where each breaks.
11.1 The primary metaphor: HTML
A web page is not a screenshot of a web page. It is source, rendered by anything that speaks the format, differently on every screen, identical in meaning. This is the closest honest analogy to a song as source, and it is the one to reason from.
| The web | This language |
|---|---|
| HTML document | The song source |
| Browser | The engine / runtime |
| Rendered pixels | Rendered audio |
| "View source" | source — the song prints itself |
| Multiple browsers, one spec | Two engines, one format |
| The page is kilobytes; images are not | The song is kilobytes; the WAV is not |
| Anyone can save, edit, re-host | Anyone can fork, edit, republish |
| CSS cascade, DOM, progressive enhancement | Sections, tracks, fail-open rendering |
Where it breaks: HTML is rendered to a static frame; audio is rendered to a timeline under a hard real-time deadline. The browser can reflow when a font loads. An audio engine that "reflows" produces a click, and a click is unforgivable. Everything about the scheduler in §13 exists because of this asymmetry.
11.2 The recipe and the cake
The most useful metaphor for non-technical audiences, and it survives pressure better than most. A recording is a cake: delicious, finished, un-uningredient-able. The source is the recipe: smaller, teachable, adaptable, scalable, and it makes the cake on demand. You can halve a recipe. You can substitute an ingredient. You can see that the previous baker used too much salt. You can hand it to someone in another country and they will produce the same cake.
Where it breaks: recipes are famously under-specified — "season to taste," "bake until golden" — and depend on a skilled human. Our source is not; it is executable, and the determinism law is precisely the promise a recipe does not make. The metaphor sells the value and understates the rigor.
11.3 The seed and the tree
A seed is small, carries the whole plan, and unfolds into something vastly larger. 232 bytes becomes five minutes of stereo audio — a compression ratio of roughly 200,000:1 against the WAV. Useful for explaining why the economics change (§20) and why the format can ride in a URL, a chat message, or a model's context window.
Where it breaks: a seed's unfolding depends on soil and weather; ours must not depend on anything. Growth is generative and variable — determinism is the opposite. Use for the size argument only.
11.4 Source and build artifact
The precise engineering framing, and the one that generates the correct
answer to almost any question about what to store. You do not commit binaries.
You do not email a colleague your node_modules. You do not archive
a PDF and delete the document it was typeset from. The build artifact is
reproducible, therefore disposable, therefore not the thing of record.
Applied here: a WAV is a build artifact. A "recording" is a compiled output, the way a PDF is compiled from a document. Storing audio is unnecessary, and a take is simply the text that reproduces it.
Where it breaks: people have feelings about recordings that they do not have about binaries. A specific render, at a specific moment, on specific speakers, is sometimes the artistic object — a live take with a mistake in it that is better than the correct version. The framing is right about storage and wrong about sentiment, and a mature system should let you keep an audio render when you want to, without pretending that is where the value is.
11.5 Chess notation
The most exact analogy in the entire list, and the most underused.
1. e4 e5 2. Nf3 Nc6 is a complete, unambiguous game in a few dozen
bytes. It is human-readable to anyone who plays, machine-readable to every
engine, searchable across millions of games, diffable, and it has produced an
entire literature of annotation, an industry of databases, and a culture in
which studying other people's games is how you learn.
Chess got that culture because it had a portable notation for four hundred years. Music, for electronic genres, has not. The analogy predicts what happens if the notation arrives: not "people make music differently," but people start learning from each other's music the way chess players learn from each other's games — by reading it, move by move, at leisure, with the reasoning visible.
Where it breaks: chess notation records only legal moves in a closed, discrete system with no timbre, no continuous parameters and no aesthetics. Ours must encode continuous values and sound design. The analogy is about the cultural consequences of a portable notation, not its structure.
11.6 Knitting patterns, architectural plans, DNA
Three more, each carrying one specific insight:
- Knitting patterns.
K2, P2, rep from * to endis a compact, iterated, domain-specific notation that non-programmers have used fluently for centuries, and which nobody considers "technical." It is the counter-example to "ordinary people cannot read code": they can, when the notation is about something they care about and uses their vocabulary. This is the single best rebuttal to the objection that a text interface is elitist. - Architectural plans. Plans are not a picture of a building; they are a buildable specification, plus a professional culture of review, annotation, revision numbers, and standards. Blueprints made buildings reviewable before they exist. That is exactly what a song source does for arrangement, and it suggests the missing practice: musical plan review.
- Genotype and phenotype. DNA is a compact, discrete,
copyable, mutable encoding; the organism is the expressed result. Mutation,
inheritance, lineage and recombination all operate on the genotype. Our
remix_offield is literally a parent pointer, and a remix tree is a phylogeny. The metaphor predicts the feature: if you have genotypes, you get inheritance, and if you get inheritance you should record lineage. We did, because of this metaphor.
11.7 Two metaphors to avoid
Equally important, and rarely written down.
"It's like sheet music." Superficially apt and actively harmful. Sheet music is instructions for a performer and is deliberately under-specified — it does not determine the sound, and its incompleteness is the point, because interpretation is where the art lives. Our source is executable and complete. Reaching for this metaphor leads people to propose expressive-marking features ("play it warmly") that have no meaning in a deterministic system, and to expect a human in the loop who is not there.
"It's a DSL." Technically true, rhetorically fatal. The moment you describe it as a domain-specific language you have told your actual audience — musicians — that this is a programming product, and you have told programmers to evaluate it as a language rather than as an instrument. It is an instrument whose interface is text. The distinction determines who tries it, and therefore whether it works.
The architecture
How it is actually built, with the numbers. The layer cake, the scheduler, the extension mechanism, and the parity discipline that makes the format real.
12The layer cake
Six layers, each with one job, each replaceable without touching the others. The seams are where the value is; the layers themselves are unremarkable.
12.1 Why the IR is the load-bearing layer
New designers reach for the parser or the DSP. Both are the easy parts. The document IR is where the design is decided, because every capability you will ever add reads it:
- Playback reads it and produces samples.
- Offline render reads it and produces a file.
- MIDI export reads it and produces events — no audio involved.
- Diff reads two of them and produces English.
- Search reads a million of them and produces results.
- The composer writes it — generation is a function that emits an IR, not a function that plays notes.
- An AI reads it and writes commands that edit it.
- The visualizer reads per-track band energy that the renderer parked there.
If the IR is right, adding an output is a day. If it is wrong — if, say, it represents drums as pre-baked audio offsets rather than as patterns — then MIDI export is impossible, structural search is impossible, and diffing is meaningless, and you will not discover this until you try the third feature.
Before writing a line of engine code, write down five outputs you do not intend to build yet — MIDI, notation rendering, a similarity search, a karaoke-style visualiser, a hardware sequencer driver — and check that each is obviously implementable from the IR alone. If any requires information that only exists inside the renderer, the IR is under-specified, and that is much cheaper to learn now than in eighteen months.
13One bar ahead
The hardest engineering constraint in the system is not synthesis. It is the real-time deadline: an audio callback runs every few milliseconds, must return in less time than the buffer it fills represents, and must never allocate, block, take a lock, or garbage-collect. Miss once, and the listener hears a click — and a click is not a bug report, it is the moment somebody decides your software is cheap.
13.1 The three architectures, and why two are dead
| Approach | Latency | Cost at scale | Verdict |
|---|---|---|---|
| Render server-side, stream audio | 200 ms+ round trip | CPU per listener per second | Dead. You cannot play an instrument through it, and free is impossible |
| Run the Python engine in WASM (Pyodide) | 10 MB download; GC pauses in the audio path | Free | Dead for live. Fine for offline render |
| Port the DSP to Web Audio | Sub-10 ms, native | Free — it runs on the visitor's machine | This one |
Note how Law 7 — ten seconds to first sound — does the killing. Server rendering is not rejected on cost grounds first; it is rejected because a 200 ms round trip is not an instrument. The economics only confirm the verdict. When a law is well chosen, it decides architecture questions before you have finished asking them.
13.2 The bar-ahead scheduler
The design that makes real-time safety almost trivial:
# The audio callback slices a pre-rendered bar and does nothing else.
callback(out, frames):
out[:] = current_bar[pos : pos+frames]
pos += frames
if pos >= len(current_bar):
bar_done() # swap in the bar rendered ahead; pos = 0
Everything expensive — synthesis, filtering, effects, automation, the whole arrangement engine — happens on another thread, one bar early, into a buffer. The callback is a memory copy and a pointer increment. It cannot glitch because there is nothing in it to be slow.
The numbers that make this work: a cold render of one bar takes roughly 0.2 s; at 132 BPM a bar lasts about 1.8 s. That is a 9× margin. And if a render ever overruns anyway — a pathological effect chain, a busy machine — the callback reuses the current bar and increments a drop counter. The listener hears a repeated bar instead of a click, which is musically almost invisible, and the drop count is displayed rather than hidden (Law 8 again).
13.3 What a bar-quantized world costs
Being honest about the trade, because it is a real one. Edits take effect at the next bar line, not instantly. Type a new hi-hat pattern and you hear it in up to 1.8 s.
Three reasons this is the right call anyway, and one caveat:
- It is musically correct. Changes landing on the bar line is what a human DJ does and what every hardware sequencer with pattern-change quantization does. Instant, mid-bar changes sound like mistakes even when they are intentional.
- It makes the engine simple enough to be right. No partial re-render, no crossfade logic, no state that is half-old and half-new. A live edit is: render a whole new bar off-thread, swap the pointer at the boundary. Correctness by construction beats cleverness with edge cases.
- It makes the two engines agree. A bar is an atomic unit in both runtimes. Sub-bar mutation semantics would be the single hardest thing to keep identical across languages, and it would be the first thing to diverge.
- The caveat: live performance effects — filter sweeps, stutters, tape stops — must be immediate or they are useless. These are handled as pointer math over the already-rendered bar, not as re-renders: a stutter re-reads a slice, a spinback advances the read pointer non-linearly, a tape stop scales the rate. Performance effects operate on the buffer, not on the document. That distinction — document edits are quantized, performance gestures are immediate — is the seam that lets both be true at once, and it is worth stealing.
14Voices that ask
How do you add a new sound to a language like this without the core knowing anything about it?
The usual answers are a plugin interface (every voice implements
ISynthVoice) or a registry (every voice declares a parameter
schema). Both work. Both are ceremony, and both mean the core has an opinion
about what a voice is.
The mechanism here is smaller: a voice declares what it wants by naming its parameters, and the core supplies whatever the track has.
def v_bass303(hz, accent, cutoff, res, slide_from): ...
def v_hat(accent, tune, decay): ...
def v_kick(accent): ...
At render time, the core inspects the function's signature and passes the
track's values for exactly the names that appear. A voice that asks for
hz is by definition pitched, so the pitch machinery engages
automatically. A voice that asks for cutoff gets the track's filter
frequency. A voice that asks for nothing gets an accent flag and plays.
Consequences:
- Adding a voice is writing a function. No registration, no schema, no interface, no core edit. Import it and it is playable.
- "Is this track pitched?" is derived, not declared. It is computed from whether the voice's signature mentions a frequency, which means that property can never fall out of sync with reality — a whole class of bug deleted rather than fixed.
- Parameters cannot be silently misrouted. Names are the contract. There is no positional argument order to get wrong.
- The cost is one reflection call, memoized because signatures are static.
This is dependency injection by parameter name — the same trick behind pytest fixtures and Angular's original DI. It is worth reaching for whenever you have many small plugins with heterogeneous needs and one caller that holds all the data. The alternative — a uniform interface — forces every plugin to accept a context object and dig through it, which is more code in every plugin so that the core can have less.
Where it stops working: across a language boundary, and in a minified JavaScript bundle where parameter names do not survive. The browser engine therefore uses an explicit small table instead. When a technique does not port, do not port it — port the property (voices declare their needs; the core supplies them) and let each runtime achieve it idiomatically.
15Effects, and the duck
Two details of the audio path that carry design lessons well beyond audio.
15.1 Sidechain is a first-class citizen
In most audio software, sidechain compression — the pumping that makes the bass duck out of the kick's way, the single most characteristic sound in electronic dance music since the 1990s — is assembled from primitives: insert a compressor, route a send, configure a key input, tune four knobs. Perhaps fifteen minutes and some expertise.
Here it is one token: sidechain bass 0.7.
The implementation is a ducking envelope computed once per bar from the kick positions and multiplied into every track that asked for it. It is not a compressor at all — it is the effect a compressor is used to produce, implemented directly, deterministically, and in one line of source.
This is a general principle for domain languages, and possibly the most transferable idea in this document: make the genre's signature move a primitive. A language for techno in which sidechain requires five steps has mis-ranked its vocabulary. A language for the web that made forms hard would be absurd. Look at what practitioners of your domain do every single time, and check that it is one token. If it is three, you have built a general system and called it a domain one.
The corollary is the harder discipline: ranking means demoting things too. Reverb, delay and chorus are ordinary here, expressed the same way as everything else, because they are ordinary in this genre. Generality is the default; primitives are earned by frequency.
15.2 The effect chain that cannot break the song
for name, params in chain:
fn = FX.get(name)
if fn is None: continue # unknown effect: skip, do not crash
try: x = fn(x, **params)
except Exception: pass # a broken effect is skipped, not fatal
Six lines, and they encode Law 8 for the entire effects system. A song using an effect from a newer version of the format plays on an older engine, minus that effect. A malformed parameter costs you one effect, not the track. The alternative — validate the chain up front and refuse — is more "correct" and strictly worse: it converts a partially working song into no song at all, in a context where partial is enormously more useful than nothing.
15.3 Analysis rides along
One more small thing with outsized consequences: while rendering each track, the engine records that track's RMS level and its energy in a handful of frequency bands. It is nearly free — the buffer is already in cache — and it means the visualiser does not need its own FFT of the mixed output. It gets per-track data the mix cannot provide, so a visual can respond to the bass and the hats separately rather than to a single lump of loudness.
The lesson: the renderer knows things nobody else can recover. Analysis performed downstream of the mix is strictly less informative than analysis performed during it. If a later stage will want a measurement, take it where the information still exists.
16Two runtimes, one theory
Law 5 requires at least two independent implementations. This is the most expensive law and the one that most directly determines whether you have built a format or a program.
16.1 What actually ports, and what gets rewritten
The codebase splits cleanly, which is not luck — it is what falls out of keeping the seams pure, and the split is a good diagnostic for whether your own architecture is sound:
| Layer | Ports? | Why |
|---|---|---|
Song model, state_at, automation | Mechanically | Pure logic, no I/O, no numerics library |
| Music theory: scales, chords, motifs | Mechanically | Tables and arithmetic; derived from one shared file |
| Arrangement grammar, energy curves | Mechanically | Pure functions over the IR |
| Layout, key tables, string-building UI | Mechanically | Pure |
| Oscillators and envelopes | Rewritten | Formulas, not libraries — straightforward, just not shared |
| Filters, delay, reverb | Rewritten | Web Audio has BiquadFilter, DelayNode, ConvolverNode natively |
| The bar renderer | Rewritten | Becomes a worklet that renders a bar into a buffer |
The proportion is the point: most of a music language is not DSP. The parts that are hard to port are the parts that are also small, formula-shaped, and well-defined. If you find that porting your engine means porting everything, your logic and your numerics are entangled, and the format you think you have is actually an implementation.
16.2 The parity gate
Two implementations that agree in spirit are two implementations. Two implementations held to identical output by an automated test are a specification. The gate:
- Generate 360 arrangements across styles, tempos, seeds and lengths.
- Run both engines over each.
- Compare every musical decision — every hit, every probabilistic step, every resolved parameter, every section boundary.
- Any divergence fails the build.
Plus golden renders within the Python engine: hash the output of a fixed corpus and assert byte-identity across changes. That catches "a refactor changed the sound by 0.001%," which no listening test would ever find and which is exactly how determinism dies — not in one dramatic break but in a slow drift nobody notices until two versions no longer agree.
A format with one implementation is a save file. A format with two implementations that agree by accident is a coincidence. A format with two implementations held identical by a gate is a specification.
16.3 One source of truth for the theory
Both engines derive their music theory — scales, chord tables, Camelot wheel adjacencies, motif transformations — from one file. Not "kept in sync," not "documented in both": generated from one artifact, with a check that fails the moment a copy drifts. The same discipline covers the shared engine file served by two different sites: a byte-comparison test fails if the copies differ.
Duplication you cannot avoid must be duplication you cannot forget. If two copies of something must exist for deployment reasons, the check that they are identical is not optional hygiene; it is the only thing standing between you and a bug that appears on one site and not the other, six months later, with no obvious cause.
17Generation over the IR
The composer — the thing that writes a five-minute arranged track from a genre name and a seed — is not a separate system. It is a pure function that emits the same IR a human edits.
compose(style, minutes, seed) → document
That single design choice has consequences worth spelling out, because the alternative (a generator that plays notes, or that drives the engine directly) is the more obvious build and forecloses all of them:
- Generated music is editable. The output is a document, so every command works on it. There is no "generated" mode that behaves differently from "made" mode.
- Generation is testable without listening. Assert that the energy curve rises into the drop, that the break is quieter, that section lengths are powers of two, that the bass stays in key. All structural, all fast, all in a normal test suite.
- Generation is deterministic. Same style and seed, same song. A seed becomes a shareable, memorable, tiny address for a specific track.
- Human and machine work is indistinguishable in the document. There is no metadata partition, no "AI-generated" branch of the schema. A person can generate a skeleton, rewrite half of it, and the result is one coherent song.
17.1 Arrangement as grammar
Song structure is remarkably regular within a genre, and that regularity is expressible as a grammar over section roles:
song → intro build drop break drop2 outro
role → energy target ∈ [0,1]
section → role × bars × the track state that reaches that energy
Each role carries a nominal energy — intro 0.25, build 0.60, drop 1.00, break 0.30, outro 0.20 — and generation is a walk along a curve through those targets, choosing tracks, densities, filter positions and effects that land near each one. Curves themselves are named and swappable: linear, exponential, logarithmic, eased, stepped.
This is the same insight as the arrangement grammar in generative-music research and the same insight as a tracker's order list: separate what the material is from where it goes. It also makes a song critiqueable — you can score an arrangement against its own declared curve and say "the build does not build," which is a genuinely useful thing to tell a beginner and a completely mechanical computation.
17.2 Where a language model fits
Because the document is text with a small vocabulary, a language model is unusually well suited to editing it — see §22 for why. The architectural point here is narrower: the model's output is commands, not audio. It writes lines you can read, that apply to your song, that you can undo, and that appear in the diff like any other edit.
This is the difference between a collaborator and an oracle. An oracle returns a finished render and you either accept it or ask again — you learn nothing, you cannot take half of it, and your taste never enters the loop. A collaborator hands you source. The posture is a design constraint, not a politeness: the model may only produce artifacts the human can read, partially accept, and edit.
18The proof obligations
A format that claims determinism must prove it continuously, or the claim decays into folklore. Four gates run before anything ships, and their shape is more interesting than their contents.
| Gate | Covers | Answers the question |
|---|---|---|
| Engine self-test | Every module, in-process | Does each part still do what it says? |
| Cross-engine parity + golden renders | 360 arrangements, byte-hashed output | Do both runtimes still agree, and did the sound change? |
| Browser gate | The real page, driven in a real headless browser | Does it work where people actually use it? |
| Site gate | The real server, every asset, share routes, one-tap audio | Does the deployed thing serve what it claims? |
18.1 Why "the real thing" is the only useful gate
Two of these four exist because of specific, humbling failures, and the pattern is worth internalising.
A relative script path — src="copy.js" — shipped to production.
On the home page it worked. On a share page served at /t/<id>
it resolved to /t/copy.js and 404'd, so every shared link anyone
opened loaded a page with no words on it. Forty-three green browser tests never
saw it, because they tested the app directory statically and the share route
lived in a server nothing exercised.
The fix was not a fix for that bug. It was a gate that boots the real server and asserts that every same-origin response is 200, plus a check that no asset path in the page is relative. Both assertions are about the class, not the instance.
When a bug escapes to production, the question is never "how do I fix this bug." It is "what assertion, had it existed, would have made this class of bug impossible" — and then, is that assertion cheap enough to run every time. A test that catches one instance is worth little. A test that catches the category is worth the whole incident.
18.2 Golden renders as a cultural device
Byte-hashing the audio output of a fixed corpus is technically a regression test and culturally something more useful: it makes every change to the DSP an explicit, reviewed decision. You cannot casually "improve" the kick. You must change it, see the gate fail, and consciously bless the new hashes with a note about why.
That friction is the point. It converts the sound of the instrument from an emergent property of whoever touched it last into a versioned decision with a paper trail — which is exactly the property Law 2 promises and would otherwise quietly lose, one well-meaning refactor at a time.
The consequences
What the design actually buys. Every item here is a capability that is unreachable without a source format and nearly free with one.
19Version control for music
The headline consequence, and the one whose implications are most underestimated, because people evaluate it as a feature rather than as an infrastructure change.
19.1 The diff
Two versions of a song produce a diff that means something:
--- warehouse.oontz
+++ warehouse.oontz
bpm 132
-swing 6
+swing 12
-kick X...x...X...x...
+kick X...x...X...x..x
hat ..x...x...x.x.x.
-filter bass lp 420 res 0.8
+filter bass lp 280 res 0.94
You can read that. The swing doubled, the kick gained a pickup on the last
sixteenth, the bass filter closed and got more resonant — it got darker and more
acid. That is a legible account of a musical decision, produced by
diff, a program from 1974 that knows nothing about music.
Then, at the structural level, a diff of two IRs is a diff of meaning: "the break grew from 8 to 16 bars," "drop2 lost the open hat," "the filter automation now starts 4 bars earlier." No audio comparison can produce those sentences, and no listener can produce them reliably either.
19.2 The practices that follow
Git provides the mechanics; what matters is which human practices become possible. Each of these is standard in software, unavailable in music, and becomes available the moment the artifact is text:
- Blame. Who changed the bassline, when, and — because commit messages exist — why. Attribution at the level of the musical decision rather than the credit list.
- Bisect. "It stopped hitting somewhere in the last thirty commits." Binary search the history, listening to each. This is a genuinely new capability: debugging a song.
- Branch. Try the hardstyle version without endangering the one that works. Not "save as v7 final FINAL2," but a first-class alternative with a name and a merge path.
- Pull request. Someone hears your track, changes eight characters, and opens a proposal you can read, discuss, and accept with a click. Collaboration without a shared session, a shared DAW, or a shared timezone.
- Review. Line comments on a song. "This build is four bars too long." Attached to the line that does it.
- Fork with provenance. The document carries a parent pointer, so remix lineage is data, not etiquette. Credit travels with the file, and a remix tree is a queryable phylogeny.
- Tags and releases. This is the version that got played out. That one is the radio edit. Both reproducible forever from a few kilobytes.
19.3 The hard part: merge
Honesty demands the limitation. Text merge works on a song when two people edit different lines — different tracks, different sections — which is the common case and merges cleanly with no special tooling.
It fails where it fails everywhere: two people editing the same pattern. Line-based merge will produce a conflict, and resolving it by picking one side is musically crude — the two edits may be combinable in a way no text tool can see. Semantic merge for music is an open problem, and a genuinely interesting one: a hit at step 3 and a hit at step 11 added by two people are trivially unifiable, but a pattern rewritten in a different length by both is not. See §29, where this is the first item on the research agenda.
20The economics of kilobytes
The least romantic section and possibly the most consequential, because economics decides who gets to publish.
20.1 The numbers
| Artifact | Size | Ratio |
|---|---|---|
| A loop as source | ~232 bytes | 1× |
| A five-minute arranged track as source | 15–40 KB | ~100× |
| The same track as a 320 kbps MP3 | ~12 MB | ~50,000× |
| The same track as 44.1/16 stereo WAV | ~52 MB | ~220,000× |
| The same track as a DAW project with samples | 200 MB – 2 GB | ~106× |
A five-order-of-magnitude difference is not an optimization. It is a change in what kind of thing the object is, and things change category at that scale.
20.2 What becomes possible below a megabyte
- The whole song fits in a URL. Not a link to a song — the song. Packed into the fragment, it never touches a server, so there is nothing to store, nothing to lose, nothing to take down, and no account required to share. A link that works forever with no infrastructure behind it.
- Hosting a gallery is free. No object storage, no CDN bandwidth, no transcoding pipeline, no per-play cost. A million songs is a modest database. This is why the service can be free without a business model that eventually requires it not to be — and "free" that depends on future monetization is a countdown, not a policy.
- Every track in the gallery is editable. Because you store source, every published song is automatically a playable, forkable preset. The gallery is not a jukebox; it is a library in the software sense.
- It fits in a chat message, a gist, a commit, a QR code, a tweet. Distribution channels designed for text carry it natively.
- It fits in a model's context window. Fifty songs is a handful of tokens by the standards of a modern model, which makes few-shot musical reasoning practical (§22).
- Archiving is trivial. Every song ever made on the platform fits on a phone. Preservation stops being a budget line.
20.3 Why the economics are a moral argument
Under an audio-hosting model, each additional listener costs money, so someone must eventually pay: ads, subscriptions, an upload cap, a rights regime, or a shutdown. Every free music-hosting service in history has faced this and resolved it the same way. Under a source-hosting model, the marginal cost of a listener is the listener's own CPU, because rendering happens on their machine. The service serves kilobytes of text.
That is why the free tier is credible rather than promotional — and why the architectural decision in §13.1 (client-side rendering) and the access decision are the same decision. Sustainable free is an architecture, not a pricing page.
21Searching the music itself
Music search today is search over metadata: title, artist, genre tag, and whatever an audio-fingerprinting model inferred. Search over structure is essentially unavailable outside research corpora, because the structure was thrown away when the recording was made.
With a source corpus, these are ordinary database queries:
- every track between 128 and 134 BPM in A minor with a break longer than 16 bars;
- every track whose kick pattern contains a sixteenth-note pickup before the downbeat;
- every track that uses a reese bass with resonance above 0.9;
- every track descended from this one — the remix tree, to any depth;
- the tempo distribution of everything published this month;
- the most-forked eight-bar pattern on the platform;
- tracks structurally similar to this one, by comparing resolved section states rather than audio embeddings.
21.1 Why this is a research instrument
Musicology of electronic music is bottlenecked on data. Studying how, say, hi-hat density relates to perceived energy across a genre currently means either hand-annotating recordings or trusting a signal-processing pipeline that infers structure with unknown error. Both are expensive, and both introduce a measurement layer between the researcher and the object.
A corpus of source documents removes the inference step entirely. The hi-hat density is not estimated; it is read. Section boundaries are not detected; they are declared. The provenance of a remix is not inferred from similarity; it is recorded.
This makes a class of study cheap that is currently rare: large-N, exact-measurement, longitudinal studies of how a genre's structural conventions actually change, with a ground truth that does not depend on a model. If such a corpus reaches meaningful scale, it is a more valuable research artifact than the instrument that produced it.
21.2 The corpus as a teaching object
The same property makes the corpus pedagogically potent. "Show me twenty builds that work" is a query. So is "show me how this producer's kick patterns changed over two years." Learning from examples requires examples you can read, and this is the first form in which electronic music has them at scale.
22Why machines read this well
Large language models are extraordinarily well suited to this format, and the reasons are structural rather than lucky. Understanding them tells you how to design a format that machines can collaborate on — which is now a first-class design goal rather than a nice-to-have.
22.1 Five structural reasons
- It is text. No modality gap, no encoder, no tokenizer surprise. The song is in the model's native representation, which means every capability the model has over text applies to music without adaptation.
- It is small. A whole track is 15–40 KB. A model can hold the entire song, plus a dozen reference songs, plus the full command documentation, and still have room to reason. Compare the impossibility of "read this five-minute WAV and change the third bar of the break."
- The vocabulary is tiny and domain-shaped. A few dozen commands, all named after musical concepts the model has read millions of words about. It does not need to learn an API; it needs to map words it knows to a grammar it can see in two examples.
- Output is verifiable. The model emits commands. They either parse or they do not; they either produce a structurally valid song or they do not; the arrangement either scores well against its energy curve or it does not. Every one of those is a machine-checkable signal, which means the loop can be closed automatically — the model can be told it was wrong, precisely, without a human listening.
- Edits are local and reviewable. The model changes three lines. You see three lines. You keep two. This is the interaction pattern that makes code assistants useful and image generators frustrating, and it is available here for the same reason: the artifact is composed of independently meaningful parts.
22.2 The ladder of collaboration
Four postures, increasing in autonomy, all producing source:
| Mode | The model | You |
|---|---|---|
| ask | Explains what is there, in musical terms | Learn something |
| jam | Proposes small edits to what you have | Accept, reject, tweak |
| produce | Restructures — arrangement, transitions, mix | Review a diff |
| dream | Writes a whole track from a description | Take it apart and make it yours |
Every rung returns editable source you watch apply, never a black-box render. That constraint is what keeps the human's taste in the loop: you are always looking at the decisions, so you are always able to disagree with one of them specifically rather than rejecting the whole output.
22.3 The general lesson for AI-era formats
A format that is small, textual, structured, domain-named and verifiable is a format a machine can collaborate on. Every one of those properties was chosen here for a human reason — readability, diffability, portability, determinism — and each turns out to be exactly what a model needs. This is not a coincidence. The properties that make an artifact legible to people are the properties that make it tractable to machines. Optimizing for either gets you most of the other; optimizing for neither gets you a WAV file.
23Teaching and reading
How does anyone learn to produce electronic music today? Watch videos of someone else's screen, in real time, in a tool you must own, and try to replicate mouse movements. It is apprenticeship by shoulder-surfing — the least efficient knowledge transfer humans have.
Compare how programmers learn: by reading enormous quantities of other people's source, at their own pace, with the ability to change a line and see what happens. The difference in throughput is not marginal.
23.1 What a readable song format changes
- Every published track is a lesson. Ten lines you can read, change, and hear. The distance between "I wonder why that works" and "now I know" is one keystroke.
- Technique becomes quotable. "Here is the four-line sidechain trick" can be pasted into a message. Currently the equivalent is a twelve-minute video.
- The feedback loop collapses to seconds. Change a character, hear it on the next bar. Compare loading a project, finding the right device, adjusting a control, and re-rendering.
- Concepts get names you can grep. Sidechain, swing, reese, energy curve — vocabulary that is written down and searchable rather than demonstrated and forgotten.
- Progress is visible. Your history of commits is a record of what you learned and when, which is both motivating and — for a teacher — assessable.
23.2 The ten-second rule, defended
Law 7 deserves a defence, because it looks like a marketing constraint smuggled into an engineering document.
The reason it is load-bearing: the first sound is the only moment at which a new person decides whether this is for them, and that decision is nearly irreversible. Every second before it is spent on faith, and most people have very little faith available for software. An install step, an account wall, a sample pack download, a "choose your plan" screen — each one loses a large fraction of arrivals, and the fractions multiply.
Sonic Pi's real contribution to the field is demonstrating this in classrooms at scale: children make a sound in the first minute, and that is why it works where more powerful systems did not. It is the most reliably ignored finding in music-language design, because it is not a technical result and therefore does not feel like one.
24Preservation
A question worth asking of anything you build: will this be readable in fifty years?
24.1 The rot rates
| Artifact | Fails when | Realistic lifespan |
|---|---|---|
| DAW project | Any plugin, sample, licence, or app version is missing | 5–10 years |
| Proprietary audio format | The decoder is abandoned | 10–20 years |
| WAV file | Media rots; the format itself is fine | Media-bound |
| MIDI file | Plays, but sounds like a different song | Indefinite, lossy |
| ABC notation | Nothing; it is text with a spec | 30+ years, demonstrated |
| A source song | Only if no conforming engine exists | As long as the spec is implementable |
The last row has a condition attached, and the condition is the whole discipline. Text plus a versioned spec plus multiple independent implementations is the configuration that has actually survived — demonstrated by ABC, by TeX, by PostScript, by HTML itself. Text alone is not enough: an undocumented text format with one implementation rots exactly as fast as a binary one, just more legibly.
24.2 What a preservation-grade format requires
- A versioned identifier in every document
(
oontz-song-1), so a future reader knows what it is holding. - A written spec independent of the implementation — field by field, with defaults, so the format can be re-implemented from the document alone.
- Multiple independent implementations, which is the only real proof the spec is complete (Law 5).
- Self-containment. No external samples, no plugin references, nothing to go missing.
- An open licence on the spec and a public corpus, so the format survives the disappearance of its author.
- Forward-compatible reader rules (§10.4), so old readers survive new writers.
The cost of all six is close to zero at the start and effectively infinite later. This is the single cheapest form of insurance in software design and almost nobody buys it.
25Access
A text-first instrument has accessibility properties that graphical music software mostly does not, and they are consequences of the architecture rather than features bolted on.
- Screen readers work. The interface is text and commands. Compare a DAW: a canvas of unlabelled draggable rectangles, historically among the least accessible categories of software in existence.
- No mouse required. Everything is typed. For anyone for whom precise pointing is difficult or impossible, the difference is not convenience — it is whether the activity is available at all.
- No hardware requirement. No MIDI controller, no audio interface, no sample library, no plugin budget. A browser and a keyboard.
- Low bandwidth. The instrument is a page; songs are kilobytes. It works on connections where streaming a preview would not.
- Old machines are fine. Synthesis from arithmetic with a one-bar lookahead is cheap. There is no sample streaming and no plugin overhead.
- Language-independent core.
x...x...is the same in every writing system. The command words are English, which is a real limitation, but the notation — the part you look at most — is not language-bound at all.
Worth stating plainly: a text interface is also a barrier for people who think spatially, who cannot type comfortably, or who find a blinking cursor intimidating. The honest claim is not that text is universally more accessible; it is that text is accessible along axes where graphical music software is catastrophically bad, and that those axes have been under-served for decades. The right long-term answer is both — a grid you can paint on and a text form that is the source of truth, which is exactly the tracker's arrangement and remains the correct one.
Build your own
The payload. Twelve decisions with their tradeoffs, a conformance checklist, the anti-patterns, and the problems we have not solved.
26Twelve decisions
Every audio language makes these choices. Most make several by accident. For each we give the options, our answer, and — more usefully — when the other answer is right, because these are genuine trade-offs and not a list of correct opinions.
26.1 — Scope: fragment, loop, or song?
Options: a sound · a loop · a whole arranged track.
Ours: the whole track. It is the entire thesis.
Choose otherwise if: you are building a performance instrument,
where the session is the artifact and arrangement is improvised. Loop scope is
correct for live coding and much simpler.
Consequence: whole-song scope forces you to design sections,
automation, and transitions on day one. Retrofitting arrangement onto a loop
language is the single hardest migration in this space, because the time model
has to change underneath everything.
26.2 — Time model
Options: event list · cycle function · imperative clock ·
timeline of snapshots. (§7)
Ours: timeline of snapshots, with a positional pattern grid
inside the bar.
Choose otherwise if: composability of patterns matters more to
you than arrangement — take the cycle model, and take Tidal's semantics
seriously rather than approximating them.
Consequence: this decision is effectively irreversible. Decide
it first, deliberately, in writing.
26.3 — Turing-complete, or not?
Ours: not. No user functions, no recursion, no
variables.
Why: three reasons, each sufficient. A song must render in
bounded time — an audio callback cannot wait for a fixed point. A readable
document cannot contain arbitrary computation, because reading it then requires
executing it in your head. And every conforming runtime must implement the whole
language, so each construct you add multiplies across implementations
forever.
Choose otherwise if: your users are programmers and generation
is the point. Extempore and SuperCollider are right to be full languages.
The cost, honestly: some things become verbose. A 64-bar
evolving pattern that a loop would express in three lines is written out. We pay
it, and mitigate with generation (§17) — a program writes
the document; the document stays declarative. That split is the
recommendation: keep the computation outside the artifact.
26.4 — Where does randomness live?
Options: global seed · per-object streams · hash of
coordinates.
Ours: hash of coordinates. (§8.2)
Choose otherwise: essentially never, for anything that must be
stable under unrelated edits. This is the closest thing to a universal
recommendation in this document.
26.5 — Samples, or synthesis?
Ours: synthesis only. Not a single sample; the kick is
math.
Why: self-containment. A sample-based format needs a sample
library, and a song that references files is not portable, not small, not
deterministic across machines, and not legally simple.
Choose otherwise if: your genre is sample-defined — hip-hop,
breakcore, plunderphonics — where refusing samples refuses the music. Then
design the reference mechanism carefully: content-addressed hashes, an embedded
tiny-sample option, and a documented resolution order.
Consequence: synthesis-only bounds your timbral range. We
accept it because techno is a synthesis genre; that is exactly the "notation
assumes a decoder" trade from §2.1.
26.6 — One representation, or source plus IR?
Ours: both. A command log for humans, a JSON document for
machines. (§9.4)
Choose otherwise if: your language is small enough that the
source is the structure. A pure pattern language may not need an IR at
all.
Warning: if you have two, you must decide which is canonical
and never blur it. Ours is the log; the IR is derived. A system where both are
authoritative will drift, and the bug will be "the song sounds different after a
round trip."
26.7 — Text-only, or a GUI too?
Ours: text is the source of truth; visual surfaces edit the
text.
The rule: any GUI must be a view over the document,
producing edits to it. The moment a control can change something the text cannot
express, the format has stopped being the source of truth and every downstream
property dies quietly.
Choose otherwise if: never, if you want the properties in Part
IV.
26.8 — What is the extension mechanism?
Options: plugin interface · registry with schemas ·
parameter-name injection · no extension at all.
Ours: parameter-name injection. (§14)
Choose otherwise if: extensions must be sandboxed, or written
in another language, or loaded at runtime by untrusted users — then you need a
real interface and a real ABI, and you should expect it to cost you
determinism.
Consider: no extension mechanism at all is a legitimate and
underrated answer for v1. Every voice in the reference implementation is written
by the same people who wrote the core; the mechanism exists to keep the core
ignorant, not to support third parties who do not yet exist.
26.9 — How immediate are edits?
Options: instant · quantized to a bar · quantized to a
phrase.
Ours: quantized to the bar for document edits; instant for
performance gestures. (§13.3)
Choose otherwise if: your users are sound designers rather than
composers — twiddling a filter and hearing it now is the whole activity, and
then you need parameter smoothing and a fundamentally different scheduler.
26.10 — How many runtimes?
Ours: two, gated for parity. (§16)
Cost: real. Every DSP change happens twice, and the gate must
be written and maintained.
Why pay it: it is the difference between a format and a
program, and — less obviously — the second implementation is where you discover
what your spec actually says. Ambiguities that survive one implementation
indefinitely surface within days of a second.
Choose otherwise if: you are prototyping. But write the spec as
though a second implementation is coming, or you will find that your "spec" is a
description of your code.
26.11 — What is the unit of sharing?
Options: a file · a link · a database row · a URL fragment
containing the whole song.
Ours: all four, deliberately. A file for git, a short link for
chat, a row for the gallery, and the whole song packed into a URL fragment for
sharing with no account and no server.
The insight: the fragment form is only possible because the
artifact is kilobytes. Small size is not an efficiency property; it is a
distribution property, and it unlocks channels that are otherwise closed.
26.12 — Who is this for?
Ours: anyone who can type. Not programmers. Not
live-coders.
Consequence: this is the decision that generated Law 3 and the
guessability test, and therefore most of the notation. It is the decision that
most reliably gets made implicitly — by the designers' own comfort — and it
determines everything about the surface. Write it down before you write
syntax.
27A conformance checklist
If you are building a language in this class, here is the checklist. It is ordered: each item depends on the ones above it. Anything you skip, you will retrofit at ten times the cost.
The format
- A versioned format identifier in every document.
- A written field-by-field spec with defaults, independent of any implementation.
- Reader rules: accept unknown fields; refuse unknown format versions loudly.
- Self-contained — no external file, plugin, or licence references.
- Small enough to paste into a chat message.
- Human-readable without a tool, and hand-writable in an emergency.
The semantics
- A pure function from position to state. Random-access, not replay-dependent.
- All time in musical units — bars, steps — not seconds.
- No hidden state: everything affecting the sound is in the document.
- Randomness derived from coordinates, never from a stream.
- Automation is interpolation inside a state query, not mutation over time.
- Sections as complete snapshots, not deltas.
The notation
- A guessability test, written down, applied to every proposed extension.
- Iconic where possible: the notation should look like what it does.
- The domain's vocabulary as command names, in the domain's word order.
- The genre's signature move as a single token.
- Comments. Songs need explanation as much as programs do.
- A rejection log: what you declined and why. It is the most useful document you will write and the one nobody writes.
The engine
- The real-time path does no work — pre-render, then slice.
- One renderer shared by live playback and offline render. Two paths will diverge.
- Fail open on everything except an unknown format version.
- Clamp invalid values at assignment, not at use.
- Analysis captured during rendering, where the information still exists.
The proof
- A second independent implementation.
- An automated parity gate over a large generated corpus.
- Golden renders, byte-hashed, blessed deliberately.
- A gate that drives the real, deployed thing — not a mock of it.
- For every escaped bug, an assertion that kills the class.
The product
- Ten seconds from arrival to a sound the visitor made.
- No account required to make, play, or share.
- The song prints itself — a "view source" command.
- An escape hatch to a standard format (MIDI, at minimum). A format that cannot leave is a trap.
- Provenance: a parent pointer, so forks carry credit.
- An open licence on the spec, and a public corpus.
28Anti-patterns
Failure modes we hit, watched others hit, or came close enough to hitting to have opinions about.
28.1 The expressiveness spiral
Every extension is individually justified; each adds a little power for a little syntax. Twenty extensions later, only the designers can read the language, and the readability that was the original selling point is gone — lost in increments, none of which was the wrong call in isolation. The defence is a written test that a proposal must pass, applied by someone who did not propose it. A rejection log makes the accumulated cost visible.
28.2 Building the DSP first
Synthesis is the fun part and the least important. A beautiful engine with a badly designed document format is a dead end, because the format is what everything else reads. Design the IR, write five songs by hand in it, then build the engine. If hand-writing five songs is miserable, you have learned something enormously valuable for the price of an afternoon.
28.3 The one true representation
Trying to make one text form serve humans, machines, diffs and the renderer. You get a JSON blob nobody can write or a command log tools must simulate to understand. Two forms with an explicit, one-directional relationship is correct.
28.4 Seeded chaos
A global PRNG "for reproducibility." (§8.1) It is reproducible only if nothing upstream ever changes, which is a guarantee no living project can make. Symptom: users report that editing one thing changed another, and you cannot reproduce it.
28.5 The account wall
"Sign in to try it." Every step before the first sound loses a large fraction of arrivals, and the fractions multiply. If storage requires an account, let people work without storage — local state until they want it kept.
28.6 Plugin hosting
The most seductive one, because it seems to buy unlimited timbre for free. It costs determinism, portability, the second runtime, self-containment and preservation — five of the nine laws — in exchange for something a well-chosen synthesis set mostly provides. Refusing plugins is the highest-leverage "no" in the design.
28.7 Optimizing bytes at the expense of clarity
Delta-encoded sections, packed binary fields, clever shared references. At kilobyte scale you are optimizing something free and paying in readability, diffability and mental model — all of which are the product. Instincts formed when storage was expensive systematically mis-rank this.
28.8 Testing the mock
A comprehensive suite over a static copy of the app, while the real server — where the routing, the injection and the share pages live — is exercised by nothing. Forty-three green tests and a blank page in production. Test the thing you ship, in the shape you ship it.
28.9 Treating the AI as a separate product
Bolting a generator onto the side, with its own output format and its own mode. The result is a two-class system where generated songs are not quite editable and edited songs are not quite regenerable. Generation must emit the same document a human edits, or it is a different product wearing your logo.
28.10 Silence as an error message
The specific worst outcome: something is wrong, and the tool responds by making no sound and saying nothing. The user concludes the software is broken — or worse, that they are. Every silence must be explained.
29Open problems
What we have not solved, stated precisely enough to be worked on. This is the research agenda, and contributions to any of it are more valuable than contributions to the instrument.
29.1 Semantic merge
Two people edit the same pattern. Text merge conflicts; musical merge should often succeed. A hit added at step 3 and a hit added at step 11 are trivially unifiable. A pattern rewritten at a different length by both is not. What is the correct merge algebra for patterns, notes, and automation curves? This is the highest-value unsolved problem in the space, because collaboration at scale depends on it, and it is genuinely tractable — the objects are small, discrete and well-typed. Adjacent prior art: operational transformation, CRDTs, and structured-document merge (3DM, GumTree).
29.2 Structural diff that reads like a review
We can diff text and we can diff resolved states. Neither produces the sentence a person would write: "the build is now eight bars longer and the bass enters earlier." What is the right edit-distance metric over song structure, and how do you render it in musical English? Tree edit distance over the section graph is the obvious start; the hard part is ranking which of many valid edit scripts is the one a musician would describe.
29.3 Perceptual equality testing
Golden renders assert byte-identity, which is too strict: a legitimate refactor that changes the last bit fails, and blessing it by hand erodes the gate's authority. Audio similarity metrics are too loose and are not stable under the changes we care about. What is the right equivalence relation for "this is the same song" that is strict enough to catch drift and loose enough to permit refactoring?
29.4 Sub-bar structure without losing guessability
Subdivision groups — [xx][x.] — are the most requested
extension and pass the guessability test on their own. They interact badly with
the "any pattern length divides the bar" rule that gives us free polymeter.
Is there a formulation that keeps both? A wrong answer here costs the
most elegant property in the notation.
29.5 Microtiming as data
Swing is one number; real groove is a per-step timing profile that varies by genre, by drummer, by machine. Can a groove be a small, readable, transferable object — a "feel" you apply to a pattern, extractable from a performance and legible as text? MPC-style swing templates and MIDI groove quantize are the industrial prior art; none of it is readable.
29.6 Structural similarity search at scale
Given a corpus of source documents, find structurally similar tracks — not by audio embedding, but over the actual structure. What is the right feature representation and index? This is a well-shaped machine-learning problem with unusually clean ground truth, and nobody has had the corpus to attempt it.
29.7 Notation for timbre
Patterns are beautifully readable. Sound design is a list of parameters, and
a list of parameters is not notation — you cannot look at it and hear it. Is
there an iconic notation for timbre with the property that
x...x... has for rhythm? This may be impossible; the negative
result would itself be interesting, and would tell us where the readable
boundary of the format actually lies.
29.8 A package registry for music
If a pattern, a groove, or a sound design is a value, it should be distributable, versioned, and depended upon. What does npm for musical material look like, and what are the semantics of a dependency you can hear? The version-resolution question is genuinely novel: a "patch release" of a bassline that sounds slightly different is a breaking change to every song that uses it.
29.9 Formal semantics
The model in §6 is stated in prose and code. A formal denotational semantics for the language would make conformance provable rather than testable, and would let a third implementation be verified rather than compared. Nyquist's behavioural abstraction and Tidal's pattern algebra are the closest existing work.
29.10 Cross-language notation
The pattern characters are language-neutral; the command words are English. Can the command vocabulary be localized without fragmenting the corpus? Compare the failure of localized spreadsheet formulas and the success of localized keyboard layouts — the difference appears to be whether the artifact crosses borders, and songs do.
Appendices
Grammar, schema, the hash, glossary, bibliography, and the terms on which all of this may be reused.
AGrammar
The command log, as EBNF. This is the human-facing surface in full; it is deliberately small enough to fit on this screen.
document = { line } ;
line = comment | command | blank ;
comment = "#" , { any } ;
command = word , { WS , arg } ;
(* patterns *)
pattern = step , { step } , [ WS , "*" , integer ] ;
step = "x" (* hit *)
| "X" (* accented hit *)
| "." (* rest *)
| "-" (* tie: hold the previous hit *)
| "?" (* maybe: 70%, hashed on track|bar|step *) ;
(* pitched tracks *)
notes = note , { WS , note } ;
note = ( pitch , [ accidental ] , octave | "." ) , { modifier } ;
pitch = "a".."g" ;
accidental = "#" | "b" ;
octave = digit ;
modifier = "~" (* slide into this note from the previous *)
| "!" (* accent *) ;
(* the commands that matter *)
command = "bpm" , number
| "swing" , number (* percent of a step *)
| track , pattern
| track , notes
| "filter" , track , mode , number , [ "res" , number ]
| "sidechain" , track , number (* 0..1 *)
| "gain" , track , number
| "pan" , track , number (* -1..1 *)
| "fx" , track , name , { param , number }
| "section" , name , integer
| "order" , { name }
| ... ;
track = "kick" | "hat" | "oh" | "clap" | "snare" | "perc"
| "bass" | "stab" ;
mode = "lp" | "hp" | "bp" ;
Semantics not captured by the grammar
- A pattern of length L divides the bar into L equal steps. Any L is legal; unequal lengths across tracks produce polymeter.
*Nrepeats the pattern N times, capped at 64 steps.- Swing delays odd-indexed steps by
swing%of one step. - For pitched tracks the nth note token corresponds to the
nth step of the pattern;
.holds no pitch. - Unknown commands and unknown effects are skipped with a message, never fatal (Law 8).
BThe document schema
{
"format": "oontz-song-1",
"name": "warehouse", "bpm": 138, "swing": 8,
"key": "a", "scale": "minor",
"order": ["intro", "build", "drop", "break", "drop2", "outro"],
"sections": {
"drop": {
"bars": 32, "role": "drop", "energy": 1.0,
"order": ["kick", "hat", "bass"],
"tracks": {
"bass": { "voice": "reese", "pat": "x.x.x.x.x.x.x.x.",
"notes": ["a1", ".", "a1", "c2"], "gain": 1.0,
"filt": "lp", "fc": 340, "res": 0.94,
"sc": 0.7, "pan": 0, "tune": 0, "fx": [] }
},
"automation": [], "master_fx": []
}
},
"meta": { "style": "hardtechno", "curve": "classic" },
"viz": { "theme": "acid", "mode": "auto" },
"remix_of": "a1b2c3d4e5f6"
}
| Field | Meaning | Note |
|---|---|---|
format | Schema identifier | Unknown value: refuse loudly |
order (song) | Section names in playback order | Material and arrangement stay separate |
sections | Name → full snapshot | Snapshots, not deltas (§6.2) |
role, energy | What this section is for, 0–1 | What generation walks along a curve |
order (section) | Track render order | Explicit; render order affects nothing musical but must be stable |
pat / notes | The pattern and its pitches | Any length; index is time |
sc | Sidechain amount, 0–1 | The genre's signature move, as one field (§15.1) |
automation | Parameter ramps within the section | Interpolated inside state_at |
viz | The song's own look | A custom palette embeds whole — a track brings its colours with it |
remix_of | Parent song id | Provenance travels in the source |
CThe determinism hash
Reproduced in both languages, because a spec that describes an algorithm in prose is a spec with two behaviours. Any conforming runtime must produce identical results from this function.
# Python
def maybe(name, bar, i):
s = "%s|%d|%d" % (name, bar, i)
h = 0
for ch in s:
h = (h * 31 + ord(ch)) & 0xFFFFFFFF
h ^= h >> 15
return (h % 1000) < 700
// JavaScript
function maybe(name, bar, i) {
var s = name + "|" + bar + "|" + i, h = 0;
for (var k = 0; k < s.length; k++)
h = (h * 31 + s.charCodeAt(k)) >>> 0;
h ^= h >>> 15;
return (h % 1000) < 700;
}
Design notes, each deliberate: 32-bit unsigned integer arithmetic only, so no float behaviour can differ between platforms; character codes rather than string hashing, so no locale or Unicode normalization is involved; the multiply-add is the classic Java/Bernstein-style construction, chosen for being the one every programmer can reimplement from memory; the xor-shift decorrelates the low bits, which matters because the decision reads them; and the threshold is expressed in parts per thousand rather than as a float comparison.
Do not improve this function. Its only virtue is that two independent implementations agree, and every improvement is a compatibility break for every song ever written.
DGlossary
| Term | Meaning |
|---|---|
| Bar | One musical measure; the atomic unit of rendering and of edit quantization |
| Step | One character of a pattern; one bar divided by the pattern's length |
| Pattern | A string of steps; index is time, character is what happens |
| Track | One voice plus its pattern, notes, and parameters |
| Voice | A synthesis function that renders one hit |
| Section | A named span of bars holding a full snapshot of every track |
| Role | What a section is for — intro, build, drop, break — carrying a nominal energy |
| Energy curve | The intended arc of intensity across a song; what generation walks |
| Automation | A parameter ramp within a section, interpolated inside state_at |
| Sidechain | Ducking a track out of the kick's way; the pump. One token here |
| Swing | A systematic delay on odd steps, as a percentage of a step |
| Humanize | Per-track timing jitter in milliseconds |
| Maybe step | ? — a hit that happens 70% of the time, decided by coordinate hash |
| Polymeter | Patterns of different lengths cycling against each other |
| IR | Intermediate representation — the compiled timeline document |
| Command log | The canonical human source: the commands that make the song |
| Golden render | A byte-hashed reference output that must not change without a deliberate blessing |
| Parity gate | An automated test that two runtimes make identical musical decisions |
| Guessability test | Can a non-programmer, shown one example, guess what it does? |
| Fail open | Keep playing, say what went wrong; never stop, never conceal |
EBibliography
Systems
- MUSIC I–V — Max Mathews, Bell Labs, 1957–1968. The unit generator; the orchestra/score split.
- Csound — Barry Vercoe, 1986. Music-N with forty years of momentum. csound.com
- MIDI 1.0 — 1983. The interchange standard that standardized events rather than documents.
- Ultimate Soundtracker / MOD, XM, IT — Karsten Obarski, 1987, and successors. Whole-song files; the grid as interface; the order list.
- MML — Microsoft BASIC, late 1970s, and the Japanese home-computer and chiptune lineage. Patterns as terse strings.
- ABC notation — Chris Walshaw, 1991. The preservation case study. abcnotation.com
- SuperCollider — James McCartney, 1996. Language/server split over OSC. supercollider.github.io
- Max/MSP — Miller Puckette / Cycling '74, and Pure Data, 1996. Dataflow patching; the visual counterpoint to all of this.
- Nyquist — Roger Dannenberg. Functional synthesis; behavioural abstraction; sound as a first-class value.
- ChucK — Ge Wang and Perry Cook, 2003. Strongly-timed programming; time as a first-class type.
- TidalCycles — Alex McLean, from 2009. Patterns as functions of time; mini-notation. tidalcycles.org
- Sonic Pi — Sam Aaron, 2012. Time-to-first-sound as the governing design constraint. sonic-pi.net
- Faust — GRAME. Functional DSP compiled to C++/WASM; the right tool for the synthesis layer. faust.grame.fr
- Extempore / Impromptu — Andrew Sorensen. Temporal recursion; live-coded systems programming.
- Overtone — Clojure over SuperCollider.
- ORCA — Devine Lu Linvega. A two-dimensional esolang for live sequencing; the most radical recent rethink of the notation surface.
- Alda — a deliberately unglamorous text score language. alda.io
- Strudel — 2022–. Tidal semantics in the browser; parser → AST → interchangeable outputs. strudel.cc
- bytebeat — Viznut, 2011. One C expression over a sample counter; the extreme point of the compression axis.
Ideas
- TOPLAP manifesto, 2004, and the algorave movement, 2012–. Evidence that audiences will read code.
- Denotational semantics — Strachey and Scott. Define what a program means; let the implementation compute it.
- Conal Elliott, functional reactive programming. Behaviours as functions of time — the intellectual root of the cycle model.
- Charles Sanders Peirce, icon vs. symbol. Why
x...x...is different in kind fromeuclid(5,16). - Donald Norman, The Design of Everyday Things. The gulf of execution; why one keystroke matters.
- Bret Victor, Inventing on Principle, 2012. Immediate connection between a change and its effect.
- Godfried Toussaint, The Geometry of Musical Rhythm. Euclidean rhythms and why they recur across cultures.
- Curtis Roads, The Computer Music Tutorial. The standing reference for the synthesis layer.
- Fred Brooks, No Silver Bullet. Essential versus accidental complexity — the frame for most of Part V.
This project
- github.com/charley-forey/oontz — the whole implementation, both engines, the gates, the songs.
docs/OONTZ-FORMAT.md— the format specification.docs/PRIOR-ART.md— the audit: what was taken from each system and what was declined.docs/THE-LANGUAGE.md— the outline of this article.- oontz.sh — the instrument. oontz.music — this site.
FReuse and citation
This document exists to be used. Fork it, argue with it, take the parts that are right and publish a correction to the parts that are not. The most useful possible outcome is a better language that cites this one as the thing it improved on.
If you build something from this, we would like to know — an issue or a pull request on the repository is enough. If you find an error, the same. If you disagree with a design decision, the rejection log in §26 is where the argument should happen, because that is where the reasoning is written down and therefore where it can be shown wrong.
The Song Is the Source: Notes Toward an Audio Programming Language.
oontz, 2026. https://oontz.music/language
Written alongside the implementation, which is the only reason any of it is specific. Everything claimed here is running at oontz.sh; where something is unsolved, it is in §29 rather than described as though it works.