A field, a score, a listening position
Lichen is a Canvas 2D study that pairs fine silver threads on mineral paper with a four-part synthesizer. It now lives in the Blue Bucket application, where one timeline connects the score, artwork and listening position. Scroll to move through its sound field.
The vertical veil, folded ribbon and broad closing sweep carry Lichen's drawing language into the new study. Periodic waveforms, slow detuned pairs, gentle filters, finite reflections and HRTF panning shape its sound. The supplied transcription contains 508 notes across four parts. This is a synthesized interpretation of Aphex Twin — Lichen, not a recording.
One score position determines the harmony, visible part of the field and head orientation. In the supplied transcription, the last note begins at beat 182; the final flute note and four-part closing harmony end at beat 183. The transport and progress controls cover all 183 beats. At the transcription's approximate 45 BPM tempo, that span lasts 244 seconds; the default 32 BPM interpretation lasts 343.125 seconds. These timings describe the supplied MIDI, whose events and tails do not reproduce every detail of the original recording.
The veil, ribbon and sweep repeat as neighbouring copies in document space, so the last viewport flows into the first without a geometry reset.
Play, scroll, sustain
Nothing sounds on arrival. Press Play to enable Web Audio and start automatic playback at the chosen position. The interface shows “Starting” until activation succeeds. Seeking or leaving during activation cancels that request, so a late response cannot start playback unexpectedly. Before activation, scrolling only selects a position.
After activation, downward wheel or trackpad movement continues or resumes Auto. Upward movement selects an earlier harmony and immediately enters Sustain. A vertical touch swipe, arrow keys, head dragging and the ruler use the same bounded pixel-to-beat mapping and the Sensitivity dial, from 0.25× to 3×. Tempo sets Auto's separate 24–48 BPM pace. Timeline scrubbing selects an exact beat: a forward seek continues Auto, while a backward seek sustains its destination.
Pause holds the harmony; Mute silences it. The score beat remains fixed in Sustain while filters breathe and the threads respond to hover, tap and a held press. Press Play, or move downward, to resume Auto from that harmony. Explicit Play is still required after arriving, hiding the tab or cancelling activation. The expanded panel always shows Play/Pause, Mute, position, Volume, Tempo and Sensitivity.
Lichen always loops. Relative movement and Auto wrap in either direction with overshoot preserved. A continuous travel coordinate keeps the head and audio orbit moving through repeated cycles while score position wraps across all 183 beats. The timeline, Home, End and reset still select exact positions. The generic transport retains finite playback and optional manual exploration for other studies.
In Auto and Sustain, moving the pointer across drawn threads plays a harmony-safe arpeggio on one foreground voice. The field records the thread lines it draws, so hover sounds only when pointer movement crosses the ink within a small radius; the check follows the pointer path to catch fast crossings. Idle paper, a stationary pointer and artwork moving beneath a stationary pointer stay silent. Hover ends within about 120 ms after movement stops, or immediately when the pointer leaves the threads. A click or quick tap plays a rounded three-note motif; rapid taps replace it with a bounded seven-note flourish. Their pitches come from the current harmony, and the foreground voice uses Lichen's detuned ambient timbre.
Vertical position changes the foreground voice's register: the upper field sounds an octave above the lower. Horizontal movement selects chord degree and direction, while pointer speed sets a pace of roughly 0.12–0.34 seconds per note. Hover does not change the four score parts. Entering the controls, hiding the tab, seeking or muting ends the phrase with natural tails. The pointer never activates audio, unmutes or seeks.
Press and hold, or drag across the threads, to keep the same foreground arpeggio sounding. A stationary press sustains it; dragging changes its pitch, direction and pace. Leaving the threads, losing pointer capture, seeking, muting, hiding the tab or closing the study stops pending notes while sounding notes finish their short tails.
Touch has no hover. A touch hold or deliberate horizontal drag plays the arpeggio; a vertical swipe explores the score, without triggering an accidental tap or brush. Per-track drag reflections and their ghost mix remain inactive, so they do not affect the current arpeggio or score.
Reverb and space
The reverb is permanent, not a setting: there is no audition control or switch. Press Play to hear it with the score. One AudioContext.createConvolver uses a deterministic, energy-normalized stereo impulse of about 3.5 seconds, preserving the existing pre-delay and damping.
The wet return splits in two. Its dominant directional branch joins the score's mono sum after the dry gate and before the same HRTF and lateral panners used by the dry score. This keeps the circling source locatable and the moving head aligned with the position and level meter. A quieter diffuse stereo branch goes to the master mix as a room bed.
The direct score remains in the foreground: it is not ducked, and the original per-part ambience is unchanged. Holding the artwork still plays the current arpeggio; reverb does not respond to press, hold, drag or hover. Mute, cancellation, tab hiding and disposal close the wet return until the finite impulse has drained, preventing an old tail from returning after a quick restart. If a browser cannot build a convolver, the score plays without reverb. The master limiter and dry synthesis phases, envelopes and ambience remain unchanged.
Mute silences output and stays muted through movement, Sustain and Auto. Hiding the interface keeps the performance running. Hiding the browser tab, navigating away or disposing the study cancels sounding and queued voices; returning does not restart sound. Play is required again.
The audio clock and harmony
Automatic playback uses the audio context clock, with a 20-millisecond scheduler and a 120-millisecond lookahead. Rendering reads the transport state; animation frames neither advance the score nor schedule music. A delayed scheduler drops missed onsets and reconstructs only the harmony at its current position instead of replaying a burst of crossed notes.
Manual performance reconciles the target harmony on the same audio-clock timer. It retains unchanged oscillator voices and their phase, releases notes that no longer belong, and starts only new notes at the selected beat. Reverse exploration and large seeks select the destination harmony directly. Tiny wheel events do not repeatedly cancel the master or reattack the same chord.
Timeline traversal suppresses foreground accents. Touch waits for release, hold duration or horizontal intent before adding them. Playback controls and the head ruler sit outside the scene gesture surface, so their clicks and drags cannot pluck the field. Sustained voices have no scheduled gate while held; slow detuning and filter modulation keep the texture alive.
In a score gap, Lichen retains the most recent valid chord, selected from the last note-on before the current beat. It deduplicates notes by pitch and part and limits the harmony to twelve voices. This supplies a last valid harmony without inventing notes or allowing unbounded voices.
Synthesis is capped at 32 active or releasing voices, including foreground patterns. The foreground scheduler uses the audio clock, queues at most 80 milliseconds ahead, limits its own voices to twelve and drops overdue steps. Releasing hover or a held press, pointer cancellation, lost capture, leaving the scene, seeking, muting, hiding the tab or closing the study stops its pending pattern and cancels future foreground onsets. Sounding notes keep bounded release tails.
During Auto, foreground admission forecasts upcoming score notes through each candidate foreground note's full release. It reserves oscillator slots for score onsets from lookahead allocation through their natural release and stop. Already queued score voices are counted once. Dense passages reduce embellishment density without dropping score onsets, truncating score tails, ducking the master or raising the 32-voice limit. A held press keeps its twelve-voice foreground budget while it sounds.
Auto reconstruction still uses the score's remaining note duration:
export function notesAt(notes, beat) {
return notes
.filter(n => n[0] <= beat && n[0] + n[1] > beat)
.map(note => ({ note, remaining: note[0] + note[1] - beat }));
}BPM changes, loop changes and Auto resume reconcile current voices without restarting oscillators, moving the master gain or clearing reflection tails. Future onsets are rescheduled against the new audio-clock epoch; current notes receive continuous replacement gates for their remaining durations. Score-event identity and the current gate distinguish a repeated pitch's new attack from an older release tail. Natural tails continue without being revived, while manual Hold voices retain their phase through Auto resume. Repeating the same dial value has no effect.
Cancellation is reserved for true silence boundaries. It captures the instantaneous gain before fading sounding voices, silences future onsets before they can start, clears pending visual notes and stops oscillators. Activation requests carry serial tokens, and disposal is idempotent and closes the audio context.
At a loop seam, the first score note schedules once on the new cycle while previous score releases and finite reflections continue. The panner follows continuous travel; the seam does not reset the master or spatial phase.
An anatomical listening view
The navy inset uses Infinite, 3D Head Scan, by Lee Perry-Smith, based on a work at www.triplegangers.com, under Creative Commons Attribution 3.0 Unported. The geometry and original license come from the official three.js asset directory, pinned r182. Attribution and the original license remain with the source.
The scan is modified in Blender: its chest is cropped and tapered into a short neck, surface contour sections are derived, and smooth surface-projected guides are added for the jaw and chin, cheek and muzzle, closed eye socket and lid, brow, nose bridge, nostrils and lips.
build-lichen-head.py runs in an isolated Blender factory scene. It imports mesh data only, preserves the real skull, eyelids, nose, lips, ear and jaw geometry, and saves a reproducible .blend with the anatomical contour guides. It exports a compact contour asset plus a simplified mesh for view-dependent silhouettes. The browser does not load Blender, scan textures or the original modeling mesh.
The high-DPI canvas projects three-dimensional curves through yaw. Surface depth and normals hide rear contours; mesh silhouettes preserve the head's profile. Anatomical feature curves are weighted more heavily than the lighter cross sections.
Pale cyan dashes, degree ticks and a triangular pointer form the custom ruler. Head and ruler dragging are relative: each pixel uses the same sensitivity as a wheel pixel, avoiding absolute angle jumps. Arrow keys use a small bounded step; Home and End remain exact timeline commands. Wrapped degrees report continuous travel and do not determine score position.
A luminous ball shows the main music source relative to the listener. It follows the music panner's current computed X/Y/Z values, including smoothing, rather than its future target. The Web Audio listener looks along −Z and the model's face points along +Z, so listener-right +X maps to model −X in a frontal view. The head and orbit share a camera projection.
A rear marker dims and the rear orbit becomes dashed. The direction label stays in listener coordinates regardless of camera yaw. Main-bus RMS drives the ball's glow and radius, excluding the separate foreground gesture bus. Mute, zero volume and inactive audio dim the ball. Reduced motion removes its radius pulse while retaining its location and energy brightness. A cached head canvas avoids reprojecting the mesh during stationary harmony metering.
The head begins at Lichen's original 31.5° angle and follows two full view turns across the score. Its view angle and the audible source bearing are distinct quantities. The field's decorative breathing continues during Sustain; reduced-motion preferences remove decorative deformation while preserving explicit control.
A small shared shell
StudyShell supplies metadata, help, appearance, fullscreen, hide and restore, optional panel density, accessible custom controls, credits and the listening view. Lichen owns its score, synthesis adapter, renderer and control ranges.
A study can declare labelled select controls. The shell places them in the playback panel and passes values to setControl(id, value) without interpreting their meaning. Lichen does not use this option because its reverb is permanent. Lichen uses an always-expanded panel and always-on looping; other studies can expose either setting. An optional spatial snapshot keeps the shell independent of Lichen's sound engine.
Optional capabilities let silent DOM or WebGL studies omit score, pace, volume and seeking without importing MIDI or Lichen's drawing code.
Both instrument panels can move. Drag a panel's title grip to reposition it one-to-one with the pointer, without easing or inertia. The clamp keeps the whole panel inside the viewport with an 8px margin, or keeps its grip reachable when it is larger than the screen. It settles again on resize, rotation, zoom and fullscreen.
Arrow keys on a focused grip move the panel by 10px; Shift increases the step to 40px. Home or double-click returns it to its default corner. Escape during a drag restores its starting position. Moving a panel never seeks, plays, pauses or plucks the score. Its buttons, dials and the head ruler keep their existing behavior.
The transport is constructed by the study:
const transport = new ScoreTransport({
duration: MIDI_SCORE.durationBeats,
pace: 32,
adapter,
onChange,
directionalPlayback: true,
});The adapter supplies activation, audio-clock scheduling, harmony reconciliation, spatial positioning and cancellation. To adapt it, preserve explicit activation, meaningful Hold, silent disposal, bounded voices and destination-only reconstruction. A score gap needs an intentional bounded policy.
The source below mirrors this build's renderer, transport, sound, shell, tests, anatomical data, Blender build script, attribution and article. The manifest records SHA-256 hashes. The transcription was supplied with the original Lichen study; access to source does not grant rights to Aphex Twin's composition. The anatomical scan and contour derivative retain their separate CC BY 3.0 terms.
Source
Readable files delivered by this build. Each opens as plain text and can be saved individually.
- Article ↗
- Article copy ↗
- Canonical article route ↗
- Canonical launch route ↗
- Shell ↗
- Shell styles ↗
- Study contract ↗
- Lichen definition ↗
- Transport ↗
- Synthesis adapter ↗
- Sound engine ↗
- Isolated reverb ↗
- Canvas renderer ↗
- Supplied MIDI score ↗
- Projected head ↗
- Anatomical contours ↗
- Blender build script ↗
- Head provenance ↗
- Head license ↗
- Head component ↗
- Shell lifecycle ↗
- Transport tests ↗
- Adapter tests ↗
- Shell and gesture tests ↗
- Canonical tests ↗
- Canonical export tests ↗
