Chapter 17
FFT & Spectrums
Learning Objectives
- Understand FFT in depth
- Work with frequency data
- Create spectrograms
- See how MixMind analyzes frequencies
FFT Windowing
FFT analyzes audio in small chunks called windows. To reduce artifacts, we apply window functions that fade the edges:
// Hann window (most common)
function hannWindow(size) {
const window = new Float32Array(size);
for (let i = 0; i < size; i++) {
window[i] = 0.5 * (1 - Math.cos(2 * Math.PI * i / (size - 1)));
}
return window;
}
// Apply window to audio data
function applyWindow(data, window) {
const result = new Float32Array(data.length);
for (let i = 0; i < data.length; i++) {
result[i] = data[i] * window[i];
}
return result;
}
// Common window types:
// - Hann: Good general purpose
// - Hamming: Similar to Hann
// - Blackman: Better sidelobe suppression
// - Kaiser: Adjustable
Magnitude and Phase
FFT output contains both magnitude (amplitude) and phase (timing) information:
// Complex FFT output
function fft(input) {
// Returns array of complex numbers: {real, imag}
// Magnitude = sqrt(real² + imag²)
// Phase = atan2(imag, real)
}
// Convert to magnitude spectrum
function getMagnitudeSpectrum(complexData) {
const magnitude = new Float32Array(complexData.length);
for (let i = 0; i < complexData.length; i++) {
const { real, imag } = complexData[i];
magnitude[i] = Math.sqrt(real * real + imag * imag);
}
return magnitude;
}
// Convert to decibels (dB)
function toDecibels(magnitude) {
const dB = new Float32Array(magnitude.length);
for (let i = 0; i < magnitude.length; i++) {
// Avoid log(0)
dB[i] = 20 * Math.log10(Math.max(magnitude[i], 1e-10));
}
return dB;
}
Spectrograms
A spectrogram shows how the frequency spectrum changes over time:
// Create spectrogram data
async function createSpectrogram(audioBuffer, hopSize = 512) {
const fftSize = 2048;
const numFrames = Math.floor(audioBuffer.length / hopSize);
const numBins = fftSize / 2;
const spectrogram = [];
for (let frame = 0; frame < numFrames; frame++) {
const start = frame * hopSize;
const segment = audioBuffer.slice(start, start + fftSize);
// Apply window
const window = hannWindow(fftSize);
const windowed = applyWindow(segment, window);
// FFT (simplified)
const complex = fft(windowed);
const magnitude = getMagnitudeSpectrum(complex);
spectrogram.push(magnitude);
}
return spectrogram; // 2D array: [time][frequency]
}
// Visualize spectrogram
function drawSpectrogram(spectrogram, canvas) {
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
for (let x = 0; x < width; x++) {
const frameIndex = Math.floor(x / width * spectrogram.length);
const frame = spectrogram[frameIndex];
for (let y = 0; y < height; y++) {
const binIndex = Math.floor((1 - y / height) * frame.length);
const magnitude = frame[binIndex];
// Convert to color (dB scale)
const dB = 20 * Math.log10(Math.max(magnitude, 1e-10));
const normalized = (dB + 100) / 100; // Map -100dB to 0dB -> 0 to 1
const hue = 240 - normalized * 240; // Blue to red
ctx.fillStyle = `hsl(${hue}, 100%, ${normalized * 50}%)`;
ctx.fillRect(x, y, 1, 1);
}
}
}
🎵 MixMind Connection
How MixMind Uses FFT
// MixMind's spectral analysis
class SpectralAnalyzer {
async analyze(audioBuffer) {
const spectrogram = await createSpectrogram(audioBuffer);
// Extract features
const features = {
spectralCentroid: this.calculateCentroid(spectrogram),
spectralRolloff: this.calculateRolloff(spectrogram),
spectralFlatness: this.calculateFlatness(spectrogram),
zcr: this.calculateZeroCrossingRate(audioBuffer)
};
return features;
}
// Spectral centroid - "brightness" of sound
calculateCentroid(spectrogram) {
let sum = 0;
let totalWeight = 0;
for (const frame of spectrogram) {
for (let i = 0; i < frame.length; i++) {
sum += i * frame[i];
totalWeight += frame[i];
}
}
return sum / totalWeight;
}
}
// Used for:
// - Timbre analysis
// - Instrument detection
// - Mood/energy classification
// - Genre detection
Exercise 17.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.