Halftone Misprint is a printing fault rather than an electronic one — the image is never damaged, only separated and reassembled out of register, and nothing else in the library is made of dots. Droste Feedback scales the previous frame where Time Smear translates it, so the image never clears. Analog Wow is the continuous, wet counterpart to Scan Tear and Block Mosh: the error varies smoothly down the frame because every line was written at a different moment. Three things that had to be got right. The halftone ruling is a count of dots across the frame, not a pixel pitch, or a 4K export is the same dot on a bigger sheet. The Droste loop has to CONTRACT — expanding pushes every copy off the edge and leaves a plume instead of a corridor. And all three expressed style only through grain at first, which is the Side Quest 1 complaint: they now put the track's line weight and edge softness into the dot, the ring and the band boundary, taking the measured style response from 24/11/18 to 86/255/88. Neither feedback scene declares a slow axis, with the numbers recorded in the files: their own history moves the ten-second average further than any parameter does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
101 lines
4.7 KiB
JavaScript
101 lines
4.7 KiB
JavaScript
// Glitch family: tape. The transport is not quite steady, so every line of the
|
|
// image is displaced by where the tape was when that line was written — a
|
|
// continuous horizontal warp, chroma trailing behind luma, and the occasional
|
|
// dropout where the oxide has worn through.
|
|
//
|
|
// Scan Tear and Block Mosh are digital faults: they are quantised, blocky, and
|
|
// they happen to whole regions at once. This is analogue and wet — nothing has
|
|
// an edge, the error varies smoothly down the frame, and the colour lags the
|
|
// picture instead of being replaced by it. Wow and flutter, not corruption.
|
|
//
|
|
// No declared slow axis: the dropouts and the head smear put two windows of
|
|
// identical parameters 0.041 apart, the second-highest noise floor in the
|
|
// library, and chroma bleed against that measured 0.02x.
|
|
|
|
export const analogWow = {
|
|
name: 'Analog Wow',
|
|
family: 'glitch',
|
|
kind: 'fragment',
|
|
texture: 0.9,
|
|
traits: ['camera', 'style'],
|
|
|
|
params: {
|
|
wow: { type: 'float', range: [0, 0.12], default: 0.035, uniform: 'u_wow' },
|
|
flutter: { type: 'float', range: [0, 0.03], default: 0.008, uniform: 'u_flutter' },
|
|
bleed: { type: 'float', range: [0, 0.05], default: 0.015, uniform: 'u_bleed' },
|
|
dropout: { type: 'float', range: [0, 0.6], default: 0.2, uniform: 'u_dropout' },
|
|
smear: { type: 'float', range: [0.4, 0.92], default: 0.7, uniform: 'u_smear' },
|
|
bars: { type: 'float', range: [1, 9], default: 3.5, uniform: 'u_bars', bias: 'density' },
|
|
speed: { type: 'float', range: [0.05, 0.7], default: 0.2, uniform: 'u_speed', bias: 'motion', rate: true },
|
|
palette: { type: 'palette', count: 5 },
|
|
},
|
|
|
|
reactive: {
|
|
dropout: { feature: 'flux', amount: 0.3, response: 'spike' },
|
|
wow: { feature: 'bandLow', amount: 0.25, response: 'smooth' },
|
|
smear: { feature: 'loudness', amount: 0.15, response: 'smooth' },
|
|
},
|
|
|
|
shader: `
|
|
// What was recorded: broad soft bars of colour, the kind of thing that shows
|
|
// tape damage clearly because it has no detail of its own to hide it behind.
|
|
vec3 programme(vec2 q, float t) {
|
|
float band = q.y * u_bars + fbm(q * 1.4 + vec2(t * 0.3, 0.0), 3) * 1.2;
|
|
vec3 col = palRamp(0.1 + band * 0.12);
|
|
col *= 0.55 + 0.45 * sat(0.5 + 0.5 * sin(band * 3.14159 + t));
|
|
col += pal(4) * smoothstep(0.85, 1.0, sat(0.5 + 0.5 * sin(q.x * 3.0 - t * 1.3))) * 0.25;
|
|
// The boundary between bands is drawn in the track's weight, so even a
|
|
// damaged signal is damaged in the video's own hand.
|
|
col += pal(4) * sigEdge(fract(band) - 0.5) * (0.15 + u_sigLine * 0.9);
|
|
return col * 0.7;
|
|
}
|
|
|
|
vec4 scene(vec2 uv, vec2 p) {
|
|
float t = u_time * u_speed + u_seed;
|
|
p = sigCamera(p);
|
|
|
|
// Transport error for THIS line: a slow wow, a faster flutter, and a little
|
|
// noise on top. It is a function of y, which is what makes the frame skew
|
|
// rather than slide — every line was written at a different moment.
|
|
float line = p.y * 30.0;
|
|
float err = sin(p.y * 2.1 - t * 1.7) * u_wow
|
|
+ sin(p.y * 11.0 + t * 5.3) * u_flutter
|
|
+ (vnoise(vec2(line, t * 3.0)) - 0.5) * u_flutter * 2.0;
|
|
|
|
vec2 q = p + vec2(err, 0.0);
|
|
|
|
// Luma is where it should be; chroma trails it. Sampling the programme
|
|
// three times at three displacements is exactly what a chroma delay does.
|
|
vec3 col;
|
|
col.r = programme(q + vec2(u_bleed, 0.0), t).r;
|
|
col.g = programme(q, t).g;
|
|
col.b = programme(q - vec2(u_bleed * 0.7, 0.0), t).b;
|
|
|
|
// Dropouts: bands where the tape has lost contact. They travel slowly down
|
|
// the frame, and they replace the picture with the tape's own noise floor
|
|
// rather than with black — a black band is a cut, this is a fault.
|
|
float bandId = floor(p.y * 8.0 + t * 2.0);
|
|
float hit = step(1.0 - u_dropout * 0.35, hash11(bandId * 1.7 + floor(t * 3.0)));
|
|
float band = smoothstep(0.5, 0.15, abs(fract(p.y * 8.0 + t * 2.0) - 0.5)) * hit;
|
|
vec3 noise = pal(1) * (0.25 + 0.5 * hash12(vec2(uv.x * 400.0, bandId)));
|
|
col = mix(col, noise, band * 0.7);
|
|
|
|
// Head smear: the previous frame, dragged sideways by the same transport
|
|
// error, is what gives tape its characteristic horizontal ghosting. The
|
|
// programme above stands on its own, so a seek recovers immediately.
|
|
vec3 past = prev(uv - vec2(err * 0.4 + 0.004, 0.0));
|
|
col = mix(col, max(col, past), u_smear * 0.55);
|
|
|
|
// Head-switching noise at the bottom of the frame — the one part of the
|
|
// image that is always damaged.
|
|
float sw = smoothstep(0.06, 0.0, uv.y) * u_dropout;
|
|
col = mix(col, pal(2) * hash12(vec2(uv.x * 300.0, floor(t * 12.0))), sw * 0.6);
|
|
|
|
col += sigGrain(uv);
|
|
return vec4(col, 1.0);
|
|
}
|
|
`,
|
|
};
|
|
|
|
export default analogWow;
|