3Voice dictation that never leaves your browser. Press **Record**, talk, and
4watch a document build itself. Edit it as you go, pause and resume, copy it out
5when you're done.
7There is no server and no API key. Speech recognition runs entirely on your
8machine via [transformers.js](https://github.com/huggingface/transformers.js) —
9the model weights are downloaded once from HuggingFace, cached by the browser,
10and every subsequent visit works offline. No audio is ever transmitted.
12## Using it
14- **Record / Pause** — starts and stops listening. Text is appended when you
15 pause between sentences; greyed italic text below the document is the
16 in-progress guess for what you're currently saying.
17- The document is a plain textarea. Type in it, fix mistakes, rearrange —
18 dictation appends to the end and leaves your cursor alone.
19- **Copy** puts the whole document on the clipboard. **Clear** empties it.
20- Everything is saved to `localStorage`, so a reload picks up where you left
21 off. There is exactly one document.
23## Models
25Pick a model from the dropdown; the size shown is the one-time download for the
26backend you're actually going to run on, and already-cached models are marked
27*downloaded*. Your choice is remembered.
29| Model | CPU | WebGPU | Notes |
30| --- | --- | --- | --- |
31| Moonshine Tiny | 32 MB | 79 MB | Fastest; lighter punctuation |
32| Whisper Tiny | 44 MB | 122 MB | Quick; less accurate on hard words |
33| Moonshine Base | 67 MB | 157 MB | Fast and accurate; lighter punctuation |
34| **Whisper Base** | 80 MB | 209 MB | Default — good accuracy and punctuation |
35| Whisper Small | 252 MB | 588 MB | Most accurate; slow without WebGPU |
37The two families behave quite differently on short utterances. Whisper pads
38every clip to 30 seconds, so a two-second phrase costs the same as a full one;
39[Moonshine](https://github.com/usefulsensors/moonshine) scales with the real
40audio length. Measured on one CPU core for a 3-second phrase: Moonshine Tiny
410.17 s, Whisper Tiny 1.3 s, Whisper Base 2.7 s. Whisper punctuates noticeably
42better, which is why it is the default — but if dictation feels sluggish on the
43CPU backend, Moonshine is the fix.
45## Backends
47WebGPU is used automatically whenever the browser offers an adapter, and the
48footer always names the backend actually in use. It is much faster than the CPU
49path, which matters most for the larger Whisper models.
51The catch is download size. The two backends want different quantizations —
52int8 throughout on CPU, versus an fp32 encoder and a 4-bit decoder on WebGPU,
53since int8 matmul is poorly served there — and the 4-bit decoder only quantizes
54matmul weights, leaving the embeddings at full precision. The result is a 2–3×
55larger download on WebGPU, which is why the picker quotes both.
57Detecting an adapter does not guarantee one that works, so a WebGPU failure
58falls back to CPU. That fallback restarts the worker rather than retrying in
59place: once ONNX Runtime has failed to bring up WebGPU, its backend registry
60stays poisoned and a CPU request in the same worker resolves right back to
61WebGPU and fails identically. Phrases spoken during a load or a restart are
62held on the main thread and submitted once a model is ready, so switching
63backends never costs you words.
65## How it works
67`worklet.js` captures microphone audio at 16 kHz. `app.js` runs an energy-based
68voice-activity detector over it that adapts to your room's noise floor: it keeps
69a rolling pre-roll buffer so the start of a word is never clipped, opens an
70utterance when speech begins, and closes it after ~0.9 s of silence. Closed
71utterances go to `worker.js` for transcription and are appended to the document;
72while you are still speaking, the same audio is periodically re-transcribed to
73produce the live interim line.
75Segmentation is tuned to avoid splitting sentences at dramatic mid-sentence
76pauses — short fragments transcribe poorly because the model loses the
77surrounding words it needs for context — and to keep trailing silence, since a
78final fricative is quiet enough to read as silence and trimming into it eats the
79end of the word.
81Only one transcription runs at a time; interim requests are skipped whenever the
82worker is busy, so a slow model degrades to fewer live updates rather than an
83ever-growing backlog. Finished phrases are never skipped — if no model is ready
84yet they queue on the main thread (up to about two minutes of speech) and are
85submitted as soon as one is.
87### Why transformers.js 3.8.1
89Pinned deliberately. In 4.2.0 the int8 path fails to create an ONNX session for
90encoder-decoder ASR models:
92```
93qdq_actions.cc:137 TransposeDQWeightsForMatMulNBits
94Missing required scale: model.decoder.embed_tokens.weight_merged_0_scale
95```
97This affects Whisper and Moonshine alike, so the whole model list is unusable on
984.x. 3.8.1 runs all five correctly.
100## Running locally
102Any static file server will do — there is no build step:
104```bash
105python3 -m http.server 8000
106```
108Then open <http://localhost:8000>. A secure context is required for microphone
109access, which `localhost` and HTTPS both satisfy.
111## Browser support
113Needs `AudioWorklet`, module workers, and WebAssembly — Chrome, Edge, Firefox,
114and Safari 15+ all qualify. WebGPU is used where available and is not required.
115The CPU backend runs single-threaded: multi-threaded WASM needs cross-origin
116isolation headers, which GitHub Pages cannot set.