notepat: reverse replay is locked to visual cursor + capture pauses
Fixes the "hjanky" reverse-replay timing where the needle and the audio
were drifting out of sync, and the capture ring was eating the reverse
echo + any overdubs during the hold.
Key change: during spacebar-held reverse playback the output history
ring is PAUSED at the C level (new audio->output_history_paused flag,
skipped in the per-sample capture block in audio.c:1448). No new data
enters the ring while space is held — the reverse voice's mix and any
notes played "on top" are audible but invisible to the visualizer and
won't re-enter the captured buffer.
Visual cursor is now driven directly by elapsed-since-press wall-clock
time instead of the lerped waveDriftSpeed model:
spaceHeld: waveViewOffsetSec = min(MAX, (now - spacePressStartMs)/1000)
the needle sits on exactly the sample the reverse voice
is reading — no drift physics, no ramp, no desync.
released: waveViewOffsetSec = 0 immediately; space handler also
calls speaker.setCapturePaused(false) to re-open the
ring, and resets spacePressStartMs. Since write_pos
didn't advance during the hold, "release" literally
snaps the cursor back to the moment of press, and
recording continues seamlessly from there.
New JS API: sound.speaker.setCapturePaused(bool)
→ audio_set_output_history_paused(audio, paused) in C
Also consolidated the DJ-deck empty-state title so "no usb" and
"no tracks" are sibling states in one readout instead of being
split between an inline label and a transient djMsg overlay —
USB mounted + 0 tracks → "no tracks"; no USB → "no usb"; scan
pending → "tap scan".
Queued follow-ups (tracked): whistle blow / per-instrument Shift
alternates, wobble/flange FX, aux-pad legend, side-key mapping,
drop noise voice from sampler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>