BlockedInRead story (opens in a new tab)2025
Yapflow
A local speech stack I can unplug: push-to-talk dictation on a Jetson, and an Indic ASR fine-tuning track shaped by the limits of the same hardware.
Results
19.46h
curated Kannada speech
Results
7,185
valid clips after filtering
Overview
Yapflow is two pieces on the same home network: a small Mac app, and a server on a Jetson Orin Nano sitting on my desk. Hold Right-Command, say the sentence, let go — a second later the cleaned-up text is typed into whatever window was in front, whether that’s a terminal, a browser, or a text field in an app that knows nothing about any of this.
The reason it exists is the last clause of that sentence. Good dictation is a solved problem if you are willing to stream your microphone to somebody else’s API all day. I wasn’t, so the model runs on a board I can unplug.
It’s a personal tool, not a product — there is no installer and no account, just a repo and a systemd unit. Source is on github.com/shreyas-sreedhar/yapflow.
What it does
- Hold to talk: The Mac app captures the microphone while the hotkey is down, resamples it, and streams it to the Jetson over a single WebSocket that stays open between dictations rather than reconnecting each time.
- Transcribe while you speak: Moonshine v2 runs on the Jetson and emits partial transcripts as the audio arrives, so the final text is ready almost as soon as the key comes up rather than starting from scratch on release.
- Tidy the result: A deterministic pass removes filler words and applies a personal dictionary — the names and jargon you have corrected before, which generic speech models reliably get wrong.
- Put it at the cursor: A small Swift helper writes to the clipboard and synthesizes Cmd+V. Unglamorous, but it is the one method that works in every macOS app instead of only the well-behaved ones.
The one decision worth recording: the tidy-up step used to be a Gemma 3 4B call, and it was removed. Moonshine already returns cased, punctuated text, so most of the prompt was asking a language model to redo finished work — and a blocking 4B generation sits right on the critical path, where it costs seconds. What genuinely remained was mechanical, so it became string operations. Dropping it also freed enough of the board’s 8GB to run a larger, more accurate speech model.
Model research
The product exposed a second problem: an English-first speech stack is not enough for Indic languages. I started an Indic ASR track, starting with Kannada, around the same constraint as Yapflow itself — the full data and training pipeline had to remain practical on an 8 GB Jetson.
- Curate for the hardware: Combined FLEURS and quality-filtered IndicVoices into 7,185 valid Kannada clips: 19.46 hours of mono, 16 kHz audio. Clips over 25 seconds, empty transcripts, persistent noise and unclear speech were removed before training.
- Make evaluation reproducible: Produced a deterministic 80/10/10 manifest split: 5,748 training samples, 718 validation samples and 719 held-out test samples.
- Try the memory-efficient path: Quantized the base model and applied LoRA so adaptation could fit the board instead of requiring a separate training machine.
- Find the actual bottleneck: The first run did not learn Indic speech. The base tokenizer had no useful Kannada vocabulary, while standard LoRA trained attention projections and left the newly needed embedding rows frozen.
That failure changed the architecture. The next training pass expands the tokenizer with Indic characters and common sub-words — starting with Kannada — resizes the model’s embedding matrix, and trains those new rows alongside the LoRA attention adapters. The useful result so far is not a flattering WER number — there is no working checkpoint yet — but a reproducible corpus and a specific, testable explanation for why the cheaper adaptation path failed.
A speech stack you can unplug
Source is the product. There is no installer.
View on GitHub (opens in a new tab)Keep reading
BlockedInRead story (opens in a new tab)
CameraShyRead story (opens in a new tab)
AreYouAIRead story (opens in a new tab)