v0.1 — Zero-latency, fully-local dictation menubar app. Right Shift (via Karabiner) → speak → Right Shift again → text appears in your focused app.
No clipboard. No network. No Python at inference time.
Definition of Done (v0.1): Right Shift dictation (via Karabiner) transcribes local audio via Moonshine-tiny and injects text into the focused app; builds from a clean checkout (
./bin/build); tests pass (./bin/test— 62/62). ✅ Met.The Next steps below are explicitly *post-v0.1_. v0.1 ships the working MVP; those are enhancements, not blockers. Resist treating them as unfinished work.
┌─────────────────────────────────────────────────────────┐
│ Single Swift process (menubar app) │
│ │
│ Karabiner Right Shift → SIGUSR1 toggle │
│ → AudioCapture (64-frame buffers, 1.3ms) │
│ → MoonshineEngine (CoreML encoder ANE + │
│ decoder CPU, Accelerate KV projection) │
│ → Paster (voice-typer keystroke injection) │
│ → HUD overlay (1.5s fade) │
└─────────────────────────────────────────────────────────┘
./bin/setup
This runs two Python scripts:
Scripts/export_models.py — Encoder + KV weights + tokenizerScripts/export_decoder.py — Decoder (cross-KV as inputs, self-KV stateful)Artifacts go to ~/.cache/moonshine-coreml/tiny-streaming/.
Requirements: coremltools, transformers, moonshine-onnx, torch, sentencepiece.
./bin/build
./bin/run
Requires Xcode Command Line Tools (xcode-select --install).
This builds the release binary, installs it to ~/local/bin/voice-engine,
code-signs it, and starts it in the background.
A mic icon appears in the menubar. Trigger dictation with Right Shift via Karabiner to start recording, press again to stop. Text appears in your focused field.
Import karabiner-voice-engine.json in Karabiner-Elements, or add this rule manually:
{
"description": "VoiceEngine: Right Shift toggles dictation",
"manipulators": [
{
"from": { "key_code": "right_shift", "modifiers": { "optional": ["any"] } },
"to": [{ "shell_command": "kill -USR1 $(pgrep -x voice-engine)" }],
"type": "basic"
}
]
}
VoiceEngine itself does not require Accessibility permission.
./.build/release/voice-engine --file test_audio.wav
Measures end-to-end: audio load → encoder → KV projection → decoder loop → token decode.
The production dictation path is a single Swift process:
AudioCapture records microphone input and converts it to mono 16 kHz
Float samples.AppController runs VAD after recording stops. Silent captures are
filtered before inference.MoonshineEngine.transcribeLong splits audio into 10 second windows with a
2 second overlap. Short final windows below the minimum chunk size are
skipped.Paster
injects the final transcript into the focused app.The root Python scripts are audit and benchmark helpers for the same model
artifacts in ~/.cache/moonshine-coreml/tiny-streaming/:
| Script | Purpose |
|---|---|
bench.py | Loads encoder.mlpackage, decoder_stateful.mlpackage, and cross_kv_weights.npz, then reports per-stage timings for one audio window. |
chunk_transcribe.py | Runs long WAV files through bench.py's model wrapper using the same 10 second chunking and overlap-dedup strategy. It validates mono 16 kHz WAV input before model loading, accepting either 16-bit PCM or the Swift app's archived 32-bit float WAVs. |
bench_full.py | Wraps bench.py --json and adds wall-clock and child-process peak RSS measurements. |
bench_libri.py | Runs bench.py over LibriSpeech test-clean WAVs, compares transcripts to LibriSpeech references, and summarizes timings by duration bucket. Defaults to /tmp/librispeech/test-clean; use --wav-dir and --limit for targeted runs. |
export_dataset.py | Exports labeled VoiceEngine recordings from ~/Library/Logs/voice-engine/audio to JSONL and can compare stored transcripts against Whisper. |
chunk_transcribe.py does not run VAD itself; use the Swift app path when
validating VAD behavior. Full Python inference requires ./bin/setup to
populate the CoreML model cache.
| Component | Hardware | Precision | Notes |
|---|---|---|---|
| Encoder | ANE | fp16 | Moonshine-tiny, padded to 10 s window |
| Decoder | CPU | fp32 | Stateful self-attn KV (fp16). Cross-KV is input. |
| KV projection | CPU (Accelerate BLAS) | fp32 | Hidden @ K/V weights per layer |
| Tokenizer | CPU (JSON id→piece) | — | SentencePiece BPE, loaded once at startup |
7 commits
Python
45.0%
Swift
33.3%
C++
15.5%
Shell
5.5%
v0.1 — Zero-latency, fully-local dictation menubar app. Right Shift (via Karabiner) → speak → Right Shift again → text appears in your focused app.
No clipboard. No network. No Python at inference time.
Definition of Done (v0.1): Right Shift dictation (via Karabiner) transcribes local audio via Moonshine-tiny and injects text into the focused app; builds from a clean checkout (
./bin/build); tests pass (./bin/test— 62/62). ✅ Met.The Next steps below are explicitly *post-v0.1_. v0.1 ships the working MVP; those are enhancements, not blockers. Resist treating them as unfinished work.
┌─────────────────────────────────────────────────────────┐
│ Single Swift process (menubar app) │
│ │
│ Karabiner Right Shift → SIGUSR1 toggle │
│ → AudioCapture (64-frame buffers, 1.3ms) │
│ → MoonshineEngine (CoreML encoder ANE + │
│ decoder CPU, Accelerate KV projection) │
│ → Paster (voice-typer keystroke injection) │
│ → HUD overlay (1.5s fade) │
└─────────────────────────────────────────────────────────┘
./bin/setup
This runs two Python scripts:
Scripts/export_models.py — Encoder + KV weights + tokenizerScripts/export_decoder.py — Decoder (cross-KV as inputs, self-KV stateful)Artifacts go to ~/.cache/moonshine-coreml/tiny-streaming/.
Requirements: coremltools, transformers, moonshine-onnx, torch, sentencepiece.
./bin/build
./bin/run
Requires Xcode Command Line Tools (xcode-select --install).
This builds the release binary, installs it to ~/local/bin/voice-engine,
code-signs it, and starts it in the background.
A mic icon appears in the menubar. Trigger dictation with Right Shift via Karabiner to start recording, press again to stop. Text appears in your focused field.
Import karabiner-voice-engine.json in Karabiner-Elements, or add this rule manually:
{
"description": "VoiceEngine: Right Shift toggles dictation",
"manipulators": [
{
"from": { "key_code": "right_shift", "modifiers": { "optional": ["any"] } },
"to": [{ "shell_command": "kill -USR1 $(pgrep -x voice-engine)" }],
"type": "basic"
}
]
}
VoiceEngine itself does not require Accessibility permission.
./.build/release/voice-engine --file test_audio.wav
Measures end-to-end: audio load → encoder → KV projection → decoder loop → token decode.
The production dictation path is a single Swift process:
AudioCapture records microphone input and converts it to mono 16 kHz
Float samples.AppController runs VAD after recording stops. Silent captures are
filtered before inference.MoonshineEngine.transcribeLong splits audio into 10 second windows with a
2 second overlap. Short final windows below the minimum chunk size are
skipped.Paster
injects the final transcript into the focused app.The root Python scripts are audit and benchmark helpers for the same model
artifacts in ~/.cache/moonshine-coreml/tiny-streaming/:
| Script | Purpose |
|---|---|
bench.py | Loads encoder.mlpackage, decoder_stateful.mlpackage, and cross_kv_weights.npz, then reports per-stage timings for one audio window. |
chunk_transcribe.py | Runs long WAV files through bench.py's model wrapper using the same 10 second chunking and overlap-dedup strategy. It validates mono 16 kHz WAV input before model loading, accepting either 16-bit PCM or the Swift app's archived 32-bit float WAVs. |
bench_full.py | Wraps bench.py --json and adds wall-clock and child-process peak RSS measurements. |
bench_libri.py | Runs bench.py over LibriSpeech test-clean WAVs, compares transcripts to LibriSpeech references, and summarizes timings by duration bucket. Defaults to /tmp/librispeech/test-clean; use --wav-dir and --limit for targeted runs. |
export_dataset.py | Exports labeled VoiceEngine recordings from ~/Library/Logs/voice-engine/audio to JSONL and can compare stored transcripts against Whisper. |
chunk_transcribe.py does not run VAD itself; use the Swift app path when
validating VAD behavior. Full Python inference requires ./bin/setup to
populate the CoreML model cache.
| Component | Hardware | Precision | Notes |
|---|---|---|---|
| Encoder | ANE | fp16 | Moonshine-tiny, padded to 10 s window |
| Decoder | CPU | fp32 | Stateful self-attn KV (fp16). Cross-KV is input. |
| KV projection | CPU (Accelerate BLAS) | fp32 | Hidden @ K/V weights per layer |
| Tokenizer | CPU (JSON id→piece) | — | SentencePiece BPE, loaded once at startup |
7 commits
Python
45.0%
Swift
33.3%
C++
15.5%
Shell
5.5%