How we checked Murmur, and what it cannot do
Every number on this page is produced by a script in the repository and can be regenerated.
What Murmur is not
It does not measure water quality.
Sound tells you who is living by a stream, not what is in the water. That is why every site page shows the lab results beside what people heard.
It makes mistakes.
BirdNET is a probabilistic model and confuses similar calls. Every suggestion comes with its 3-second clip and is checked by the recordist, then by other listeners and, when they disagree, by an expert.
Silence is not absence.
Rushing water and traffic mask quiet calls. Murmur hatches the windows too noisy to judge and says so, instead of reporting nothing.
Range lists are regional.
The geomodel says which species are plausible in a region and week, not at one stream. Unusual names are listed apart, not hidden.
It is not a replacement for expert surveys.
It adds many cheap, repeatable, verifiable observations between the lab's visits; it does not replace them.
Phones are not calibrated.
Indices are relative (NDSI, audibility), never absolute decibels, so recordings from different phones stay comparable.
Checks
How rushing water hides birdsong
167 clean calls of 14 stream-side species, each mixed with real recordings of flowing water at controlled loudness. Script: pipeline/murmur_pipeline/benchmark.py
- Detected at score ≥ 0.3
- Detected at score ≥ 0.5
Show the numbers
| Signal-to-noise ratio (dB, call vs water) | Detected at score ≥ 0.3 | Detected at score ≥ 0.5 |
|---|---|---|
| -15 dB | 35% | 28% |
| -10 dB | 57% | 48% |
| -5 dB | 74% | 64% |
| 0 dB | 84% | 76% |
| +5 dB | 92% | 88% |
| +10 dB | 95% | 91% |
On a real recording Murmur cannot know the true signal-to-noise ratio, so it uses the audibility index measured from the sound itself. Below 10 dB the benchmark detected only 53%–66% of calls, against 82%–98% above it, so those windows are hatched as hard to hear. On water noise alone, the model named a plausible species in 1% of windows at score ≥ 0.3.
The browser hears what the reference run hears
scripts/parity-check.mjs drives the real page in headless Chrome.
Across the 10 public recordings, the model running in the browser (Web Audio resampling, onnxruntime-web) and the reference run (ffmpeg, onnxruntime on CPU) agreed on 63 of 72 species–recording pairs (Jaccard 0.88), with identical species lists for 5 recordings. Where both heard a species, scores differed by 0.01–0.11 on average. The disagreements are calls close to the 0.25 threshold, where resampling differences decide which side they fall.
Voices never leave the device
lib/privacy/speech.ts · Silero VAD v5.1.2 (MIT)
Speech is a run of at least 8 consecutive 32 ms frames (250 ms) above 0.5 probability. In our check, synthetic speech ran for 74 frames and was muted, while a White-throated Dipper's call never exceeded 3 frames and was kept. A clip that is mostly talking is not shared at all; in the end-to-end test, a clip with a voice over the dipper stayed on the device.
FHIR that a real server accepts
scripts/validate-fhir.mts
A representative bundle validated on the public HAPI FHIR R4 server with no structural errors. The only errors say that the OneAquaHealth profiles are not loaded on that server, which is expected; the remaining warnings concern code systems that server does not know (the OAH temporary system, GBIF, and Murmur's own, which is published at /fhir/CodeSystem/murmur).
Tested logic
npm test
Unit and integration tests cover the FFT and acoustic indices, windowing and detection merging, the range filter, the soundscape summary, speech muting, the WAV encoder, FHIR and Darwin Core exports, consensus rules, and the commons repository on a real in-process Postgres (PGlite).
What we noticed in OneAquaHealth's own data
Lab samples taken after three dry days
90 of 96
Dry means less than 5 mm of rain in the 3 days before the sampling day (ERA5 reanalysis). Microbial risk in urban streams peaks after rain, when sewers overflow, and one visit per site cannot catch it. Recordings can be made any day, including the morning after a storm, so a network of listeners fills the gaps between lab visits.
Associations across the 96 samples (Spearman ρ)
Single samples per site, pooled across five cities: associations, not causes. City differences (climate, sewer systems) are confounders.
- Pathogen risk vs distance to the nearest sewage works-0.35
- Pathogen risk vs maximum temperature on the sampling day+0.43
- Faecal risk vs rain in the 3 days before sampling+0.01
Data, models and licences
| Source | Used for | Licence |
|---|---|---|
| BirdNET+ V3.0 developer preview 3.1 (Kahl et al.), Zenodo 20703646 | Species identification in the browser | CC BY-SA 4.0; no poaching or military use |
| BirdNET+ geomodel V3.0.4 | Species plausible per place and week | CC BY-SA 4.0 |
| Silero VAD v5.1.2 | Finding speech in shared clips | MIT |
| OneAquaHealth public API, snapshot 2026-09-28 | Sites, ecology classes, health risks, urban context | © OneAquaHealth consortium |
| xeno-canto recordings via GBIF (credited per file) | Demo recordings and benchmark calls | CC BY-NC-SA 4.0 and compatible |
| GBIF Backbone Taxonomy | Taxon identifiers in exports | CC BY 4.0 |
| ERA5 via the Open-Meteo archive API | Rain before lab sampling | CC BY 4.0 |
| OpenFreeMap, OpenMapTiles, OpenStreetMap | Base map | ODbL and open terms |