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]_
.. code-block:: text
:caption: 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]_
.. code-block:: javascript
:caption: 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``.
.. [G1] `README `_.
.. [G2] `Effect stack (stack.js) `_.
.. [G3] `Renderer wrapper (renderer.js) `_.
.. [G4] `Parameter interpolation (effects.js) `_.
.. [G5] `Capture (capture.js) `_.
.. [G6] `Stack persistence (persistence.js) `_.
.. [G7] `Application controller (app.js) `_.