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)
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.
| Range | Frequency | Character |
|---|---|---|
| Sub-bass | 20-60 Hz | Deep rumble, felt more than heard |
| Bass | 60-250 Hz | Fundamental frequencies, rhythm |
| Low midrange | 250-500 Hz | Warmth, body |
| Midrange | 500-2kHz | Presence, clarity |
| High midrange | 2-4 kHz | Definition, articulation |
| Highs | 4-6 kHz | Brightness, detail |
| Air | 6-20 kHz | Spatial 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.
Review the key concepts from this chapter and make sure you understand each one before moving to the next chapter.