Chapter 19

Beat Detection

Learning Objectives

  • Understand how beat detection works
  • Learn about onset detection
  • Implement beat tracking algorithms
  • See how MixMind finds beats

What is Beat Detection?

Beat detection is the process of identifying the rhythmic pulse in music. The beat is what you tap your foot to—the regular, repeating pattern that makes you want to dance.

Beat detection algorithms analyze audio to find:

  • Beat positions: When each beat occurs (in milliseconds)
  • Tempo (BPM): How many beats per minute
  • Beat strength: How prominent each beat is
  • Time signature: How beats are grouped (4/4, 3/4, etc.)

Onset Detection

Onsets are the moments when a new sound begins—like a drum hit or a plucked string. Beat detection starts by finding onsets.

// Detect onsets by finding energy increases
function detectOnsets(audioData, sampleRate) {
    const windowSize = Math.floor(sampleRate * 0.02); // 20ms windows
    const hopSize = Math.floor(sampleRate * 0.01); // 10ms hop
    
    const energy = [];
    
    // Calculate energy in each window
    for (let i = 0; i < audioData.length - windowSize; i += hopSize) {
        let sum = 0;
        for (let j = 0; j < windowSize; j++) {
            sum += audioData[i + j] * audioData[i + j];
        }
        energy.push(sum / windowSize);
    }
    
    // Find onsets (sudden energy increases)
    const onsets = [];
    const threshold = 1.5; // Energy must increase by 50%
    
    for (let i = 1; i < energy.length; i++) {
        const ratio = energy[i] / energy[i - 1];
        if (ratio > threshold) {
            onsets.push({
                time: i * hopSize / sampleRate,
                strength: ratio
            });
        }
    }
    
    return onsets;
}

Beat Tracking

Once we have onsets, we need to find the regular pattern. This is called beat tracking.

// Simple beat tracking using autocorrelation
function trackBeats(onsets, minBPM = 60, maxBPM = 200) {
    // Convert BPM range to interval range (in seconds)
    const minInterval = 60 / maxBPM;
    const maxInterval = 60 / minBPM;
    
    // Test different tempos
    let bestTempo = 120;
    let bestScore = 0;
    
    for (let bpm = minBPM; bpm <= maxBPM; bpm += 0.5) {
        const interval = 60 / bpm;
        let score = 0;
        
        // Count how many onsets align with this tempo
        for (let i = 1; i < onsets.length; i++) {
            const diff = onsets[i].time - onsets[i - 1].time;
            
            // Check if this interval matches our tempo
            const remainder = diff % interval;
            const tolerance = interval * 0.1; // 10% tolerance
            
            if (remainder < tolerance || (interval - remainder) < tolerance) {
                score += onsets[i].strength;
            }
        }
        
        if (score > bestScore) {
            bestScore = score;
            bestTempo = bpm;
        }
    }
    
    return bestTempo;
}

// Place beats at regular intervals
function placeBeats(tempo, duration, startTime = 0) {
    const interval = 60 / tempo;
    const beats = [];
    
    for (let t = startTime; t < duration; t += interval) {
        beats.push(t);
    }
    
    return beats;
}

Advanced Beat Detection

Real beat detection is more sophisticated:

  • Frequency bands: Analyze bass, mid, and treble separately (drums are usually in bass)
  • Dynamic programming: Find the most likely beat sequence
  • Metrical levels: Detect beats, bars, and phrases
  • Tempo changes: Handle songs that speed up or slow down

🎵 MixMind Connection

How MixMind Detects Beats

// MixMind's beat detection system
class BeatDetector {
    async analyze(audioBuffer) {
        // Focus on bass frequencies (where drums are)
        const bassData = this.extractBass(audioBuffer);
        
        // Detect onsets
        const onsets = this.detectOnsets(bassData, audioBuffer.sampleRate);
        
        // Track tempo using multiple methods
        const tempo1 = this.trackBeatsAutocorrelation(onsets);
        const tempo2 = this.trackBeatsSpectral(onsets);
        
        // Use the more confident result
        const bpm = this.selectBestTempo(tempo1, tempo2);
        
        // Generate beat positions
        const beats = this.placeBeats(bpm, audioBuffer.duration);
        
        return {
            bpm: bpm,
            beats: beats,
            confidence: this.calculateConfidence(onsets, beats)
        };
    }
    
    // Extract bass frequencies (20-250 Hz)
    extractBass(audioBuffer) {
        // Apply low-pass filter
        const filtered = this.applyFilter(audioBuffer, 'lowpass', 250);
        return filtered.getChannelData(0);
    }
}

// Used for:
// - Syncing tracks
// - Beat grid visualization
// - Quantizing effects
// - Transition timing

Exercise 19.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.