# Implementation notes

The app compares one layer at a time. It opens on a quiet D3 minor add-nine chord with sine oscillators, a 0.60-second attack, 2.40-second release and reverb off. The drone uses two closely detuned oscillators. The phrase uses seven notes plus a rest in an eight-beat loop. These share the root, waveform and output level, so switching gives a useful listening comparison.

All sound comes from native Web Audio oscillators. A slow sine oscillator changes the volume over 17 seconds, and another moves a low-pass filter over 23 seconds. The movement control reaches zero for a still comparison. Reverb uses a generated two-second stereo impulse response. There are no audio files, fonts, libraries or network calls.

The audio context is created only by clicking Start listening. Fade out uses the selected release time. Stop all sound and Escape silence the entire output, including the reverb, with a 35 ms click-prevention fade. Closing the page stops its audio. A compressor limits combined peaks. Default level is -24 dB with a deliberately restricted -42 to -10 dB range.

The attack and release controls apply to subsequent note envelopes. For a held drone or chord, restart or switch layers to hear a new attack. A phrase note releases after 63 percent of its beat. If its attack is longer than this, the note releases before reaching full level, as a played synth would. Tempo changes affect the next scheduled beat. The scheduler looks ahead 120 ms.

The initial complete implementation is preserved in attempts/001/index.html. Do not modify that copy. Root agent owns browser and listening checks. This worker performed source review and JavaScript syntax checks only, without starting a server or using browser tools.

Known limits: generated reverb is an approximation of a small room. Output loudness still depends on the user's device and system volume. The visual waves indicate playback state; they are decorative, not an audio meter. Notes use equal temperament and a fixed minor phrase rather than a scale editor.

Revisions after first complete copy: global stop now also catches contexts that are already fading out, including reverb tails. Fade out lets held oscillator envelopes use the selected release. Added an on-page explanation that attack shapes new notes and layers. The mobile header stacks the stop button beneath the introduction to avoid a narrow-screen collision.

## Second attempt: phrase playback correction

Student report: switching from Soft chord to Short phrase leaves the phrase almost silent.

Cause: the phrase-only branch in makeVoice called OscillatorNode.stop before OscillatorNode.start. Native Web Audio rejects this order with InvalidStateError. The chord still faded out during the switch, but the first phrase note failed before its oscillator could start.

Change: schedule a phrase oscillator's future stop immediately after its start call. The drone and chord envelopes, note pattern, levels, tempo, modulation, reverb and stop controls are unchanged. The root diagnostic script reference remains in the page.

Checks: checks/audio-lifecycle.cjs reproduces the old failure against attempts/002-before/index.html using an AudioContext mock that enforces the start-before-stop rule. The same check passes against the corrected index.html. It exercises chord to phrase, more than one phrase cycle, phrase to drone, drone to chord, a return to phrase, root change, Stop all, restart, and Stop all during an existing fade. A separate JavaScript syntax check passes. These checks validate scheduling and cleanup without generating sound. Root agent owns the native browser and listening retest.

Saved copies: attempts/002-before/index.html remains untouched. The corrected page is also saved in attempts/002-after/index.html. attempts/002-changes.md records this correction separately.
