I needed to read a track's BPM and musical key in seconds, offline, on a
phone. No ML black box, no server. Here's the signal-processing pipeline I ended
up with — hand-rolled, since the usual packages weren't an option.
Tempo
Tempo is periodicity in the track's energy. The steps:
- Spectral flux → onsets. Sum the positive frame-to-frame magnitude increases of the STFT. Peaks are onsets (kick, snare, synth attacks). That's the onset envelope.
flux[n] = Σ_k max(0, |X[n,k]| - |X[n-1,k]|)
- Autocorrelation. The lag where the onset envelope best matches itself is the beat period.
- Comb over multiples. Weight candidate periods by their multiples (½, 2×) so tempo isn't confused with its harmonics.
- Octave folding + prior. Fold candidates into 60–180 BPM and softly prefer typical dance tempos — kills the "174 vs 87" error.
- Median smoothing for stability.
For the beat grid, I use dynamic programming (Ellis-style): reward beats that
land on onsets, penalize deviation from a steady interval. The DP gives a grid
that doesn't drift.
Key
- Chromagram. Fold the spectrum (~55–2000 Hz) into 12 pitch classes, accumulated with a leaky integrator (~8 s memory) → an averaged chroma vector.
- Krumhansl–Schmuckler correlation. Correlate against 24 reference key profiles (12 major + 12 minor); best match wins.
key = argmax_i corr(rotate(chroma, i), profile)
- Map to Camelot for harmonic mixing. This lineage goes back to libKeyFinder.
Honest limits
- Atonal/dense harmony blurs the chroma vector → less certain key.
- Mid-track modulation → it reports the dominant key.
- Weak percussion/rubato → fewer onsets, shakier BPM.
Sources
- Krumhansl, Cognitive Foundations of Musical Pitch (1990)
- Sha'ath, Estimation of Key in Digital Music Recordings (2011)
- Ellis, Beat Tracking by Dynamic Programming (2007)
The pipeline ships in BeatScope, a free offline analyzer for macOS/iPhone:
https://beatscope.pro — happy to answer DSP questions in the comments.
Top comments (0)