Speech-to-text (STT) and text-to-speech (TTS) domain. Exposes the openhuman.voice_* RPC namespace for transcription, synthesis, availability checks, provider configuration, agent reply-speech (with mascot lip-sync visemes), and a standalone voice dictation server (hotkey → record → transcribe → insert text). Routing between cloud (hosted backend proxy) and local engines (whisper.cpp / Piper) is decided by a provider factory driven by config. The low-level inference implementations themselves now live under crate::openhuman::inference::voice and are re-exported through this module's surface for back-compat.
- Transcribe audio (file path, raw bytes, or base64) via whisper.cpp (local) or the hosted backend STT proxy, with optional LLM cleanup and hallucination filtering.
- Synthesize speech via local Piper, the hosted ElevenLabs proxy, or third-party providers (OpenAI-compatible / Deepgram / ElevenLabs) keyed by slug.
- Resolve effective STT/TTS providers from config and construct boxed
SttProvider/TtsProvidertrait objects (the factory). - Run a hotkey-driven dictation server: capture mic audio, gate on duration/silence/hallucination, transcribe, then either deliver via Socket.IO (when OpenHuman is focused) or paste into the active external app.
- Provide a core-side dictation hotkey listener that broadcasts press/release and transcription events to the Socket.IO bridge.
- Synthesize agent replies with Oculus-15 viseme alignment for mascot lip-sync.
- Persist STT/TTS provider selection and the voice provider registry into config.
- Report STT/TTS binary + model availability and the active provider selection.
| File | Role |
|---|---|
mod.rs |
Module docstring + exports; re-exports inference-side voice submodules (cloud_transcribe, hallucination, local_speech, postprocess, streaming); defines cloud_transcribe_default_model() ("whisper-v1"). |
types.rs |
RPC DTOs: VoiceSpeechResult, VoiceTtsResult, VoiceStatus + From<LocalAi*> conversions. |
ops.rs |
Business logic returning RpcOutcome<T>: voice_status, voice_transcribe, voice_transcribe_bytes, voice_tts, normalize_extension. |
schemas.rs |
Controller schemas, registry exports, and all handle_voice_* / handle_overlay_stt_notify RPC handlers (param structs, provider-string validation, voice_list_models presets, voice_test_provider). |
factory.rs |
SttProvider / TtsProvider traits; cloud/whisper/piper/external implementations; create_stt_provider / create_tts_provider; effective_*_provider; slug:model parsing; DEFAULT_WHISPER_MODEL, DEFAULT_PIPER_VOICE, WHISPER_MODEL_PRESETS. |
server.rs |
The VoiceServer dictation runtime: hotkey event loop, recording lifecycle, duration/silence/hallucination gates, background processing, global singleton (global_server / try_global_server / start_if_enabled / run_standalone). |
hotkey.rs |
rdev-based global hotkey listener; ActivationMode (Tap/Push), HotkeyEvent, HotkeyCombination, parse_hotkey, start_listener. |
audio_capture.rs |
cpal mic capture → 16 kHz mono WAV bytes; RecordingHandle, silence-gate ring buffer, peak-RMS reporting. |
text_input.rs |
Clipboard-paste text insertion (insert_text) — writes clipboard then simulates Cmd/Ctrl+V via enigo, restoring prior clipboard. |
dictation_listener.rs |
Core-side dictation broadcast bus: DictationEvent, publish_dictation_event / subscribe_dictation_events, publish_transcription / subscribe_transcription_results, rdev listener lifecycle (start_if_enabled / stop), normalize_hotkey_for_rdev. |
reply_speech.rs |
Agent reply synthesis via backend /openai/v1/audio/speech; ReplySpeechResult, VisemeFrame, AlignmentFrame, ReplySpeechOptions, synthesize_reply, tolerant response normalization. |
cli.rs |
openhuman voice / openhuman dictate subcommand adapter — runs a blocking standalone dictation server (domain-owned, since it blocks forever and doesn't fit the controller registry). |
*_tests.rs |
Sibling test suites: audio_capture_tests.rs, schemas_tests.rs, server_tests.rs (wired via #[path = ...]); other files use inline #[cfg(test)]. |
- Types:
VoiceSpeechResult,VoiceStatus,VoiceTtsResult(fromtypes). - Ops (
pub use ops::*):voice_status,voice_transcribe,voice_transcribe_bytes,voice_tts. - Factory:
create_stt_provider,create_tts_provider,default_stt_provider,default_tts_provider,effective_stt_provider,effective_tts_provider, traitsSttProvider/TtsProvider,SttResult,ExternalSttProvider,ExternalTtsProvider, constantsDEFAULT_PIPER_VOICE,DEFAULT_WHISPER_MODEL,WHISPER_MODEL_PRESETS. - Schemas:
all_voice_controller_schemas,all_voice_registered_controllers,voice_schemas. - Re-exported inference submodules:
cloud_transcribe,hallucination,local_speech,postprocess,streaming. - Submodules
server,hotkey,dictation_listener,reply_speech,text_input,audio_capture,factoryarepub.
Namespace voice (registered in all_voice_registered_controllers):
| RPC method | Purpose |
|---|---|
voice.status |
STT/TTS binary + model availability and active provider selection. |
voice.transcribe |
Transcribe a file path (whisper.cpp), optional LLM cleanup. |
voice.transcribe_bytes |
Transcribe raw audio bytes (writes temp file), with hallucination filter + cleanup. |
voice.tts |
Synthesize speech to a file via Piper. |
voice.reply_synthesize |
Synthesize an agent reply through the effective TTS provider; returns base64 audio + visemes. |
voice.cloud_transcribe |
Transcribe base64 audio via the hosted backend STT proxy (back-compat path). |
voice.stt_dispatch |
Factory-dispatched STT (cloud / whisper / <slug>:<model>); returns { text, provider }. |
voice.tts_dispatch |
Factory-dispatched TTS (cloud / piper / <slug>:<voice>); returns ReplySpeechResult. |
voice.set_providers |
Persist STT/TTS provider + model/voice into config.loc
58C3
al_ai.*. |
voice.update_provider_settings |
Persist the voice provider registry + routing strings (mirrors inference model settings). |
voice.list_models |
List models/voices for a provider (static presets for built-in slugs). |
voice.test_provider |
Test/validate a provider endpoint (silent-WAV STT, "Hello" TTS, or key-only validate). |
voice.server_start / voice.server_stop / voice.server_status |
Control the global dictation server. |
voice.overlay_stt_notify |
Bridge chat-button STT state transitions into the dictation/transcription buses. |
Provider strings follow the grammar cloud/openhuman (backend proxy), whisper/piper (local), <slug> or <slug>:<model|voice> (registry lookup in config.voice_providers).
Does not use the typed DomainEvent bus. Instead dictation_listener owns two process-global tokio::sync::broadcast channels:
DictationEvent(pressed/released) —publish_dictation_event/subscribe_dictation_events.- transcription text —
publish_transcription/subscribe_transcription_results.
src/core/socketio.rs subscribes to both and forwards them to Socket.IO clients (so dictation hotkeys and results reach the frontend without Tauri-side shortcut registration).
No dedicated store.rs. State is persisted into the shared TOML Config via the config domain: config.local_ai.{stt,tts}_provider, config.local_ai.{stt_model_id,tts_voice_id}, top-level config.{stt,tts}_provider, and config.voice_providers (the registry). The dictation VoiceServer keeps in-memory runtime state only (state machine, transcription count, rolling recent-transcript buffer for whisper context) behind a OnceCell singleton.
crate::openhuman::inference— local AI runtime (local::global, model id/path resolution) and the relocated voice inference impls (inference::voice::{cloud_transcribe, local_speech, hallucination, postprocess, streaming}); alsoinference::provider::factory::lookup_key_for_slugfor provider API keys.crate::openhuman::config—Config,config::rpc::load_config_with_timeout, voice-server / dictation config sections, andconfig::schema::voice_providers(VoiceProviderCreds, capability/auth/API-style enums).crate::openhuman::desktop::accessibility(macOS only) — focused-text inspection (focused_text_context_verbose) and the Swift globe-key listener (globe_listener_start/globe_listener_poll) used in place of rdev for the Fn key.crate::api—BackendOAuthClient,effective_backend_api_url,get_session_tokenfor backend-proxied reply-speech.crate::core::all(ControllerFuture,RegisteredController),crate::core::{ControllerSchema, FieldSchema, TypeSchema},crate::core::logging(CLI run init), andcrate::rpc::RpcOutcome.- External crates:
cpal(capture),rdev(hotkeys),enigo+arboard(paste insertion),reqwest(external provider HTTP),tokio/tokio-util,once_cell.
src/core/all.rs— registers the voice controllers.src/core/socketio.rs— subscribes to the dictation/transcription broadcast buses;streaming::handle_dictation_ws.src/core/jsonrpc.rs— wiring.src/openhuman/desktop_companion/pipeline.rs,src/openhuman/meet/agent/brain.rs,src/openhuman/voice/audio_toolkit/ops.rs,src/openhuman/security/credentials/ops.rs— call factory / TTS / transcription helpers.src/openhuman/inference/local/install_piper.rs— references voice constants/presets.
- macOS hotkey safety (#2677): rdev's CGEventTap callback calls
TSMGetInputSourcePropertyoff the main thread, which crashes withEXC_BREAKPOINTon macOS 26. So on macOS the core-sidedictation_listener::start_if_enabledis a no-op and the voice server only supports thefn(Globe) key via the Swift globe listener; all other keys return an error. Non-macOS uses rdev for all keys. - Two server instances:
global_serverregisters the singleton observed by thevoice.server_*RPCs;run_standalone(CLI) deliberately creates an isolated, unregisteredVoiceServer. - Reply-speech approval-gate classification is "internal" — if
reply_speechis ever wrapped in aTool,external_effect()MUST stayfalseso the approval gate never prompts on TTS (see file docstring + #1339/#1206). - Provider precedence:
effective_*_providerprefers the top-levelconfig.{stt,tts}_provider, falls back toconfig.local_ai.*_provider, then defaults to"cloud". - Piper-voice guard: dispatch handlers only default to
DEFAULT_PIPER_VOICEwhen the active provider ispiper; sending a Piper voice id to a cloud/external endpoint would be invalid. - Dictation pipeline gates (in
server.rs): minimum duration → peak-RMS silence threshold → hallucination filter → empty-text — each drops the recording before delivery. Asession_generationcounter discards stale state transitions from superseded recordings. - Kokoro TTS is intentionally not implemented in this cut; the doc in
factory.rsdescribes how to add it as a new branch + sibling module.