Diversify palette by timbre, tame the grain
Palette hue was pinned to the spectral centroid, which is essentially a bass-vs-treble number most mastered pop lands mid-range on, so different songs converged on the same blue/green/purple wedge and warm red/yellow was unreachable. Derive the base hue from a timbre signature instead: the track's spectral mass in the body (sub/low/mid) against the trebles (high/air), mapped onto a cool(blue)->warm(red) ramp, with BPM+tonality +dynamics driving vibrance. Energy and timbre are now two orthogonal axes, and palettes vary meaningfully between tracks. Grain was also overused: every scene adds its own surface grain and the grade adds another on top. Drop the per-scene texture floor, the grade's grain range, and the default, so tonal tracks read clean.
This commit is contained in:
parent
2e4e1d7613
commit
0ee7a6a3b3
@ -10,7 +10,7 @@ const DEFAULT_POST = {
|
||||
bloomThreshold: 0.6,
|
||||
bloomKnee: 0.3,
|
||||
chroma: 0.15,
|
||||
grain: 0.04,
|
||||
grain: 0.02,
|
||||
vignette: 0.35,
|
||||
contrast: 1.05,
|
||||
saturation: 1.1,
|
||||
|
||||
@ -160,7 +160,7 @@ function derivePost(summary, rng) {
|
||||
bloomThreshold: 0.45 + bright * 0.25,
|
||||
bloomKnee: 0.25,
|
||||
chroma: 0.05 + noisy * 0.25 + rng.range(0, 0.08),
|
||||
grain: 0.02 + noisy * 0.05,
|
||||
grain: 0.012 + noisy * 0.03,
|
||||
vignette: 0.25 + (1 - bright) * 0.25,
|
||||
contrast: 1.0 + dynamic * 0.15,
|
||||
saturation: 1.0 + (1 - noisy) * 0.25,
|
||||
|
||||
@ -96,7 +96,11 @@ export function generatePersonality(summary, rng, countEligible = null) {
|
||||
const style = {
|
||||
lineWeight: 0.4 + bright * 0.4 + rng.range(-0.15, 0.25),
|
||||
softness: 0.25 + (1 - bright) * 0.4 + rng.range(-0.1, 0.2),
|
||||
texture: noisy * 0.5 + rng.range(0, 0.25),
|
||||
// Surface grain is a texture trait, not a default. The old floor of
|
||||
// ~0.12 put visible film grain on even a perfectly tonal track, and
|
||||
// since every scene adds sigGrain AND the grade adds its own, that read
|
||||
// as "grainy by default". Only tracks that want grit carry any.
|
||||
texture: noisy * 0.4 + rng.range(0, 0.1),
|
||||
// Fold counts stay low and are usually off. Symmetry is the fastest way
|
||||
// to make a library look like one series and also the fastest way to
|
||||
// make every track look like a screensaver.
|
||||
|
||||
@ -100,21 +100,47 @@ export class AudioPalette extends PaletteSource {
|
||||
}
|
||||
|
||||
generate(count = 6) {
|
||||
const { meanCentroid = 0.5, meanFlatness = 0.2, dynamicRange = 0.5 } = this.summary;
|
||||
const {
|
||||
meanCentroid = 0.5, meanFlatness = 0.2, dynamicRange = 0.5,
|
||||
bandBalance = {}, bpm = 120,
|
||||
} = this.summary;
|
||||
const rng = this.rng;
|
||||
|
||||
// Centroid 0..1 mapped onto roughly violet -> blue -> cyan -> green -> amber.
|
||||
// Offset by a seeded jitter so two tracks with similar spectra still differ.
|
||||
const baseHue = (4.9 - meanCentroid * 3.6) + rng.range(-0.45, 0.45);
|
||||
// --- Temperature: the track's timbre signature, not its loudness ---
|
||||
// Warmth places spectral mass from the body (sub/low/mid) against the
|
||||
// trebles (high/air). It is folded through the whole band profile rather
|
||||
// than the centroid alone, because the centroid is a one number that
|
||||
// most mastered pop sits in the middle of — which was why every track
|
||||
// flared green/purple. This is the feel of the sound: a voice-and-body
|
||||
// forward track belongs to the warm end of the wheel, a crisp or airy
|
||||
// track to the cool end.
|
||||
const body = (bandBalance.sub ?? 0.4) * 0.6
|
||||
+ (bandBalance.low ?? 0.4) * 0.9
|
||||
+ (bandBalance.mid ?? 0.3) * 0.4;
|
||||
const treble = (bandBalance.high ?? 0.3) * 0.7
|
||||
+ (bandBalance.air ?? 0.3) * 0.5
|
||||
+ meanCentroid * 0.5;
|
||||
const warmth = body / (body + treble + 1e-6); // 0 = cold, 1 = warm
|
||||
|
||||
// Hue sweeps cold(blue, 240°) -> cyan -> green -> yellow -> warm(red),
|
||||
// so warm material finally reaches red/yellow rather than pooling in the
|
||||
// blue/green gap. A little seeded jitter keeps identical tracks apart.
|
||||
const baseHue = (1 - warmth) * (Math.PI * 4 / 3) + rng.range(-0.45, 0.45);
|
||||
|
||||
const schemeName = rng.pick(SCHEME_NAMES);
|
||||
const hues = SCHEMES[schemeName](baseHue, rng);
|
||||
|
||||
// Noisy material desaturates; tonal material is allowed to sing.
|
||||
const chromaBase = 0.10 + (1 - Math.min(1, meanFlatness * 3)) * 0.11;
|
||||
// A dynamic track gets a wider light-to-dark range.
|
||||
const spread = 0.30 + Math.min(1, dynamicRange) * 0.34;
|
||||
const anchor = 0.36 + rng.range(-0.05, 0.10);
|
||||
// --- Energy: how vivid and punchy the palette is ---
|
||||
// Upbeat, dynamic material earns saturated colour; chill, flat material
|
||||
// stays muted. This is the "does it pop" axis, orthogonal to timbre.
|
||||
const fast = Math.min(1, Math.max(0, (bpm - 80) / 150));
|
||||
const energy = Math.min(1, fast * 0.4 + (1 - Math.min(1, meanFlatness)) * 0.4 + dynamicRange * 0.3);
|
||||
const chromaBase = 0.10 + energy * 0.16;
|
||||
|
||||
// A dynamic mercury gets a wider light-to-dark range; warmth keeps warm
|
||||
// tones from sinking into brown, since dark + orange is mud.
|
||||
const spread = 0.30 + Math.min(1, dynamicRange) * 0.30;
|
||||
const anchor = 0.40 - warmth * 0.06 + rng.range(-0.05, 0.10);
|
||||
|
||||
const colors = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user