This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass.
What I Built
Touch Grass Birder identifies bird calls, and it does the whole thing on your own machine.
You tap Listen, hold your phone up while a bird sings for about six seconds, and it tells you what you are hearing. Then you put the phone away and go look for the bird. That is the point: the screen is only in the way for a moment.
I built it for the places where phone apps tend to fall over. A trailhead with one bar. A tent before sunrise. A park where the warblers are moving through and you have no idea what you are listening to.
What it does:
- Records six seconds of audio in the browser and encodes it to WAV on the device.
- Identifies the species locally with BirdNET, the open acoustic classifier from the Cornell Lab of Ornithology.
- Writes a short field note about the bird with Gemma 3 1B, an open-weight model running through
llama.cpp. - Keeps a field log of everything you have heard, in a plain JSON Lines file you own.
- Installs as a PWA and keeps working with the network turned off.
There is no API key, no audio upload, and no network round trip.
Demo
Live demo: https://touch-grass-birder.onrender.com
The hosted demo runs BirdNET, so you can point it at a recording right now. Gemma field notes only work when you run it yourself, because a free Render instance is too small for the 0.8 GB model. Run make models && make run for those.
Code
https://github.com/mukuvi/touch-grass-birder (MIT licensed).
How I Built It
Two open models run on the device, wired together behind a small FastAPI server.
flowchart LR
A[Phone browser<br/>records 6s, encodes WAV] --> B[FastAPI]
B --> C[ffmpeg<br/>decode to 16-bit mono WAV]
C --> D[BirdNET acoustic model<br/>open, local inference]
D --> E[species + confidence]
E --> F[Gemma 3 1B<br/>open-weight via llama.cpp]
F --> G[plain-language field note]
E --> H[(data/sightings.jsonl<br/>your field log)]
G --> A
H --> A
The pieces:
-
Browser side: the MediaRecorder API captures six seconds, an
AudioContextdecodes it, and a small function inapp.jswrites a real 16-bit mono WAVBlobbefore it is sent. The server never has to touch the audio to get a usable clip. - BirdNET does the acoustic work. It is trained on thousands of species for passive acoustic monitoring, and it is the reason this works offline at all. It loads through the LiteRT runtime and stays warm in memory after the first call.
-
Gemma 3 1B (Q4_K_M, about 0.8 GB) runs through the
llama.cppCLI to write a two or three sentence field note: what the bird sounds like, where and when you would hear it, and one field mark to look for. If the model is not on disk, the app skips the note and still identifies the bird. -
FastAPI ties it together with four endpoints (
/api/identify,/api/sightings,/api/health, and the static app). The field log isdata/sightings.jsonl.
Everything is overridable with environment variables (LLAMA_BIN, GEMMA_MODEL, MIN_CONF, LLAMA_THREADS, BIRDER_DATA_DIR), so the same code runs on a laptop, a Raspberry Pi, or something sitting next to a phone.
Why Does Open Innovation Matter?
The short version: this project only exists because the models are open.
The best places to hear birds are the places with no signal, and a cloud API cannot run there at all. Every closed identifier needs a round trip to a server. Running BirdNET on the device means the app is not a worse version of a cloud tool in the backcountry. It is the only version that works there. The recording is six seconds because nothing has to be uploaded and there is no spinner to wait on. The answer is already on the phone.
A rare sighting is sensitive. A closed app can learn where you were and when, which matters if you are near a nesting site or reporting a scarce species. Here the only record is a line in a file you own, on hardware you control. There is no server for it to go to.
Open weights also mean I can change the model. Gemma runs from a local GGUF file, so moving to a larger Gemma, a different quantization, or a different model entirely is a path and an environment variable, not a conversation with a vendor. I can fine-tune on a regional species list later. The classifier itself, BirdNET, is open and inspectable, which feels important when the output is a claim about a living thing.
And it costs nothing to run. No per-identification fee, no rate limit, no key that expires. A birder on a budget, a school group, or a citizen science project can run it as long as they want.
The open parts are the point. For this app the offline, private, zero-cost version is simply the better one.
My Agent Session
I built this with an agent. The session is saved, so you can see how it went:
Prize Categories
-
Best Use of Gemma: Gemma 3 1B runs locally through
llama.cppand writes the field notes. -
Best Use of Render: the live demo runs on Render with the included
render.yaml.
Built for Hacktoberfest 2026. Files, models, and data stay on the machine, with no key, no upload, and no network.
Top comments (0)