Chapter 20
BPM Detection
Learning Objectives
- Understand BPM calculation
- Learn tempo estimation methods
- Handle tempo variations
- See how MixMind detects BPM
What is BPM?
BPM stands for "Beats Per Minute"βit's the tempo of a song.
- 60 BPM: One beat per second (slow)
- 120 BPM: Two beats per second (common in pop/dance)
- 140 BPM: Fast (dubstep, drum & bass)
- 170+ BPM: Very fast (hardcore, speed metal)
π‘ Common Tempos:
- House: 120-130 BPM
- Techno: 125-140 BPM
- Hip-hop: 85-115 BPM
- DnB: 170-180 BPM
- Dubstep: 140 BPM (half-time feel)
Autocorrelation Method
Autocorrelation measures how similar a signal is to a delayed version of itself. If the delay matches the beat interval, correlation is high.
// Calculate autocorrelation
function autocorrelation(data, lag) {
let sum = 0;
for (let i = 0; i < data.length - lag; i++) {
sum += data[i] * data[i + lag];
}
return sum / data.length;
}
// Find tempo using autocorrelation
function detectTempoAutocorrelation(onsets, sampleRate) {
// Test lag range: 60-200 BPM
const minLag = Math.floor(sampleRate * 60 / 200);
const maxLag = Math.floor(sampleRate * 60 / 60);
let bestLag = 0;
let bestCorrelation = -Infinity;
for (let lag = minLag; lag <= maxLag; lag++) {
const correlation = autocorrelation(onsets, lag);
if (correlation > bestCorrelation) {
bestCorrelation = correlation;
bestLag = lag;
}
}
// Convert lag to BPM
const bpm = (sampleRate * 60) / bestLag;
return bpm;
}
Spectral Flux Method
Spectral flux measures how quickly the frequency spectrum changes. Beats often cause sudden spectral changes.
// Calculate spectral flux
function spectralFlux(spectrogram) {
const flux = [];
for (let i = 1; i < spectrogram.length; i++) {
let sum = 0;
for (let bin = 0; bin < spectrogram[i].length; bin++) {
const diff = spectrogram[i][bin] - spectrogram[i - 1][bin];
// Only count increases (onsets)
if (diff > 0) {
sum += diff * diff;
}
}
flux.push(Math.sqrt(sum));
}
return flux;
}
// Find periodicity in spectral flux
function findTempo(flux, sampleRate, hopSize) {
const autocorr = autocorrelation(flux, ...);
// Similar to beat detection
}
Handling Tempo Variations
Songs don't always have a constant tempo. Common variations:
- Tempo changes: Song speeds up or slows down
- Tempo drift: Gradual tempo change
- Human performance: Slight timing variations
// Detect tempo changes
function detectTempoChanges(onsets, windowSize) {
const tempos = [];
// Analyze tempo in sliding windows
for (let i = 0; i < onsets.length - windowSize; i++) {
const window = onsets.slice(i, i + windowSize);
const tempo = estimateTempo(window);
tempos.push({
time: onsets[i].time,
bpm: tempo
});
}
// Find significant changes
const changes = [];
for (let i = 1; i < tempos.length; i++) {
const diff = Math.abs(tempos[i].bpm - tempos[i - 1].bpm);
if (diff > 5) { // More than 5 BPM change
changes.push(tempos[i]);
}
}
return changes;
}
π΅ MixMind Connection
How MixMind Detects BPM
// MixMind's BPM detection
class BPMDetector {
async analyze(audioBuffer) {
// Multiple detection methods for robustness
const results = await Promise.all([
this.autocorrelationMethod(audioBuffer),
this.spectralFluxMethod(audioBuffer),
this.beatHistogramMethod(audioBuffer)
]);
// Consensus voting
const bpm = this.selectConsensus(results);
// Refine with local analysis
const refined = this.refineTempo(audioBuffer, bpm);
return {
bpm: refined.bpm,
confidence: refined.confidence,
tempoChanges: refined.changes
};
}
// Beat histogram - count intervals between onsets
async beatHistogramMethod(audioBuffer) {
const onsets = await this.detectOnsets(audioBuffer);
// Create histogram of inter-onset intervals
const histogram = new Array(200).fill(0); // 60-260 BPM
for (let i = 1; i < onsets.length; i++) {
const interval = onsets[i].time - onsets[i - 1].time;
const bpm = 60 / interval;
if (bpm >= 60 && bpm <= 260) {
const bin = Math.floor(bpm) - 60;
histogram[bin]++;
}
}
// Find peak
let maxBin = 0;
let maxCount = 0;
for (let i = 0; i < histogram.length; i++) {
if (histogram[i] > maxCount) {
maxCount = histogram[i];
maxBin = i;
}
}
return maxBin + 60;
}
}
// Critical for:
// - Syncing tracks together
// - Calculating transition timing
// - Beat grid alignment
// - Quantization
Exercise 20.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.