A song's measured mix (tools/measure): per-part loudness recorded in its
SPEC.md frontmatter as measured:, for the art critic. Node only (build
time and tools): the browser gets the result through songs.mjs.
A measurement is of one version of song.js, so it records that file's
hash, and measuredFor returns it only while song.js still hashes the
same: an edited song is not judged by the old song's numbers.
8import { createHash } from 'node:crypto';
10export const songHash = (code) => createHash('sha256').update(code).digest('hex').slice(0, 12);
In the frontmatter it is one line of JSON, which is YAML too, so Astro
reads it as a map and the tools need no YAML parser:
measured: {"date":"…","song":"<hash>","total":{…},"parts":{"riff":{…},…},
"sections":{"1-4":{"total":{…},"parts":{…}},"5-8":{…},…}}
Each level is { loudnessDbfs, peakDbfs }. Sections follow the song's
cadence (sectionCycles), keyed by the cycles they span, counted from 1
as a listener counts bars; null where the meter heard too little.
19const LINE = /^measured: (\{.*\})[ \t]*$/m;
Sets measured: in a SPEC.md, replacing any, after song:.
The measurement for this song.js, or null when there is none or it is stale.
A song's section length in cycles: its .every(N) cadence, the longest
when it has several (a jev asked less often spans the others), 1 when it
has none. tools/check-songs times sections by it too.
The sections of a song bars cycles long, every cycles each: the
last is cut short when the length is not a multiple.
The measurement without each section's parts: what the art critic reads, and all the browser needs (songs.mjs), since every section's every part would add kilobytes per song to every page.