SKILL.md
Judges a cloned narration before it ships — measures the ending energy, the sibilance, the gaps between lines — instead of asking a person to describe what sounds wrong.
--- name: voice-doctor description: Judge and repair David's cloned narration before it reaches him. Use whenever narration is generated, an episode is rendered, a voice profile is built or changed, or he says the voice sounds wrong — "swallowed", "cut", "no space", "sounds Indian/English", a named letter (R, T, D, S), or a specific word that never comes out right. Also use before delivering any audio or video with his voice in it. --- # Voice doctor He should not have to describe what he heard. Measure it, name it, fix it, and show him the numbers. ## The rule **Never deliver narration or a rendered video without running the doctor on it first.** One command: ```bash python3 audio/voice_doctor.py audio/voice/<ep>.wav # cheap, per line python3 audio/voice_doctor.py audio/voice/<ep>.wav --deep # per word, needs whisper ``` Exit status is 1 when something is scored `BAD`. A `BAD` finding is not a note — it is a defect he will hear, and it gets fixed before the file is sent. ## What each measurement means, and what it earned its place from | Metric | What it catches | Where it came from | |---|---|---| | `gap` | Two lines talking over each other, or no room to breathe | He said "words are cut, not enough space between sentences". The measurement found 7 overlapping lines in episode 02, up to −0.60s | | `rate` | Syllables per second; above ~6.5 the model elides endings | `build_voice.py` already re-rolls a line over this rate | | `end` | High-frequency energy in the last 120 ms of real sound — the R, T, D he hears swallowed | "harder never comes out right". Whisper transcribed it perfectly, so intelligibility was never the defect | | `sib` | 4–9 kHz against the 300–3400 Hz body — a dull or hissing S | He asked for the S to be fixed | | `lvl` | A line quieter than the rest | Found two in episode 02 | ## Thresholds — what is real and what is not - **An overlap is absolute.** A negative gap is two voices at once. No judgement needed. - **`MIN_GAP = 0.30s`** and **`MAX_RATE = 6.5`** are the only other fixed numbers. - **Everything else is judged against this narration's own median**, with a robust (MAD-based) fence. A line is flagged for being unlike the rest of *his own* delivery, not for missing a number invented in advance. - **Known limit, say it out loud when reporting:** the per-line `end` spread is wide because lines end on different sounds — a line ending in a vowel measures lower than one ending in a T, and that is not a fault. When an ending is the question, run `--deep` and compare words with words. Do not present the per-line `end` column as a verdict on a single line. - An earlier version of this work used a fixed −18 dB ending threshold. It was uncalibrated — the real median across 30 words was −22.6. Never re-introduce a fixed floor without the measurement that justifies it. ## The repair ladder — cheapest fix first 1. **Overlap or run-on** → the cause is fitting speech into fixed video slots. Build the narration naturally and move the picture to it: ```bash python3 audio/build_voice.py --cues video/<build>.html --out audio/voice/<ep>.wav python3 export/retime.py video/<build>.html audio/voice/<ep>-cues.json --out video/<build>-paced.html ``` Never reach for `--fit` to solve pacing; `--fit` is what caused it. 2. **A word arrives soft** → change the word, do not fight the model. `python3 audio/script_lint.py` names the class (unstressed -ER, -LE/-BLE, -LY, R+cluster, flapped T, final -TH, -ENT) and offers a replacement. This is what worked: `harder` → `think hard`, `reversible` → `something you can undo`. 3. **The word cannot change** (the brand line "setups that actually work") → `audio/line_doctor.py` generates candidates across seeds and respellings, ranks them by ending energy, and stores his pick in `audio/voice/profile/line-choices.json` so future renders reproduce the approved take instead of re-rolling the dice. 4. **A whole sound is wrong across the board** (accent, not one word) → `audio/phoneme_audit.py` over 30 carrier sentences, and if it is really the reference and not the text, rebuild the profile from a better excerpt with `audio/build_reference.py`. ## Two things measurement cannot decide - **Accent.** There is no score for "sounds Indian" or "sounds English". When he judges accent, his ear wins and the number loses — that already happened once: the similarity metric preferred a 28.8s reference, he picked the 18.4s one, and the stored profile is his. Record the disagreement in `audio/voice/profile/profile.json` rather than overruling it. - **Which take he connects with.** Generate options, announce them, let him pick, then lock the pick. ## Reporting to him Give the numbers, in Hebrew, in a small table, with what changed and what is still open. Never say "improved" without the before and after figure beside it. If a defect he named is not reproducible by measurement, say that plainly and ask for the second and the word — do not quietly re-render and hope.