Birdsong
A web app that listens to your microphone and identifies the birds it hears, in real time.
Everything runs in the browser: the BirdNET v2.4 model
(~6,500 species) executes locally via TensorFlow.js, so no audio ever leaves your device.
Run it
npm install # also downloads the BirdNET model (~55 MB) into public/models
npm start # http://localhost:8080
Click Start listening and allow microphone access. Microphones only work on https://
pages or http://localhost, so deploy public/ behind HTTPS if you host it elsewhere.
Features
- Live identification: analyzes the last 3 seconds of audio every second and shows the
top candidates with confidence bars.
- Session log: every species identified, with best confidence and number of encounters. Per bird:
- Details: up to 10 photos (male/female first, then iNaturalist's curated set) that open in a
full-screen, full-resolution viewer (swipe or ‹ › / arrow keys to browse, pinch to zoom).
The first tile is a range map: the expert range and iNaturalist sightings over OpenStreetMap,
fitted to where most sightings are, with your position when the location filter is on; it opens
in the same viewer, redrawn at screen size. Reference recordings play in place. The notes skip range and focus on recognizing the
bird: what it sounds like, behavior and what to look for (from Wikipedia), or with Claude: ID
tricks, behaviors, male vs. female, look-alikes and fun facts. Then links to All About Birds,
Wikipedia, iNaturalist, xeno-canto and YouTube. Detection pauses while a sound plays so the mic
doesn't "hear" it.
- Heard clips (Settings › Keep clips of each bird, on by default): the 3 s of audio behind each
identification, the strongest window per encounter, up to 4 per bird. Clips are volume-normalized,
play from Details, and can be downloaded as WAV. They stay in memory until you clear the list or
close the page, unless you mark the bird Saw it.
- Saw it: records a verified sighting in My sightings, together with its 2 strongest heard
clips (3 s, 48 kHz WAV, about 290 KB each, so at most ~580 KB per sighting; nothing extra if clips
are turned off). The server keeps the sighting in
data/sightings.json and its clips in
data/sightings/; static hosting keeps both in IndexedDB. Saved sightings have the same
Details dropdown as the live list, with their clips to play back or download. Sightings saved
before clips were kept show the reference details only.
- Skip: hides the bird for the rest of the session. × removes it until it's heard again.
- Live spectrogram with a label at the moment each new bird is detected.
- Location filter (optional): BirdNET's range model uses your location and the week of the year
to drop species that wouldn't plausibly be there. This removes most false positives. The note
under the switch shows the position in use (and flags a vague, IP-based fix), and species the
filter hides still appear greyed in the live list with their range score, so its decisions
can be checked.
- Session recording: Record captures exactly what the model hears (48 kHz mono WAV), along
with the timestamps of live detections. Audio streams to the server every few seconds, so long
sessions don't build up in browser memory and a closed tab still leaves a usable file.
- Recordings library: upload any audio file (it's analyzed automatically), download, rename,
delete. The player shows a scrollable spectrogram of the whole session with detections as labeled
bars: drag or use the overview strip to move around, ctrl/⌘+scroll or −/+ to zoom, click to seek,
click a bar to hear that detection. Find birds re-runs identification over the whole file.
- Microphone picker: choose any input device; the choice is remembered, and switching while
listening takes effect immediately. It sits quietly under the listen button and turns into a
highlighted picker when the mic fails or sends only silence.
- Sensitivity: minimum confidence defaults to 15%, with 3 s windows analyzed every 0.5 s (live and
in recordings). On a noisy test soundscape this caught 2.5-3x more birdsong than 25% with
back-to-back windows. Turn on the location filter to weed out the extra out-of-range guesses.
- Noise filter (optional): a 150 Hz high-pass for steady traffic or hum. In quiet or windy
conditions filtering made detection worse (the model is trained on unfiltered audio), so it's off
by default.
Tests
npm test # needs `npm install` first: the tests run the downloaded BirdNET range model
Two ways to run it
| npm start (local server) | Static hosting (GitHub Pages, birdsong.live) |
|---|
| Live identification, spectrogram, location filter | ✓ | ✓ |
| Bird details | iNaturalist + Wikipedia, or Claude / xeno-canto with keys | iNaturalist + Wikipedia, fetched by the browser |
| My sightings, recordings | Stored by the server in data/ | Stored in each visitor's browser (IndexedDB); download recordings to keep copies |
The page checks /api/health at startup and picks the server or browser storage automatically.
Deploying
Pushing to main runs .github/workflows/pages.yml, which runs npm ci (downloading the model
files, pinned to a fixed BirdNET commit) and publishes public/ to GitHub Pages. The custom domain
comes from public/CNAME.
Optional API keys
Bird details work without any keys. Two optional environment variables improve them:
ANTHROPIC_API_KEY=sk-ant-... XC_API_KEY=... npm start
This is the beginning of the README. Read the rest at the original. Read the full README ↗