Examples
# Shot a preset under a loud beat, at a custom sizecargo run -p standalone --example shot -- --preset "Drift" \ --set bass=1,onset=1,beat=1 --size 960x540 --out pulse.png
# Both sides of a gate, e.g. `select(treb > 0.55, 12, 8)` on kaleido_order.# The subject is a docs/examples file rather than a shipped preset on purpose:# NO preset in the library binds `tempo`, so the tempo-gate example this# replaces named a file that had been deleted and a variable nothing uses.cargo run -p standalone --example shot -- \ --preset-file docs/examples/tuning/step-2-naive-bands.toml \ --set treb=0.2 --out below-the-gate.pngcargo run -p standalone --example shot -- \ --preset-file docs/examples/tuning/step-2-naive-bands.toml \ --set treb=0.9 --out above-the-gate.png
# Labeled contact sheet of the whole librarycargo run -p standalone --example shot -- --all --out gallery/
# Metrics report as a text table, or JSON for parsingcargo run -p standalone --example shot -- --reportcargo run -p standalone --example shot -- --report --json > report.json
# Beat filmstrip from a synthesized click track (no asset needed)cargo run -p standalone --example shot -- --preset "Drift" \ --signal click:120 --strip 8 --out click.png
# The frame a reseed actually fired on, at the tier the app starts incargo run -p standalone --example shot -- --preset-file presets/attractor_ink.toml \ --signal click:120 --at 44,46,48,54 --tier rich --out reseed.png
# ...or from the one synthesized kind with dynamicscargo run -p standalone --example shot -- --preset "Drift" \ --signal dynamic:110 --strip 8 --out groove.png
# Filmstrip from a real clip (16-bit PCM WAV)cargo run -p standalone --example shot -- --preset "Perseids" \ --audio assets/test/clip.wav --strip 8 --out clip.pngThree committed scripts drive shot, all self-documenting in their headers — run them with
node, no arguments needed:
| Script | What it renders | Output |
|---|---|---|
scripts/tuple-sheets.mjs | one labeled contact sheet per attractor family, a cell per roster entry — the menu a tuple curation judges | target/, not committed |
scripts/tuple-paths.mjs | one filmstrip per candidate tuple_from/tuple_to pair, a cell per morph step; pairs the engine refuses a walk for are skipped rather than rendered as identical cells | target/, not committed |
scripts/docs-shots.mjs | every documentation image, from an inline manifest naming the preset file, stimulus, hop, size and tier behind each one | docs/images/, committed |
The first two read the roster straight out of core/src/render/scenes/particles/family.rs, so a
roster edit needs no script edit, and their output is a scratch artifact — re-run them when a
judgement is owed.
docs-shots.mjs is the odd one: its output is committed, so the manifest is the provenance record
for every picture in the documentation, and swapping which preset represents a family is one line
plus a re-run (ADR-0100). It
writes only under docs/images/ and refuses an entry pointing anywhere else. It is not a CI gate
and must not become one — renders are not byte-reproducible across machines, so freshness is a
close-ceremony sweep duty rather than a check.
--signal kinds: click:<bpm>, bass:<hz>, treble:<hz>, noise:<seed>,
chord, dynamic:<bpm>. The synth path needs no committed asset. --audio
reads uncompressed 16-bit PCM WAV only (a hand-rolled reader — no decoder
dependency); other encodings are a followup.
dynamic:<bpm> — the one kind that rises and falls
Every other kind is a steady tone or steady noise, and the band report says
so: bass:60 reads min/mean/max 0.187 / 0.187 / 0.187 — zero variance — and
chord 0.058 / 0.059 / 0.060. A filmstrip of those exercises the DSP with
material that never changes, which is not what any preset is authored against.
click:<bpm> has real transients but peaks at bass ≈ 0.011, far below anything
a shipped preset is gained for.
dynamic:<bpm> is three layers on a beat grid — a pitch-dropping kick every
beat (bass), eighth-note hats (treble), a harmonic pad that swells across each
beat (mid) — under an 8-beat phrase that builds for six beats and rests for
two. Measured at 110 BPM through the real analyzer:
| band | min | mean | max | max / mean |
|---|---|---|---|---|
| bass | 0.0035 | 0.0399 | 0.1063 | 2.67 |
| mid | 0.0005 | 0.0062 | 0.0189 | 3.07 |
| treb | 0.0000 | 0.0059 | 0.0320 | 5.45 |
against noise:<seed>’s 1.78 / 1.15 / 1.07 — and it was the liveliest kind there
was. Like every generator here it is a pure function of its arguments, so a
filmstrip of it is reproducible.
Those are raw magnitudes, so since ADR-0049 they describe bass_raw /
mid_raw / treb_raw. Through the normalizers the same clip reads:
| variable | min | mean | max |
|---|---|---|---|
bass | 0.035 | 0.661 | 1.000 |
mid | 0.031 | 0.575 | 1.000 |
treb | 0.002 | 0.281 | 1.000 |
onset | 0.001 | 0.145 | 1.000 |
The crest factors above are what normalization preserves — it divides by a
slowly-moving peak, so a clip’s dynamics survive while its absolute level does
not. Note every variable reaches 1.000: full scale is a state real material
visits, not a corner.
The waveform trace is levelled the same way
(ADR-0139)
and is absent from the table above for a different reason than the *_raw twins
are: it is not reachable from a preset expression at all, because the grammar is
scalar. What it has instead is an escape hatch the band variables lack —
waveform_gain is the divisor, so waveform[i] * waveform_gain is the amplitude
the analyzer actually read. That is what makes a capture of a wave_mode figure
independent of the fader it was taken at, and it is why the foobar component and
the standalone draw one picture from one track while tapping their streams on
opposite sides of the output volume.
It exercises dynamics. It is not evidence about real loopback levels. A preset that looks right under
dynamic:110is a preset that survives material which rises and falls — that is all this says. Nothing synthesized can tell you whether your gains match what your music actually produces; only--audioon real material does — see the reference range below. Do not read a lively filmstrip as a calibration check.
What real material actually produces
Measured 2026-07-27 through --audio on three local clips, none committed (see
the note above about assets/test/). All three were peak-normalized to −1 dBFS
first, because two of them arrived 20–26 dB under-levelled and every band read
zero — a level problem in the file, not a fact about the music.
These are raw magnitudes, so they now describe
bass_raw/mid_raw/treb_raw(ADR-0049). They used to be “the numbers to calibrate a gain against”, and for the normalizedbass/mid/trebthey no longer are — that is the entire point of normalizing. Calibrate those against the0–1table in Expression language and they will hold on material like this without re-tuning. This section is now the reference for the*_rawvariables, and for understanding why the normalized ones exist: look at how far apart the three rows below are.
| material | RMS | bass min / mean / max | mid | treb |
|---|---|---|---|---|
| electric-guitar loop, ~101 BPM, no drums | −17.7 dBFS | 0.000 / 0.000 / 0.004 | 0.000 / 0.002 / 0.013 | 0.000 / 0.000 / 0.000 |
| hi-hat percussion loop, ~102 BPM | −21.1 dBFS | 0.000 / 0.000 / 0.001 | 0.000 / 0.000 / 0.002 | 0.000 / 0.002 / 0.011 |
| trap with 808 sub, ~140 BPM | −19.9 dBFS | 0.000 / 0.007 / 0.190 | 0.000 / 0.001 / 0.026 | 0.000 / 0.001 / 0.006 |
The shape of it matters more than any single number. The 808’s bass peak
(0.190) sits right on a full-scale 60 Hz sine (0.187) — the analyzer is not
quietly attenuating anything. Its mean is 0.007, about 25× lower, because
real material is transient and spectrally sparse in a way no steady generator is.
Everything else here reads lower still: a guitar loop with no drums puts
essentially nothing in bass or treble, which is correct and is what most material
does in most bands most of the time.
So, in descending order of how far a stimulus is from real music — on the raw
scale, i.e. what bass_raw sees:
| stimulus | bass_raw it produces | vs. a real mean |
|---|---|---|
--set bass_raw=0.8 | 0.800 | ~100× too hot |
--signal bass:60 (full-scale sine) | 0.187 | ~25× too hot |
--signal dynamic:110 | mean 0.040, max 0.106 | ~6× too hot, right order for peaks |
| real music (above) | mean 0.000–0.007, max up to 0.190 | — |
This ladder is exactly what ADR-0049 abolished for the normalized variables.
Its four rows span three orders of magnitude, and picking a threshold meant
knowing which rung you were standing on — which is why nine shipped mechanisms sat
dead for months. On the normalized scale every rung that carries real dynamics
lands in the same 0–1 range, so bass > 0.8 means “near this material’s own
peak” whether the material is an 808 at −1 dBFS or a quiet guitar loop. The ladder
survives here because *_raw still climbs it.
Two practical consequences, both of which still apply to *_raw and to bin():
- Calibrate against a mean, not a peak, for anything continuous — a size, a
zoom, a hue drift. Those spend their life near the mean, so a gain tuned to look
right at
0.19barely moves. - Calibrate against a peak for anything percussive — a flash, a burst, a beat-latched accent. Those exist to fire on the hit, and the hit really does reach a full-scale tone’s level.
The shipped library predates this measurement and is gained against the older figures; whether it needs a re-gain pass is design-backlog 0020, not something to fix preset-by-preset.
Test audio is added manually and never committed. Drop a 16-bit PCM WAV into
assets/test/— that folder is gitignored (only its README is tracked), so no licensed audio lands in the repo. Use your own or a royalty-free / CC0 clip; factory-library samples are fine to point at on disk but must not be committed. The--signalpath needs no file, so the whole audio pipeline can be validated without adding anything.
The --report --json schema is a nested object of numbers keyed by
family/preset: per-band reactivity, reactivity_low and reactivity_footprint,
animation, drive, rate, coverage, level, transient (rise_frames /
fall_frames as integers plus their ratio), reachability, the pairwise
pixel/shape distinctness matrices, and near_duplicates.
rate is an object, not a bare number: mean, settled, and
measured_at_px — the size it was captured at, which is not the one the columns
beside it use (the two motion readings). A consumer
that drops measured_at_px and compares a rate against a differently-sized run
is reading a different statistic.
reachability carries dead_branches, unapproached_ceilings and
saturated_clamps counts, the full gates list (each with param, source,
kind, and one of always / peak_fraction_of_bound / occupancy), a holds
list (each with param and edge — see held bindings),
and a probe object naming the signal, BPM and duration they were observed
under. holds is the one member read off the compiled preset rather than
observed, so the probe provenance does not apply to it.
kind is "select", "compare", "clamp" or "saturated" — matching the
GATE / COMP / CEIL / SAT lines above — and dead_branches counts the
first two together. Keep the provenance when you consume it: a flag only ever
means not observed under this stimulus.
Built from a8ce055 at version 0.115.0. This site tracks main and is not versioned per release.