How an Algorithm Finds the Key of Any Song (and Why DJs Care)
Krumhansl-Kessler key detection with an honest confidence, Camelot codes for harmonic mixing, and what 3,141 tracks look like in key space.

The train wreck every DJ knows
Play two songs in clashing keys over each other and the room hears it. Harmonic mixing avoids that by matching keys, and Mixed In Key's Camelot wheel makes it mechanical: every key is a number and a letter (A for minor, B for major), and the safe moves are the same code, the same number with the other letter, or one step up or down the wheel.
That only works if you know the key. Spotify closed its key and mode features to new API apps in November 2024, so in my own streaming platform the signal engine computes them. This is part 10 of 14.
Key detection, in a 40 second skit

Chroma, then 24 profiles
The harmony stage computes a CQT chroma on the 60 second excerpt (36 bins per octave), smooths it with a median filter over time, and averages it into one 12-number vector: how much of each pitch class the song uses. It then correlates that vector with the Krumhansl-Kessler major and minor profiles, rotated to all 12 roots, and keeps the best of the 24.
The confidence is not the correlation. It is the margin of the best over the runner-up, relative to the best. C major and A minor use exactly the same notes, so a song between them should report a small margin rather than a confident coin flip.
Key estimation, from harmony_analysis.py in my signal engine
MAJOR = np.array([6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88])
MINOR = np.array([6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17])
def estimate_key(chroma_mean):
scores = []
for root in range(12):
for prof, mode in ((MAJOR, "major"), (MINOR, "minor")):
r = np.corrcoef(chroma_mean, np.roll(prof, root))[0, 1]
scores.append((float(r) if np.isfinite(r) else -1.0, root, mode))
scores.sort(reverse=True)
(r1, root, mode), (r2, _, _) = scores[0], scores[1]
return NOTE_NAMES[root], mode, r1, float(np.clip((r1 - r2) / (abs(r1) + 1e-9), 0.0, 1.0))What 3,141 tracks look like
1,756 tracks read as minor (55.9%). A minor is the most common key (177 tracks), then G minor (172) and D major (170); A# major is the rarest (65). By genre, electronic is the most minor (74.1%), hip-hop 61.1%, pop 44.5%, rock 40.7% and classical 39.0%.
The median confidence is 0.14, with the middle half between 0.07 and 0.23. That is the relative-key problem showing up in numbers, and it is why the confidence is stored next to the key.
Minor keys by genre

Camelot, computed
Mapping every stored key to its Camelot code turns "what mixes with this?" into a lookup. Jack (Radio Edit) by Breach, the track under the video, reads F# minor with a confidence of 0.384, among the highest in the catalogue. That is 11A, so 11A, 11B, 10A and 12A are safe, and 16.5% of my catalogue sits on those four codes. Averaged over every track, a safe set covers 17.1% of the catalogue.
The stage also lays beat-synchronous triad chords (cosine similarity with 24 templates, "N" below 0.55) and a tonal clarity score. For the genre models, the 12 chroma bins are rotated so index 0 is the estimated root: a genre does not change when a song is transposed.
Every key in my catalogue

What is not proven
No track in my catalogue came with a key, so the engine is the first writer of that column and reports no accuracy. Its known-answer test is a synthesised C F G C progression, which must read C major and contain those chords. The next step is a labelled set to compare Krumhansl-Kessler with other profiles or a learned model. Until then, a DJ should treat a low confidence as "check by ear".
Part 10 of 14, next: Why Spotify Turns Your Song Down: LUFS, True Peak and the Loudness War
Part 10 of 14, next: Why Spotify Turns Your Song Down: LUFS, True Peak and the Loudness War
More: LinkedIn · Instagram. Portfolio and case studies: designsbyduhart.org.
If any of this saved you an afternoon, Buy me a coffee.