tools/measure
Measures each song's mix as written and records it in the song's SPEC.md
frontmatter as measured:, so the art critic judges craft on how the parts
actually sit, not only on the code.
nix run .#measure # every song, print only
nix run .#measure -- --write # and record in each SPEC.md
nix run .#measure -- --song jev/lightning-in-a-bottle --write
nix run .#measure -- --url http://localhost:4322 --tabs 4
It needs the dev server running (buck2 run //:dev, default
http://localhost:4322). Each song plays in its own silent headless tab
(?session=measure-<pid>-N, unique per run so runs side by side never share a tab), at normal speed for its length as written plus a
few seconds for the ending to ring, with the Jev relay blocked, so every
jev() plays its fallbacks and the song plays as written. The page's meter
(website/src/jev/meter.mjs) then gives loudness and peak in dBFS, in total
and per part. A part is the orbit a song names for its question
(part('riff', …) plays on .orbit('riff')); numbered orbits are reported
together as other.
Songs run --tabs at a time (default 4), so the whole set takes about as
long as its longest few songs: Lightning alone is 102 s of music.
Each song is measured whole and per section. Sections follow the song's
.every(N) cadence (its longest, when it has several: sectionCycles in
website/src/jev/measured.mjs, which tools/check-songs uses too), and are
keyed by the cycles they span, counted from 1: "1-4", "5-8", …, the last
cut short where the written length ends. So a spec's promise such as "each
drop at least 6 dB over the think before it" can be checked against the
numbers. The ending's ring-out after the last bar counts in the whole-song
figures only.
The record is one JSON line, which is also YAML:
measured: {"date":"2026-09-24","song":"<hash of song.js>","total":{"loudnessDbfs":-16.2,"peakDbfs":-1.4},"parts":{"riff":{…},…},"sections":{"1-4":{"total":{…},"parts":{"riff":{…},…}},…}}
A section the meter heard too little of is null. The line runs to a few
kilobytes for a long song; the site's pages and the art critic take only each
section's total (withoutSectionParts, and measuredMix in
website/src/jev/critic.mjs), since every part of every section would
multiply both for detail the whole-song parts already give.
The total is what reaches the speakers, after superdough's master limiter at
-1 dBTP (packages/superdough/superdoughoutput.mjs); parts are each orbit's
output, before it. A total peak of -1.0 means the limiter is holding the mix
down: turn the song's postgain down until it measures under.
song is the hash of the song.js measured
(website/src/jev/measured.mjs). Once the song changes, the measurement no
longer applies and the critic stops reading it: measure again after editing
a song, then rescore it with tools/critic/.
Run from a worktree while the dev server serves another checkout, songs hear
the worktree's samples/ (re-rendered and new sounds included), and the
output says so first (tools/worktree-samples/). The site's code is still
the server's: to measure a worktree's superdough or site changes, run a dev
server from it on another port and pass --url.
flake.nix's measure app runs this script from the working tree (it
writes the working tree's specs), with Playwright and its browsers from the
same nixpkgs.