Talk, Don't Type: Local Voice Dictation That Beats the Cloud
I stopped typing prompts to my AI. I talk now.
Not into a phone, not into a cloud service. Into a fanless ThinkPad, offline, with the mic only listening while I hold a key. I hold Super+Ctrl+Space, speak, let go, and the text lands in whatever window has focus. My terminal, my editor, a browser field, this post.
Here is the part that surprised me: the local setup is better than the cloud one it replaced.
The Assumption I Was Wrong About
For a while I ran dictation against a remote Whisper server on my own infrastructure. It worked, technically. It also had a seven second latency and only worked when I was on the home network. Away from home, dead.
So I did what everyone does: I assumed good real-time speech-to-text meant the cloud. Whisper large on a GPU somewhere, or an API with a per-minute price and my voice leaving the building.
Then I actually measured what a local model does on hardware nobody would call fast. An i7-1160G7. Four cores, low power, no discrete GPU, the kind of chip that throttles if you look at it wrong.
The result: real-time factor of 0.07. A three and a half second sentence transcribed in a quarter of a second. Roughly thirteen times faster than real time, on the CPU, with the fan off. German word error rate around three percent, punctuation and capitalization included. It beat the remote server on every axis that mattered: latency, accuracy, and the small detail of working everywhere instead of only at home.
The engine is sherpa-onnx running a German-tuned Parakeet transducer, quantized to int8. The interesting news is not that one model is good. It is that local speech-to-text quietly crossed the line from “toy” to “better than the thing I was paying latency for,” and most people have not noticed yet.
The Architecture Is Boring, On Purpose
The whole thing is three moving parts, and I want it boring because I have to maintain it at 3am someday.
A warm daemon loads the model once and holds it in RAM. When nothing is happening it blocks on a pipe and burns zero CPU. That is the trick for instant activation: the model never reloads, it just waits. The cost is about a gigabyte of resident memory sitting there, which on a 16GB laptop I will happily pay for a feature I use all day.
The microphone only runs while I hold the key. The daemon does not listen on its own. Push the key, a capture process starts and streams raw audio into the pipe; release, it stops and the daemon transcribes the tail. No always-on mic, no ambient recording, no “why is the mic light on.”
The recognized text gets typed into the focused window with wtype. That is what makes it system-wide instead of app-specific. There is no integration, no plugin, no per-app support matrix. It types. Everything that accepts a keyboard accepts my voice.
The Bug That Taught Me To Measure
The first version cut off the beginning of every sentence. Not a little. A lot. I would say “refactor the auth module” and get “the auth module.”
My instinct was to blame the voice activity detector, the model warmup, the pipe. All wrong. I stopped guessing and timed the one thing I had assumed was free: opening the microphone.
sox capturing from PipeWire had a 1.9 second startup latency. Nearly two full seconds between “start recording” and the first sample actually arriving. Every sentence lost its opening because the recorder was still clearing its throat.
Swapping the capture tool to pw-record, which talks to PipeWire natively, dropped that to 45 milliseconds. Forty times faster, and the bug simply evaporated.
The lesson is the oldest one in this job. The bottleneck is never where your intuition points. It is in the boring line you assumed was instant. Measure the thing you are sure about.
If You Can’t Grep It, You Don’t Own It
The last piece is a flat text file. Speech is messy, and I say some things constantly that the model hears wrong. It writes “worklock” when I say “worklog.” So there is a plain, editable list of rules:
worklock => worklog
lachender smiley => 😄
neue zeile => \n
Corrections, emoji on command, punctuation and line breaks by voice. No database, no config UI, no service. The daemon re-reads the file on every activation, so I edit a line and the next sentence uses it. When the model mishears a trigger, I add the mishearing as a rule and move on.
This is the same principle I keep coming back to. The valuable part of a system should be a file you can read, diff, and back up. A voice dictation setup where the vocabulary lives in someone else’s product is a setup you are renting. This one I own, in a text file, on my disk.
The Point
I am not here to sell you a specific model. Models change monthly. The point is that “just use the cloud” stopped being the obvious answer for real work, and a lot of people are still defaulting to it out of habit.
A fanless laptop does real-time German dictation, offline, more accurately than the server I used to route it through. The stack is a warm daemon, a native capture tool, and a text file of rules. Boring, local, mine.
I dictated most of this post.