Speak naturally. Get polished prose. A real-time AI dictation pipeline for macOS.
Somesh Kumar, Mahika Khanna
April 22, 2026
MK-I
None
I built imlistening because I think faster than I type, and I wanted a dictation tool that simply gets out of the way. It’s a sub-700 ms real-time AI dictation pipeline engineered as a tight, two-part system: a native macOS client written in Swift, backed by a local Python server powered by FastAPI. It delivers clean, highly polished text without ever sending a single byte of your data to the cloud.
The pipeline is designed to be frictionless, transitioning from raw audio to polished prose in four steps:
AVAudioEngine and streams it to the backend as binary WebSocket frames in real time. A second tap sends a "done" signal to gracefully end the stream.tiny to large model.receive() loop safely running on the @MainActor, instantly updating the UI's live pipeline tracker before dropping the final text into a persistent JSON history.I designed a two-pane SwiftUI window that feels alive but unobtrusive. It features an animated gradient background—drifting blue, purple, and teal blobs—paired with a frameless, transparent title bar where the macOS traffic lights float cleanly over the dark content.
You're never left guessing what the backend is doing. A three-step indicator (Record → Whisper → Polish) tracks the exact state of the system in real time. You watch each stage transition from pending, to a spinning progress wheel, right through to a satisfying green checkmark.
Every completed recording is saved to a local history.json file and surfaced immediately in the right-hand pane. Each card displays a relative timestamp and your polished text, complete with a one-click copy button.
Want the LLM to write differently? You can rewrite the system instructions directly from the settings panel to change the tone, output format, or correction style. The app persists your prompt in UserDefaults and injects it right into the WebSocket session header.
The app enumerates all of your Core Audio input devices, letting you pinpoint exactly which microphone to use. Once selected, the device is explicitly set on AVAudioEngine via AudioUnitSetProperty before the recording session even begins.
Both the Whisper and LLM models can be swapped out at runtime. The backend dynamically fetches available models from Ollama's /models endpoint to populate the UI pickers. Better yet, Whisper models are kept in memory, so switching sizes means zero reload penalty when you speak again.
Privacy is the default. No audio ever leaves your machine beyond the localhost WebSocket. Whisper runs entirely on-device utilizing your CPU or Apple Silicon (MPS), and Ollama hosts the LLM locally. There are no accounts, no subscriptions, and absolutely no API keys to manage.
macOS client (Swift)
│ AVAudioEngine tap → Float32 PCM
│ URLSessionWebSocketTask (binary frames)
▼
ws://localhost:8000/ws/audio
│
▼
FastAPI backend (Python)
│ PCM → numpy float32 → Whisper ASR
│ raw transcript → Ollama LLM → polished text
│ stage events + result over WebSocket
▼
Swift client receives on @MainActor async loop
│ updates pipeline UI + history pane
Rather than abstracting the network layer away, I kept the WebSocket communication brutally simple and strictly typed. Here is the exact flow of data over the wire during a session:
{"sampleRate":N, "channels":N, "whisperModel":"...", "llmModel":"...", "systemPrompt":"..."}."done" is fired to signal the end of the capture.{"type":"stage", "value":"transcribing"} or "polishing", to drive the UI tracker.{"type":"result", "text":"..."}.Project created — imlistening MK-I
Status: Active