DEV Community

Gabby Gill
Gabby Gill

Posted on

TrailBirder — offline bird-song ID and a local-LLM field journal for Alberta trails

Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass Submission 🌿

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass

What I Built

Hearing birds on Calgary walks and not knowing what they were.

TrailBirder is a small C# / .NET 8 app that listens to birds on the trail, names them, and writes you a short field-journal entry. It runs entirely on your own device, with no signal, account or cloud.

The idea is that the screen should be the shortest part of the walk. You start it and put the device away. This is what live mode looks like (example output from BirdNET's sample recording):

▸ Listening · Inglewood Bird Sanctuary · Oct 10 — press Ctrl+C to stop
  11:18:17  Black-capped Chickadee       0.81 likely    ★ new today
  11:18:26  House Finch                  0.64 probable  ★ new today
  11:18:58  Dark-eyed Junco              0.74 likely    ★ new today
Enter fullscreen mode Exit fullscreen mode

It's built for Alberta. It has presets for places like Inglewood Bird Sanctuary, Fish Creek, Frank Lake, Kananaskis, Elk Island and Waterton. You can also ask it before you leave:

trailbirder species --where fish-creek
▸ Likely around Fish Creek Provincial Park the week of Oct 10
   88 %  Black-billed Magpie
   81 %  Black-capped Chickadee
   77 %  Common Raven
   ...
Enter fullscreen mode Exit fullscreen mode

It's for anyone who wants to learn the birds they keep hearing, from a first-time birder to someone keeping a season list. It works three ways: a phone voice memo you analyze later, a laptop at a viewpoint, or a Raspberry Pi with a USB mic left by a marsh.

Demo

The CI run is a working demo anyone can inspect. On every push, GitHub Actions does two things:

  • It builds the app on Windows, macOS and Linux and runs the bird models on each.
  • On Ubuntu, it installs Ollama, pulls Gemma 3 (1B), and analyzes BirdNET's example recording end to end. The journal it writes appears in the run summary.

You can also run it yourself in about 20 minutes. The README has a step-by-step Windows setup, and BirdNET's sample recording makes a quick first test.

Code

TrailBirder 🐦

CI

⚠️ Models are for non-commercial use only. The BirdNET models in models/ are licensed CC BY-NC-SA 4.0 (see models/LICENSE.md). The TrailBirder source code is MIT licensed.

Offline bird-sound ID and field journal for Alberta trails, built in C# / .NET 8.

Leave your phone in your pocket, record or listen on the trail, and get a species list plus a short field-journal entry. Everything runs on your own device. No signal, account, or cloud is needed.

▸ Listening · Inglewood Bird Sanctuary · Oct 10 — press Ctrl+C to stop
  11:18:17  Black-capped Chickadee       0.81 likely    ★ new today
  11:18:26  House Finch                  0.64 probable  ★ new today
  11:18:58  Dark-eyed Junco              0.74 likely    ★ new today

How it works




















Step Open piece Runs on
Hear Your mic (NAudio on Windows, arecord on Linux/Pi, ffmpeg on macOS) or recordings from your phone Device
Identify
BirdNET v2.4 (open-weight model from the
…





How I Built It

There are three open pieces, and all of them run locally:

Step Open piece What it does
Identify BirdNET v2.4 (Cornell Lab of Ornithology and TU Chemnitz), run with ONNX Runtime in .NET Turns 3 s of audio into scores for 6,522 sound classes
Filter BirdNET's range model Turns latitude, longitude and week into which species are plausible here right now
Write Gemma 3 through Ollama (any Ollama model works via --model) Turns the detection table into a grounded journal entry

Getting BirdNET into C# was the interesting part. BirdNET ships as TensorFlow Lite, and .NET has no good TFLite runtime, so I converted it to ONNX. The standard converter (tf2onnx) failed on BirdNET's built-in spectrogram layer. That layer takes an FFT and then casts the complex result to float, which keeps only the real part. That's an unusual pattern the converter doesn't support.

Because the weights are open, I could fix it myself. The real part of an FFT is just a matrix multiply by a cosine table:

Re(RFFT(x))[k] = Σₙ x[n] · cos(2πkn/N)
Enter fullscreen mode Exit fullscreen mode

I wrote a small graph rewriter that swaps that step for a MatMul, then checked the result against the original model on real audio. The largest difference in any bird's probability was 0.00005, so the converted model is effectively identical. The converter script is in the repo.

The range filter does more work than you'd expect. On the test recording, the raw classifier also suggested a Chestnut-winged Cuckoo (Southeast Asia) and a Hawfinch (Europe). The range model scores both at 0.000 for Calgary in October, so they never reach the user. It also reflects the season: mid-October around Calgary means magpies, ravens, chickadees, Bohemian waxwings and late waterfowl, and that's what it expects.

The LLM is fenced in. Small models will happily invent a Great Horned Owl. The journal prompt gives the model only two lists, both computed on the device: what BirdNET detected and what the range model expects nearby. It is told to:

  • never mention species outside those lists,
  • flag IDs below 0.5 confidence as uncertain and point you to the clip,
  • remember that "4 clips" doesn't mean 4 birds.

If Ollama isn't running, nothing breaks. Detections and clips are still saved, and trailbirder journal writes the entry later.

Other details:

  • Microphone input uses NAudio on Windows, arecord on Linux/Pi and ffmpeg on macOS.
  • There's a built-in resampler for when ffmpeg isn't installed.
  • The models are split into pieces under 25 MB for GitHub and reassembled on first run, with a SHA-256 check.

Why Does Open Innovation

It costs nothing to run. On a CPU with no GPU, two minutes of audio is analyzed in about two seconds. A full morning of recordings costs nothing, and nobody needs an API key.

Taking It Outside

On Sunday, October 11, I went to Inglewood Bird Santuary around 10:00 am. It was -1 °C with snow/wind, an early taste of Alberta winter.

I kept my phone in my pocket and only took my glove off to start voice memos of about minutes each. The screen was out for less than a minute in total.

At home, TrailBirder analyzed 1 minutes of audio in 25 seconds on my laptop, fully offline.

Black-capped chickadee clips 1 best 0.81 likely
Black eyed junco clips 4 best 0.74 likely
House finch clips 4 best 0.64 probable
American Goldfinch clips 3 best 0.48 possible
Blue Jay clips 1 best 0.44 possible

My Agent Session

I built this with an AI coding agent (Claude). We worked through choosing the stack and the model conversion, then testing against the original model, CI on three operating systems, and an end-to-end run with a real Ollama model.

Building TrailBirder: BirdNET to ONNX in C#, a local Gemma field journal, and CI on 3 operating systems

Prize Categories

  • Gemma. Gemma 3 runs locally through Ollama to write the field journal. It's the default model (gemma3:4b), and CI tests it end to end with gemma3:1b.

Credits

  • BirdNET v2.4 models © K. Lisa Yang Center for Conservation Bioacoustics (Cornell Lab of Ornithology) and Chemnitz University of Technology, licensed CC BY-NC-SA 4.0. Kahl et al. (2021), Ecological Informatics 61, 101236.
  • ONNX Runtime, tf2onnx, Ollama, Gemma, NAudio, ffmpeg.
  • TrailBirder code: MIT.

Top comments (0)