FretHero · SoundCheck
One session took FretHero from a bare game loop to a complete guitar coaching experience: Milestone 1 scoring, Milestone 2 chromagram, a chromatic tuner, a feel Trainer, and a full SoundCheck rebrand.
The game loop used a separate `setInterval` counter alongside `audio.currentTime`. Over a 3-minute song the two clocks drifted by hundreds of milliseconds, causing notes to register as misses even when tapped correctly.
Refactored to use `audio.currentTime` as the single source of truth for all scoring. The interval only triggers UI updates — it no longer owns the timestamp.
On first load, audio autoplay was triggered before the user selected a song. The game would begin with the track already 2-3 seconds in, making the first notes unscorable.
Added a "not started" gate — audio does not begin until the user explicitly hits play from the song select screen.
The frequency analysis was scoring silence as "correct" when no string was played — the chromagram bins were reading ambient noise as a weak but non-zero chord match.
Added an amplitude threshold gate. Below a minimum RMS level the scorer returns no-match regardless of frequency content.
The 16th-note subdivision grid rendered with correct spacing on desktop but collapsed unevenly on small screens. Tap zones shifted off the visual targets.
Switched grid from fixed pixel widths to percentage-based flex layout. Added auto device detection to adjust tap window tolerances for mobile vs desktop.
The Trainer summary added a grade headline that pushed the content just past the viewport height on phones, adding an unwanted scrollbar to what should be a single-screen result.
Wrapped summary in `overflow: hidden` and used `flex-shrink` on the details section so the grade headline takes fixed space and details compress.
The refactor to a single clock source fixed every timing complaint in one commit. Two clocks that drift is a class of bug — using the media element's own clock eliminates it entirely.
Tiered background images (D-tier grimace face → S-tier RockGod outro) gave the scoring system emotional weight without any extra UI. The image IS the feedback — no score bar needed.
Using frequency bin analysis (chromagram) instead of fundamental pitch detection made chord scoring far more robust. Players don't need perfect intonation — they just need to be in the right harmonic neighborhood.
Committing at Milestone 1 (jukebox + reliable scoring) before building Milestone 2 (chromagram) meant we always had a working app to fall back to. No mid-feature breakage reaching the user.
Both the tuner and the Trainer use the Web Audio API via `getUserMedia`. This worked out of the box on iOS Safari and Android Chrome without any native wrapper — no Capacitor, no Expo, no app store.
Never maintain a separate counter alongside `audio.currentTime`. Dual clocks drift. `audio.currentTime` is the ground truth — use it directly in all scoring calculations.
Frequency analysis on silence produces garbage. Always check RMS amplitude before running chromagram or pitch detection. Below threshold = no match, not a wrong match.
Ship a working version before building the next layer. Milestone 1 → commit → Milestone 2. Never build two major features on an unstable base.
Fixed pixel widths break on small screens. Any grid that users interact with (tap zones, subdivision grids) must use percentage-based or flex layout to stay aligned with visual targets.
"It went from a thing that plays audio and registers taps to an actual guitar coaching app. In one session."