Squashed from work; the fine-grained history is under tag archive/work-2026-08-26: - vjfx: 104 new presets — the transition lane fills out and the screen family goes wide - vjfx: three lanes land — engine hooks + hold stage, the videomesh engine, and the audio picture - vj: the thumbnail pipeline becomes one honest machine, and effects go livecodable - vj: thumbnails become mp4 — hardware-coded sheets at measured-4K cells, and the bake stops racing the GPU - store: the ceremony dies — batch publish, one transaction, and the engine stops re-reading its own log - store: the ceremony dies — batch publish, one transaction, and the engine stops re-reading its own log - vj: the console grows real transports, and the deck stops lying about reverse - vj: reverse earns a memory, and the effects stop aging - fab: a 3D creation shell and the viewer built on it
112 lines
4.8 KiB
Text
112 lines
4.8 KiB
Text
// SPECTRUM SEA — the whole audio texture read as a LANDSCAPE. Frequency
|
|
// runs left-right, TIME runs away toward the horizon, and the height of
|
|
// the ground is the magnitude that was there: the last five seconds of
|
|
// spectrum as ridges rolling out to the vanishing line.
|
|
//
|
|
// This is the preset that shows why the texture keeps HISTORY. A single
|
|
// instantaneous FFT can only ever be a line; the ring gives a surface.
|
|
//
|
|
// Lighting is analytic, not marched: the surface normal comes from three
|
|
// extra texture reads (a central difference in ground space), so the sea
|
|
// is lit and shaded at four samples per pixel and never self-occludes.
|
|
{
|
|
name: "Spectrum Sea"
|
|
engine: "screen"
|
|
seed: 17
|
|
|
|
speed: 1.0
|
|
beat_pulse: 0.5
|
|
beat_rate: 1.0
|
|
bar_beats: 4
|
|
fog: 0.05
|
|
glow: 1.0
|
|
|
|
p0: 0.5 p1: 0.5 p2: 0.5
|
|
dials: [
|
|
{name: "SWELL", bind: "p0", default: 0.5},
|
|
{name: "SPREAD", bind: "p1", default: 0.5},
|
|
{name: "HAZE", bind: "p2", default: 0.5}
|
|
]
|
|
|
|
color_bg: #x060714
|
|
color_a: #x36e0c8
|
|
color_b: #x8a3cff
|
|
color_c: #xffd489
|
|
|
|
stages: [
|
|
{kind: "bloom", threshold: 0.6, strength: 0.95, levels: 3}
|
|
]
|
|
|
|
shader: draw.DrawVjFxScreen {
|
|
// Ground height at a point on the plane. `gx` is lateral (mapped to
|
|
// frequency, symmetric about the centre line so bass sits under the
|
|
// camera); `age` is how far back in time this ground is.
|
|
ground: fn(gx: float, age: float) -> float {
|
|
let f = clamp(abs(gx), 0.0, 1.0)
|
|
let h = self.audio_fft(f, clamp(age, 0.0, 1.0))
|
|
// The swell that keeps a silent sea alive: two slow crossing
|
|
// waves, small enough that any real spectrum buries them.
|
|
let idle = 0.05
|
|
+ 0.035 * sin(gx * 7.0 + self.time_beat.x * 0.9)
|
|
+ 0.025 * sin(age * 19.0 - self.time_beat.x * 1.4)
|
|
return max(h, idle)
|
|
}
|
|
|
|
fx_color: fn(uv: vec2, content: vec4, cmix: float) -> vec4 {
|
|
let aspect = 16.0 / 9.0
|
|
let hor = 0.40
|
|
let below = uv.y - hor
|
|
|
|
// ---- SKY (above the horizon) --------------------------------
|
|
if below <= 0.0 {
|
|
let sky = clamp((hor - uv.y) / hor, 0.0, 1.0)
|
|
let mut s = self.col_bg.xyz.mix(self.col_b.xyz * 0.35, sky)
|
|
// A lamp on the horizon that swells with the low end.
|
|
let d = length(vec2((uv.x - 0.5) * aspect, (uv.y - hor) * 1.9))
|
|
s = s + self.col_c.xyz
|
|
* (exp(0.0 - d * 5.5) * (0.15 + 0.8 * self.audio_env.x))
|
|
// Sparse stars, fixed by a hash — no motion, no cost.
|
|
let star = fract(sin(floor(uv.x * 420.0) * 12.9898
|
|
+ floor(uv.y * 420.0) * 78.233) * 43758.5453)
|
|
s = s + vec3(1.0, 1.0, 1.0) * (step(0.9985, star) * sky * 0.7)
|
|
return vec4(s * self.fog.y, 1.0)
|
|
}
|
|
|
|
// ---- GROUND -------------------------------------------------
|
|
// Perspective: screen rows below the horizon are distances.
|
|
let dist = 0.30 / max(below, 0.0015)
|
|
let spread = 0.5 + 1.6 * clamp(self.user.y, 0.0, 1.0)
|
|
let gx = (uv.x - 0.5) * aspect * dist * spread * 0.34
|
|
// Distance becomes AGE: the horizon is the oldest row kept.
|
|
let age = clamp(dist * 0.055, 0.0, 1.0)
|
|
|
|
let swell = 0.25 + 1.5 * clamp(self.user.x, 0.0, 1.0)
|
|
let h = self.ground(gx, age) * swell
|
|
|
|
// Normal by central difference, in ground units.
|
|
let e = 0.05 + dist * 0.006
|
|
let hx = (self.ground(gx + e, age) - self.ground(gx - e, age)) * swell
|
|
let hz = (self.ground(gx, age + 0.012)
|
|
- self.ground(gx, max(age - 0.012, 0.0))) * swell
|
|
let n = normalize(vec3(0.0 - hx, e * 3.0, 0.0 - hz * 0.35))
|
|
|
|
let lightd = normalize(vec3(0.45, 0.75, 0.42))
|
|
let lam = clamp(dot(n, lightd), 0.0, 1.0)
|
|
let rim = pow(1.0 - clamp(n.y, 0.0, 1.0), 2.0)
|
|
|
|
// Colour by height: troughs cold, crests hot.
|
|
let t = clamp(h * 1.25, 0.0, 1.0)
|
|
let body = self.col_a.xyz.mix(self.col_b.xyz, t)
|
|
let mut rgb = body * (0.18 + 0.9 * lam) + self.col_c.xyz * (rim * 0.35 * t)
|
|
|
|
// A grid of contour lines so the surface reads as a surface.
|
|
let lines = smoothstep(0.86, 1.0, abs(sin(h * 46.0)))
|
|
rgb = rgb + self.col_c.xyz * (lines * 0.10 * (1.0 - age))
|
|
|
|
// Haze toward the horizon: the far rows dissolve into the sky.
|
|
let haze = clamp(age * (0.6 + 1.4 * clamp(self.user.z, 0.0, 1.0)), 0.0, 1.0)
|
|
rgb = rgb.mix(self.col_bg.xyz.mix(self.col_b.xyz * 0.35, 1.0), haze)
|
|
return vec4(rgb * self.fog.y, 1.0)
|
|
}
|
|
}
|
|
}
|