makepad/apps/vj/resources/effects/264_spectrum_sea.splash
Admin cb49b032df vjfx: 104 presets, engine hooks + hold stage, the videomesh engine, the audio picture, livecodable effects, and mp4 thumbnail sheets
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
2026-08-26 08:49:48 +02:00

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)
}
}
}