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,
|
bloomThreshold: 0.6,
|
||||||
bloomKnee: 0.3,
|
bloomKnee: 0.3,
|
||||||
chroma: 0.15,
|
chroma: 0.15,
|
||||||
grain: 0.04,
|
grain: 0.02,
|
||||||
vignette: 0.35,
|
vignette: 0.35,
|
||||||
contrast: 1.05,
|
contrast: 1.05,
|
||||||
saturation: 1.1,
|
saturation: 1.1,
|
||||||
|
|||||||
@ -160,7 +160,7 @@ function derivePost(summary, rng) {
|
|||||||
bloomThreshold: 0.45 + bright * 0.25,
|
bloomThreshold: 0.45 + bright * 0.25,
|
||||||
bloomKnee: 0.25,
|
bloomKnee: 0.25,
|
||||||
chroma: 0.05 + noisy * 0.25 + rng.range(0, 0.08),
|
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,
|
vignette: 0.25 + (1 - bright) * 0.25,
|
||||||
contrast: 1.0 + dynamic * 0.15,
|
contrast: 1.0 + dynamic * 0.15,
|
||||||
saturation: 1.0 + (1 - noisy) * 0.25,
|
saturation: 1.0 + (1 - noisy) * 0.25,
|
||||||
|
|||||||
@ -96,7 +96,11 @@ export function generatePersonality(summary, rng, countEligible = null) {
|
|||||||
const style = {
|
const style = {
|
||||||
lineWeight: 0.4 + bright * 0.4 + rng.range(-0.15, 0.25),
|
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),
|
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
|
// 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
|
// to make a library look like one series and also the fastest way to
|
||||||
// make every track look like a screensaver.
|
// make every track look like a screensaver.
|
||||||
|
|||||||
@ -100,21 +100,47 @@ export class AudioPalette extends PaletteSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generate(count = 6) {
|
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;
|
const rng = this.rng;
|
||||||
|
|
||||||
// Centroid 0..1 mapped onto roughly violet -> blue -> cyan -> green -> amber.
|
// --- Temperature: the track's timbre signature, not its loudness ---
|
||||||
// Offset by a seeded jitter so two tracks with similar spectra still differ.
|
// Warmth places spectral mass from the body (sub/low/mid) against the
|
||||||
const baseHue = (4.9 - meanCentroid * 3.6) + rng.range(-0.45, 0.45);
|
// 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 schemeName = rng.pick(SCHEME_NAMES);
|
||||||
const hues = SCHEMES[schemeName](baseHue, rng);
|
const hues = SCHEMES[schemeName](baseHue, rng);
|
||||||
|
|
||||||
// Noisy material desaturates; tonal material is allowed to sing.
|
// --- Energy: how vivid and punchy the palette is ---
|
||||||
const chromaBase = 0.10 + (1 - Math.min(1, meanFlatness * 3)) * 0.11;
|
// Upbeat, dynamic material earns saturated colour; chill, flat material
|
||||||
// A dynamic track gets a wider light-to-dark range.
|
// stays muted. This is the "does it pop" axis, orthogonal to timbre.
|
||||||
const spread = 0.30 + Math.min(1, dynamicRange) * 0.34;
|
const fast = Math.min(1, Math.max(0, (bpm - 80) / 150));
|
||||||
const anchor = 0.36 + rng.range(-0.05, 0.10);
|
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 = [];
|
const colors = [];
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user