-
Notifications
You must be signed in to change notification settings - Fork 3
configuration
VoxCtrl uses three configuration files, all stored under ~/.config/voxctrl/.
| File | Format | Purpose |
|---|---|---|
config.json |
JSON | Application settings |
targets.toml |
TOML | Output target definitions |
bindings.toml |
TOML | Hotkey binding definitions |
All files are hot-reloaded — the app watches for external changes and applies them without restart.
Full schema with defaults:
{
"engine": {
"backend": "whisper-cpp",
"whisper_cpp": {
"model_dir": "",
"model_size": "tiny",
"device": "auto",
"threads": 0
},
"moonshine": {
"model_size": "base",
"language": "en"
},
"parakeet": {
"model_size": "tdt-0.6b-v3",
"language": "auto"
},
"remote_openai": {
"endpoint": "http://localhost:8000/v1",
"api_key": null,
"model": "whisper-1",
"language": "",
"timeout_secs": 30
},
"s1_mini": {
"enabled": false,
"styling": "semi-formal"
}
},
"audio": {
"vad_threshold": 0.5,
"input_device_index": null,
"evdev_device": null,
"noise_suppression": false,
"gain": 1.0,
"dynamic_stream": true
},
"ui": {
"show_overlay": true,
"show_command_overlay": true,
"command_overlay_duration_secs": 3,
"overlay_style": "blue_wave",
"overlay_position": "center",
"overlay_monitor": "primary",
"auto_show_settings": false,
"show_notification": false,
"setup_completed": true
},
"features": {
"remove_fillers": true,
"custom_vocabulary": [],
"spoken_punctuation": true,
"auto_format_lists": true,
"snippets": {},
"early_command_detection": true
},
"openai": {
"enabled": false,
"endpoint": "http://localhost:11434",
"api_key": null,
"model": "llama3.2:1b",
"mode": "clean",
"system_prompt": "Fix grammar and punctuation only. Return only the corrected text, no commentary.",
"user_prompt": "{text}",
"timeout_secs": 8
},
"tts": {
"enabled": false,
"engine": "piper",
"voice": "en-us-lessac-medium",
"voice_dir": "",
"stop_key": ["KEY_ESCAPE"],
"response_overlay": true,
"speed": 1.0,
"gpu": false,
"hf_token": null,
"pocket_tts": {
"voice": "alba",
"prewarm": false,
"voice_dir": "",
"gpu": false
},
"inflect_micro": {
"model_dir": "",
"seed": 0,
"noise_scale": 0.667,
"prewarm": false
},
"breeze_tts_2": {
"voice_mode": "prompt",
"speaker_prompt": "A calm and clear female voice speaking at a natural pace",
"cloned_voice": "alba",
"voice_dir": "",
"model_dir": "",
"prewarm": false,
"gpu": false
},
"vox_cpm_2": {
"voice_mode": "prompt",
"speaker_prompt": "A calm young female voice speaking clearly with a gentle tone.",
"cloned_voice": "alba",
"voice_dir": "",
"ultimate_cloning": false,
"model_dir": "",
"prewarm": false,
"gpu": false
},
"memory_mode": "always_loaded",
"idle_unload_secs": 900,
"snippets": {
"VoxCtrl": "Vox Control"
}
},
"mcp": {
"server_enabled": false,
"record_timeout": 15.0,
"visual_feedback": true
}
}The engine config supports four distinct speech-recognition backends.
Top-level fields:
| Key | Type | Values | Description |
|---|---|---|---|
backend |
string |
"whisper-cpp" (default), "moonshine", "parakeet", "remote-openai"
|
Which speech-recognition backend to use. |
whisper_cpp sub-object:
| Key | Type | Default | Description |
|---|---|---|---|
model_size |
string | "tiny" |
Whisper model to load (see valid values below). tiny/tiny.en auto-download silently on first launch; other sizes require an explicit download in Settings → Engine. |
device |
string | "auto" |
Compute device: auto/cpu/cuda/vulkan
|
threads |
integer | 0 |
CPU thread count; 0 = one per physical core |
model_dir |
string | "" |
Custom model directory; empty = ~/.local/share/voxctrl/models/. Supports ~ expansion. The directory must already exist. |
Valid model_size values: tiny, tiny.en, base, base.en, small, small.en, medium, medium.en, large-v2, large-v3, large-v3-turbo
The .en variants are English-only but slightly faster. large-v3-turbo is a distilled model balancing quality and speed.
moonshine sub-object (only used when backend = "moonshine"):
| Key | Type | Default | Description |
|---|---|---|---|
model_size |
string | "base" |
"base" or "tiny"
|
language |
string | "en" |
BCP-47 language code (output label only) |
Build requirement: the Moonshine backend is a default compile-time feature, so a standard build includes it. It links ONNX Runtime, which is fetched at build time — a build made with
--no-default-featuresomits it, and selecting"moonshine"then transparently falls back towhisper-cpp, still using the Whisper model configured above. The Settings → Engine panel shows whether Moonshine is available in the running build.
parakeet sub-object (only used when backend = "parakeet"):
| Key | Type | Default | Description |
|---|---|---|---|
model_size |
string | "tdt-0.6b-v3" |
Parakeet model size (INT8 ONNX) |
language |
string | "auto" |
Target language code |
remote_openai sub-object (used when backend = "remote-openai" — Bring Your Own Voice Engine):
| Key | Type | Default | Description |
|---|---|---|---|
endpoint |
string | "http://localhost:8000/v1" |
URL of the OpenAI-compatible speech-to-text service (e.g. http://192.168.1.50:8000/v1 or https://api.openai.com/v1). |
api_key |
string or null | null |
Optional Bearer token authentication. |
model |
string | "whisper-1" |
Remote model name (e.g. whisper-1, large-v3). |
language |
string | "" |
Language code for transcription, or blank/auto. |
timeout_secs |
integer | 30 |
Request timeout in seconds (min 5, max 300). |
s1_mini sub-object (dictation cleanup processor):
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Enable on-device text normalization using Superwhisper's fine-tuned S1-mini (Qwen3-0.6B) model. |
styling |
string | "semi-formal" |
Text styling passed to S1-mini ("semi-formal", "formal", "casual", etc.). |
Model files: Enabling S1-mini requires downloading
s1-mini-q4_k_m.gguf(~462 MB) andtokenizer.json(~11.4 MB) from Hugging Face (~480 MB download total). When toggled on in Settings → Post-Processing, VoxCtrl automatically checks for these files in~/.local/share/voxctrl/models/s1-mini/and downloads them if missing.
| Key | Type | Default | Description |
|---|---|---|---|
vad_threshold |
float | 0.5 |
Voice Activity Detection sensitivity (0.0–1.0); higher = more sensitive (lower RMS gate) |
input_device_index |
integer or null | null |
CPAL device index; null = auto-detect |
evdev_device |
string or null | null |
Linux evdev keyboard device path for hotkeys, e.g. "/dev/input/event4"
|
noise_suppression |
bool | false |
Run captured audio through RNNoise before transcription. Needs a build with the noisereduce feature (the shipped app has it); see audio.md
|
gain |
float | 1.0 |
Microphone amplification multiplier |
dynamic_stream |
bool | true |
Open mic on-demand (true) vs. always-on (false) |
VAD threshold note: The threshold maps as rms_gate = (1.0 - vad_threshold) * 0.006. At 0.5 (default), the gate threshold is 0.003 RMS. At 1.0 (maximum sensitivity), there is no gate (0.0 RMS). At 0.0 (minimum sensitivity), the gate is 0.006 RMS.
| Key | Type | Allowed | Default | Description |
|---|---|---|---|---|
show_overlay |
bool | true |
Show visual HUD overlay during recording | |
show_command_overlay |
bool | true |
Show temporary UI overlay pill when a voice command trigger is activated | |
command_overlay_duration_secs |
integer |
1–10
|
3 |
Duration in seconds to display the voice command overlay pill |
overlay_style |
string |
"voice_card", "waveform", "pulse", "blue_wave", "mono_bars", "spectrum", "terminal", "vinyl", "none"
|
"blue_wave" |
HUD visualization style |
overlay_position |
string |
"top", "center", "bottom"
|
"center" |
Screen positioning of the overlay window |
overlay_monitor |
string |
"primary" or monitor name |
"primary" |
Specific display screen for visual overlay |
auto_show_settings |
bool | false |
Auto-show Settings window on startup | |
show_notification |
bool | false |
Desktop toast notification after text delivery | |
setup_completed |
bool |
false on a new install |
Whether the first-run wizard has been finished. Absent from a config file written by an earlier VoxCtrl, which is read as true — an existing install has plainly been set up already, and must not be handed a setup wizard on upgrade |
| Key | Type | Default | Description |
|---|---|---|---|
remove_fillers |
bool | true |
Strip filler words (uh, um, hmm, er, ah, etc.) |
spoken_punctuation |
bool | true |
Convert spoken punctuation words to symbols (e.g. "period" → ".") |
auto_format_lists |
bool | true |
Detect "first/second/third" patterns and reformat as a numbered list |
custom_vocabulary |
string[] | [] |
Custom words; VoxCtrl uses fuzzy Levenshtein matching to correct near-matches post-transcription |
snippets |
object | {} |
Short code → expansion map |
early_command_detection |
bool | true |
Transcribe the first 6 seconds of a recording while it is still going, so a voice command shows its overlay and starts loading the TTS model before the hotkey is released. Routing is still decided by the final transcript. Skipped for the remote backend and CPU-only medium/large Whisper models |
Example with snippets:
"features": {
"remove_fillers": true,
"spoken_punctuation": true,
"snippets": {
"addr": "742 Evergreen Terrace, Springfield",
"sig": "Best regards,\nAlice"
}
}LLM post-processing through any OpenAI-compatible API server — a local server or a hosted provider. Exposed in the GUI under Settings → OpenAI API.
Configs written before this section was renamed used the key
ollama; that key is still accepted (via a serde alias) and loads transparently intoopenai.
Each request sends two chat messages: the system prompt (how to transform the
text) and the user prompt (the message itself). The user prompt must contain
{text}, which is replaced with the dictated speech.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Enable LLM post-processing |
endpoint |
string | "http://localhost:11434" |
OpenAI-compatible API base URL. A /v1 suffix is optional — it is added automatically when missing (e.g. requests go to {endpoint}/v1/chat/completions). |
api_key |
string or null | null |
API key sent as a Bearer token. Required by most remote providers; usually unnecessary for a local server. |
model |
string | "llama3.2:1b" |
Model name |
mode |
string | "clean" |
Preset that fills the system prompt in the GUI: clean/formal/casual/bullet/concise/custom. Built-in presets are read-only in the GUI; choose custom to edit system_prompt/user_prompt. Generation itself is driven by system_prompt/user_prompt. |
system_prompt |
string | "Fix grammar and punctuation only…" |
System message describing the transformation. Empty = no system message. |
user_prompt |
string | "{text}" |
User message template. Must contain {text}, replaced with the dictated speech. |
timeout_secs |
integer | 8 |
HTTP request timeout in seconds |
The legacy
custom_promptfield (used whenmodewascustom) is migrated intouser_promptautomatically the first time an old config is loaded.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Enable TTS subsystem |
engine |
string | "espeak" |
Synthesis engine: "breeze_tts_2", "vox_cpm_2", "piper", "pocket_tts", "inflect_micro", or "espeak"
|
voice |
string | "en-us-lessac-medium" |
Active Piper voice name (hyphen-delimited, e.g. "en-us-ryan-high") |
voice_dir |
string | "" |
Directory for Piper voice files; empty = ~/.local/share/voxctrl/piper-voices/. Supports ~ expansion. |
stop_key |
string[] | ["KEY_ESCAPE"] |
Keys that cancel current TTS playback |
response_overlay |
bool | true |
Show overlay indicator while TTS is speaking |
speed |
float | 1.0 |
Speech synthesis speed multiplier (0.5 – 2.0); not used by Pocket-TTS |
gpu |
bool | false |
Enable GPU acceleration (CUDA) for Piper |
breeze_tts_2 |
object | Breeze-TTS-2 engine sub-configuration (see below) | |
vox_cpm_2 |
object | VoxCPM2 engine sub-configuration (see below) | |
pocket_tts |
object | Pocket-TTS engine sub-configuration (see below) | |
inflect_micro |
object | Inflect-Micro-v2 engine sub-configuration (see below) | |
memory_mode |
string | "always_loaded" |
"always_loaded" keeps the model resident for the session; "on_demand" loads it when it is needed and unloads it again after idle_unload_secs of no use. Affects the model-backed engines (Pocket-TTS, Breeze-TTS-2, VoxCPM2, Inflect-Micro-v2) — Piper and eSpeak hold no model between utterances. |
idle_unload_secs |
int |
900 (15 min) |
Idle time before the model is unloaded in "on_demand" mode. The countdown restarts on every use. Values below 30s are clamped to 30s. |
snippets |
object | Word/phrase pronunciation overrides expanded prior to synthesis (e.g. {"VoxCtrl": "Vox Control"}) |
breeze_tts_2 sub-object:
Breeze-TTS-2 is a bilingual speech generation model with reference voice cloning, released under the BreezeBlue Research and Non-Commercial License. VoxCtrl runs it through audio.cpp; the GGUF model mirror is ungated, so no HuggingFace token is needed to download it — Settings and the setup wizard still show the non-commercial license warning before you do. The model also advertises natural-language Voice Design prompts, but audio.cpp doesn't reliably apply them, so the UI only offers Voice Cloning (speaker_prompt/voice_mode: "prompt" remain in the config for compatibility).
| Key | Type | Default | Description |
|---|---|---|---|
voice_mode |
string | "prompt" |
"prompt" for Voice Design, "clone" to use a reference audio clip from voice_dir
|
speaker_prompt |
string | "A calm and clear female voice speaking at a natural pace" |
Natural-language prompt describing the desired speaker voice for Voice Design |
cloned_voice |
string | "alba" |
Voice ID from the shared voice folder, used in "clone" mode |
voice_dir |
string | "" |
Directory holding reference clips; empty = ~/.local/share/voxctrl/cloned-tts-voices/
|
model_dir |
string | "" |
Directory holding the GGUF model; empty = ~/.local/share/voxctrl/models/breeze-tts-2/
|
prewarm |
bool | false |
Pre-warm the model on startup for faster first synthesis (see tts.md) |
gpu |
bool | false |
Run synthesis on the GPU via Vulkan; falls back to the CPU whenever no usable Vulkan device is found |
vox_cpm_2 sub-object:
VoxCPM2 is an Apache-2.0 open-weight model by OpenBMB offering high quality 24 kHz speech synthesis with Voice Design and Voice Cloning.
| Key | Type | Default | Description |
|---|---|---|---|
voice_mode |
string | "prompt" |
"prompt" for Voice Design, "clone" for reference voice clip cloning |
speaker_prompt |
string | "A calm young female voice speaking clearly with a gentle tone." |
Natural-language prompt for Voice Design |
cloned_voice |
string | "alba" |
Reference voice clip ID from voice_dir
|
voice_dir |
string | "" |
Directory holding custom .wav reference clips; empty = ~/.local/share/voxctrl/cloned-tts-voices/
|
ultimate_cloning |
bool | false |
Enables Ultimate Cloning when paired audio + transcript files are available |
model_dir |
string | "" |
Directory holding the GGUF model; empty = ~/.local/share/voxctrl/models/voxcpm2/
|
prewarm |
bool | false |
Pre-warm the model on startup for faster first synthesis (see tts.md) |
gpu |
bool | false |
Enable Vulkan GPU acceleration |
pocket_tts sub-object:
Pocket-TTS's built-in voices each resolve to a precomputed embedding shipped
alongside the model; a custom .wav clip dropped into voice_dir is instead
cloned live. VoxCtrl runs it through audio.cpp — the GGUF model mirror is ungated, so no HuggingFace token is needed.
| Key | Type | Default | Description |
|---|---|---|---|
voice |
string | "alba" |
Bundled voice ID ("alba", "anna", "vera", "charles", "michael"), or the filename stem of a custom clip in voice_dir
|
prewarm |
bool | false |
Pre-warm the model on startup for faster first synthesis (see tts.md) |
voice_dir |
string | "" |
Directory scanned for custom .wav voice clips; empty = ~/.local/share/voxctrl/cloned-tts-voices/. Drop a <id>.wav file in to add it to the voice list — naming it after a built-in voice (e.g. alba.wav) overrides that voice's embedding and clones from the clip instead. Supports ~ expansion. |
gpu |
bool | false |
Enable Vulkan GPU acceleration |
inflect_micro sub-object:
Inflect-Micro-v2 is a ~9.4M-parameter VITS-family
ONNX model with a single fixed English voice at 24 kHz, so it has no voice setting. Requires the
inflect-micro cargo feature at build time and espeak-ng at runtime; speaking rate comes from the
shared tts.speed. See tts.md for the full engine notes.
| Key | Type | Default | Description |
|---|---|---|---|
model_dir |
string | "" |
Directory holding the ONNX graphs and phoneme vocabulary; empty = ~/.local/share/voxctrl/models/inflect-micro/. Point at an existing copy to skip downloading. Supports ~ expansion. |
seed |
int | 0 |
Sampling seed. The model is deterministic for a fixed seed, so repeated synthesis of the same text is identical. |
noise_scale |
float | 0.667 |
Latent sampling temperature (0.0 – 1.0) — higher is more varied, lower is flatter |
prewarm |
bool | false |
Load the ONNX graphs on startup so the first synthesis has no load delay |
| Key | Type | Default | Description |
|---|---|---|---|
server_enabled |
bool | false |
Start the MCP socket server on launch |
record_timeout |
float | 15.0 |
How long transcribe_voice listens when the MCP client passes no timeout_seconds. Read per call, so a change applies without restarting the server; a non-positive value falls back to 15.0
|
visual_feedback |
bool | true |
Show overlay indicator while MCP server is listening to microphone |
VoxCtrl never checks for updates on its own — there is nothing to configure.
Settings → General → "Check for updates" sends the only network request
VoxCtrl ever makes unasked: an unauthenticated GET to
api.github.com/repos/JRufer/VoxCtrl/releases/latest, with a User-Agent of
VoxCtrl/<version> and nothing else — no machine, user or install identifier.
See privacy.md.
Each output destination is a [[target]] block.
[[target]]
id = "default"
label = "Focused Window"
delivery = "inject"[[target]]
id = "my_target"
label = "My Target"
delivery = "inject"
# Text formatting
strip_newlines = false # Default: false. Replaces newlines with spaces and strips \r (inject and command targets)
# Per-target post-processing overrides (all optional; null = inherit global)
[my_target.processing]
remove_fillers = true
spoken_punctuation = true
auto_format_lists = true
code_mode = falsefile:
delivery = "file"
file_path = "~/Documents/notes.md"
file_prefix = "- "
file_timestamp = true # Default: true
file_timestamp_format = "%Y-%m-%dT%H:%M:%SZ" # strftime pattern, rendered in UTC
file_mode = "append" # "append" or "write"http:
delivery = "http"
http_url = "http://localhost:8080/voice"
http_method = "POST" # Default: "POST"
# Optional: custom headers and JSON templatewebhook:
delivery = "webhook"
webhook_url = "https://example.com/hook"
webhook_secret = "my-hmac-secret"Note: webhook uses webhook_url, while http uses http_url.
exec:
delivery = "exec"
command = "/usr/local/bin/handle-voice.sh"socket (supports Unix and TCP):
delivery = "socket"
socket_unix = "/tmp/myapp.sock" # Unix domain socket
# OR:
socket_host = "127.0.0.1"
socket_port = 9000pipe:
delivery = "pipe"
pipe_path = "/tmp/voice.fifo"speak:
delivery = "speak"command — Voice Command Router (routes speech based on spoken target names):
delivery = "command"See Output Routing for dynamic keyword parsing rules and conversational lead-in examples.
chat — conversational LLM over an OpenAI-compatible API, with history:
delivery = "chat"
chat_url = "http://localhost:8080" # /v1 suffix optional
chat_model = "hermes-4-14b"
chat_system_prompt = "You are a concise voice assistant."
chat_reply_mode = "speak" # speak | inject | clipboard | none
chat_max_history = 20 # messages sent per turn; 0 = whole conversation
chat_timeout_secs = 120
chat_reset_phrase = "new conversation" # optional spoken phrase that clears history
# chat_api_key = "sk-..." # optional bearer tokenSee Output Routing for details.
TTS response pipe:
response_pipe = "/tmp/tts-response.fifo" # Optional FIFO for TTS outputEach hotkey is a [[binding]] block.
[[binding]]
id = "dictate_hold"
label = "Dictate (Hold)"
keys = ["KEY_LEFTMETA", "KEY_SPACE"]
gesture = "hold"
target_ids = ["default"]
hold_threshold_ms = 200 # Default: 200ms min hold to register
disabled = false
openai_enabled = true # Enable LLM rewrite specifically for this hotkey
openai_mode = "formal" # Rewrite output in formal style for this hotkey
[[binding]]
id = "dictate_notes"
label = "Dictate to Notes + Clipboard"
keys = ["KEY_LEFTCTRL", "KEY_LEFTSHIFT", "KEY_N"]
gesture = "toggle"
target_ids = ["notes", "clipboard"]
[[binding]]
id = "quick_code"
label = "Code Dictation (Double-tap)"
keys = ["KEY_F12"]
gesture = "double_tap"
tap_ms = 250 # Default: 250ms inter-tap window
target_ids = ["code_editor"]
[[binding]]
id = "double_tap_hold_dictate"
label = "Double-Tap & Hold to Dictate"
keys = ["KEY_LEFTMETA", "KEY_SPACE"]
gesture = "double_tap_hold"
tap_ms = 300
hold_threshold_ms = 200 # Hold threshold on the second tap
target_ids = ["default"]
[[binding]]
id = "toggle_dictation"
label = "Toggle Dictation"
keys = ["KEY_LEFTCTRL", "KEY_LEFTMETA", "KEY_SPACE"]
gesture = "toggle"
target_ids = ["default"]| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id |
string | Yes | Unique identifier | |
label |
string | Yes | "" |
The target's name; also the phrase that routes dictation here through a command target |
keys |
string[] | Yes | Key names (evdev format, on every platform). Any number of modifiers plus exactly one regular key — see Hotkeys | |
gesture |
string | Yes |
"hold", "toggle", "double_tap", or "double_tap_hold"
|
|
target_ids |
string[] | Yes | Ordered list of target IDs to route to | |
target_id |
string | No | Single target (legacy; use target_ids) |
|
hold_threshold_ms |
integer | No | 200 |
Min hold duration in ms for hold / double-tap-hold gesture |
tap_ms |
integer | No | 300 |
Longest gap between the first tap's release and the second tap's press, in ms |
disabled |
bool | No | false |
Disable without deleting |
openai_enabled |
bool | No | null |
Enable/disable LLM post-processing specifically for this hotkey (null = inherit global config) |
openai_model |
string | No | null |
LLM model override specifically for this hotkey |
openai_mode |
string | No | null |
LLM mode override specifically for this hotkey (clean/formal/casual/bullet/concise/custom) |
openai_prompt |
string | No | null |
User prompt template override for this hotkey (must contain {text}) |
openai_system_prompt |
string | No | null |
System prompt override for this hotkey (empty inherits the global default) |
s1_mini_enabled |
bool | No | null |
Enable/disable S1-mini dictation cleanup specifically for this hotkey (null = inherit global engine config) |
The per-hotkey field names were renamed from
ollama_*toopenai_*; the legacyollama_*names are still accepted via serde aliases.
VoxCtrl auto-migrates older config formats on load. Known migrations:
-
features.show_notification→ui.show_notification(moved in an early release; the migrated config is immediately re-saved to disk to clean up the old key)
Unrecognized fields are silently ignored. Missing fields use their defaults. This ensures compatibility when upgrading or downgrading.