refactor(audio): schedule every tone through one envelope helper - #136
Merged
wolasss merged 1 commit intoAug 21, 2026
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The metronome click and the two-tone session-end chime were each hand-rolling
the same oscillator-plus-gain envelope: create both nodes, pin the gain at the
0.0001 floor, ramp exponentially up to a peak, ramp back down, wire them to the
destination, start, stop, track. Three copies of eight lines, in
playClickAtand twice inside
playSessionEndChime'splayTone.What changed
AudioEngine.scheduleTone({ type, frequency, startTime, attack, peak, decayEnd, stopAt, nodes })insrc/lib/audio/engine.tsand expressed allthree call sites through it.
startTime. That is deliberate: each caller keeps evaluating its ownarithmetic (
toneStart + duration * 0.88and friends) exactly as before, sothe floats that reach the hardware are bit-for-bit the ones it computed. A
duration-based helper would re-associate those expressions and quietly move
the ramps.
nodesdefaults to the transport'sscheduledNodes; the chime passesthis.chimeNodes, sostopScheduledSounds(true)still spares it.No behaviour change and no public API change. Every scheduled value is
identical: the click's 1320/880 Hz, 0.12/0.08 peaks, 0.01 attack, 0.12 decay and
0.14 stop; the chime's 783.99/523.25 Hz bodies with shimmers an octave up at a
0.42 peak factor and the 0.88/0.9 duration factors.
Verifying that
The existing tests asserted the click's frequency and start/stop but never the
chime's ramps or the click's decay, so before touching
engine.tsI added twocharacterization tests to
src/lib/audio/engine.test.tsthat record every fakeoscillator and gain call for one click and one chime and compare them with exact
equality —
toEqualovermock.calls, nocloseTo, and every expected numberwritten as the same expression the engine evaluates so the bits match by
construction. The chime test also asserts all four oscillators survive
stopScheduledSounds(true).Those tests passed before the refactor and pass unchanged after it; nothing in
the test file was relaxed to accommodate the new code.
npx vitest run src/lib/audio/engine.test.ts— 44 passed, both before and afternpm run check(lint →typecheck:e2e→vitest run→tsc -b && vite build) —all four green, 939 tests across 67 files
How to test
This is an internal refactor with no observable change — the click and the chime
sound exactly as they did. The proof is the test suite:
npm run check— all four stages should pass (939 tests).npx vitest run src/lib/audio/engine.test.tsand look for"a click schedules one exact envelope" and
"the end chime schedules four exact envelopes". Nudging any scheduled
number in
scheduleToneor a call site — say the shimmer's0.42to0.43—should fail them immediately.
npm run dev, start a session with the metronome on tohear the click, and let a short session run out to hear the two-tone chime.
Live preview (this branch): https://agents.marten-gecko.ts.net:10744/