Synth — live coded
Inspired by Sonic Pi, Overtone, and TidalCycles — live-coded music as a first-class way to explore sound.
A polyphonic subtractive synth whose patch parameters and
control panel UI are both authored in Clojure. The DSP graph
(oscillator → filter → gain → output) is a fixed Web Audio graph
in the browser; an animation-frame loop polls the cljrs-side
(patch) vector and pushes any changes onto the
audio nodes. The control panel below is rendered by
cljrs.ui — the editor source contains the entire
hiccup tree, so the same code that draws the sliders also
runs at build time to produce SEO-visible HTML.
Hold Alt (or Ctrl) and drag any number to scrub it
live. Editing the source re-evals it; def'd atoms
reset to source values, so source = preset / sliders = live tweak.
Keyboard: press Start first, then play with a s d f g h j k (white keys, C major) and w e t y u (black keys). Hold multiple keys for chords — up to 8 voices ring simultaneously. Each voice has its own ADSR.
preset
patch
filter
envelope
LFO → cutoff
amp
sends
Patch values stream into Web Audio every frame. Pick a preset to snap all knobs, then edit sliders for a live tweak.
What's happening
- The right-hand panel is rendered by
cljrs.ui— the source defines a(ui-tree)hiccup vector and a(boot)entry point that calls(cljrs.ui/mount! "cljrs-root" ...). - Each patch param is an
atomdeclared withdefat the top of the source. Slider:on-inputhandlersreset!the atom and re-mount; the editor's auto-apply re-binds them to the source values on every edit (so source = preset, sliders = live tweak). - JS owns the AudioContext and the audio graph. An rAF loop calls
repl.eval_floats("(patch)")every frame; if the vector changed it pushes new values onto the existing nodes withsetValueAtTime. WAVE-TYPEis a keyword atom toggled by clicking the wave button; JS reads its name viarepl.eval.Triggerre-runs the ADSR envelope. The wave-button + Trigger demonstrate two cljrs-driven controls (one inside the cljrs.ui tree, one outside in plain JS toolbar).- Polyphony. Each held key gets its own Web Audio voice (osc → filter → gain) with an independent ADSR envelope. The voice table caps at 8 — past that, the oldest voice is stolen. Releasing a key triggers the release ramp; a delayed cleanup tears the voice down once it's silent. Live param edits propagate to every active voice so the chord morphs as you tweak.