Chapter 21
Key Detection
Learning Objectives
- Understand musical keys
- Learn chroma features
- Implement key detection algorithms
- See how MixMind detects keys
What is a Musical Key?
A key defines which notes sound "right" in a piece of music. It's based on a scale and a root note.
- Major keys: Happy, bright sound
- Minor keys: Sad, dark sound
- 12 possible root notes: C, C#, D, D#, E, F, F#, G, G#, A, A#, B
- 24 total keys: 12 major + 12 minor
π‘ Example:
C Major uses notes: C, D, E, F, G, A, B (all natural notes). A Minor uses the same notes but starts on A.
Chroma Features
Chroma features represent how much energy is in each of the 12 pitch classes (C, C#, D, etc.), regardless of octave.
// Calculate chroma features
function calculateChroma(spectrogram, sampleRate) {
// 12 bins: C, C#, D, D#, E, F, F#, G, G#, A, A#, B
const chroma = new Array(12).fill(0);
// Map frequency bins to pitch classes
const binFrequencies = getBinFrequencies(spectrogram.length, sampleRate);
for (let frame = 0; frame < spectrogram.length; frame++) {
for (let bin = 0; bin < spectrogram[frame].length; bin++) {
const freq = binFrequencies[bin];
const magnitude = spectrogram[frame][bin];
// Convert frequency to pitch class
const pitchClass = frequencyToPitchClass(freq);
// Accumulate energy
chroma[pitchClass] += magnitude;
}
}
// Normalize
const max = Math.max(...chroma);
return chroma.map(c => c / max);
}
// Convert frequency to pitch class (0-11)
function frequencyToPitchClass(freq) {
// MIDI note number
const midi = 69 + 12 * Math.log2(freq / 440);
// Pitch class (0 = C, 1 = C#, etc.)
return Math.round(midi) % 12;
}
Key Detection Algorithm
Compare the chroma features to key profilesβtemplates of what each key sounds like.
// Krumhansl-Kessler key profiles (research-based)
const majorProfile = [6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88];
const minorProfile = [6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17];
// Correlate chroma with each key
function detectKey(chroma) {
const correlations = [];
// Test all 24 keys
for (let root = 0; root < 12; root++) {
// Rotate profile to this root
const majorRotated = rotateArray(majorProfile, root);
const minorRotated = rotateArray(minorProfile, root);
// Correlate with chroma
const majorCorr = correlate(chroma, majorRotated);
const minorCorr = correlate(chroma, minorRotated);
correlations.push({
key: getNoteName(root),
mode: 'major',
correlation: majorCorr
});
correlations.push({
key: getNoteName(root),
mode: 'minor',
correlation: minorCorr
});
}
// Find best match
correlations.sort((a, b) => b.correlation - a.correlation);
return correlations[0];
}
// Pearson correlation
function correlate(x, y) {
const n = x.length;
const meanX = x.reduce((a, b) => a + b) / n;
const meanY = y.reduce((a, b) => a + b) / n;
let numerator = 0;
let denomX = 0;
let denomY = 0;
for (let i = 0; i < n; i++) {
const dx = x[i] - meanX;
const dy = y[i] - meanY;
numerator += dx * dy;
denomX += dx * dx;
denomY += dy * dy;
}
return numerator / Math.sqrt(denomX * denomY);
}
π΅ MixMind Connection
How MixMind Detects Keys
// MixMind's key detection system
class KeyDetector {
async analyze(audioBuffer) {
// Extract chroma features over time
const chromaSequence = await this.extractChromaSequence(audioBuffer);
// Detect key for each segment
const segments = this.segmentTrack(chromaSequence);
const keyPerSegment = segments.map(seg => this.detectKey(seg));
// Find overall key (most common)
const overallKey = this.findDominantKey(keyPerSegment);
// Detect key changes
const keyChanges = this.detectKeyChanges(keyPerSegment);
return {
key: overallKey,
keyChanges: keyChanges,
confidence: this.calculateConfidence(keyPerSegment, overallKey)
};
}
// Used for compatibility checking
areKeysCompatible(key1, key2) {
// Compatible if:
// 1. Same key
// 2. Relative major/minor (e.g., C major β A minor)
// 3. Perfect fifth apart (e.g., C major β G major)
return this.compatibilityMatrix[key1][key2];
}
}
// Critical for:
// - Harmonic mixing
// - Key compatibility warnings
// - Pitch shifting suggestions
Exercise 21.1: Practice
Goal: Apply what you've learned in this chapter
Try the concepts from this chapter in your browser's console or a simple HTML file. Experiment with the code examples and modify them to see what happens.
π Quick Reference:
Review the key concepts from this chapter and make sure you understand each one before moving to the next chapter.