I built PitchTester, a free online pitch detector. You open the page, allow microphone access, and sing or play an instrument — it shows the note name, frequency in Hz, and how sharp or flat you are in cents, all in real time.
The entire thing is one vanilla JavaScript file. No backend, no WebAssembly, no dependencies. Here's how the audio math works, and how I kept it accurate enough to actually be useful.
Getting from microphone to a note name is four steps: Capture — getUserMedia → AudioContext → AnalyserNode Detect pitch — YIN autocorrelation on the time-domain buffer Map to a note — twelve-tone frequency → note name + cents Constrain by voice range — filter octave mis-reads
An AnalyserNode gives us the raw time-domain samples we need for autocorrelation. No FFT required for pitch detection — that's a common misconception. The YIN algorithm works directly on the time-domain buffer.
YIN (the "You In" pitch detection algorithm, de Cheveigné & Kawahara 2002) is the workhorse here. The core idea: find the smallest period at which the signal correlates with itself. That period is the fundamental frequency's wavelength.
First, compute the difference function — for each candidate period τ, sum the squared difference between the signal and itself shifted by τ:
At the true period, the signal aligns with itself, so the difference is at a minimum. But raw difference values aren't comparable across pitches, so YIN normalizes with the cumulative mean normalized difference (CMND):
Then find the first τ where the CMND drops below a threshold (0.15 in my implementation). That's the estimated period:
Finally, parabolic interpolation around the minimum gives sub-sample accuracy — this is what takes the frequency estimate from "close" to "in tune":
Twelve-tone mapping is straightforward math. Semitones from A4, rounded to the nearest note, and cents as the residual:
Raw YIN is accurate for monophonic audio, but it has a classic failure mode: octave errors. Sing a low A and it might report A5 — the algorithm latched onto a harmonic instead of the fundamental.
The fix is domain-specific and cheap: let the user pick their voice range, and clamp detection to that octave band:
The YIN search range (tauMin/tauMax) is derived from these bounds, so the detector simply can't lock onto a harmonic outside the singer's range. This one feature eliminated the most common "wrong note" complaint.
While I was at it, the same engine powers a song key finder. It builds a pitch-class histogram from detected notes, then correlates it against the Krumhansl-Schmuckler major and minor key profiles:
For each of the 12 roots × 2 modes, rotate the profile and compute Pearson correlation against the observed histogram. The best match wins:
The confidence (gap between best and second-best correlation) tells the user how sure the detector is — a common real-world pitfall is that a song with mostly C and G notes is ambiguous between C major and G major.
Key takeaways YIN beats FFT for monophonic pitch detection. It's simpler to implement correctly, more accurate at the edges, and needs no windowing tricks. Domain constraints beat generic algorithms. The voice-range filter is the difference between a demo and a usable tool. Sub-sample interpolation is what makes it "in tune". Without parabolic interpolation, the cents reading jumps ±10 cents randomly. Everything runs in the browser, privately. No audio ever leaves the device — which is both a privacy win and an infrastructure win (zero servers, zero cost).
The live tool is at pitchtester.com — try singing into it and see how close you are to perfect pitch. The full engine is a single dependency-free JS file.
