// Visual Live Coding IDE — press ? for API reference // Write shaders as plain JS — no GPU knowledge needed const s = new Shader(({ uv, time }) => { const r = Math.sin(uv.x * 10 + time) * 0.5 + 0.5; const g = Math.cos(uv.y * 8 - time) * 0.5 + 0.5; return [r, g, 0.5, 1.0]; }); s.start();
    • Motion
    • Gamepad
    • Geolocation
    • Battery
  • Camera:

    API QUICK REFERENCE

    Pattern Syntax

    "C4 E4 G4"Space-separated — equally spaced across one measure
    "~ . x"Rest — ~ or . are silences; x = generic trigger
    "C4 [E4 G4]"[ ] groups share one time slot — E4 G4 play twice as fast
    "<C4 G3> E4"< > alternates each cycle — C4 on cycle 0, G3 on cycle 1
    "C4*3 G4"*N repeats N times inside that slot

    Pattern API

    pat(str, synth)Create pattern from mini-notation + synth or callback fn
    .speed(n)Play n× faster (e.g. .speed(2) = double time)
    .slow(n)Play n× slower
    .euclid(k, n)Distribute k hits across n equal steps (Euclidean rhythm)
    .every(n, fn)Apply fn(events, cycle) every n cycles; return modified events
    .start() / .stop()Start/stop the pattern loop
    stack(p1, p2, …)Layer patterns; call .bpm(120).start() on result
    pat(str, (note, time, dur) => {})Callback form — full control; route to multiple synths

    Synths

    audio.synth(opts)Basic oscillator
    audio.poly(opts)Polyphonic — play chord arrays
    audio.fm(opts)FM synthesis — metallic/electric tones
    audio.am(opts)AM synthesis
    audio.pluck(opts)Karplus-Strong plucked string
    audio.kick(opts)Membrane synth — kick / tom drums
    audio.metal(opts)Metallic — hi-hats, cymbals (no note arg)
    audio.noise(opts)White noise (no note arg)
    s.play(note, dur, time?)Trigger synth — dur: '8n', '4n', '1n', or seconds
    s.chain(fx1, fx2)Route synth through effects in series to output
    s.connect(fx)Route synth to single effect

    Effects

    audio.reverb(decay)Reverb — decay in seconds
    audio.delay(time, feedback)Feedback delay — time in sec, feedback 0–1
    audio.distort(amount)Distortion 0–1
    audio.chorus(freq, delay, depth)Chorus effect
    audio.filter(type, freq, Q)Filter — types: lowpass highpass bandpass notch
    audio.autoFilter(rate)LFO-swept filter — rate in Hz
    audio.vibrato(freq, depth)Pitch wobble
    audio.tremolo(freq, depth)Volume wobble
    audio.phaser(rate, octaves)Phase shifting
    audio.wah(baseFreq)Auto-wah
    audio.pitchShift(semitones)Pitch shift up/down
    audio.compressor(threshold, ratio)Dynamic range control
    audio.eq(low, mid, high)3-band EQ in dB

    Scales & Modulation

    audio.scale('C4', 'minor')Returns note array for scale — names: major minor dorian phrygian lydian mixolydian pentatonic blues chromatic
    audio.note(scaleArr, degree)Pick scale note by degree index (wraps around)
    audio.lfo(freq, min, max)LFO signal — connect to synth param: lfo.connect(s._.frequency)
    audio.meter()RMS amplitude meter — use s.chain(m); then m.getValue() each frame
    audio.analyser(bins)FFT analyser — use s.chain(a); then a.getValue() → Float32Array
    audio.bpm(120)Set transport tempo
    audio.volume(-6)Master output volume in dB

    Audio Signal Bus

    Any audio source — synth, pattern, mic, effect chain — can be treated as a live signal that drives shaders, draw calls, window properties, or anything else. Three abstractions, one idea: audio is a data bus, not just sound.

    audio.signal(source)Live {value, bass, mid, high, fft} object — read any getter each frame, no setup loop needed
    audio.fftCanvas(source, bins)Live bins×1 canvas, R channel = FFT magnitude 0–1 — feed directly into any Shader as video:
    shader.bind(source)Auto-fills custom = [rms, bass, mid, high] every frame — chainable with .start()

    source accepts: any Tone instrument/node, Tone.Analyser, Web Audio AnalyserNode, or 'mic' (harness mic — enable with toolbar toggle).

    SIGNAL CHAINS
    synth → audio.signal() → .stream(s => draw.circle(…, s.bass * 300)) — RAF push, no loop
    synth → shader.bind(sig) → custom.x=rms, .y=bass, .z=mid, .w=high in WGSL
    synth → audio.fftCanvas() → Shader({ video: fftTex }) → textureSample by uv.x
    'mic' → any of the above — full spectrum from microphone
    viz.canvas → Shader({ video: viz.canvas }) → viz.shader(fn) → chained post-processing

    Video Signal Bus

    The inverse of the audio signal bus — sample any canvas or camera feed as a live signal to drive audio params, effects, or anything numeric. Camera, viz canvas, layer canvas — all valid sources.

    video.signal(source, opts)Live {brightness, r, g, b, motion, hue} — all lazy getters, sampled at fps (default 30)
    sig.brightnessLuminance 0–1 of sampled region (perceptual: 0.299r + 0.587g + 0.114b)
    sig.motionFrame-delta RMS 0–1 — how much changed since last sample. Cheap motion detection, no Vision API needed
    sig.hueDominant hue 0–360 — map to scale degree, filter cutoff, palette index
    sig.r / .g / .bRaw color channels 0–1 — drive independent synth voices, effect amounts
    opts: x, y = normalized center of sample region (0–1), radius = region size (0–1), fps = sample rate.
    source: 'camera' (toolbar camera), any HTMLCanvasElement (getCanvas(0), viz.canvas, mirror canvas).
    Pass an existing video.signal() object as source to onMotion/onBrightness to share one sampling loop across multiple triggers.
    video.onMotion(src, thr, onEnter, onExit?)Edge-triggered when motion crosses threshold — fires once on enter, once on exit
    video.onBrightness(src, thr, onEnter, onExit?)Edge-triggered on brightness threshold — like audio.onLevel but for camera/canvas
    SIGNAL CHAINS
    'camera' → sig.brightness → synth._.frequency.value = sig.brightness * 880
    'camera' → sig.motion → kick.play() when motion > 0.15
    'camera' → sig.hue / 360 * scale.length → scale degree → note
    getCanvas(0) → sig.brightness → reverb.decay / filter.frequency
    viz.canvas → sample different x regions for per-frequency-band triggers

    Sensor Signal Bus

    Every sensor returns a signal object: live getters you can read any frame + .stream(fn) RAF push + edge triggers. All streams auto-cancel on reset.

    APISignal fieldsTriggers
    sensors.mouse()x, y, vx, vy, speed, left, right, middle.onMove(thresh, fn) .onButton(btn, fn)
    sensors.keyboard()held (Set), last, is(key), any(...keys).onKey(key, onDown, onUp?)
    sensors.gamepad(i)connected, axis(i), button(i), pressed(i).onButton(i, fn) .onAxis(i, thresh, fn)
    sensors.motion()ax/ay/az (m/s²), gx/gy/gz (°/s), alpha/beta/gamma (°).onShake(thresh, fn) .onTilt(axis, thresh, fn)
    sensors.geo()lat, lon, altitude, accuracy, speed, heading.stream(fn) (fires once ready)
    sensors.network()online, type, downlink (Mbps), rtt (ms).onChange(fn)
    await sensors.battery()level (0–1), charging, timeToEmpty (s).onChange(fn)
    // Mouse → shader custom uniform
    const ms = sensors.mouse();
    const s = new Shader(({ uv, custom }) => {
      const d = length(uv - vec2(custom.x, custom.y));
      return [1.0 - smoothstep(0.0, 0.1, d), uv.y, uv.x, 1.0];
    });
    ms.stream(m => s.set([m.x, m.y, 0, 0])); s.start();

    // Gamepad left stick → draw
    sensors.gamepad(0).stream(g => {
      draw.circle(400 + g.axis(0) * 200, 225 + g.axis(1) * 200, 20, 'white');
    });

    // Device tilt → camera shader (mobile)
    const mot = sensors.motion();
    const s2 = new Shader(({ uv, col, custom }) => {
      const shift = vec2(custom.x * 0.05, custom.y * 0.05);
      return [col.r, col.g, col.b, 1.0]; // shift uv by tilt
    }, { video: cam });
    mot.stream(m => s2.set([m.gamma / 90, m.beta / 180, 0, 0])); s2.start();

    // iOS motion permission (call from button click)
    btn.onclick = async () => { const mot = await sensors.requestMotion(); }

    WebGPU Shaders

    Write shaders as plain JavaScript — no WGSL, no GPU knowledge required. Pass an arrow function to new Shader(fn) and the IDE compiles it to WebGPU automatically.

    ParamTypeDescription
    uvvec2Normalised pixel coord 0–1 (x right, y down)
    timef32Seconds since shader started
    colvec4Video/camera pixel at current uv — needs { video: src } opt
    customvec4x/y/z/w — filled by .bind(audio.signal(...)) or .set([…])
    resvec2Canvas resolution in pixels
    mousevec2Mouse position normalised 0–1

    return [r, g, b, a] — use an array literal; automatically becomes vec4f.

    Math.* → all map to WGSL equivalents: Math.sin, Math.abs, Math.pow, Math.floor, etc.

    vec2(x,y) / vec3(r,g,b) / vec4(r,g,b,a) — vector constructors available globally.

    WGSL built-ins available as-is: mix, clamp, smoothstep, step, fract, length, normalize, dot, cross, reflect.

    Ternary cond ? a : b — works; compiles to WGSL select(b, a, cond).

    if / else, for, while — all supported.

    Helper functions — declare inside the arrow fn body; hoisted to WGSL. Type params with default values: r = 0.0 → f32, center = vec2(0,0) → vec2f.

    // Generative — no video needed
    new Shader(({ uv, time }) => {
      const r = Math.sin(uv.x * 10 + time) * 0.5 + 0.5;
      return [r, uv.y, 0.5, 1.0];
    }).start();

    // Camera filter — col = pixel at current uv
    new Shader(({ uv, col, time }) => {
      const grey = col.r * 0.299 + col.g * 0.587 + col.b * 0.114;
      return [grey, grey * Math.abs(Math.sin(time)), grey, 1.0];
    }, { video: cam }).start();

    // Audio-reactive — .bind() fills custom.x=rms, .y=bass, .z=mid, .w=high
    const s = new Shader(({ uv, time, custom }) => {
      const r = Math.sin(uv.x * 20 * (1.0 + custom.x * 5.0) + time) * 0.5 + 0.5;
      return [r * custom.x, r * 0.3, 1.0 - r, 1.0];
    });
    s.bind(audio.signal(synth)).start();

    // Helper function — default values are type hints
    new Shader(({ uv, time }) => {
      function circle(center = vec2(0,0), p = vec2(0,0), r = 0.0) {
        return 1.0 - smoothstep(r - 0.01, r + 0.01, length(p - center));
      }
      const c = circle(vec2(0.5, 0.5), uv, 0.25 + Math.sin(time) * 0.1);
      return [c, c * 0.4, c * 0.9, 1.0];
    }).start();

    // Apply shader directly to any window
    wm.applyShader('win-camera', ({ uv, col, time }) => {
      return [1.0 - col.r, 1.0 - col.g, 1.0 - col.b, 1.0];
    });

    Audio → Visual (patterns & timing)

    Beat as state machine: Each pat() callback fires at precise musical times — use it to advance a CA, flip a palette, emit a particle burst. Audio is the clock, not the picture.

    Amplitude as physics force: sig.value or sig.bass → feed to gravity, repulsion, blur strength, shader uniform. Read any frame with no setup.

    Note value as index: The note string in a callback can select palette index, drawing mode, CA rule — anything discrete.

    Shared parameter: The same variable that drives synth detune can drive visual distortion — change one number, both domains shift.

    Press ? or Esc to close