- Status
- Open source (MIT)
- Stack
- Swift · CoreML / Apple Neural Engine · llama.cpp · MLX LoRA fine-tuning · Accessibility APIs
- Evidence
- Public, MIT-licensed source code and a demo recording at github.com/ikamal97/local-dictation.
Dictation that keeps up with speech.
An on-device macOS dictation app: hold fn, talk, release, and cleaned-up text appears at the cursor. Nothing leaves the Mac.
At a glance
The problem
I was paying $15 a month for a cloud dictation app with a one-to-two-second delay between pressing the key and the app actually listening. That delay broke my flow. Investigating it showed why: the microphone opened cold on every keypress, Bluetooth headsets added a profile switch on top of that, and audio passed through several processes before transcription.
My role
I built it solo with Claude Code. I benchmarked speech-recognition and cleanup models on my own voice before building, then built the native Swift app, the audio pipeline, the fine-tuning data and run, and the evaluation. Along the way I diagnosed and fixed the recognizer outputting “Thank you.” on trailing silence, fixed an MLX-to-GGUF convolution-weight layout incompatibility for LFM2, and set up a self-signed signing identity so macOS privacy permissions survive rebuilds.
Decisions that mattered
- 01Run as one native Swift menu-bar process. The microphone stays open in a ring buffer with 0.3 s of pre-roll, and a listen-only event tap watches the fn key, so recording starts about 50 ms after key-down.
- 02Segment audio at pauses and transcribe while I am still talking, using Parakeet TDT 0.6B v3 on the Apple Neural Engine through FluidAudio and CoreML, so little work is left at release.
- 03Fine-tune a small cleanup model instead of prompting a larger one: LFM2.5 1.2B Instruct, LoRA-trained with MLX on about 3,700 of my own raw-to-cleaned dictation pairs and run through llama.cpp on Metal.
- 04Read the surrounding text through Accessibility APIs so dictation can continue mid-sentence in lowercase, fix spacing, and spell names that are visible on screen correctly. Paste, then restore the clipboard.
- 05Use llama.cpp’s prebuilt framework rather than MLX-Swift so the app builds and ships with Command Line Tools only, without Xcode.
What changed
On an M5 MacBook Air, across 130 held-out real recordings (44 minutes): recording starts about 50 ms after key-down, speech recognition takes 0.09 s median for clips up to 20 s, and text is pasted about 250 ms after release. Word error rate was 3.2% against the cloud service’s own transcripts; Whisper large-v3-turbo scored 8.0% and was three times slower. The fine-tuned 1.2B cleanup model scored 10.5% content WER against 15.0% for a prompted Qwen3 4B, at under half the latency, with no dropped or invented content in testing. The app also includes searchable history, a personal dictionary, settings, and one-click history import from the cloud app.
What this establishes
These results come from one speaker on one Mac. Error rates are measured against the cloud service’s transcripts rather than hand-checked references, and the cleanup result reflects the held-out test set, not a guarantee for every dictation.
What I would measure next
Evaluate across more speakers, microphones, and Macs, and add a hand-checked reference set so error rates do not depend on another service’s transcripts.
Contact
Want a walkthrough of this work?