• feat(tts): config-driven voices + two-voice dialogue/narration split (DEC-11)

    vh released this 2026-08-11 08:02:30 -07:00 | 1 commits to main since this release

    Voice assignment moves from the hardcoded server map to ~/.config/ratatoskr/
    voices.json (per-agent voice + optional narration_voice). An agent with a
    narration_voice gets a two-voice split: quoted speech in voice, narration in
    narration_voice, synthesized per-span and stitched under one WAV header.

    • new src/ratatoskr/voices.py: load_voice_config (degrade-not-crash),
      segment_dialogue (quote-based, straight + curly), resolve_voice_spans
    • tts.py: tts_stream_stitched replaces tts_stream — serial per-span synth,
      span 0 verbatim, spans 1..N header-stripped -> one gapless 48kHz stream;
      a single-span list is a byte-identical passthrough (no single-voice regression)
    • server.py: _tts_endpoint resolves spans from app.state.voice_config; the
      hardcoded _TTS_VOICE_MAP is retired; create_app gains a voice_config param
    • entrypoint.py: loads voices.json at startup
    • contract DEC-11 + INV-TTS-5/6/7; initial config donut->donut,
      sindra->miranda (dialogue) / emmie (narration)

    Live-verified on :8765: Sindra mixed turn -> 2 dots calls (emmie+miranda)
    stitched into one 48kHz WAV with a single RIFF header; Donut single-voice
    unchanged. 545 tests green (incl. new test_voices.py).

    Downloads