Live at srp1357.github.io/song-key-detection
Detect the musical key of a song, track key changes (modulations), estimate its tempo, and watch a live piano roll of every transcribed note — entirely in your browser. No uploads, no servers, no external API calls. The transcription can be downloaded as a standard MIDI file.
Everything runs client-side in a Web Worker:
- Decode — the browser's own audio decoder handles mp3/wav/ogg/m4a/flac, resampled to 22050 Hz mono. Files must be between 10 seconds and 15 minutes.
- Chromagram — Hann-windowed 32768-point FFT frames over C1–B6 (the
six-octave band validated by KeyFinder's
evaluation), folded into 12 pitch classes with circular-mean tuning
correction and CLP-style log compression (Müller & Ewert, ISMIR 2011) —
src/lib/chroma.ts. - Key detection — each 8-second window is scored against all 24 keys by
Pearson correlation with libKeyFinder's
production tone profiles, then Viterbi decoding with a uniform switch
penalty (the local-key HMM structure of Schreiber, Weiß & Müller, ICASSP
2020) turns window scores into stable key segments, so brief chromatic
passages don't register as modulations —
src/lib/keyDetection.ts. Each segment reports its measured evidence (profile fit, margin, runner-up key) rather than a synthetic confidence percentage. - Tempo & beats — onset strength from mel-band spectral flux, global
tempo from a prior-weighted autocorrelation, and beat positions from
Ellis's dynamic-programming beat tracker (J. New Music Research, 2007 —
the method librosa ships as its default). The piano roll's gridlines are
drawn on these beats, with bars assuming 4/4 —
src/lib/tempo.ts. - Note transcription — Spotify's basic-pitch neural network (TensorFlow.js) transcribes every note for the piano roll, using Spotify's published default thresholds.
The transcription is exportable as a standard MIDI file (notes, the detected
tempo, and the detected key as the key signature) — src/lib/midiExport.ts.
The model weights (~0.9 MB) are vendored into public/model/ and served from
this site's own origin — the app has zero runtime dependencies on any third
party. Audio never leaves the tab.
Why basic-pitch? Newer open transcription models exist (e.g. MuScriptor, 2026: multi-instrument, higher accuracy) but need 393 MB+ of weights, have no official browser runtime, and carry a non-commercial license. Basic-pitch is Apache-2.0, ships an official TensorFlow.js port, and its entire model fits in under a megabyte — the right trade-off for a fully client-side static site.
npm install
npm run dev # dev server at http://localhost:5173
npm run build # production build into dist/Verification and diagnostics (no browser required):
npx tsx scripts/verify-keys.ts # all 24 keys + modulation, exact-match test
npx tsx scripts/verify-tempo.ts # tempo/beat tracking on synthesized tracks
npx tsx scripts/verify-midi.ts # MIDI export round-trip (tempo, key sig, notes)
npx tsx scripts/sanity-check.ts # key detection on synthesized chords
npx tsx scripts/diagnose-file.ts song.wav # full evidence dump for a real file (wav/mp3)
npx tsx scripts/peak-check.ts song.wav # raw spectral peaks, bypasses the pipeline
npx tsx scripts/sanity-transcribe.ts # transcription check (needs `npm run dev` running)Pushing to main triggers the GitHub Actions workflow in
.github/workflows/deploy.yml, which builds the site and deploys it to GitHub
Pages. In the repo settings, set Pages → Source to GitHub Actions.
- Vite + React + TypeScript
- Tailwind CSS 4 (slate palette, JetBrains Mono)
- @spotify/basic-pitch + TensorFlow.js (WebGL backend, CPU fallback)
- @tonejs/midi (MIDI file export)
- Custom FFT / chromagram / tone-profile correlation + Viterbi / tempo & beat tracking implementation (no DSP libraries)