DEV Community

Kaleem Ullah
Kaleem Ullah

Posted on

Building a 10-Band Equalizer with the Web Audio API

Introduction

A few weeks ago, I set out to build a fully functional 10-band audio equalizer that runs entirely in the browser—no server uploads, no signups, just pure client-side audio processing.

The result is SonicLab's Equalizer, a free online tool that lets users boost or cut specific frequency ranges in real-time.

In this post, I'll walk through how I built it using the Web Audio API's BiquadFilterNode, and share some lessons learned along the way.

Why Build a Browser-Based Equalizer?

Most online audio tools require uploading files to a server, which raises privacy concerns and adds latency. By processing audio entirely in the browser using the Web Audio API, we can:

Keep user data private — audio never leaves the device

Provide real-time preview — users hear changes instantly

Eliminate server costs — all processing is client-side

Support offline usage — no internet connection required after the page loads

The Core Technology: BiquadFilterNode

The Web Audio API provides a BiquadFilterNode that implements various filter types. For an equalizer, we use the peaking filter type, which boosts or cuts a specific frequency range.

// Create a filter node for a specific frequency band
const createFilter = (audioContext, frequency, gain, Q) => {
  const filter = audioContext.createBiquadFilter();
  filter.type = 'peaking';
  filter.frequency.value = frequency;  // e.g., 32Hz, 64Hz, 125Hz...
  filter.gain.value = gain;            // -12dB to +12dB
  filter.Q.value = Q || 1.0;           // Bandwidth control
  return filter;
};
Enter fullscreen mode Exit fullscreen mode

The 10 Frequency Bands

A 10-band equalizer typically covers the audible spectrum with these center frequencies:

Band Frequency Typical Use
1 32 Hz Sub-bass
2 64 Hz Bass
3 125 Hz Low-mid
4 250 Hz Mid-low
5 500 Hz Mid
6 1 kHz Upper-mid
7 2 kHz Presence
8 4 kHz High-mid
9 8 kHz Brilliance
10 16 kHz Air
Wiring It Together
The signal flow is straightforward:

AudioBufferSourceNode

BiquadFilterNode (32Hz)

BiquadFilterNode (64Hz)

... (all 10 filters in series)

AudioDestinationNode (speakers/headphones)

Here's the simplified implementation:

class EqualizerEngine {
  constructor(audioContext) {
    this.ctx = audioContext;
    this.filters = [];
    this.source = null;
  }

  // Create all 10 filters and connect them in series
  setupFilters(frequencies, gains) {
    // Create the filter chain
    let previousNode = null;

    frequencies.forEach((freq, index) => {
      const filter = this.ctx.createBiquadFilter();
      filter.type = 'peaking';
      filter.frequency.value = freq;
      filter.gain.value = gains[index] || 0;
      filter.Q.value = 1.0;

      this.filters.push(filter);

      if (previousNode) {
        previousNode.connect(filter);
      }
      previousNode = filter;
    });

    return this.filters[0]; // Return the first filter (input)
  }

  // Apply EQ to an audio buffer
  async applyEQ(audioBuffer, gains) {
    const frequencies = [32, 64, 125, 250, 500, 1000, 2000, 4000, 8000, 16000];
    const firstFilter = this.setupFilters(frequencies, gains);

    // Connect the last filter to the destination
    const lastFilter = this.filters[this.filters.length - 1];
    lastFilter.connect(this.ctx.destination);

    // Create and start the source
    this.source = this.ctx.createBufferSource();
    this.source.buffer = audioBuffer;
    this.source.connect(firstFilter);
    this.source.start(0);

    return this.source;
  }

  // Clean up
  dispose() {
    this.filters.forEach(filter => filter.disconnect());
    this.filters = [];
    if (this.source) {
      this.source.stop();
      this.source.disconnect();
      this.source = null;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Real-Time Preview vs. Offline Processing

One challenge was handling both real-time preview (for immediate feedback) and offline rendering (for export).

For preview, I use a standard AudioContext:

javascript

const ctx = new (window.AudioContext || window.webkitAudioContext)();
Enter fullscreen mode Exit fullscreen mode

For export, I use OfflineAudioContext, which renders the audio faster than real-time:

const offlineCtx = new OfflineAudioContext(
  2,                              // Stereo
  audioBuffer.length,             // Same duration
  audioBuffer.sampleRate          // Same sample rate
);
Enter fullscreen mode Exit fullscreen mode

The UI Challenge: Responsive Sliders
With 10 frequency bands, the UI needs to be both compact and usable. I used a horizontal layout with sliders, where each slider's position visually represents the gain value.

<div className="equalizer-bands">
  {frequencies.map((freq, index) => (
    <div key={freq} className="band">
      <input
        type="range"
        min="-12"
        max="12"
        value={gains[index]}
        step="0.5"
        onChange={(e) => handleGainChange(index, parseFloat(e.target.value))}
        className="eq-slider"
        style={{
          transform: `rotate(-90deg)`,
          height: '150px'
        }}
      />
      <span className="frequency-label">{freq}Hz</span>
    </div>
  ))}
</div>
Enter fullscreen mode Exit fullscreen mode

Performance Considerations

A 10-band equalizer processes audio in real-time, so performance is critical. Here are a few optimizations I applied:

Use AudioWorklet for heavy processing — but for a 10-band EQ, BiquadFilterNode is efficient enough.

Limit the number of concurrent AudioContexts — only one context at a time.

Disconnect nodes when not in use — prevents memory leaks.

Use OfflineAudioContext for exports — faster than real-time.

Lessons Learned

The Q factor matters — A Q value of 1.0 provides a smooth, musical EQ curve. Lower values create wider bands, higher values create narrower, more surgical cuts/boosts.

User expectations — Most users expect visual feedback. Adding a waveform display or spectrum analyzer significantly improves the experience.

Headphone warning — Like with 8D audio, equalizer effects are more noticeable on headphones. A gentle reminder helps set expectations.

Try It Yourself

You can test the live equalizer here: SonicLab Equalizer

Have you built an audio tool with the Web Audio API? I'd love to hear about your experience in the comments!

Top comments (0)