12 Commits

Author SHA1 Message Date
7cae72b936 Graceful shutdown, blocking speak() with progress, and cancel support
speak() now blocks until playback finishes, reporting progress via SSE
(5% entry tone → 30% synthesis → 35-99% playing → 100% done). Entry
tone fires immediately on call to cover synthesis latency.

Queue shutdown waits for current speech to finish (configurable timeout,
default 30s) before draining pending items — no more mid-sentence
cutoffs on container restart.

Cancellation via cancel_speech() tool or MCP notifications/cancelled
kills pw-play and plays a vinyl scratch tone. Consumer continues to
next item after cancel.

Progress tracking uses a background ticker task instead of
asyncio.wait_for polling — the latter causes stale CancelledError
propagation to the consumer under Python 3.13.
2026-03-02 17:45:48 -07:00
44d4f5a3d6 Rename Caddy domain to mctalkbox.l.supported.systems 2026-03-02 15:30:57 -07:00
eaff3e8861 Add project-aware voice identity with round-robin assignment
Each project gets a distinct voice from a curated English pool,
assigned via round-robin with gender/accent interleaving for
maximum perceptual contrast between consecutive projects.

- New voice_identity.py: pool filtering, interleaving, persistence
- Round-robin replaces SHA-256 hashing (no collisions until pool
  exhaustion at 22 voices)
- Assignments persist to /data/voice-assignments.json across restarts
- speak() and generate_audio() accept optional project= parameter
- MCP roots fallback with 2s timeout for future bidirectional clients
- English-only pool (af_/am_/bf_/bm_/ef_/em_ prefixes)
- af_nicole excluded from auto-assign (whispery), still explicit-ok
- Fix voice blacklist to use full identifiers (am_adam, af_jessica)
2026-02-24 11:58:10 -07:00
6a81e0760a Add exit tone (roger beep) and standby tone after speech
Queue-aware exit tones: descending roger beep when queue empties
("over"), ascending standby blip when more items are queued ("standby,
more coming"). Includes quindar-out (2475 Hz) as Apollo-themed
alternative. Configurable via TTS_EXIT_TONE env var.

Also adds CLAUDE.md documenting the full tone system.
2026-02-23 14:44:27 -07:00
25de529bf4 Add entry tone (Nextel chirp) before queued speech playback
Generate 48kHz WAV tones at startup (chirp and apollo/quindar) using
numpy. The queue consumer plays the selected tone before each speech
item via pw-play. Configurable via TTS_ENTRY_TONE env var: chirp
(default), apollo, none, or path to a custom WAV file.
2026-02-23 14:40:36 -07:00
bf0dfa7a5e Decode SNAC in single pass to fix audio artifacts
SNAC's convolutional decoder has a receptive field spanning multiple
frames. Batched decode (28 tokens at a time) created boundary
discontinuities that produced muddled audio. Since pw-play waits for
the full WAV anyway, single-pass decode costs only ~2s extra and
produces clean audio.
2026-02-21 21:51:42 -07:00
538b8a513e Replace Ollama with llama-server for 15x Orpheus throughput
Build llama.cpp from source with SM 120 CUDA kernels and FORCE_CUBLAS
for RTX 5070 Blackwell. Rewrite OrpheusEngine to stream tokens via SSE
and decode SNAC in overlapping 28-token batches (4 frames), replacing
the blocking requests+stream:false approach.

Performance: 13.5 → 170-213 tok/s. 100s audio generates in ~48s (2x
faster than realtime). Replaces requests with httpx async client.

Also switch MCP transport to stateless_http mode so container restarts
don't invalidate client sessions.
2026-02-21 21:33:23 -07:00
7f9557d93b Increase Orpheus Ollama timeout from 120s to 600s
Long emotional monologues with multiple <sigh>/<laugh>/<gasp> tags
generate ~4000 tokens at ~12 tok/s, easily exceeding 2 minutes.
2026-02-21 17:09:13 -07:00
48b518771d Refactor speech queue: producer-consumer with priority and timeouts
Replace asyncio.Lock with bounded PriorityQueue + dedicated consumer
coroutine. Addresses Hamilton review findings:

- Playback timeout: pw-play subprocess killed if it exceeds 2x
  expected duration + margin (prevents deadlock on PipeWire hang)
- Bounded queue: max 20 items with backpressure rejection
- Priority tiers: urgent messages jump ahead of normal items
- Graceful shutdown: consumer cancelled, pending items drained,
  in-flight subprocess killed on lifespan teardown
- Structured errors: all failure modes return dicts, not raw exceptions
2026-02-21 15:56:01 -07:00
4698d8b0d2 Lazy-load SNAC decoder to reduce idle memory
SNAC + torch no longer load at startup — deferred to first Orpheus
call via double-checked locking. Startup drops from ~13s to 0.5s,
idle RAM reduced by ~200MB. OrpheusEngine constructor no longer
takes snac_model; it self-loads on demand.
2026-02-21 13:04:35 -07:00
c53db4b251 Add Docker deployment with Caddy HTTPS reverse proxy
CPU-only torch image (~180MB vs 873MB CUDA), PipeWire socket
passthrough for audio playback, SNAC HuggingFace cache volume.
Served at voice.l.supported.systems via caddy-docker-proxy.
2026-02-20 20:47:22 -07:00
2fd84f0df7 Initial TTS MCP server with 3 engines
FastMCP 3.0 Streamable HTTP server exposing Piper (Wyoming/Docker),
Kokoro (ONNX), and Orpheus (Ollama+SNAC) as MCP tools. Includes a
FIFO speech queue so concurrent agents don't talk over each other —
waiting callers get queue position updates via ctx.info().

Tools: speak, generate_audio, list_voices, list_engines
Resource: audio://recent
2026-02-20 18:10:15 -07:00