| name | maintain-voice-output |
| description | Maintain request-scoped local TTS synthesis, ordered bounded speech admission, Chat CPAL playback, Live AVAudioEngine playback, ownership fences, and cancellation. Use when changing spoken replies, adapters, playback, or speech lifecycle events. |
Maintain voice output
Role and boundary
Keep local speech as a native-owned, request-scoped stream with a small typed frontend producer. Chat and Live may use different playback backends, but they share identity, ordering, cancellation, and one exclusive output owner; never send PCM through WebView IPC or logs.
Source map
src/tts.ts: Chat reply speech controller, one shared cancellation promise per active reply, native model/stage filtering, speechSessionId/requestId/chunkIndex event guards, Stop speaking, and legacy wrapper.
src/assistant-coordinator.ts, src/sentence-chunks.ts, src/live.ts, src/speech-metrics.ts: ordered semantic chunks, Chat/Live speech targets, lifecycle state, and bounded text-free timing.
src-tauri/src/tts/mod.rs: TtsState, adapter tuning, Chat reply commands/events, legacy speak_text, and model warmup/selection; src-tauri/src/tts/output_ownership.rs: the exclusive Chat/Live/legacy transition state machine, stale-command checks, cancellation, and release.
src-tauri/src/tts/stream.rs: capacity-four ordered queue, asynchronous backpressure, coalescing, synthesis worker, cancellation, first-render, chunk fences, and exactly-once terminal events.
src-tauri/src/tts/reply_resampler.rs: shared stateful Chat/Live reply resampling; src-tauri/src/tts/playback.rs: one-second CPAL ring, 120 ms startup buffer, callback drain/underrun accounting; src-tauri/src/live/playback.rs and audio_macos.rs: the SpeechPlayback implementation plus 48 kHz 2,048-frame AVAudioEngine timeline and horizon.
src-tauri/src/artifact.rs: shared pinned download, exact verification, archive installation, and marker lifecycle used by speech adapters.
src-tauri/src/tts/model.rs, pocket.rs, and benchmarks/tts/*: pinned cache/artifacts, retained actors, Alba seed/reference limits, and cache-only benchmark fixtures; tests in src-tauri/src/tts/*.rs, tests/tts.test.ts, tests/assistant-coordinator.test.ts, tests/sentence-chunks.test.ts, tests/speech-metrics.test.ts, tests/live.test.ts, and tests/benchmark-chunks.test.ts.
Cover
- Keep one semantic chunk admission in flight; advance indices only after native acknowledgement, never drop on saturation, and call
finish only after every accepted chunk.
- Preserve adapter-specific tuning (two ONNX threads, 240 hard cap, Supertonic coalescing versus sentence-at-a-time Pocket), first chunk immediacy, bounded Live horizon, and no progressive PCM unless callback evidence proves it safe.
- Keep Chat CPAL callback allocation-free and drain only after rendered frames; keep Live AVAudioEngine continuous timeline, epoch/fence protection, block scheduling, and stop-induced callback rejection.
- Retain one frontend cancellation promise on each
ActiveChatSpeech: the first caller chooses the reason and issues the native command; concurrent user, replacement, navigation, and Live callers must await that same promise before the controller clears the reply.
- Preserve exclusive Chat/Live/legacy ownership through
OutputOwnership: claim transitions snapshot Chat work under the synchronous lock, cancel and await outside it, then identity-fence either replacement installation or failed-transition cleanup. A failed cancellation must leave ownership retryable without clearing a newer owner. Preserve request/session correlation, terminal drained/cancelled/error exclusivity, and speech-only Stop speaking semantics.
- Inspect current adapter and playback code before tuning. Zero underruns or benchmark stability do not establish audible quality or device acceptance.
Validate and accept
- Run
bun test tests/tts.test.ts tests/assistant-coordinator.test.ts tests/sentence-chunks.test.ts tests/speech-metrics.test.ts tests/live.test.ts tests/benchmark-chunks.test.ts plus native TTS tests/benchmark checks relevant to the change; run git diff --check against the actual files changed, and run just check when the change crosses TypeScript/Rust/build boundaries.
- Exercise Chat and Live replacement, stop, barge-in, queue saturation, cancellation, drain, and model-switch snapshots with
$test-voice-layer-app.
- Treat real cached model readiness, CPAL/AVAudioEngine routing, prompt cancellation, audible quality, and human/device signoff as separate gates; use
$integrate-local-tts for deep adapter work.
Relationships
Coordinate with $maintain-assistant-orchestration, $maintain-chat, $maintain-live, $maintain-navigation, $maintain-settings, and $maintain-diagnostics.