Music generated from code. Zero samples.
A procedural music generator. Original tracks synthesised entirely from code — no samples, no loops, no machine learning models, no copyrighted audio. Every sound comes from oscillators, noise, envelopes, filters and nonlinear stages computed at runtime, on your own device.
Live: nullsample.maqsudjon.com · How it works · 20 demo seeds
There is no audio file anywhere in this repository or on the site. No one-shots, no loops, no impulse responses for the reverb, no trained model. The reverb is a feedback delay network with a procedurally generated decay; the drums are synthesised per hit; the "vocal" texture is a band-passed saw with formant peaks and is not an imitation of a singer.
That means there is nothing in a track you generate that came from anyone else's recording. Audio you generate is yours, under either licence, including for commercial release.
A seed is the only input. Paste one and you get that exact track — on any machine, in any browser, today or in ten years.
This is harder than it sounds, and it is the constraint that shaped the whole
engine. ECMAScript specifies +, -, * and / exactly, but explicitly
leaves Math.sin, Math.cos, Math.tan, Math.exp, Math.log, Math.pow
and Math.sqrt as implementation-approximated — and V8, JavaScriptCore and
SpiderMonkey really do differ in the last bit. One differing bit inside a
filter's feedback path is audible within a second.
So the engine calls none of them. core/dmath.ts rebuilds them from exact
arithmetic — polynomial series and a fixed number of Newton steps — accurate to
about 1e-15 and identical on every host. tools/lint-determinism.mjs fails the
build if any file under core/, compose/, presets/ or render/ calls the
built-in versions, and npm run test:browser renders the same seeds in Node and
in headless Chrome and compares the files byte for byte.
The web app renders progressively — playback starts after a few bars while the rest computes — and the master chain is fully streaming, so the audio you hear and the file you download are the same bytes. That is asserted by a test, not assumed.
Requires Node 22.6 or later. The engine runs unbundled under Node's type stripping, so there is no build step for the CLI or the tests.
npm install # three dev dependencies; the engine itself has none
npm run verify # lint, typecheck, testsnpm run render -- --seed 42 --out track.wav
npm run render -- --seed 42 --darker 0.5 --harder -0.3
npm run stems -- --seed 42 --out stems-42.zip
npm run benchThis is the mechanism that makes the output good, and it is not optional tooling. Render a batch, listen, rate, narrow the ranges, repeat.
npm run batch -- --preset hyperpop --count 50 --out ./batch
npm run sheet -- ./batch # writes ./batch/sheet.html
# open ./batch/sheet.html directly — no server needed — rate with 1..5,
# press "Save ratings.json", move it into ./batch/
npm run narrow -- ./batchnarrow correlates every sampled parameter against your ratings, runs a
permutation test so it does not chase noise, and proposes a tightened ranges
file plus a readable diff. It never overwrites the ranges file. The preset is
done when fewer than 5 in 100 random renders are rated poor.
npm run brand # regenerates every icon and the OG card from one mark
npm run build # static site into dist/
node tools/serve.mjs # preview on http://localhost:4321The boundary between core/ and presets/ is the most important structural
decision here. core/ is generic machinery that knows nothing about genre;
presets/ is data and recipe logic that knows nothing about DSP internals.
If you need to edit core/ to change how something sounds, the boundary is in
the wrong place.
A preset is two files:
presets/<name>.ts— pattern banks, arrangement templates, harmony options, and the word-slider mappings. Structure and taste.presets/<name>.ranges.json— every numeric parameter as a range with a distribution. Separate from code becausenarrowrewrites it.
Register it in presets/index.ts. Nothing else needs to change.
core/ DSP primitives. Deterministic maths, oscillators, filters,
nonlinear stages, drums, the 808. Frozen after M1.
compose/ Musical decisions: harmony, motif, rhythm, arrangement.
presets/ Genre recipes and parameter ranges. The real work lives here.
render/ Plan, bus renderers, streaming master chain, WAV, ZIP.
cli/ Batch renderer, contact sheet, narrowing.
web/ Static app. Engine in a worker, progressive playback.
brand/ One mark definition; every icon and the OG card derive from it.
test/ Determinism, golden files, mix assertions, unit tests.
A 117-second track renders in about 15 seconds on an M-series laptop and about 26 seconds in Chrome, at roughly 5–7x realtime. The original spec asked for 1.5 seconds. That budget is not reachable: 4x oversampling around the nonlinear stages costs ~1.5 s per oversampled channel-stream and there are seven of them, and removing every oversampler entirely still leaves ~9 s. Sections 5, 8 and 9 of the spec could not all hold in scalar JavaScript.
Rather than trade quality for it, the budget was replaced with a time-to-first-sound budget — under two seconds — met by rendering progressively and starting playback after the first bars. Render throughput stays well above realtime on a phone, so playback stays ahead of the playhead; if it ever does not, the app pauses and buffers rather than glitching.
Dual.
- Engine: AGPL-3.0-or-later. Use it, modify it, run it. If you run a modified version as a network service, publish your source.
- Commercial: LICENSE-COMMERCIAL.md. For embedding in closed-source products, or for the tuned preset range files.
- Preset ranges: presets/LICENSE-PRESETS.md. The v1 ranges committed here are AGPL. Tuned range files are the commercial asset and are licensed separately.
Audio you generate is yours under either licence, including for commercial release.
JetBrains Mono is used under the SIL Open Font License (web/fonts/OFL.txt).
