# Lester Voice — Max Expressiveness, Not Glitches

**Route:** https://drwu-htmls.vercel.app/lester-voice · **Dates:** fixed 2026-07-01, sweep-locked 2026-07-02

The ask: *"find and fix our lester voice max expressiveness not glitches."*

Lester (the XTTS local clone, the committed Chiron chalkboard voice) should sound as
alive as possible while never tripping the babble-loop / runaway / silence / gibberish
failures that `xtts-glitch-qa` flags. Expressiveness comes from hotter sampling; glitches
come from sampling that runs *too* hot. The fix runs hot on the dials that add life and
clamps the dials that cause glitches, and the final temperature was locked by a 12-clip
A/B sweep, not by taste.

## What was wrong

Every Lester render call passed **zero** generation kwargs, so the voice rode XTTS v2
bare defaults and never enabled sentence splitting:

| Render path | Call site | Problem |
|---|---|---|
| Chalkboard production default (chiron-matilda + tarot-chapter video) | `book-to-video/scripts/xtts_render.py:51` | bare defaults · no `enable_text_splitting` → long lines can RUNAWAY/OOM |
| Chiron audio reading batch | `chiron-audio-reading/scripts/xtts_batch.py:29` | bare defaults · un-tuned prosody |
| Chiron tarot video batch | `chiron-tarot-reading-video/scripts/xtts_batch.py:29` | identical copy · bare defaults |
| Book videos `--voice lester` | `book-to-video/scripts/book_video.py` | routed to the flat committed renderer |

The committed renderer (`xtts_render_committed.py`, temperature 0.45, top_k 30, top_p 0.80)
was the opposite failure: flat/monotone, retired in memory as "the root cause of glitches."

## The fix — one shared expressive profile

Single source of truth `~/.claude/skills/_shared/xtts_lester_profile.py`, imported by all
four Lester render paths so the setting can't drift:

| Dial | Committed (flat) | Bare default | Locked profile | Why |
|---|---|---|---|---|
| temperature | 0.45 | ~0.75 | **0.80** | hottest setting with perfect word fidelity (see sweep) |
| repetition_penalty | 3.0 | default | **2.0** | kills the babble LOOP without flattening |
| top_k / top_p | 30 / 0.80 | default | **50 / 0.85** | wider sampling → more pitch variety |
| enable_text_splitting | False | unset | **True** | sentence-by-sentence → no RUNAWAY/OOM |
| length_penalty / speed | 1.0 / 1.0 | default | **1.0 / 1.0** | neutral pacing |

## The empirical sweep — 12 clips, one verdict

Same expressive test line, six sampling profiles, every clip transcribed with
faster-whisper and run through `qa_narration.py`. **All 12 passed, zero flags.** The
heard-vs-intended transcripts decided the temperature:

| Profile | QA flags | Whisper fidelity per rep | f0 spread | Verdict |
|---|---|---|---|---|
| committed 0.45 | 0 | 0.980 | 58.2 Hz | clean but flat |
| old default (~0.75) | 0 | 1.000 | 63.7 Hz | fine, un-tuned, unguarded |
| temp 0.65 | 0 | 1.000 · 0.742 | 55.2 Hz avg | one rep inserted a phantom phrase |
| **temp 0.80 ★** | **0** | **1.000 · 1.000 · 0.998** | **58.8 Hz avg (peak 65.0)** | **perfect words every rep — LOCKED** |
| temp 0.85 | 0 | 0.998 · 0.612 · 0.886 | 69.2 Hz avg | phantom words in 2/3 reps ("I'm disease.", grip→"rip") |
| temp 0.95 | 0 | 0.987 · 1.000 | 54.4 Hz avg | clean but LESS expressive |

**Why 0.80 and not 0.85:** both pass the gate, but 0.85 speaks words that are not in the
script. A phantom "I'm disease." in a chalkboard video is a glitch to any listener,
whatever the flag count says. Past 0.85 the heat stops buying expressiveness at all.

## Hear it — A/B the same line

- Committed 0.45 (flat): https://drwu-htmls.vercel.app/lester-voice/audio/committed_045_flat.mp3
- Old production default: https://drwu-htmls.vercel.app/lester-voice/audio/old_default.mp3
- **New locked 0.80:** https://drwu-htmls.vercel.app/lester-voice/audio/new_expressive_080.mp3
- Too hot 0.85 (phantom words at ~2s): https://drwu-htmls.vercel.app/lester-voice/audio/too_hot_085_phantom_words.mp3

## Verification ledger

- Shared profile loads with exact values: `temperature=0.8, rep=2.0, top_k=50, top_p=0.85, split=True`
- All 4 Lester render paths wired to it (`ast.parse` + `grep` confirmed)
- A/B sweep: 12/12 clips rendered, `qa_narration.py` = 0 flags
- Temperature 0.80 empirically locked (hottest with perfect whisper fidelity)
- This page + this `.md` live on Vercel (200)

## Files

- `~/.claude/skills/_shared/xtts_lester_profile.py` (canonical profile, sweep verdict in docstring)
- `~/.claude/skills/book-to-video/scripts/xtts_render.py`
- `~/.claude/skills/chiron-audio-reading/scripts/xtts_batch.py`
- `~/.claude/skills/chiron-tarot-reading-video/scripts/xtts_batch.py`
- `~/.claude/skills/book-to-video/scripts/book_video.py` (Lester routed to expressive renderer)
- Sweep: `~/.claude/skills/xtts-glitch-qa/tune_lester_profile.py` → `~/Projects/_outputs/xtts-glitch-qa/lester_sweep/`
