FFT & Visualizer
Since I shifted my focus onto the group project, i will make a lite version of what I wanted to do (music animation), for this week’s making assignment. I’ll make a visualizer that can visualize any songs you put into the p5js project.
I’m using sulfur as my tester sound track, which I load in the preload().
let audio;
let fft;
function preload() {
audio = loadSound('Sulfur.mp3');
}
Now comes the cool part: the FFT setup. If you’re unfamiliar with FFT, think of it as the audio data whisperer. It breaks the sound wave down into its frequency components. Instead of looking at a raw wave, you get data that describes how much energy is present in different frequency ranges (i.e., bass, mids, and highs).
In this project, I use p5.FFT to grab that information. Here’s how I set it up:
fft = new p5.FFT(0.8, 1024);
audio.play();
- The 0.8 is the smoothing factor. It controls how smooth transitions are between frames. A value of 0 is no smoothing, and 1 is maximum smoothing.
- 1024 is the number of frequency bins. Think of it as dividing the sound wave into 1024 tiny pieces of data (kind of like pixels for sound).
With that setup, I can play the audio file and analyze it frame by frame!
> Code Breakdown: The Visualizer
Here’s what happens every frame in draw():
function draw() {
drawVisualizer();
}
All of the heavy lifting happens in drawVisualizer(). To start, I wipe the canvas clean each frame by resetting the background to black:
background(0);
Then, I analyze the audio data using the FFT object:
let spectrum = fft.analyze();
This spectrum array holds 1024 values, each representing the magnitude of a specific frequency band.
>>> Pixel Style Animation
Here’s where the pixel grid magic happens. I divide the canvas into a neat grid of square cells. Each cell’s color and size are determined by the sound data (the spectrum array). Let’s break down the important variables:
const pixelSize = 52.5;
const spacing = 2;
const numColumns = Math.floor(width / (pixelSize + spacing));
const numRows = Math.floor(height / (pixelSize + spacing));
const totalCells = numColumns * numRows;
I set the size of each pixel (pixelSize) and the spacing between them. I calculate how many rows and columns fit inside the canvas based on these parameters.
The really fun part is deciding how to map the audio data to these cells. There are 1024 values in the spectrum, but there are fewer cells than bins (because I’ve got limited space on the canvas). So, I figure out how many bins go into each cell:
const binsPerCell = Math.max(1, Math.floor(spectrum.length / totalCells));
Here, I ensure that there’s at least one bin per cell, and then I loop over all rows and columns to assign values from the spectrum to each cell.
>>> Coloring Cubes
Now it’s time to draw! The color of each cell is determined by the corresponding frequency data. I use the following logic to map the sound data to RGB values:
const index = Math.floor((row * numColumns + col) * binsPerCell) % spectrum.length;
let barHeight = spectrum[index];
barHeight = Math.max(barHeight, 50);
Here, I map each cell to a bin in the spectrum. The barHeight is simply the magnitude of that frequency bin, with a minimum value of 50 to keep the visual interesting (no black squares).
Next, I map barHeight to some funky colors:
const r = barHeight + 100;
const g = barHeight * 2;
const b = 255;
const alpha = barHeight / 255;
- Red (r) is based on the bar height + 100 for some extra boost.
- Green (g) is twice the bar height.
- Blue (b) is fixed at 255 for a nice contrast.
- Alpha (transparency) is proportional to
barHeight(more intense sound, more opaque the rectangle).
Finally, I draw each rectangle on the canvas:
fill(r, g, b, alpha * 255);
rect(
col * (pixelSize + spacing),
row * (pixelSize + spacing),
pixelSize,
pixelSize
);
>>> Threshold
So, in the spirit of optimization (or so I thought), I decided to introduce a threshold for the spectrum values. The idea was simple: set a minimum value to avoid any weak frequencies turning into boring, low-intensity visuals. I thought, “Let’s make everything that’s too quiet a bit more visible.”
const threshold = 50;
for (let i = 0; i < spectrum.length; i++) {
spectrum[i] = max(spectrum[i], threshold);
}
This code ensures that every frequency component is at least 50, which I assumed would make the visuals more consistent. But It turned out not having obvious effects, so I gave up.

