Designs by Duhart All writing

·3 min read·dj · musictech · machinelearning · audioprocessing · musictheory

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.

Cover slide: How an Algorithm Finds the Key of Any Song (and Why DJs Care). Bottom left, an "Interviewing at" badge with the logos of Spotify, Apple Music, YouTube Music, SoundCloud.

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

The numbers in the second half are from my engine's records for 3,141 tracks.
Opening frame of the video: How an Algorithm Finds the Key of Any Song (and Why DJs Care)
The numbers in the second half are from my engine's records for 3,141 tracks. Watch the video: https://designsbyduhart.org/blog/how-an-algorithm-finds-the-key-djs/

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

python
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

Slide 6 of 9 of "How an Algorithm Finds the Key of Any Song (and Why DJs Care)", near-black background, white Inter type with mono labels. Who plays in minor. Share of each genre's labelled tracks that read as a minor key. Chart from the video at its final state.
Share of each genre's trusted-label tracks that my engine reads as minor.

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

Tracks per key, minor in violet. Hover for counts. Open full screen
Slide 5 of 9 of "How an Algorithm Finds the Key of Any Song (and Why DJs Care)", near-black background, white Inter type with mono labels. 55.9% minor. Top six keys of 3,141 tracks, with their Camelot codes. Chart from the video at its final state.
Tracks per key, minor in violet. Hover for counts. Interactive version: https://designsbyduhart.org/blog/how-an-algorithm-finds-the-key-djs/

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.