A web app that listens through the mic and shows in real time whether you hit the note. Warm-ups, a range test, karaoke with your own tracks and a ukulele singing report. No install, works offline, on phone and laptop. AudioWorklet + Essentia WASM · Python DSP · Vercel + Redis — product, design, front end, audio DSP and back end, solo.
Vocal practice runs into a feedback problem: without a teacher you can't tell whether you hit the note. Existing tuners show a frequency but don't teach — no exercises, no progress, no songs. I wanted a tool that measures honestly and yet doesn't punish a beginner for searching for the note.
"A voice is not a guitar: vibrato, scoops, a weak fundamental, background hum. Naive autocorrelation drops an octave, the needle jitters, and the user stops trusting the instrument."

The live tuner: the note on a dot-matrix display, deviation in cents, a pitch trace with vibrato and the "Mic signal" panel.
Analysis runs in an AudioWorklet on the Essentia WASM core (PitchYin + probabilistic YIN), with a Viterbi pass over a 4-frame trajectory on top: a single octave glitch never reaches the screen, while vibrato and portamento pass freely. A fixed 94 measurements/s regardless of the main thread.
"Wait until I hit the note" mode. The note doesn't advance on a clock; it waits until the voice holds within ±35 cents for 0.6 s. A bar shows accumulated hold; on a slip it drains slower than it filled. Scoring uses only the held portion: the path to the note is searching, not singing, and penalising it is pointless.
const HOLD_TOL = 35; // cents — half a semitone with margin
const HOLD_MS = 600; // how long to hold for the note to count
if(c!=null && Math.abs(c)<=HOLD_TOL) this.hold=Math.min(HOLD_MS, this.hold+dt);
else this.hold=Math.max(0, this.hold-dt*0.4); // drain gentler than fill: a slip doesn't reset to zero

A range test that listens rather than commands. Two steps — lowest and highest note; the app never asks you to repeat anything, it just remembers the edges and discards stray squeaks. From then on warm-ups are transposed into a comfortable key and karaoke melodies are moved by octaves into the singer's range.

Upload an mp3 → Demucs splits it into backing and vocals → librosa.pyin transcribes the melody into notes → a scrolling note track, live voice line, score and combo.
absorb parameter in transcription. 28% of "extra" notes in songs turned out to be fragments of one note torn apart by vibrato at a semitone boundary. Merging removed 43–69% of notes with 98–99% contour match.autoLatency(){
if(this.raw.length<200) return null;
let best=null;
for(let off=-0.10; off<=0.70001; off+=0.02){
const r=this.scoreWithOffset(off);
if(r.n<5) continue;
if(!best || r.acc>best.acc) best={off:Math.round(off*1000)/1000, acc:r.acc};
}
return best;
}

One mic records a take where you play and sing at once. The reference is the instrument's own tuning, not A=440: nylon drifts, and measuring the voice against a standard while the ukulele sits at −18 cents means scolding a perfect hit.
Three errors are kept separate because they are fixed differently:
Pitch is refined to fractions of a cent by parabolic interpolation of the first four harmonics — pyin's 10-cent grid is fatal here: every deviation lines up on the same remainder, and the "offset" can turn out to be entirely a grid step.

XP, levels, day streak, 16 achievements. Cross-device sync by an anonymous key; the server stores only its SHA-256. Merging is per-field, not last-writer-wins: records keep the best, achievements are unioned, range is widened. No karaoke leaderboard — deliberately: accuracy is only comparable on identical songs, and everyone's library is their own.

Instrument style: monospace type, a rigid grid, one signal colour. A dot-matrix note display. No emoji — letter codes (TON, SCL, ARP) and section numbers. A colour language for signals: lime — in tune, orange — off, red — miss. The "Mic signal" panel says in words what's wrong: hum, quiet, unclean.

Vanilla JS, a single index.html with no build step · Web Audio API, AudioWorklet · Essentia.js (WASM) · Python: Demucs, librosa, faster-whisper, mido · Vercel serverless + Redis · ~5,400 lines of code
A vocal trainer in the browser: pitch detection you can trust, warm-ups without a timer, a range test, karaoke with your own tracks and a ukulele singing report. Product, design, front end, audio DSP and back end — solo.