← Back to projects
Personal project

Sound Source Localization

Estimating a sound source's 3-D position from a microphone array

Role
2018 master's project at Johns Hopkins, solved in 2026
Problem
Estimate a sound source's 3-D position from a microphone array in a reverberant room
Approach
Per-cluster direction-of-arrival, then least-squares ray triangulation
Status
Open source, 77 tests, live demo hosted

Before

My master's project at Johns Hopkins located heart sounds recorded by a microphone array, the front half of an automated murmur detector. I went back to it this year to put a demo online and found it had never solved the problem. It swept a fixed radius and emitted around 500 candidate points. Nothing in the pipeline turned them into a position, and in 2018 I didn't recognize why: this is a geometry problem, not a search problem.

Four more errors underneath were corrupting results without ever raising one, the worst being a hardcoded speed of sound at a tenth of its real value. None of them crashed anything, which is why they survived eight years.

What I built

The missing piece was triangulation. Each microphone cluster gives a bearing, a ray with unknown distance. Finding the point that best explains every ray at once is a linear system with a closed-form solution, solved directly, no sampling grid. Real bearings are noisy, so three combiners ship: ordinary least squares, iteratively reweighted least squares with a Huber loss as the default, and RANSAC when a large fraction of rays are corrupted.

The key design decision

Array geometry imposed two constraints, both of which surfaced as wrong numbers before I understood them.

A cluster where every microphone sits at the same height cannot tell a source above it from its mirror image below. The delays are identical. A flat six-mic array reported 111.08° of colatitude for a true 68.86°, confidently returning the reflection. Every cluster now has vertical extent, a four-microphone tetrahedron, so one cluster resolves elevation alone.

Direction-finding also needs microphone spacing under half a wavelength, centimeters at these frequencies. Pooling microphones from clusters meters apart aliases the phase and collapses the estimate toward the average of the cluster positions, 1.27 m of error when I measured it. Direction now uses only each cluster's own microphones, one ray per cluster. Triangulation cares about ray geometry, not how far apart the rays start.

Results

Given exact bearings, triangulation is exact: ~10⁻¹³ m, machine precision, verified by 13 unit tests. On real audio it holds up in a quiet room and degrades with reverberation.

Near-anechoic: mean 0.031 m. Moderately furnished room: mean 0.121 m. Heavily reverberant: mean 1.033 m.

I targeted sub-centimeter and missed by a factor of thirty in the reverberant case. That's the physical limit of direction-finding across a few centimeters of aperture, not a remaining bug. Reflections corrupt the direct-path timing the method depends on. Closing it needs a larger aperture, synchronized time-of-flight hardware, or a direction model trained on reverberation. I checked the degradation wasn't an artifact of my test signal by rerunning against real speech and environmental audio, 135 runs across 3 datasets and 3 reverberation levels. Same result.

Known limits

One source at a time. Multiple sources need rays grouped before triangulating each group. All numbers come from one room geometry, one array layout, and simulated audio, with no validation against real microphone hardware. The default frequency range is still tuned to heart sounds.

Where it stands

The repo runs end-to-end and reports its own accuracy, including the target it misses. A Streamlit demo takes a preset or an uploaded multi-channel recording and plots the estimate against the true position in 3-D. It's live online, and also runs locally through Docker. Open for anyone who wants to point it at a different room, a different array, or real hardware.

What I actually took from this: the 2018 version failed because I reached for a search when the problem wanted geometry. Describing the problem correctly did more than any amount of tuning would have.

4 silent bugs found in my own graduate code
10⁻¹³ m triangulation error given exact bearings
1.03 m mean error in a heavily reverberant room, target was under 1 cm

Stack

pyroomacoustics NumPy SciPy SRP-PHAT MUSIC TOPS Huber IRLS RANSAC pytest Streamlit Docker
Accuracy figures come from simulated audio in one room geometry with a 5-position sample. No real-hardware validation. The original project targeted heart sounds; this version is general-purpose and makes no clinical claim.