A single-mic, single-speaker prototype that detects dominant tone(s) in ambient noise and continuously emits a phase-compensated anti-tone to cancel them.
This is not a full active-noise-cancellation system in the headphone sense — it's a narrowband tonal canceller, closer to what's used for transformer hum or motor-drone suppression than to consumer ANC. Read the Limitations section below before judging results.
- Acquisition — every ~1.5s, an FFT scan of a rolling buffer finds the top 3 dominant frequency peaks (with parabolic interpolation for sub-bin accuracy).
- Tracking — each acquired tone is handed to its own Phase-Locked Loop (PLL). The mic signal is demodulated (I/Q) against a local oscillator; the resulting phase error steers the oscillator's frequency via a PI controller. This is what lets the system self-correct drift instead of relying on a single stale FFT snapshot.
- Anti-noise synthesis — once locked, each tracked tone generates a 180°-inverted, latency-compensated sine wave, summed and sent to the output device.
- Latency calibration — a chirp is played and cross-correlated against what the mic records, giving a measured round-trip latency instead of a guessed constant.
Working, with known issues:
- Lock is achievable and holds for single steady tones once the PLL settles.
- Occasional input/output underflow — the per-sample Python loop in the
I/Q low-pass filter is not fast enough to reliably keep up with the
real-time audio callback. This is a known, understood bug — the fix
(vectorizing the filter, e.g. with
scipy.signal.lfilter) is scoped but not yet merged. Do not be surprised by audio glitches/underflow prints. - Cancellation depth is currently mild — audible but partial.
- Single mic, single tone family. This can only ever cancel stationary tonal noise (hums, whines, motor/fan drones). It cannot and will not cancel broadband noise (traffic, speech, transients) — that's a mathematical limitation of frequency-peak tracking, not a bug.
- Acoustic isolation matters. If the speaker output leaks back into the mic (no headphones, open air), the frequency/phase estimate is being computed on a self-contaminated signal.
- Real ANC headphones use FxLMS with a separate reference mic and error mic — a materially different (and better) architecture. This project is a narrowband PLL tracker, a legitimate but different technique.
pip install sounddevice numpy matplotlib
# Linux also needs:
sudo apt install libportaudio2python src/narrowband_anc_pll.py- Click "Auto-Calibrate Latency" first — stay quiet, uses open speakers.
- Switch to headphones for output.
- Play a steady tone or hum near the mic.
- Watch the status line — wait for [LOCKED] before judging cancellation.
- Adjust gain/latency sliders if needed.
- Vectorize the I/Q low-pass filter to fix input/output underflow
- Add a second (error) microphone and move toward FxLMS
- Log cancellation depth (dB) automatically for reporting
- Multi-tone amplitude balancing when tones are close in frequency
This started as an ESP32 + desktop hybrid ANC attempt during an internship. The original single-tone, blocking-playback version did not achieve usable cancellation due to architectural issues (unsynchronized mic/speaker clocks, non-continuous output, single-frequency-only tracking, open-loop frequency estimation). This repo is the rebuilt, closed-loop version developed to directly address those failure modes.