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>
This commit is contained in:
@@ -35,41 +35,43 @@ import { synthesizeSong } from './synth.js';
|
||||
*/
|
||||
export const SONGS = [
|
||||
// --- slow, quiet, spacious -------------------------------------------
|
||||
{ name: 'drone', bpm: 62, arrangement: 'ambient', brightness: 0.10, noise: 0.05, dynamics: 0.90,
|
||||
covers: 'slowest · darkest · beatless · most dynamic' },
|
||||
{ name: 'air', bpm: 76, arrangement: 'ambient', brightness: 0.85, noise: 0.35, dynamics: 0.85,
|
||||
{ name: 'drone', bpm: 62, arrangement: 'ambient', brightness: 0.05, noise: 0.02, dynamics: 0.95,
|
||||
covers: 'beatless · darkest · most dynamic' },
|
||||
{ name: 'air', bpm: 76, arrangement: 'ambient', brightness: 0.90, noise: 0.25, noiseColour: 0.15, dynamics: 0.85,
|
||||
covers: 'beatless but bright — brightness without a beat' },
|
||||
{ name: 'elegy', bpm: 84, arrangement: 'ballad', brightness: 0.20, noise: 0.10, dynamics: 0.80,
|
||||
covers: 'slow with one build and one payoff' },
|
||||
{ name: 'hymn', bpm: 92, arrangement: 'ballad', brightness: 0.75, noise: 0.20, dynamics: 0.70,
|
||||
{ name: 'ember', bpm: 68, arrangement: 'club', brightness: 0.20, noise: 0.05, dynamics: 0.55,
|
||||
covers: 'slowest track with a detectable beat — anchors the low tempo end' },
|
||||
{ name: 'elegy', bpm: 84, arrangement: 'ballad', brightness: 0.10, noise: 0.03, dynamics: 0.90,
|
||||
covers: 'slow, dark, one build and one payoff' },
|
||||
{ name: 'hymn', bpm: 92, arrangement: 'ballad', brightness: 0.80, noise: 0.20, dynamics: 0.70,
|
||||
covers: 'slow and bright — separates tempo from brightness' },
|
||||
{ name: 'still', bpm: 100, arrangement: 'sparse', brightness: 0.35, noise: 0.08, dynamics: 0.75,
|
||||
{ name: 'still', bpm: 100, arrangement: 'sparse', brightness: 0.35, noise: 0.06, dynamics: 0.75,
|
||||
covers: 'the degenerate two-section case, which must not break' },
|
||||
|
||||
// --- mid tempo, the bulk of the space ---------------------------------
|
||||
{ name: 'dusk', bpm: 118, arrangement: 'classic', brightness: 0.15, noise: 0.05, dynamics: 0.60,
|
||||
covers: 'full arrangement, dark and tonal' },
|
||||
{ name: 'centre', bpm: 124, arrangement: 'classic', brightness: 0.50, noise: 0.20, dynamics: 0.55,
|
||||
{ name: 'dusk', bpm: 118, arrangement: 'classic', brightness: 0.02, noise: 0.01, dynamics: 0.60,
|
||||
covers: 'darkest and most tonal with a full arrangement' },
|
||||
{ name: 'centre', bpm: 124, arrangement: 'classic', brightness: 0.50, noise: 0.20, dynamics: 0.55,
|
||||
covers: 'the middle of every axis — the null hypothesis' },
|
||||
{ name: 'glare', bpm: 128, arrangement: 'classic', brightness: 0.90, noise: 0.55, dynamics: 0.50,
|
||||
{ name: 'glare', bpm: 128, arrangement: 'classic', brightness: 0.95, noise: 0.55, dynamics: 0.50,
|
||||
covers: 'bright and noisy together' },
|
||||
{ name: 'slab', bpm: 126, arrangement: 'club', brightness: 0.45, noise: 0.25, dynamics: 0.15,
|
||||
{ name: 'slab', bpm: 126, arrangement: 'club', brightness: 0.45, noise: 0.25, dynamics: 0.05,
|
||||
covers: 'limitered — lowest dynamic range, longest sections' },
|
||||
{ name: 'chrome', bpm: 132, arrangement: 'club', brightness: 0.80, noise: 0.15, dynamics: 0.35,
|
||||
{ name: 'chrome', bpm: 132, arrangement: 'club', brightness: 0.85, noise: 0.06, noiseColour: 0.1, dynamics: 0.35,
|
||||
covers: 'bright and tonal at club tempo' },
|
||||
{ name: 'murk', bpm: 88, arrangement: 'classic', brightness: 0.40, noise: 0.65, dynamics: 0.70,
|
||||
covers: 'noisy but dark — separates flatness from brightness' },
|
||||
{ name: 'murk', bpm: 88, arrangement: 'classic', brightness: 0.12, noise: 0.85, noiseColour: 0.95, dynamics: 0.70,
|
||||
covers: 'dark tonal content under bright hiss — the quadrant that breaks the brightness/noise correlation' },
|
||||
|
||||
// --- fast, dense, broken ----------------------------------------------
|
||||
{ name: 'lattice', bpm: 140, arrangement: 'breaks', brightness: 0.60, noise: 0.40, dynamics: 0.45,
|
||||
{ name: 'lattice', bpm: 140, arrangement: 'breaks', brightness: 0.60, noise: 0.35, dynamics: 0.45,
|
||||
covers: 'most sections — makes rosters actually rotate' },
|
||||
{ name: 'grit', bpm: 150, arrangement: 'breaks', brightness: 0.25, noise: 0.70, dynamics: 0.40,
|
||||
covers: 'noisiest and dark — the corrupt director\'s home ground' },
|
||||
{ name: 'plate', bpm: 138, arrangement: 'club', brightness: 0.30, noise: 0.60, dynamics: 0.12,
|
||||
{ name: 'grit', bpm: 150, arrangement: 'breaks', brightness: 0.15, noise: 0.90, noiseColour: 0.85, dynamics: 0.40,
|
||||
covers: "noisiest and dark — the corrupt director's home ground" },
|
||||
{ name: 'plate', bpm: 138, arrangement: 'club', brightness: 0.25, noise: 0.70, noiseColour: 0.9, dynamics: 0.10,
|
||||
covers: 'noisy AND compressed — two extremes at once' },
|
||||
{ name: 'runner', bpm: 174, arrangement: 'breaks', brightness: 0.70, noise: 0.45, dynamics: 0.50,
|
||||
{ name: 'runner', bpm: 174, arrangement: 'breaks', brightness: 0.70, noise: 0.40, dynamics: 0.50,
|
||||
covers: 'fastest with a broken arrangement' },
|
||||
{ name: 'flare', bpm: 168, arrangement: 'classic', brightness: 0.95, noise: 0.80, dynamics: 0.30,
|
||||
{ name: 'flare', bpm: 168, arrangement: 'classic', brightness: 0.95, noise: 0.85, dynamics: 0.25,
|
||||
covers: 'brightest and noisiest — the far corner' },
|
||||
];
|
||||
|
||||
@@ -93,6 +95,7 @@ export function song(name, { fps = 60 } = {}) {
|
||||
arrangement: spec.arrangement,
|
||||
brightness: spec.brightness,
|
||||
noise: spec.noise,
|
||||
noiseColour: spec.noiseColour ?? null,
|
||||
dynamics: spec.dynamics,
|
||||
seed: 1 + SONGS.indexOf(spec),
|
||||
}), { fps }),
|
||||
@@ -118,13 +121,20 @@ export function songBank({ count = null, fps = 60 } = {}) {
|
||||
export const AXES = [
|
||||
{ key: 'bpm', label: 'tempo', of: (t) => t.summary.bpm, range: [60, 180],
|
||||
reads: 'motion bias · animation rate · personality pace' },
|
||||
{ key: 'centroid', label: 'brightness', of: (t) => t.summary.meanCentroid, range: [0, 1],
|
||||
// Not [0,1]: centroid is mapped to a LOG frequency axis, so a track made of
|
||||
// nothing but sub-bass and a 55Hz pad still measures ~0.28 and pure hiss
|
||||
// measures ~0.9. The range here is what the statistic can really take on for
|
||||
// music, and calling it [0,1] would report a permanent 50% gap that no
|
||||
// synth change could close.
|
||||
{ key: 'centroid', label: 'brightness', of: (t) => t.summary.meanCentroid, range: [0.25, 0.95],
|
||||
reads: 'director odds · line weight · softness · bloom · vignette' },
|
||||
{ key: 'flatness', label: 'noisiness', of: (t) => t.summary.meanFlatness, range: [0, 1],
|
||||
reads: 'director odds · chroma · saturation · surface texture' },
|
||||
{ key: 'dynamics', label: 'dynamic range', of: (t) => t.summary.dynamicRange, range: [0, 1],
|
||||
reads: 'feedback amount and decay · contrast' },
|
||||
{ key: 'loudness', label: 'loudness', of: (t) => t.summary.meanLoudness, range: [0, 1],
|
||||
// Raw mean spectral magnitude, not a normalised 0..1 — biasFor divides
|
||||
// section energy by it, so its scale is whatever the analyser produces.
|
||||
{ key: 'loudness', label: 'loudness', of: (t) => t.summary.meanLoudness, range: [0, 0.012],
|
||||
reads: 'section energy · temperament intensity' },
|
||||
{ key: 'sections', label: 'section count', of: (t) => t.sections.length, range: [2, 9],
|
||||
reads: 'roster rotation · how often the image is allowed to change' },
|
||||
|
||||
Reference in New Issue
Block a user