Chapter 16

Frequency Analysis

Learning Objectives

  • Understand frequency and pitch
  • Learn about the frequency spectrum
  • Use the AnalyserNode
  • See how MixMind analyzes audio

What is Frequency?

Frequency is how fast a sound wave vibrates, measured in Hertz (Hz). It determines the pitch we hear.

  • 20 Hz: Lowest human hearing (deep bass)
  • 250 Hz: Low midrange (bass instruments)
  • 1,000 Hz (1 kHz): Midrange (vocals, guitars)
  • 4,000 Hz (4 kHz): High midrange (clarity)
  • 20,000 Hz (20 kHz): Highest human hearing (air, sparkle)
💡 Musical Notes:

A4 (the A above middle C) = 440 Hz. Each octave doubles the frequency:

  • A3 = 220 Hz
  • A4 = 440 Hz
  • A5 = 880 Hz

The Frequency Spectrum

Most sounds contain multiple frequencies mixed together. The frequency spectrum shows how much energy is at each frequency.

RangeFrequencyCharacter
Sub-bass20-60 HzDeep rumble, felt more than heard
Bass60-250 HzFundamental frequencies, rhythm
Low midrange250-500 HzWarmth, body
Midrange500-2kHzPresence, clarity
High midrange2-4 kHzDefinition, articulation
Highs4-6 kHzBrightness, detail
Air6-20 kHzSpatial quality, shimmer

AnalyserNode

The Web Audio API's AnalyserNode performs real-time frequency analysis:

// Create analyser
const analyser = audioContext.createAnalyser();

// Configure
analyser.fftSize = 2048; // Must be power of 2
analyser.smoothingTimeConstant = 0.8;

// Connect to audio
source.connect(analyser);
analyser.connect(audioContext.destination);

// Get frequency data
const frequencyData = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(frequencyData);

// frequencyData[i] contains amplitude (0-255) at frequency i
// Each bin represents a frequency range
const binWidth = audioContext.sampleRate / analyser.fftSize;
console.log(`Each bin = ${binWidth} Hz`);

// Get waveform data (time domain)
const timeData = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteTimeDomainData(timeData);

FFT (Fast Fourier Transform)

FFT is the algorithm that converts time-domain audio into frequency-domain data. It's what makes spectrum analysis possible.

// FFT size determines frequency resolution
// Larger FFT = better frequency resolution, worse time resolution

// 2048 is common for music analysis
analyser.fftSize = 2048;

// frequencyBinCount is always fftSize / 2
console.log(analyser.frequencyBinCount); // 1024

// Each bin covers: sampleRate / fftSize Hz
// At 44100 Hz with 2048 FFT: 43.07 Hz per bin

// Visualizing the spectrum
function drawSpectrum(analyser, canvas) {
    const ctx = canvas.getContext('2d');
    const data = new Uint8Array(analyser.frequencyBinCount);
    analyser.getByteFrequencyData(data);
    
    const barWidth = canvas.width / data.length;
    
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    for (let i = 0; i < data.length; i++) {
        const barHeight = (data[i] / 255) * canvas.height;
        
        ctx.fillStyle = `hsl(${i / data.length * 360}, 100%, 50%)`;
        ctx.fillRect(
            i * barWidth,
            canvas.height - barHeight,
            barWidth,
            barHeight
        );
    }
    
    requestAnimationFrame(() => drawSpectrum(analyser, canvas));
}

🎵 MixMind Connection

How MixMind Uses Frequency Analysis

// MixMind's analysis engine
class AudioAnalyzer {
    constructor() {
        this.context = new AudioContext();
        this.analyser = this.context.createAnalyser();
        this.analyser.fftSize = 8192; // High resolution for analysis
    }
    
    // Calculate energy in frequency bands
    async analyzeEnergy(audioBuffer) {
        const source = this.context.createBufferSource();
        source.buffer = audioBuffer;
        source.connect(this.analyser);
        
        const data = new Uint8Array(this.analyser.frequencyBinCount);
        this.analyser.getByteFrequencyData(data);
        
        // Energy in different bands
        const bass = this.getAverage(data, 0, 10); // 0-430 Hz
        const mid = this.getAverage(data, 10, 100); // 430-4300 Hz
        const high = this.getAverage(data, 100, 500); // 4300+ Hz
        
        return { bass, mid, high };
    }
    
    // Detect dominant frequency
    findDominantFrequency(data) {
        let maxIndex = 0;
        let maxValue = 0;
        
        for (let i = 0; i < data.length; i++) {
            if (data[i] > maxValue) {
                maxValue = data[i];
                maxIndex = i;
            }
        }
        
        const binWidth = this.context.sampleRate / this.analyser.fftSize;
        return maxIndex * binWidth;
    }
}

// Used for key detection, energy mapping, etc.

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