back to blog

DQ-DSP: A $20 DSP Brain for My DIY 2-Way Active Speakers 🔊

May 2, 2026 · the one where i replace a $200 miniDSP with a $5 ESP32

> tl;dr: built a USB Audio → DSP → dual-I2S box on an esp32.
> drives a 2-way active speaker. $20 BOM. open-source. 😎

full demo on YouTube — UI walkthrough + live PEQ tweaks

DQ = my daughter's name. Yes, I named a DSP after her. Don't @ me. 🍼

The build 📸

A 2-way bookshelf I cut from MDF with a friend's router, drivers from Parts Express, the DSP wired up on a breadboard while I figured out the firmware. The receipts, before the words:

fresh out of the wood workshop — bookshelf cab #1
fresh out of the wood workshop — bookshelf cab #1
inside the cab — woofer + tweeter, no passive XO
inside the cab — woofer + tweeter, no passive XO
the brain — ESP32-S3 + 2× PCM5102A on a breadboard
the brain — ESP32-S3 + 2× PCM5102A on a breadboard
one speaker + its dedicated amp — the active rig
one speaker + its dedicated amp — the active rig
current setup, vibing 😎
current setup, vibing 😎

The itch — and the things I tried first 🤔🥲

Active speakers were the destination from day one. Passive crossovers — those chunks of inductors and caps inside the cab — have tradeoffs you can't fix from outside the box: phase shift, insertion loss, no per-driver EQ, no time alignment. They're also expensiveeee in Vietnam — decent air-core inductors and audiophile-grade film caps are painful to source here, and the ones that do show up usually cost more than the actual drivers. So: active bi-amp, with the crossover done in DSP. The DSP was just the question of how.

First stop: a Wondom 2-in / 4-out board with the Analog Devices ADAU1701 SigmaDSP chip. You program it with SigmaStudio — ADI's Windows-only graphical tool whose UI hasn't had a serious refresh since roughly the iPhone 4 era. It worked. The sound was… fine. But it never quite landed where I was hearing it in my head, and tweaking anything meant booting Windows + clicking through that ancient block-diagram editor.

Wondom ADAU1701 SigmaDSP unit
the Wondom ADAU1701 SigmaDSP unit — first attempt. did the job. didn't spark joy.

Plan B was the miniDSP 2x4 HD — the de-facto gold standard for hobby active crossovers. Plot twist: the regular miniDSP 2x4 runs the exact same ADAU1701 I already had on the Wondom, just in a nicer enclosure with a USB UAC bridge bolted to the front. The HD version moves up to a SHARC DSP — i.e. fancier = more money I didn't have :)) Either way, getting one into Vietnam meant ~USD 200 for the box plus ~USD 60+ in international shipping, or roping a friend abroad into smuggling one back in their carry-on. Neither happened.

Plan C: roll my own. The ESP32-S3 on my desk was a leftover from a different abandoned project. Two PCM5102A breakouts cost less than my morning coffee. The rest of this post is what came out of that.

The parts I bought 🎤💸

For about $15 in chips and a couple of $30 drivers, the whole rig fits on one corner of my desk:

The drivers — cho ai quan tâm:

  • Tweeter · Dayton Audio NHP25Ti-41" titanium dome, 4Ω. ~$28. Smooth top end, plays nicely from ~2 kHz up.
  • Mid/Bass · Dayton Audio TCP115-45" paper-cone polypropylene-coated, 4Ω. ~$38. Tight bottom, easy to crossover ~2 kHz.

Dayton drivers are cheap and well-measured — Parts Express publishes proper FRD/ZMA files so you can simulate the crossover before cutting wood.

And on the DSP side — the actual $20:

  • ESP32-S3-DevKitC-1 N16R8 — 16 MB flash + 8 MB Octal PSRAM. ~$8 on AliExpress. Two USB-C ports, one for audio, one for control.
  • 2× GY-PCM5102A breakout — purple PCB, I²S DAC with 3.5 mm jack. ~$2 each. Surprisingly clean output for the price.
  • Dupont jumper wires — 8 minimum. You probably already have a bag.
  • 2× USB-C data cables — one for UAC audio, one for Web Serial control. Both must be data-capable.
  • 1× 3.5mm pigtail per DAC — line-level out into your power amps.
ESP32-S3-DevKitC-1 N16R8
ESP32-S3-DevKitC-1 · the brain · ~$8
GY-PCM5102A I2S DAC breakout
GY-PCM5102A · the DAC · ~$2 each

Total: ~$15–20 in chips. ESP-IDF v5.2 is the only software dependency.

The web UI 🖥

Open Chrome, click Connect Serial, pick the DSP's USB port — and every parameter running on the chip becomes a live knob in your browser tab. Drag a PEQ band, the change lands on the DAC output in a few milliseconds. Pull the crossover frequency from 2 kHz down to 1.8 kHz, hear the woofer take more of the band. Drop a REW measurement file in and the room-correction filters get auto-fitted to the target curve you picked (flat, Harman, custom tilt). Group the L and R outputs so tweaking one mirrors the other. There's a 2 × 4 routing matrix if you want to do something weirder than stereo. The CPU-load and buffer-fill telemetry charts are right there too, so you can see if your filter chain is about to overrun.

DQ-DSP web UI screenshot
web UI: Room EQ, routing, telemetry, save-to-flash

When the preset finally sounds the way it should, hit Save to Flash — the box remembers and keeps playing from any USB host forever, no laptop needed. No installer, no driver hunt, no firmware recompile. Everything happens in a browser tab.

Live at dq-dsp.tamduongs.com. Chrome / Edge / Brave / Opera desktop only — Web Serial is still browser-locked.

just go play with it 👇

Every knob, every slider, every cryptic acronym has a hover tooltip explaining what it does. The UI is built to teach itself — open it, hover anything you don't recognise, and you'll know more about active DSP crossovers in 10 minutes than I knew in 3 weekends.

Stack, for the curious: React 19 + TypeScript + Tailwind 4 + Zustand for state, Web Serial as the only transport. SLIP-framed CRC-8 packets on UART0 over the DevKit's USB-Serial-JTAG port. Source on GitHub, deployed on Vercel. Plus 6 weekends of my life, an unreasonable amount of coffee, and CLAUDEEEE. ☕🤖

Under the hood (& a love letter to the ESP32-S3) 💚

ESP-IDF v5.2, pure C, FreeRTOS underneath. Core 0 handles USB and the control plane; Core 1 runs the entire DSP pipeline. Code splits cleanly into two parts: `main/` is the ESP-IDF component (USB, I2S, NVS, serial transport), and `shared/dsp/` is a pure-C DSP core — no Espressif headers in there at all, so it compiles and unit-tests on a laptop without an MCU in sight.

Why the ESP32-S3 is, frankly, an absurd deal at $5:

  • Dual-core 240 MHz Xtensa LX7 — one core shovels samples through the biquad chain at ~3% load; the other answers USB and the web-UI faster than any human can drag a slider.
  • 8 MB Octal PSRAM — headroom for the USB ring buffer, ASRC scratch space, and parameter shadow-buffers without ever sweating internal SRAM.
  • 16 MB flash — firmware, preset slots, and room to grow. Nothing fancy, just nice.
  • Two USB peripherals on one chip — native USB-OTG (handles the UAC audio stream) and USB-Serial-JTAG (handles the Web Serial control channel) running simultaneously on two physical USB-C ports of the DevKit. No external USB-to-anything chip required.
  • Two independent I²S TX — I²S0 and I²S1, each with their own DMA, both lockable to a single APLL so the two PCM5102A DACs stay sample-accurate forever.
  • TinyUSB upstream — UAC 1.0 + CDC over the same chip is roughly a hundred lines of glue. The UAC stream descriptor, the volume/mute controls, the CDC parameter channel — all already there as components.
  • ESP-IDF that actually works — `idf.py build flash monitor` on a fresh install builds and boots. No 6-hour vendor-IDE odyssey. Documentation that's correct. A debugger that lives on the same USB-C as the flasher.

The signal chain

Stereo USB audio in → input EQ → 2×4 routing → per-output crossover → output EQ → delay + gain → 2× I²S → 2× PCM5102A → 4 line-outs. All of it lives on Core 1.

SIGNAL CHAIN · CORE 1INPUT CHAIN · 2 CHOUTPUT CHAIN · 4 CH2 → 4 channels🎵USB UACfrom laptop🔁ASRCPI · ±1400ppm🎛Input PEQ10-band · 2ch🔀Routing 2×4matrix · gain✂️CrossoverLR / Butter🎛Output PEQ10-band · 4chDelay + Gain0–10 ms / ch🔊2× I²S TX→ 2× PCM5102Apure C · ~3% CPU @ 48 kHz · 1 core of an ESP32-S3

Honestly, the chip itself is the most romantic part. Two USB peripherals, two I²S TX, 8 MB PSRAM, 240 MHz dual-core, hardware crypto, hardware floating-point, and the whole thing costs less than two iced coffees. Espressif quietly made the entire "cheap, capable, hackable embedded audio" category make sense, and ESP-IDF is the only mainstream MCU toolchain I've used where the first compile of a fresh project just… works. 💚

Want to flash this on your own ESP32-S3? Two paths:

DSP specifications 📋

Everything that runs in real time on Core 1, fed from a USB Audio Class 1.0 stream:

BlockSpecification
USB inputUAC 1.0 · 16-bit / 48 kHz · stereocapped by the USB-driver class spec — UAC 1.0 maxes at 16-bit/48 kHz without USB 2.0 high-speed
DAC outputDual I²S · 16-bit / 48 kHz · 4 line-outs (2 × PCM5102A in this build)output is plain I²S — swap the PCM5102A for any I²S DAC you like (ES9038, AK4493, PCM1808, whatever). Pipeline tracks the USB input bit-depth
ASRCPI controller · ±1400 ppm · per-DAC drift correctionKp / Ki are tunable live in the System tab of the web UI
Input PEQ10 bands · 2 channels · biquad cascade · ±18 dB
Room EQ10 bands · 2 channels · per-input correction · REW import
Routing matrix2 × 4 · per-cell linear gain · L/R link groups
Output PEQ10 bands · 4 channels · biquad cascade · ±18 dB
CrossoverHP + LP per output · Linkwitz–Riley / Butterworth · 6 / 12 / 18 / 24 dB/oct
Per-channel trimGain · phase invert · mute · delay 0–10 ms
CPU load≈ 3 % of one Core 1 @ 48 kHz · pure C, no FPU acrobatics
Storage1 NVS preset · save-to-flash · persists across power cycles
Control planeWeb Serial · SLIP + CRC-8 frames · UART0 over USB-Serial-JTAG
Connectivity2 × USB-C · USB-OTG (audio) + USB-Serial-JTAG (control)

All numbers are from the running v1.0.0 firmware. Source of truth: shared/dsp/dsp_config.h and the protocol header in shared/dsp/serial_protocol.h.

Hooking it up 🔌

Six signal wires + shared 3V3 + GND between the ESP32 and the two DACs, then two USB-C cables out — one for the audio stream, one for the control channel. That's the whole physical install.

ESP32-S3 to 2× PCM5102A wiring diagram
six signal wires, two DACs, four line-outs
DQ-DSP system block diagram
one cable for audio (UAC), one for Web Serial control

What I learned 🧠

  • TinyUSB UAC + CDC together is fiddly — you have to share endpoint memory between UAC and the control channel. The example projects don't cover this case.
  • Dual-I2S clock alignment matters — if the two I2S TX peripherals drift, the L/R image shifts over time. ESP-IDF lets you sync them with a single APLL — use it.
  • ASRC PI tuning is a vibe check — the PCM5102A wants its own clock, USB host wants its clock, neither is the other's clock. PI controller in software keeps the buffers from over/underflowing. ±1400 ppm is comfortable.
  • Naming things is hard — DQ-DSP is the third name. The first two were objectively worse.

Achievements unlocked 🏆

FANCY UI

live web app · zero installer · runs in your browser tab

OPEN SOURCE

both repos public · GPLv3 · forks welcome

SOUNDS DECENT FOR $20

two amps + DSP + DAC for the price of a takeout meal

TOO MUCH COFFEE — WORTH IT

6 weekends · n cups of cà phê sữa đá · 0 regrets

GPLv3. Forks must stay open. Stay chill. ✌️