A stethoscope is the cheapest medical instrument a village doctor owns — and one of the hardest to learn. Today, the one I trained lives in a browser tab. No server. No upload. No pip install. Open the link, press play, and a 404K-parameter neural network listens to a real heart sound and tells you what it hears.
The model behind it is small on purpose: multi-position attention fusion over four auscultation sites, trained on the CirCor DigiScope corpus from the PhysioNet 2022 Challenge. On the official held-out metric it beats that year's champion (0.7815 vs 0.780) with roughly 200× fewer parameters. Small enough to run on a village clinic's ten-year-old PC. Small enough, it turns out, to run in a browser.
A demo that needs a GPU is a demo that doesn't get opened. I wanted something a cardiologist — or a PhD committee — could click and understand in ten seconds. So the whole pipeline had to move client-side: audio in, probabilities out, nothing leaving the machine.
decode → resample to 4 kHz → 40-band log-mel (126 frames)
→ shared 2D-CNN × 4 positions → masked attention fusion
→ 3-class probabilities (Absent / Unknown / Present)
The model exports to ONNX (1.6 MB), and ONNX Runtime Web runs it on WebAssembly. The hard part was never the neural network. It was the mel spectrogram.
Every dB of the mel front-end is baked into the trained weights. If the browser computes the spectrogram even slightly differently from the training pipeline, the model is quietly seeing a different world — and the demo lies. So I reimplemented the front-end in pure JavaScript and diffed it against the training machine until it stopped disagreeing. Five real bugs died on that diff:
power_to_db clamps at top_db=80. I forgot the clamp; quiet bands drifted by 10+ dB.None of these were visible from the browser side. Each one was caught the same way: run the same audio through the browser and through the training environment, and compare. Verification is not a step at the end — it's the whole method:
JS mel == numpy mel == librosa 1.0.0 (max diff 0.0000 dB after fixes) ONNX == PyTorch (max diff < 1e-6) browser probs == training machine probs (max diff < 1e-3)
Nothing ships on faith.
The demo is live — three real CirCor recordings (murmur positive, unlabelled, murmur negative), or upload your own audio:
https://noahisarider.github.io/open-stethoscope/
You'll see the waveform, the mel spectrogram heatmap, and three probability bars. The whole model runs in your browser via WebAssembly; the audio never leaves your machine. That privacy property isn't a footnote — for health data, it's the whole point.
Every dB of the front-end is baked into the weights. Get it wrong by a hair and the demo lies — so I made it match to four decimal places, or not ship it at all.
A 404K-parameter model in a browser tab is a small statement about where intelligence should live: not in a data center you have to rent, but in the hands of the person listening. Village clinics don't have GPUs. They have browsers.