Commit Graph
8 Commits
Author SHA1 Message Date
DejvinoandClaude Opus 5 d59dd9d5dd Give the song bank notes instead of a test tone
The tonal content was a sustained sine stack at a fixed root, and the stage
multiplier scaled that root directly — so a drop arrived at roughly 1.2kHz with
ten harmonics stacked on top of it and held there for thirty seconds. Every
statistic it was built to control came out correct. It was also unlistenable,
and a bank nobody can stand to play is a bank nobody audits.

There is a scale, a chord progression, a bass on the chord roots and a seeded
motif over the loud stages. Key, mode and progression come off the song's seed
so two entries are not the same four chords at different tempos. This is the
plainest thing that qualifies as music — not trying to be good, trying to be
playable enough that a person will listen to the bank and notice what is wrong
with it.

Register is the real fix. `stage.root` used to multiply the fundamental and now
adds voices upward instead: bass and chord stay where they belong, a lead octave
arrives when the arrangement opens up. Brightness still moves the harmonic
content and the key's register, but across an octave and a half rather than a
piercing sweep. Harmonics above Nyquist are dropped rather than left to alias,
since folded energy would corrupt the centroid and flatness the bank exists to
control.

Two measured consequences. Tempo detection on the beatless entries got better,
not worse — the bass gives a pulse where the drone gave nothing, and `drone` now
reads 62bpm instead of 88. And enveloped notes put a floor of 0.27 under the
measured dynamic range, because the gaps between notes are gaps a limitered
master does not have; note decay flattens toward a hold as dynamics falls, which
takes the compressed end back to 0.18.

Coverage still passes on all six axes and all six section kinds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 07:34:51 +02:00
DejvinoandClaude Opus 5 cfe77fd524 Finish the song bank: axis ranges that are real, and a collinearity check
Completes the bank whose first half went in with the scene commits. The
remaining work was all in what "covered" means.

Two axis ranges were wrong, and being wrong made the coverage report lie in
both directions. Loudness is a raw mean spectral magnitude, not a normalised
0..1, so every song read as 0.00 and the axis looked dead. Centroid is mapped
to a log frequency axis, so a track of nothing but sub-bass and a 55Hz pad
still measures 0.28 and pure hiss measures 0.93 — against a nominal [0,1] the
bank would have reported a permanent 50% gap that no synth change could close.
Both now carry the range the statistic can really take on, with the reason.

Spread on every axis is not coverage of the space, so the tool also measures
correlation between axes. It immediately found brightness and noisiness moving
together at r=0.95: noise colour had been tied to brightness, one axis wearing
two names, leaving the dark-and-noisy quadrant unreachable. Noise colour is now
its own parameter — hiss over a sub-bass pad is an ordinary record.

That got r to 0.94, and no further. Flatness is the geometric mean of the
spectrum over its arithmetic mean across the whole band, so a signal only
measures flat if it has energy everywhere, which is the same thing as measuring
bright; band-limiting the noise to hold the centroid down empties the top and
drops the flatness with it. It is a property of the analyser and real material
does the same, so it is allowlisted with its reason. Anything not on that list
still fails — the check is there to catch a bank that has collapsed, not to
relitigate physics every run.

The output is gitignored. It is 172MB of deterministic audio derived from a
table that will keep changing: rebuild it, do not carry it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 07:03:02 +02:00
DejvinoandClaude Opus 5 0c5b0a8d4d Close out the twenty: what the gates taught
Three lessons worth more than the scenes: a slow axis has to move a large
low-frequency area or the camera's own drift beats it, whole-frame luminance on
the kick is the strobe the flash gate exists for and it arrives by accident, and
a style trait expressed only as grain measures as no style at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 07:00:45 +02:00
DejvinoandClaude Opus 5 905c217193 Three glitch scenes: a press, a loop and a tape
Halftone Misprint is a printing fault rather than an electronic one — the image
is never damaged, only separated and reassembled out of register, and nothing
else in the library is made of dots. Droste Feedback scales the previous frame
where Time Smear translates it, so the image never clears. Analog Wow is the
continuous, wet counterpart to Scan Tear and Block Mosh: the error varies
smoothly down the frame because every line was written at a different moment.

Three things that had to be got right. The halftone ruling is a count of dots
across the frame, not a pixel pitch, or a 4K export is the same dot on a bigger
sheet. The Droste loop has to CONTRACT — expanding pushes every copy off the
edge and leaves a plume instead of a corridor. And all three expressed style
only through grain at first, which is the Side Quest 1 complaint: they now put
the track's line weight and edge softness into the dot, the ring and the band
boundary, taking the measured style response from 24/11/18 to 86/255/88.

Neither feedback scene declares a slow axis, with the numbers recorded in the
files: their own history moves the ten-second average further than any parameter
does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 06:55:43 +02:00
DejvinoandClaude Opus 5 91e74ff167 A cast census, and the arrangement it needed to be true
"Twenty-three of sixty-one scenes are never cast" was an artefact of the song it
was measured on. synthesizeSectioned has one change point, so it segments into
exactly two sections and both are quiet kinds — and intro, breakdown and outro
are restricted to the restful families for every director. Half the library was
unreachable before a seed was drawn, and the measurement reported that as a
casting failure.

synthesizeArrangement builds a real one: intro, build, drop, breakdown, drop,
outro, shaped to hit the segmenter's own classifier rather than to sound like
anything. Measured against a bank of those, two scenes out of sixty-one are
never a background, not twenty-three.

The census exists because "never cast" without a reason is unactionable. A scene
can die at the signature gate, at the director's family table, or in the roster
draw, and those are three different repairs — a trait declaration, a table, and
arithmetic that neither fixes. It reports which one, and it separates being cast
as a background from appearing as a translucent overlay, because a library can
look fully used while nine scenes carry every frame.

What it finds is that eligibility is almost entirely a function of how many
traits a scene declares. Four-trait scenes are eligible for every track and open
half of all videos; two-trait scenes are eligible for one track in fourteen; the
single scene declaring one trait is eligible for none, ever.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 06:45:42 +02:00
DejvinoandClaude Opus 5 5f25437b89 Phase 4: arc driver ("C" brain)
Three timescales now stack: per-frame reactivity, per-section seeded LFO
drift, and whole-song scene changes with lookahead. Layer instances are
cached per section and reused across crossfades — rebuilding them per frame
would recompile shaders every transition.

Crossfades run forward from a boundary: the outgoing scene holds while the
incoming one fades in over it.

Three real bugs, each found by a check that had to be rewritten first:

1. A pop exactly at every transition. buildSlope is discontinuous by
   construction (~1 before a boundary, 0 after), and the outgoing layer is
   still on screen when it flips — collapsing its lookahead ramp in one
   frame. It now holds the slope it had entering the boundary.

2. FeatureTrack.at() returns a REUSED row object, and _boundarySlope()
   called at() again mid-render, rewriting the features the layer was about
   to read. Symptom: a frame correct on every repeat and wrong the first
   time — invisible to fresh-vs-fresh comparison, and wrong in every export,
   since export renders each frame exactly once. Now indexes the typed array
   directly, with the aliasing hazard documented on at(), and a new check
   covers the whole bug class.

3. Warm-up converged to 1%, leaving a visible 0.015 difference at heavy
   feedback settings. Now targets 0.1%.

Two checks were themselves wrong and were rebuilt: a raw delta threshold
and an outlier-vs-local-median test both flag beat flashes as pops, and a
control window taken from a different scene reads an ordinary busy scene as
a 9x spike. The working formulation A/Bs each boundary against the interior
of the two scenes adjacent to it.

PLAN.md §6 corrected: boundary seeks are NOT exact for free. Layer state is
re-seeded there but the feedback buffer is global and carries across.
Clearing it at boundaries would buy exactness for a visible flash at every
transition; warm-up is the better trade and applies everywhere.

Gate 9/9.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 11:30:50 +02:00
DejvinoandClaude Opus 5 022c267888 Phase 3: look generation ("A" complete)
A track now yields a complete, coherent look with no input: palette,
per-section scene assignments, parameter sets, post and feedback settings.
Seeded from a hash of the decoded PCM, so a file always renders identically.

- palette.js builds in OKLCH, not HSL. HSL lightness is not perceptual, so
  evenly-stepped HSL palettes have colours that vanish and colours that
  dominate — which matters when nobody is supervising the choice.
  Regenerates until the contrast floor is cleared.
- Scenes are assigned per section KIND, not per section: a track's drops
  share a scene and the video reads as one piece instead of a shuffle.
- Family preference per kind keeps breakdowns off strobing glitch scenes.
- Section bias (energy/density/motion) carries track character into params
  without scenes knowing anything about audio.
- PaletteSource is the seam for cover art later; no scene would change.

Gate 9/9, including the look-space spread measurement (mean pairwise
distance 0.168 against a 0.08 floor) — the one check that catches a
generator that is deterministic and valid but visually collapsed.

Known gap, not a regression: all four battery tracks currently choose the
same two scenes. There are no 'minimal' family scenes yet, so intro and
outro sections fall through to flow/organic. Differentiation is presently
carried by palette alone. Phase 7 grows the library to fix it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 11:18:37 +02:00
DejvinoandClaude Opus 5 cb320557e6 Phase 1: offline audio pipeline
Whole-track analysis into a frame-indexed FeatureTrack. Nothing reads a
live AnalyserNode: realtime preview maps currentTime to a frame index,
export counts frames, both read the same rows.

- fft.js: radix-2 with precomputed tables, allocated once per track
- analyze.js: STFT at hop 1/60s with CENTRED windows (a window that starts
  at the timestamp reports energy arriving up to 23ms later, which reads as
  visuals lagging the music). Energy features normalised against the track's
  own percentiles; absolute stats kept in summary for the look generator.
- tempo.js: autocorrelation + grid F-measure, beat grid, downbeats
- segment.js: self-similarity novelty, boundaries snapped to the bar grid
- FeatureTrack: assembles everything, plus the lookahead fields. buildSlope
  rises through the bars leading into a higher-energy section, so a build can
  ramp into the drop rather than react after it lands.
- clicktrack.js: mixes clicks onto the detected grid for validation by ear

Three real bugs found and fixed by the tests:
- 174 BPM read as 87. Mean-energy-per-beat scores a half-tempo grid
  identically to the true one; only an F-measure penalises the missed
  onsets via recall.
- 90 BPM read as 180. Offbeat hi-hats make a double-tempo grid score
  perfectly on both precision and recall, so the grid is now interpreted
  metrically afterwards: a systematic strong/weak alternation means the
  real beat is every other grid point.
- Beat grid drifted ~30ms over 30s from integer-frame offsets. Onset peaks
  are now parabolically interpolated and the grid least-squares fitted.

Gates: 11/11 node tests against synthetic ground truth (tempo within 2%
across 90-174 BPM, beat alignment under half a frame, segmentation within
2s of a known boundary, graceful on silence, 6-minute analysis in 1.0s);
5/5 browser checks including audio-driven vs fixed-step frame parity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 11:10:01 +02:00