makepad/apps/vj/resources/effects/262_oscilloscope_ribbon.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

87 lines
3.3 KiB
Text

// OSCILLOSCOPE RIBBON — the raw waveform drawn as a lit ribbon across the
// frame, with two older copies of the SAME window trailing behind it.
//
// This is the `audio_wave(t)` demo. `t` runs 0..1 across the stored window
// (about 1.4 s); 1 is the newest sample. Sliding the read window is how a
// scope "scrolls" here — the trail copies read the same texture at an
// offset instead of keeping a history buffer of their own.
{
name: "Oscilloscope Ribbon"
engine: "screen"
seed: 21
speed: 1.0
beat_pulse: 0.45
beat_rate: 1.0
bar_beats: 4
glow: 1.1
p0: 0.5 p1: 0.5 p2: 0.5
dials: [
{name: "AMP", bind: "p0", default: 0.5},
{name: "SPAN", bind: "p1", default: 0.5},
{name: "TRAIL", bind: "p2", default: 0.5}
]
color_bg: #x02060a
color_a: #x44ffc8
color_b: #x3aa0ff
color_c: #xffffff
stages: [
{kind: "bloom", threshold: 0.4, strength: 1.3, levels: 3}
]
shader: draw.DrawVjFxScreen {
// Trace height at screen x for a window ending `lag` back.
trace: fn(x: float, lag: float, amp: float) -> float {
// SPAN squeezes the read window: a short span is a slow, fat
// wave; a long one packs seconds of audio into the frame.
let span = 0.10 + 0.85 * clamp(self.user.y, 0.0, 1.0)
let t = clamp(1.0 - lag - span * (1.0 - clamp(x, 0.0, 1.0)), 0.0, 1.0)
let w = self.audio_wave(t)
// IDLE: silence is a flat zero, so add a small travelling sine
// that the real signal instantly swamps.
let idle = 0.055 * sin(x * 24.0 - self.time_beat.x * 3.1)
* (1.0 + self.time_beat.w)
return 0.5 - (w * amp + idle) * 0.5
}
fx_color: fn(uv: vec2, content: vec4, cmix: float) -> vec4 {
let amp = 0.5 + 1.4 * clamp(self.user.x, 0.0, 1.0)
let trail = clamp(self.user.z, 0.0, 1.0)
let y0 = self.trace(uv.x, 0.0, amp)
let y1 = self.trace(uv.x, 0.02 + 0.10 * trail, amp)
let y2 = self.trace(uv.x, 0.05 + 0.24 * trail, amp)
// A line is a distance field: thin core, wide glow.
let d0 = abs(uv.y - y0)
let d1 = abs(uv.y - y1)
let d2 = abs(uv.y - y2)
let core = exp(0.0 - d0 * 460.0)
let glow = exp(0.0 - d0 * 55.0)
let g1 = exp(0.0 - d1 * 70.0) * 0.42
let g2 = exp(0.0 - d2 * 90.0) * 0.22
// The RIBBON: fill between the live trace and the oldest trail
// copy, so the scope reads as a band and not three hairlines.
let lo = min(y0, y2)
let hi = max(y0, y2)
let band = smoothstep(0.0, 0.004, uv.y - lo)
* smoothstep(0.0, 0.004, hi - uv.y) * 0.22
let tint = self.col_a.xyz.mix(self.col_b.xyz, clamp(uv.x, 0.0, 1.0))
let mut rgb = self.col_bg.xyz
// Graticule: a faint scope grid so the frame is never empty.
let grid = smoothstep(0.985, 1.0, max(
abs(sin(uv.x * 25.1327)),
abs(sin(uv.y * 25.1327))
))
rgb = rgb + self.col_b.xyz * (grid * 0.05)
rgb = rgb + tint * (band + g1 + g2 + glow * 0.35)
rgb = rgb + self.col_c.xyz * (core * 0.9)
return vec4(rgb * self.fog.y, 1.0)
}
}
}