glitchin’ out: a stack of glitch effects

Open glitchin’ out

glitchin’ out applies a stack of glitch effects to a camera feed or an image. Users add, remove, and reorder effects, and set an intensity for each effect. The GLITCHIFY button makes a random stack of 2 to 4 effects. Users can save a photo or a video to a gallery in the browser. [G1]

How it uses Noisemaker

The effect stack writes one DSL program. The program starts with media(), adds one call for each effect, and writes the result to o0. An empty stack renders the source with no effects. [G2]

Program for the Datamosh starter chain, with line breaks added
search synth, filter, classicNoisedeck

media().corrupt(intensity: 41.0, bandHeight: 6.0, sort: 0.0, shift: 0.0, channelShift: 0.0, melt: 0.0, scatter: 7.0, bits: 0.0, speed: 3)
  .lensDistortion(shape: cosine, distortion: 0.0, mode: chromaticRgb, aberration: 28.15, hueRotation: 85.079, hueRange: 36.85, saturation: 10.45, passthru: 53.6, vignetteAmt: 0.0, speed: 0.0)
  .write(o0)

render(o0)

The parameter values come from a random snapshot, so they change each time.

GlitcherRenderer wraps one CanvasRenderer. To compile a program, it finds the effect names, loads the effects that are not yet registered, and calls compile(). When the user adds, removes, or moves an effect, the app builds a new program and compiles it. [G3] [G7]

An intensity change does not compile anything. Each slot keeps a random parameter snapshot. Intensity moves each parameter between the effect’s default value and the snapshot value. EffectStack.buildLiveParams() collects these values into one map, with one entry for each step: [G2]

EffectStack.buildLiveParams() in stack.js
buildLiveParams() {
    const out = {}
    this._slots.forEach((slot, i) => {
        const params = this._computeLerp(slot)
        if (Object.keys(params).length === 0) return
        out[`step_${i + 1}`] = params
    })
    return out
}

media() is step 0, so slot i is step i + 1. The renderer merges the map into its stored values and passes them to applyStepParameterValues(). It applies the stored values again after each compile. [G3]

Numeric and vector parameters move in a straight line and stay between the parameter’s minimum and maximum. The app rounds integer parameters. A choice parameter keeps its default value at intensity 0 and changes to the snapshot value at any intensity above 0. [G4]

The source can be an image, a video, or a canvas. On every animation frame, the renderer uploads the source with updateTextureFromSource('imageTex_step_0', source, { flipY: false }). It uploads still images on every frame too. It sets imageSize on step 0 only when the canvas size changes or after a compile. [G3]

Work outside the engine

The app selects the camera, accepts image uploads, and records photos and videos. For video, it uses the first format that MediaRecorder.isTypeSupported() accepts, in this order: WebM with VP9, WebM with VP8, WebM, MP4. [G5] The gallery keeps captures in IndexedDB. [G1]

The app saves the current stack in localStorage, so a reload restores it. A share link carries the stack in the URL hash as #s= followed by URL-safe base64. [G6]

Limits

  • The renderer sets preferWebGPU: false, so the app renders only with WebGL2. [G3]

  • Camera access needs browser permission. [G1]

  • Gallery captures stay in the browser. [G1]

Sources

All links point to glitcher revision cc59756.