Phase 0 — prove the ear

Bare mic-capture + spectral scope for the piano-practice engine. Undecorated by rule — this page is instrumentation, not a product surface.

1. Capture

Safari needs a user gesture before it will open the mic or start an AudioContext. Nothing happens until you tap.

WebKit suspends audio the moment this page is hidden or the iPad locks. Capture does not silently restart itself: it goes to paused and waits for you to tap Resume, and any take running at that moment is closed and stamped interrupted. A recording that quietly lost its middle would be worse than no recording.

(Phase 0c A/B. The device may ignore the request — the readout below shows what actually happened.)

idle

2. Device readout — the Phase 0c checklist

visibilitychange / lifecycle log

WebKit suspends Web Audio when the page is hidden or the device locks (bug 237878). There is no workaround — this log is how we see it happen.

(no events yet)

3. Live ear — basic tier

Raw spectral peaks, not a transcriber. A piano note produces many partials, so harmonics show up here as their own "notes" (the 2nd partial of C3 reads as C4). Phase 1's score-informed verification is what turns this into note detection; this page exists to show what the microphone and room actually hand us.

level dBFS flux onset frames 0
#Hznotecentsrel dB
1
2
3

4. Event log

logged 0

Events are partitioned by take and carry two coordinates, because they stop agreeing the moment anything is dropped. delivered sample is an offset into that take's WAV file, end-exclusive: the hop occupies [delivered − 1024, delivered) and index delivered itself belongs to the next hop. That is the coordinate to use when locating an event in the audio. source seq is the worklet's hop counter, which keeps advancing through dropped hops and is therefore not a WAV index. Neither is a clock: AudioContext.currentTime restarts at zero with every context, so it is recorded for reference only.

takedelivered samplesource seqt (ctx s)kindnotecentsHzlevel dBpeaks 2/3

5. Record a take

Two independent paths. The WAV is written from the exact samples the ear sees (16-bit PCM, no codec in between) — that is the fixture format. MediaRecorder is whatever the browser will encode; the readout says which container it picked.

The Phase-0 script categories. Stratifies the fixture set — the eval is only meaningful per texture.
state idle · 0.0s · 0.0 MB

Hard cap 5 minutes per take, to stay clear of Safari's per-tab memory ceiling. Audio and its JSON share one basename, so a take is never separated from its events.

Takes this session

On stopping, the worklet is asked for a final snapshot and we wait for the reply before judging the take — counters carried on the audio itself cannot describe hops dropped after the last one that arrived, which is exactly when the end of a take goes missing. A take is valid only when it contains audio, clears a 0.25 s floor, and its arithmetic closes: source span − dropped = delivered. It is contiguous only if it is valid and nothing was dropped at all. audio verified means the browser confirmed echo cancellation, auto gain and noise suppression are genuinely off — requesting them off is not the same as getting it. Anything short of clean is written into the filename, so a bad take cannot be mistaken for a good one on disk.

taketexturedur (s)samplesvalidcontiguousaudio verifiedinterruptedevents