arthur/frontend/test/arthur
Your Name 5b5b9ae4c3 Play the audio mixdown instead of encoding a WAV of it
Opening the 8625 study froze the main thread for 4.7 seconds and settled at
615MB of heap. A profile put three quarters of a project open inside
mix/wav-bytes, which playback had no business calling at all.

Two separate causes. The peak scan built a lazy sequence of one boxed double
per SAMPLE -- ten million of them for a seven-minute mix -- to compute a
single maximum over data already sitting in Float32Arrays; the hand-written
loop is 73x faster and agrees to the bit. The rest was structural: the WAV
existed only because an <audio> element can hold a URL and nothing else, and
the element existed only to be the clock. So a mixdown that was already
rendered got encoded to 73MB of 16-bit PCM, on the main thread, on open, on
every tab switch and on every edit to a track -- and a symbol with no sound
got silence synthesized and encoded full length so the element had a duration
to report.

arthur.clock keeps its interface and all of its arithmetic; the position now
comes from a backend behind a protocol. clock.graph plays the AudioBuffer
through an AudioBufferSourceNode and derives the frame from the context's own
clock, which is the audio device's position in double precision rather than
whatever the media pipeline last published. clock.element is the old path,
kept switchable while the new one earns trust -- BACKEND, or use-backend! --
which is also why every one of the original clock tests passes unchanged: the
derivation they assert is shared, and the backends can only disagree about the
position under it. 6.5s to 1.8s, 4.7s of blocking to 370ms, 615MB to 68MB.

THE POSITION IS COMPENSATED FOR OUTPUT LATENCY, and piecewise because of it.
currentTime is the quantum being rendered, which the speaker is tens of
milliseconds behind; report the renderer and the picture leads the sound,
which in a lip-sync tool is the only artefact that matters. Audio already
rendered cannot be re-rated, though, so reading it back at a new rate jumped
the playhead backwards by three latencies on every press of the rate button.
Each play, pause, seek and rate change now records a segment and a position is
read against whichever segment was in force when that audio was rendered.

One duplicate fell out of this. Opening a project asked for its clock twice --
once from ::opened and once from a ::refresh-clock the shell raised because it
compared symbol ids, and two different documents both open on :main. The
sounds subscription carries the clip id now, so "an edit under the same
symbol" means what it says.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-03 04:47:35 -04:00
..
clock Play the audio mixdown instead of encoding a WAV of it 2026-10-03 04:47:35 -04:00
domain Refuse a symbol placed inside itself, and label instances by what they place 2026-10-03 03:43:07 -04:00
events Create in a lane at the frame double-clicked, palette lanes included 2026-10-03 03:52:39 -04:00
export Symbols, not timelines; no symbol is special 2026-09-29 12:46:42 -04:00
flow Support multiple regeneratable footage analyses 2026-10-03 01:24:45 -04:00
support An options map for the resolvers, and sid beside the clip it is in 2026-09-30 12:21:12 -04:00
bench_test.cljs An options map for the resolvers, and sid beside the clip it is in 2026-09-30 12:21:12 -04:00
clock_test.cljs Port steps 2-3: the data model and the player 2026-09-27 17:28:05 -04:00
export_test.cljs grand unification of time 2026-10-01 01:47:08 -04:00
synth_test.cljs Port steps 0-1: scaffold, the oracle, and the pure bottom 2026-09-27 14:43:34 -04:00