Push-to-talk voice-to-text for Windows. Local transcription uses NVIDIA Parakeet TDT 0.6B v3 on CPU or Qualcomm Hexagon NPU, plus native Whisper Large v3 Turbo on the Snapdragon X Elite NPU. Optional LLM cleanup via GitHub Copilot (Claude Haiku 4.5, GPT-5 Mini, GPT-4.1) or any OpenAI-compatible endpoint.
Microsoft Store · Website · Releases · NPU model on HF · macOS sibling
Easiest: get it from the Microsoft Store. One click, it installs the right build for your CPU automatically, keeps itself updated, and there's no SmartScreen warning.
Prefer a direct download? Pick the build for your CPU, both per-user (no admin / UAC required):
| Your machine | Build | Engine |
|---|---|---|
| Snapdragon X Elite (Surface Pro 11, etc.) | …-arm64-… |
Parakeet CPU/NPU and Whisper NPU |
| Intel / AMD laptop | …-x64-… |
CPU INT8 |
Not sure? Snapdragon laptops report "ARM-based processor" in Settings → System → About. Everything else is x64.
Installer. Download
openwritr-windows-<arch>-vX.Y.Z-setup.exe from
Releases
and run it. Sets up the Start Menu shortcut, an optional autostart-at-logon
entry, and a proper uninstaller you'll find under Settings → Apps.
Portable zip. Download openwritr-windows-<arch>-vX.Y.Z.zip and unzip
it into any folder you like (e.g. C:\Tools\OpenWritr\), then run
openwritr.exe. The app finds its DLLs next to the exe — the install
location doesn't matter. Same binaries as the installer, just no shortcuts
and no autostart.
Note: user data (settings, downloaded models, logs) always lives under
%LOCALAPPDATA%\OpenWritr\— the app creates that folder automatically on first run, you never need to create it yourself.AppDatais a hidden folder; if you want to look inside, paste%LOCALAPPDATA%\OpenWritrinto the Explorer address bar.
Logs are under %LOCALAPPDATA%\OpenWritr\logs. Tray actions Open logs
and Export diagnostics provide bounded logs plus redacted runtime/model
status; diagnostics never include audio, transcript text, clipboard contents,
or API keys.
The x64 build runs Parakeet on the CPU (no Hexagon NPU on Intel/AMD); the arm64 build adds the NPU engine. Both share the same multilingual model and UX.
Windows SmartScreen warning. The binaries are not code-signed (yet), so the first launch shows "Windows protected your PC". Click More info → Run anyway. To verify your download is authentic, compare its SHA-256 against
SHA256SUMS.txtattached to the release:Get-FileHash .\openwritr-windows-<arch>-vX.Y.Z-setup.exein PowerShell.
The default engine is Parakeet CPU. Its verified model files are fetched on
first use into %LOCALAPPDATA%\OpenWritr\models\ (~670 MB). Selecting an NPU
engine downloads its own pinned assets with visible progress and verification:
Parakeet NPU adds ~650 MB; Whisper NPU downloads a ~2.0 GB archive and uses
about 2.2 GB after extraction.
| Combo | Action |
|---|---|
| Hold Ctrl + Win | Record. Release to transcribe and paste at the caret. |
| Press or release Shift while recording | Toggle cleanup live in Additional Shift mode; the overlay changes between Listening and Enhanced immediately. |
| Press or release Shift while recording in Always mode | Bypass or restore cleanup for the current recording. |
| Tray right-click → Settings | Change hotkey, engine, LLM provider. |
A small dark pill appears at the bottom-center of the primary monitor. Its waveform and current state share one centered line, distinguishing raw and enhanced listening, queued work, transcription, enhancement, success, safe raw fallbacks, provider/credential warnings, and errors. Active recording always takes priority over background job status. Settings changes take effect after saving — no restart required.
Tray icon → right-click → Settings. All fields:
- Recording shortcut: Ctrl / Shift / Alt / Win modifiers plus an optional trigger key. Modifier-only shortcuts require at least two modifiers; F13-F20 may be used alone. The dialog shows a live shortcut preview and prevents ambiguous Shift-enhancement combinations. With Ctrl+Win held, Shift can be pressed or released during the recording to change the enhancement intent immediately. Default: Ctrl+Win.
- Transcription engine: Parakeet CPU INT8 (default), Parakeet NPU, or Whisper Large v3 Turbo NPU. Each choice shows hardware availability, first-use download size, and local cache status. NPU engines remain visible but disabled unless the ARM64 build is running on Snapdragon X Elite.
- Output and feedback: either paste into the active app or keep the transcript on the clipboard. Auto-paste is tied to the foreground top-level window captured when recording starts. If focus or the target process changes, or another recording is active when the job completes, OpenWritr does not inject keystrokes: it copies the final text to the clipboard and shows a warning. Copy-only mode always remains explicit. The overlay option covers both the recording waveform and typed job/status messages. Start/stop sounds are optional.
- Text enhancement: choose Never, When Shift is additionally held,
or Always, then select GitHub Copilot or an OpenAI-compatible API.
Shift is monitored throughout the recording, so the mode and purple overlay
indicator update immediately instead of being fixed at the initial key
press. In Always mode, additional Shift bypasses cleanup while held. The
model picker provides advisory presets while preserving
arbitrary provider-specific model or deployment IDs and legacy settings.
GitHub mode checks that
ghis installed and authenticated. OpenAI-compatible mode requires a valid base URL, model ID, and API key. API keys are stored in Windows Credential Manager, not in the settings file. Cleanup prompts can be customized per exact provider, endpoint, and model target; transcript text stays a separate untrusted user message. Provider/credential failures use the raw transcript with a provider warning. Empty or critically changed output, or an integrity-validator failure, also falls back to the raw transcript with a distinct safety warning. - Advanced: set the maximum recording duration.
- Explanatory settings copy is kept behind immediate
ihover help so the dialog remains compact; warnings, failures, and live availability remain visible. - About, credits & support (also available directly from the tray menu): view the installed version and architecture, official website, Microsoft Store, source, releases, issue reporting, privacy and license links, model/runtime credits, local third-party notices, and guidance for exporting privacy-safe diagnostics.
Settings are stored at %LOCALAPPDATA%\OpenWritr\settings.json. The app polls
the file's mtime so external edits also take effect live. Writes use an atomic
replacement, and legacy plaintext API keys are migrated only after the secure
credential can be written and read back successfully. Unsaved changes require
confirmation before the Settings window closes.
openwritr-windows/
├── src/
│ ├── main.rs entry; dispatches `--settings` subprocess
│ ├── app.rs winit event loop, tray, hotkey thread, ASR dispatch
│ ├── audio.rs cpal WASAPI capture, multi-channel downmix
│ ├── credentials.rs Windows Credential Manager API-key storage
│ ├── hotkey.rs push-to-talk combo polling against key_hook state
│ ├── key_hook.rs global WH_KEYBOARD_LL hook → atomic key bitmap
│ ├── overlay.rs custom Win32 layered top-most window, GDI bars
│ ├── settings.rs validation, migration, atomic JSON persistence
│ ├── settings_ui.rs eframe/egui dialog (subprocess)
│ ├── asr/ ONNX Runtime ASR pipelines
│ │ ├── parakeet.rs CPU/NPU Parakeet pipeline
│ │ ├── whisper_npu.rs native chunked Whisper NPU engine
│ │ ├── whisper_mel.rs 128-bin Whisper log-mel frontend
│ │ ├── whisper_decoder.rs QNN encoder/decoder and KV-cache loop
│ │ └── qnn_ffi.rs reusable typed QNN C-API sessions
│ ├── enhance.rs Copilot / OpenAI cleanup pass
│ ├── sounds.rs G3/E3 tone synth (start/stop pings)
│ └── bin/
│ ├── package.rs distributable-zip builder
│ └── cleanup_eval/ opt-in offline/live evaluator for the `cleanup`
│ core (see Development section below)
└── Cargo.toml
Key design decisions:
- Global low-level keyboard hook. Push-to-talk detection reads physical
key state from
WH_KEYBOARD_LLinstead ofGetAsyncKeyState. The OS synthesises key-ups during focus changes (PowerShell launched mid-recording, UAC prompt, system shortcut handler), which the polling API faithfully reports — and which would abort the recording. The LL hook sees only physical events. - Settings UI as subprocess. The settings dialog is the same exe re-launched
with
--settings. Spawning happens from a worker thread becauseCreateProcessWon Windows ARM64 with Defender real-time scanning can block several seconds — doing it inline would freeze the tray pump. - Overlay on its own message loop. Layered top-most window with color-key transparency, painted with double-buffered GDI. It reads the recorder atomics and receives enable/status updates through a command channel, so settings changes apply without restarting the app.
- Multi-channel downmix in the audio callback. The Qualcomm Aqstic mic array on Surface Pro exposes 4-8 interleaved channels at 48 kHz; we average to mono before resampling to 16 kHz.
NPU model preparation lives in scripts/. build_npu_encoder.py,
aihub_compile_encoder.py, wrap_qnn_context_binary.py, and
test_npu_encoder.py are build-time tools used to produce the .bin
hosted on HF. They are NOT invoked by the shipped openwritr.exe at
runtime — the native build pulls the pre-compiled binary directly.
The Rust app does not call into Python at runtime.
Python is used only at build time to fetch hash-pinned runtime wheels and validate the canonical release manifest. The resulting packages are self-contained and Python-free.
.\scripts\envup.ps1
cargo build --release --bins
cargo run --release --bin packageThe package command fetches the exact runtime tuple, validates versions,
SHA-256 hashes, PE architecture, required files, and licenses, then stages
from release-manifest.json. The ZIP lands in target/dist/.
Parakeet runs its pre-compiled encoder on the Hexagon HTP; mel preprocessing and the TDT decoder stay on CPU. Whisper runs both its encoder and autoregressive KV-cache decoder as pre-compiled QNN sessions. It processes sequential 30-second chunks, detects language once per recording, and reuses that language for every later chunk.
The compiled binary expects a fixed 8-second audio window. For push-to-talk utterances ≤ 8 s the encoder is run once on a padded window. For longer audio the encoder is run in chunks (8 s window with 1 s overlap), feature streams are stitched at the seam, and the TDT decoder runs once over the concatenated features. Tested up to ~23 s without boundary doubling.
| Audio length | Decode (preproc + encode + TDT) | × Realtime | Chunks |
|---|---|---|---|
| 3 s | 128 ms | 23× | 1 |
| 5.8 s | 221 ms | 26× | 1 |
| 16.4 s | 375 ms | 44× | 3 |
| 23.0 s | 626 ms | 37× | 4 |
The encoder itself is ~67 ms ± 0 ms per 8-second window, independent of the actual audio length within the window.
scripts/build_npu_encoder.pyconstant-folds the encoder's dynamic-shape attention mask (Shape → Gather → Range → Expand) against a frozen[1, 128, 801]input — the HTP backend cannot evaluate that subgraph as-is.scripts/aihub_compile_encoder.pysubmits the static-shape FP32 ONNX plus FLEURS calibration samples to Qualcomm AI Hub. Quantize job uses INT8 weights / INT16 activations (the standard HTP recipe for transformer encoders); compile job targetssnapdragon_x_elite_crdwith--target_runtime qnn_context_binary --truncate_64bit_io.scripts/wrap_qnn_context_binary.pywraps the resulting.binin a 408-byte EPContext-node ONNX so ORT's QNN EP can consume it.src/asr/qnn_ffi.rsloads the wrapper via directort_sysC-API calls, bypassingort2.0-rc.12's session builders (which crash inside QnnHtp when consuming EPContext-wrapper ONNX).
The QNN backend loads several sibling DLLs by name at session-create time.
scripts/fetch_runtime.py extracts the exact, hash-pinned set recorded in
runtime-manifest.json. Alongside onnxruntime_providers_qnn.dll, the
X Elite path requires QnnHtp.dll, QnnHtpPrepare.dll,
QnnHtpNetRunExtensions.dll, QnnSystem.dll, the V73 stub and skeleton,
and its catalog file. Without the SKEL + .cat pair, the stub fails
LoadLibrary with ERROR_MOD_NOT_FOUND (126) and QnnHtp later aborts
session creation with STATUS_STACK_BUFFER_OVERRUN (0xC0000409) without
a useful error.
| Model | Provider | Size | License | Auto-downloaded? |
|---|---|---|---|---|
| Parakeet TDT 0.6B v3 (CPU INT8 ONNX + companion files) | istupakov/parakeet-tdt-0.6b-v3-onnx | ~670 MB | CC-BY-4.0 | Yes, on first run |
| Parakeet TDT 0.6B v3 NPU encoder (QAIRT context binary + wrapper) | trsdn/parakeet-tdt-0.6b-v3-htp-int8-8s | ~632 MB | CC-BY-4.0 | Yes, on first NPU launch |
| Whisper Large v3 Turbo (QNN encoder, decoder, tokenizer) | qualcomm/Whisper-Large-V3-Turbo | ~2.2 GB extracted | Apache 2.0 + BSD-3 | Yes, on first Whisper launch |
The NPU encoder model is device-gated to Snapdragon X Elite (Hexagon V73). It will not run on X Plus or any other Qualcomm chipset without recompilation via AI Hub.
- OpenWritr code: MIT — see
LICENSE. - Parakeet model: CC-BY-4.0 (NVIDIA). Attribution preserved when the model is downloaded.
- Qualcomm QNN runtime DLLs (
QnnHtp.dll, QNN provider, V73 stub, etc., bundled in the release zip): Qualcomm AI Engine Direct redistributable license. The full text ships inside every release zip underthird-party-licenses/Qualcomm_LICENSE.pdf, alongside Microsoft'sThirdPartyNotices.txtfor theonnxruntime-qnnPyPI package the DLLs come from. These DLLs are redistributable as part of applications targeting Qualcomm Snapdragon hardware, which is what OpenWritr does. - ONNX Runtime DLLs (
onnxruntime.dll,onnxruntime_providers_qnn.dll): MIT (Microsoft), bundled under their respective LICENSE files in the release zip.
openwritr-windows/
├── src/ Rust native app
├── scripts/ pinned runtime and release tooling
├── models-manifest.json pinned model assets and hashes
├── runtime-manifest.json pinned ORT/QNN wheels and compatibility tuple
├── release-manifest.json canonical per-architecture package contents
└── target/ gitignored build and staging output
The shipped openwritr.exe does not call Python at runtime.
git clone https://github.com/trsdn/openwritr-windows.git
cd openwritr-windows
.\scripts\envup.ps1
cargo build --release --bins
python scripts/fetch_runtime.py --arch arm64
python scripts/prepare_release.py --arch arm64
target\stage\arm64\openwritr.exe --self-checkFor x64 packaging, stage the CPU runtime with:
python scripts/fetch_runtime.py --arch x64See docs/RUNTIME_COMPATIBILITY.md for
the shared ORT/QNN/QAIRT contract and its Snapdragon hardware gates.
src/bin/cleanup_eval/ is a focused, opt-in evaluator for the cleanup
core (prompt resolution, request/response handling, normalization, and the
integrity validator) — it reuses those production modules directly rather
than reimplementing them. It replays a committed, versioned, fully
synthetic DE/EN fixture corpus
(fixtures/cleanup-eval/v1/corpus.json)
covering punctuation, fillers, repetition, negation, digits/dates/times/
versions, commands/code/acronyms, URLs/emails, language preservation,
filler-only/[[EMPTY]], and known tricky cases. No private dictation is
included or ever required.
# Deterministic offline mode (default): no network, safe for CI.
cargo run --bin cleanup_eval
cargo run --bin cleanup_eval -- --json --category punctuation
# Focused tests: corpus versioning, expected decisions, no private fields
# in the serialized report, and production-module reuse.
cargo test --bin cleanup_evalLive-provider mode is entirely opt-in (--live) and never required by tests
or releases: it reads the same settings.json provider/model/base URL and
the same credential storage the app uses (GitHub CLI token or Windows
Credential Manager), but never prints secrets, transcripts, prompts, or raw
candidate text. Every report — offline or live — includes the model/
provider, canonical endpoint scope, prompt-profile/validator/corpus
version, latency, failure/integrity category counts, and punctuation/
filler/repetition health scores, with all content redacted by default:
cargo run --bin cleanup_eval -- --live --provider github_copilotRun cargo run --bin cleanup_eval -- --help for the full flag list.
Tagged releases live at
github.com/trsdn/openwritr-windows/releases.
Each architecture ships a ZIP and installer generated from the same validated
release stage. CI also builds an unsigned Store .msixbundle as a workflow
artifact for Partner Center ingestion, but does not attach it to the public
GitHub release. Tagged builds use the repository variables
MSIX_IDENTITY_NAME and MSIX_PUBLISHER when configured; otherwise the Store
bundle is skipped rather than publishing a placeholder identity. Every package
includes an artifact-manifest.json; CI verifies all required file hashes and
confirms that x64 artifacts contain no Qualcomm runtime files.