Skip to content

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

Share of calls still detected as the water gets louder
  • Detected at score ≥ 0.3
  • Detected at score ≥ 0.5
Share of calls still detected as the water gets louder. The same values are in the table below the chart.0%25%50%75%100%-15 dB-10 dB-5 dB0 dB+5 dB+10 dBSignal-to-noise ratio (dB, call vs water)
Show the numbers
Signal-to-noise ratio (dB, call vs water)Detected at score ≥ 0.3Detected at score ≥ 0.5
-15 dB35%28%
-10 dB57%48%
-5 dB74%64%
0 dB84%76%
+5 dB92%88%
+10 dB95%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

SourceUsed forLicence
BirdNET+ V3.0 developer preview 3.1 (Kahl et al.), Zenodo 20703646Species identification in the browserCC BY-SA 4.0; no poaching or military use
BirdNET+ geomodel V3.0.4Species plausible per place and weekCC BY-SA 4.0
Silero VAD v5.1.2Finding speech in shared clipsMIT
OneAquaHealth public API, snapshot 2026-09-28Sites, ecology classes, health risks, urban context© OneAquaHealth consortium
xeno-canto recordings via GBIF (credited per file)Demo recordings and benchmark callsCC BY-NC-SA 4.0 and compatible
GBIF Backbone TaxonomyTaxon identifiers in exportsCC BY 4.0
ERA5 via the Open-Meteo archive APIRain before lab samplingCC BY 4.0
OpenFreeMap, OpenMapTiles, OpenStreetMapBase mapODbL and open terms