# Shared sound studies Lichen and Blue Calx share the existing `StudyShell`, `ScoreTransport`, `SpatialHead`, dials, draggable panels and lifecycle. Each study supplies a `StudyDefinition` and an instance. There is one transport clock, with an unwrapped position for infinite traversal and a wrapped position for the score. Do not copy the shell into another study. ## Ownership | Shared code | Study code | | --- | --- | | Play/sustain, seek, looping and tempo | MIDI score and synthesis | | Wheel, keyboard, touch timeline and sensitivity | Pointer instruments and note-to-visual response | | Head/ruler input and actual spatial readout | Music orbit and fixed foreground audio buses | | Movable panels, appearance and mute UI | Palette, labels, ranges and defaults | | Hidden-tab/page-exit pause and disposal | Canvas, audio nodes and observers | The current `v2` directory is an internal path, retained to avoid moving existing source URLs. It is not a second public version. This is an application-level framework; no separate package or dependency is needed. ## Add another study 1. Add the score and a study-owned sound engine. Keep the sound silent until `enable()` is called from Play. Use bounded gains and own every created voice and effect node. 2. Implement the `StudyAdapter` contract in `transport.d.mts`: audio clock, enable, start, bounded lookahead scheduling, position, sustain, cancellation and disposal. Do not start a competing interval or score scheduler. Use the unwrapped progress supplied to `position()` for orbit continuity. 3. Add a renderer with `render(beat, unwrappedProgress, theme, activated)`, note feedback, and idempotent teardown. The shell owns animation cadence. Keep drawing and hit testing on the same geometry. Distinguish a vertical touch swipe from a tap or horizontal instrument gesture. 4. Define title, attribution, help, defaults, capabilities and palette. Optional `controls.ranges` are continuous study parameters; `setParameter(id, value)` receives them. Optional selects use the existing accepted/error control contract. Keep independent controls independent: Blue Calx's Metronome changes only click volume, leaving the automatic and playable drums and bass at their own levels. 5. Create the instance in a small experience component, return its timeline, render, volume/mute, spatial snapshot and disposal methods, then register its launch route. The spatial snapshot must reflect the actual panner and audible music bus, not a decorative orbit estimate. 6. Publish the source/attribution through the source preparation script. Test seek, direction changes, scheduling across the loop seam, mute before activation, repeated disposal and hidden-page pause. Review both palettes, small viewports, touch input and the head/ruler before shipping. ## Blue Calx preservation boundary The 205-beat, 120-note supplied score, chord voices, original percussion and bass synthesis, upper/lower gesture mapping, rolls, contour families, grain and palette come from `public/playground/artworks/blue-calx.html`. Its legacy standalone file remains available. Defaults are 40 BPM, 60% volume and 65% Metronome; Tempo remains 24–80 BPM. The requested metronome replaces the half-beat pulse with one click per beat and an accent every four unwrapped beats, including across score loops. Its gain is separate from the original automatic percussion/bass level of 65%. The canonical launch uses shared directional playback: down continues forward, up continues backward, and playback keeps the last traversal direction after scrolling stops. Pause holds the current harmony; Mute silences output. Scroll/head traversal is infinite in both directions. The renderer extends the original field without the old finite scroll boundary. Head rotation follows unwrapped traversal; music-source position comes from the real panner. Gesture drums and bass stay in front. ## Transport and lifecycle contract `ScoreTransport` keeps wrapped `beat` for score lookup and continuous `unwrappedBeat` for traversal/orbit. Canonical Lichen and Blue Calx opt into `directionalPlayback: true`; the transport's default is false for compatibility. Positive/negative input updates direction and continues playback only after audio activation. Scrolling before Play remains silent. `play()` enables audio asynchronously with a serial guard against stale activation. `hold()` sustains the current position/harmony without clearing activation. `pause()` is the true silence boundary for visibility/page exit/cancel: it stops scheduling, cancels voices and clears activation. `dispose()` additionally destroys adapter resources and is idempotent. Do not confuse the UI's Pause button (hold) with the lifecycle pause method. The default scheduler ticks every 20ms with 120ms lookahead using the adapter's audio clock. Direction-aware scheduling splits windows at score loop seams; `notesAt` handles reverse note boundaries. Seek/direction/tempo reconciliation cancels or retimes stale scheduled work through the adapter. Preserve event order and cancellation when modifying those paths; do not add a competing clock in the shell or field renderer. Required adapter methods are declared in `transport.d.mts`; study sound engines own voices, gesture buses and effects. Tests include `directional-playback.test.mjs`, Lichen transport/adapter tests and Blue Calx runtime checks. Actual listening is required for audible-quality claims. Current defaults, gestures, panel/keyboard behavior and other studies are indexed in [Playground controls](../../docs/playground-controls.md) and [Playground architecture](../../docs/playground.md). These guides should change with the source contract. ## Deliberate limits Do not force different songs through Lichen's arpeggio/reverb engine just to share more code. Synthesis and gesture rules are separate because they define each study. Extract another musical primitive only when a second study actually needs the same behavior. Forest worlds, MIDI file import, preset editors and a general plugin loader are separate work.