Give the sections shapes, and the songs an opinion about their own edges
Two complaints, one cause: stages were flat blocks and the noise bed was glued to the pad level, so the bed was a constant hiss under a track that never went anywhere. Sections have contours now — rise, fall, swell, dip, surge — sampled per note rather than per stage, so a build builds through its chords instead of stepping between two flat halves. The noise gets its own per-stage level instead of following the pad, which makes it an arrangement element: a riser through a build with its filter sweeping up, a wash under a drop, nearly absent in a breakdown. That is also the better test signal, since the segmenter classifies a section partly on its energy slope and a build that does not build is one it has to guess at. Transition hardness is a per-song axis. Genres differ on this more than they differ on tempo — an ambient record dissolves between its sections and a club record cuts — and until now every song in the bank cut. Soft songs crossfade across a couple of bars; the hardest get the pre-drop trick, where everything stops for most of a beat before the loud stage lands. Five soft, three mid, nine hard, and the builder fails if the bank ever loses either end. The contours immediately broke the axis they were layered onto: section-to- section contrast is dynamic range, so adding it put a floor of 0.50 under a statistic that had reached 0.18. That is what compression IS, so contour depth now scales with the track's dynamics — a limitered master has shallow section contrast as well as a shallow crest. Back to 0.29, which is as low as enveloped notes and hard cuts will go. One regression accepted rather than fixed: the soft fades cost the beatless entries their tempo detection, since the attack that gave a drone a pulse is exactly what a crossfade removes. `drone` reads 170bpm for a 62bpm source. A beatless track has no tempo and the detector is guessing either way; `ember` exists to anchor the low end with a beat that is actually there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -89,6 +89,7 @@ for (const spec of SONGS) {
|
||||
noise: spec.noise,
|
||||
noiseColour: spec.noiseColour ?? null,
|
||||
dynamics: spec.dynamics,
|
||||
transition: spec.transition ?? 0.5,
|
||||
seed: 1 + SONGS.indexOf(spec),
|
||||
});
|
||||
const track = FeatureTrack.fromAudioBuffer(buffer, { fps: 60 });
|
||||
@@ -118,12 +119,13 @@ for (const spec of SONGS) {
|
||||
const n2 = (x) => (typeof x === 'number' ? x.toFixed(2) : String(x)).padStart(7);
|
||||
|
||||
console.log('\n MEASURED — what the generator will actually read\n');
|
||||
console.log(' song tempo bright noisy dynamic loud sections arrangement');
|
||||
console.log(' song tempo bright noisy dynamic loud sections arrangement cuts');
|
||||
console.log(' ' + '-'.repeat(86));
|
||||
for (const b of built) {
|
||||
console.log(` ${b.name.padEnd(11)}${n2(b.measured.bpm)}${n2(b.measured.centroid)}` +
|
||||
`${n2(b.measured.flatness)}${n2(b.measured.dynamics)}${n2(b.measured.loudness)}` +
|
||||
`${String(b.measured.sections).padStart(10)} ${b.arrangement}`);
|
||||
`${String(b.measured.sections).padStart(10)} ${b.arrangement.padEnd(9)}` +
|
||||
` ${(b.transition ?? 0.5) >= 0.7 ? 'hard' : (b.transition ?? 0.5) <= 0.3 ? 'soft' : 'mid '}`);
|
||||
}
|
||||
|
||||
// --- coverage --------------------------------------------------------------
|
||||
@@ -197,6 +199,16 @@ for (let i = 0; i < AXES.length; i++) {
|
||||
}
|
||||
}
|
||||
|
||||
// Transition hardness is a spec input rather than a summary statistic, so it is
|
||||
// reported rather than binned: how abruptly one section becomes the next is a
|
||||
// genre difference the bank has to contain — an ambient record dissolves where a
|
||||
// club record cuts — and a bank where every song cut would be testing the
|
||||
// generator against one kind of music.
|
||||
const soft = built.filter((b) => (b.transition ?? 0.5) <= 0.3).length;
|
||||
const hard = built.filter((b) => (b.transition ?? 0.5) >= 0.7).length;
|
||||
console.log(`\n transitions ${soft} soft · ${built.length - soft - hard} mid · ${hard} hard`);
|
||||
if (!soft || !hard) problems.push('the bank has no ' + (soft ? 'hard' : 'soft') + ' transitions');
|
||||
|
||||
// Section kinds are the casting gate, so coverage of them is not optional: a
|
||||
// bank with no drop cannot reach the geometric, glitch or structural families
|
||||
// no matter how widely its tempo spreads.
|
||||
|
||||
Reference in New Issue
Block a user