LAION · Expressive TTS · design proposal

A Sentence-Level Caption Prompt Format for Fine-Tuning MOSS-TTS-Local 4.55B

A concept and design document for a fine-tuning prompt format that gives the 4.55B MOSS-TTS-Local voice-acting model both a global speaker/scene description and per-sentence delivery control, mapped onto the model's two native input fields. The dynamic, sentence-by-sentence captions are derived automatically from audio using LAION's four voice scoring models and a deviation-from-baseline (z-score) recipe — the same procedural-caption method already prototyped in voicenet_v3grid/caption.py, extended here with worked, real examples.

moss_tts_local instruction = HOW (global) text = WHAT (+ inline per-sentence) [pause Xs] ${token:N} 57 VoiceNet dims 42 EmoNet emotions genuineness vocal-burst blend

Abstract — the proposal in one screen

1 Motivation & the two-field idea 2 The MOSS two-field model & verified markup 3 The proposed format & grammar 4 Prompt templates 5 Worked examples 6 The procedural-caption pipeline 7 Real caption.py output (proof of concept) 8 The genuineness gate (in detail) 9 Deriving the general speaker caption 10 Design recommendations — what I think 11 Open questions 12 Links & resources

1 Motivation & the two-field idea

The character-voice study on this model (rounds 1–5, see the 13-voice showcase) established that MOSS-TTS-Local responds strongly to a natural-language description of how the voice sounds placed in the instruction field. That work optimised a single, static instruction per character: one description for the whole clip. It is excellent for a fixed persona reading a line.

But a real performance is not static. Within one clip a grieving woman steadies herself, breaks, whispers, then chokes on a sob; a narrator lands a punchline, drops to a hush, then swells. A single global caption cannot say "this sentence is the one that cracks." For expressive fine-tuning — where we want the model to learn to move across a clip — we need per-sentence control. Yet we also do not want to throw away the thing that works: a stable, holistic description of the speaker.

The proposal is to keep both, and to split them by timescale: what is stable across the clip goes in instruction; what is dynamic per sentence goes inline in text, right next to the words it governs. Crucially, the per-sentence captions are not authored by hand — they are derived from the audio itself by scoring each sentence and describing only where it deviates from an ordinary-speech baseline.

2native fields used
(instruction + text)
4LAION scoring models
drive the captions
57 + 42VoiceNet dims +
EmoNet emotions
top 3–5salient dims kept
per sentence

2 The MOSS two-field model & verified inline markup

MOSS-TTS-Local's build_user_message(…) / the SGLang /v1/audio/speech API expose two text inputs plus a duration dial. From the model's own README and serving guide:

instruction
(alias instruct)
HOW to speak. Free-text performance direction / stage directions. Vocal bursts are written here as (round-bracket) director notes, e.g. (gasping). In our FAIR eval, keeping bursts here (and the spoken text clean) rather than inline cut WER by roughly .
text
(input)
WHAT to speak. The words. Per the serving guide it "may carry ${token:N} and inline markup ([pause 0.5s], IPA…)". This is the field we extend with per-sentence captions.
tokens
${token:N}
Pacing dial. An explicit codec-frame target at 12.5 Hz (tokens ≈ words × 6). Set inline as ${token:150}… or via the token_count parameter. It is stripped before synthesis. Larger N → longer/slower audio.

Verified markup facts (use exactly these)

MarkerMeaningWhere it livesStatus
[pause 0.5s]A timed inter-word/inter-sentence silence, in secondsinline in textnative
${token:N}Target duration = N codec frames @ 12.5 Hz; stripped before synthesisinline in text (or token_count)native
IPAPhonetic pronunciation hintsinline in textnative
(round-bracket)Performance / burst director noteinstruction (training convention)native
reference=[codes]Voice-clone reference clip + transcript (separate path)references fieldseparate

Voice cloning (reference=[codes] from encode_audios_from_path(…)) is a different input path and orthogonal to this proposal — the caption format targets the reference-less instruction+text route, where an empty instruction otherwise gives the model no direction. It composes with cloning too (clone identity, caption delivery), but that is out of scope here.

The key observation this proposal builds on: pauses are already a native, inline text-channel construct. So the natural place to put a per-sentence marker — a (caption) before the sentence and a [pause Xs] after it — is exactly the channel MOSS already reads for timing markup. We are extending an existing inline convention, not inventing a new field.

3 The proposed format & grammar

The instruction field is unchanged in spirit from the character study: a single global description. The novelty is the text field, which becomes an interleaved sequence of per-sentence units.

The text field, labelled

text  =  (warm, storytelling, deeply genuine) Once, in a village at the edge of the frostwood, [pause 0.4s]
(hushed, intimate, slowing) there lived a girl who could hear the snow. [pause 0.9s]
(brightening, delighted, playful) "And oh, what stories the snow would tell!"
           \_______  ______/  \____________ _____________/  \___ ___/
                    \/                      \/                   \/
              per-sentence            the sentence's words     real silence
              (caption): HOW          (quotes optional)        between sentences

Grammar (EBNF-ish)

instruction  = global-speaker-caption ;   (* stable traits + overall register + stage dirs *)

text         = [ duration ] , unit , { sep , unit } ;
unit         = [ caption ] , words ;
caption      = "(" , phrase , { "," , phrase } , ")" ;   (* 0-5 short delivery keywords *)
words        = plain-text | '"' , plain-text , '"' ;   (* double-quotes OPTIONAL *)
sep          = pause | " " ;
pause        = "[pause " , seconds , "s]" ;   (* only when a real silence occurred *)
duration     = "${token:" , integer , "}" ;   (* optional pacing target, per clip or per unit *)
phrase       = { direction-word | emotion-word | style-word | burst-note } ;

Rules of the format

Design intent: the text channel now carries a time-ordered score of the performance — captions where the delivery changes, pauses where the silence is, words throughout — while instruction carries the constant who-is-speaking. During fine-tuning the model learns to bind each inline (caption) to the acoustic change in the very next span of audio.

4 Prompt templates

Template A — expressive / character narration (per-sentence dynamics)

instruction:
  <AGE + GENDER>, <TIMBRE: rough/smooth, breathy/full, bright/dark>,
  <RESONANCE: chest / throat / nasal / head>, <REGISTER + PITCH RANGE>.
  <overall emotional register of the whole clip>. <scene / stage direction>.
  High quality recording.

text:
  ${token:NN} (<dominant emotion>, <style shift>[, <genuine|burst note>]) Sentence one. [pause Xs]
  (<dominant emotion>, <style shift>) Sentence two. [pause Ys]
  A flat, unremarkable sentence with no caption. [pause Zs]
  (<dominant emotion>) Final sentence.

Template B — calm, even narration (sparse captions)

instruction:
  A <age> <gender> narrator, warm and full, <chest resonance>, even mid register,
  measured and unhurried. Calm, storybook, reassuring overall. Clean studio recording.

text:
  Sentence. [pause 0.4s] Sentence. [pause 0.5s]
  (gently emphatic) The one line that carries weight. [pause 0.6s] Sentence.
  // most units have NO caption because the delivery does not deviate from baseline

Template C — a fixed character voice (reuse a showcase archetype)

instruction:   // the stable persona = a round-5 champion instruction, verbatim
  A nasty little goblin voice, extremely nasal and pinched, buzzing high through the
  nose with a grating scratchy rasp, whiny and reedy. A weak, cowardly underling, no
  authority whatsoever. No chest, no warmth, no fullness. Gleeful, mean overall.

text:   // per-sentence emotion/energy shifts ride inline on the fixed goblin timbre
  (sly, taunting, low and conspiratorial) "You think you can stop me?" [pause 0.5s]
  (erupting, shrill, manic glee)(wicked cackle) "Nothing can stop me now!"

In Template C the timbre never changes (it lives in instruction), but the goblin goes from a conspiratorial hush to a manic shriek across two sentences — exactly what a static instruction could not express. The burst note stays outside the quotes.

5 Worked examples

Four full instruction+text pairs across different content. The captions shown are in the compact keyword style the pipeline (§6–7) produces.

Example 1 — Emotional storytelling: a grieving woman

stable persona → instruction · per-sentence grief dynamics + real pauses → text
instruction
A woman in her thirties, warm mid-low timbre with soft chest resonance, a gentle breathy edge, even register. Grief-stricken and raw throughout, fighting to hold herself together in a quiet room. Intimate close-mic. High quality recording.
text:
${token:210} (subdued, trembling, holding back) I keep expecting to hear his key in the door. [pause 0.8s]
(voice cracking, raw and vulnerable, breaking) And then I remember. [pause 1.4s]
(hushed, collapsing in energy, barely audible)(shaky breath) He's not coming back. [pause 1.1s]
(a fragile attempt at composure, distant) I should be stronger than this by now.
Long pauses (1.1–1.4 s) between the emotional beats are copied from the real inter-sentence silences and teach the model the timing of grief. Only the deviating sentences carry strong captions; "I keep expecting…" is subdued, not flat, so it gets a mild one.

Example 2 — Calm narration (documentary)

sparse captions — most sentences do not deviate from the baseline
instruction
A man in his fifties, warm and full, rich chest resonance, even low-mid register, measured and unhurried. Calm, authoritative, storybook-documentary tone overall. Clean studio recording.
text:
The glacier moves less than a metre a day. [pause 0.5s]
To the eye, it is perfectly still. [pause 0.5s]
(quietly emphatic, slowing) But it has been moving for ten thousand years. [pause 0.7s]
Everything you see here, it carved.
Three of four units have no caption — the delivery sits at the calm baseline this narrator holds, so there is nothing salient to say. Only the one line that swells and slows earns a caption. Empty captions here are correct, compact and cheap.

Example 3 — A character voice: the goblin (persona fixed, emotion moves)

reuses the round-5 goblin champion as the stable instruction
instruction
A nasty little goblin voice, extremely nasal and pinched, buzzing high through the nose with a grating scratchy rasp, whiny and reedy. A weak, cowardly underling, no authority whatsoever. No chest, no warmth, no fullness. Malicious and gleeful overall.
text:
(sly, wheedling, conspiratorial hush) Ohhh, you found the treasure, did you? [pause 0.6s]
(mock-innocent, sing-song, taunting) Pity it's mine now. [pause 0.4s]
(erupting, shrill, manic spite)(wicked cackle) All mine, all mine!
The nasal, pinched, no-chest timbre is constant (instruction); the goblin ramps from a wheedling hush to a shrieking cackle across sentences. One affect-burst, placed outside the words.

Example 4 — Upbeat, high-energy delivery (advert/trailer read)

shows brightening, high register, and a burst flagged by the blend model
instruction
A woman in her late twenties, bright and clear timbre, forward head resonance, sitting in a high register, crisp articulation. Upbeat and enthusiastic overall, high-energy studio delivery. Clean recording.
text:
${token:96} (bright, high register, eager) It finally happened. [pause 0.3s]
(surging in energy, joyful, exclaiming)(delighted laugh) The wait is over! [pause 0.4s]
(warm, sincere, landing it) And it was worth every second.

6 The procedural-caption pipeline

The heart of the proposal: the per-sentence (captions) are not written by hand — they are derived automatically from the audio by four LAION voice models plus a deviation-from-baseline recipe. This makes captioning a large fine-tuning corpus cheap, consistent, and free of human labelling bias.

The four scoring models & their roles

ModelEncoder → headsOutputRole in the caption
Empathic-Insight-Voice-Plus BUD-E-Whisper enc → per-emotion MLP heads 40 EmoNet emotions + Valence + Arousal (42) + quality The sentence's dominant emotion(s) — we use only the 40 emotions (Valence/Arousal come from VoiceNet, see below)
voicenet-dimension-predictors-commercial VoiceCLAP-commercial 768-d emb → regressors 57 VoiceNet perceptual dims (0–6): brightness, resonance cavities, register, pitch range, roughness, tempo, styles… The dominant delivery / speaker traits
voiceclap-commercial-genuineness VoiceCLAP-commercial emb → head genuineness (0–6) How genuine vs performed the sentence sounds (a gate + a note)
voiceclap-commercial-vocalburst-blend VoiceCLAP-commercial emb → head vocal-burst blend (0–10) Flags sentences with laughs / gasps / sighs → a burst note

Efficiency: one VoiceCLAP-commercial encode of the sentence feeds VoiceNet + genuineness + blend (three heads share the 768-d embedding); one BUD-E-Whisper encode feeds Empathic-Insight. Two encodes per sentence, four score vectors.

The six steps

  1. ASR with sentence-level timestamps. Transcribe with word/segment-level timing, then aggregate to sentence spans. Two strong recommended options (either works; not a hard requirement):
    • nvidia/parakeet-tdt-0.6b-v3 — a TDT (Token-and-Duration Transducer) that natively predicts token durations, giving accurate word/token-level timestamps; multilingual (incl. EN + DE). We already use it in this project for invWER scoring, so it is a natural fit — its word timestamps are aggregated up to sentence spans.
    • microsoft/VibeVoice-ASR — provides good sentence / segment-level timestamps, well-suited to long-form transcription + alignment.
    Either yields each sentence's text and its [start, end] times, which feed the format two ways: (a) the span cuts the sentence segment for the four scoring models (next step); (b) the silence gap between one sentence's end and the next sentence's start is measured and, rounded, becomes the [pause Xs] marker (kept only ~50% of the time per the dropout policy in §6).
  2. Cut each sentence segment [start, end] from the audio.
  3. Score each segment through the four models. One VoiceCLAP-commercial encode → VoiceNet (57) + genuineness + blend; one BUD-E-Whisper encode → Empathic-Insight (42). Result: a per-sentence score vector of ~99 numbers + genuineness + blend.
  4. Build an in-the-wild baseline. Run the same four models over a large, ordinary "in-the-wild" corpus and compute, per dimension/emotion, a robust median + spread (MAD or std). Most in-the-wild speech is flat and unremarkable, so this baseline captures "average delivery."
  5. Per sentence, compute z-scores. z = (value − median) / spread for every dimension and emotion; take the top-3 to top-5 by |z| — the dimensions/emotions on which this sentence most stands out. These are its dominant delivery traits and dominant emotions.
  6. Render the caption. Map each selected dim/emotion to a short phrase reflecting direction (above / below baseline) and intensity (from |z|); add the dominant EmoNet emotion word(s), a genuineness note if it deviates, and a burst note if blend is high. Flat sentences → short or empty captions (natural + cheap).
Why deviation, not absolutes. A caption that listed every dimension's absolute value would be long, repetitive, and dominated by traits shared by all speech. The z-score against a baseline keeps only what is surprising about this sentence — which is exactly what a director note should say. It also makes "boring" fall out for free: a sentence at the baseline has no high-|z| dims, so it gets no caption.

Format augmentation & annotation dropout

A fine-tuned model that only ever saw fully-marked prompts would require that markup at inference. But real users span a spectrum — from power-users who write per-sentence director notes and pauses, to novices who type a single bare line. To stay robust across all of them, the training set must vary its annotation richness on purpose. We therefore apply an augmentation / dropout policy: each sample is randomly rendered somewhere on a scale from fully-annotated to bare.

Augmentation~share of samplesWhat it does — and why
Pause dropoutkeep in ~50%Include [pause Xs] in only about half the samples, even when real pauses exist; omit them in the rest. Teaches the model to use pause markup when present without depending on it.
Caption dropout~30–50%In 30–50% of samples, drop all inline (round-bracket) per-sentence captions and pauses — leaving just plain spoken text (the current bare MOSS format). Keeps the model fully working for bare, unmarked prompts.
Instruction-field format mixmixedSometimes route the emotion / stage directions into the instruction (how-to) field instead of inline, and vary its format: a rich global caption vs a short tag ("sad, whispered") vs a bare speaker description with no emotion at all. Mixed across the dataset.
Duration dropoutkeep in ~50%Include ${token:N} pacing targets on only some samples (per-clip and/or per-sentence), omit on the rest — so pacing control is available but not required.
Net effect — a spectrum of supervision. Across the corpus the model sees the full range: fully-annotated (inline captions + pauses + tokens), instruction-only (global how-to, clean text), emotion-in-instruction vs emotion-inline, and bare (plain text, no markup at all). It therefore learns that the format is optional and compositional, and stays robust to how people actually prompt — whether they hand it a director's script or a single sentence.

7 Real caption.py output — proof of concept

The VoiceNet half of step 5–6 is already implemented and running in voicenet_v3grid/caption.py. It is the concrete proof of concept for this proposal. Its design maps one-to-one onto the recipe above:

The intensity ladder (from |z|)

INTENSITY = [(2.0, "Extremely"),
             (1.5, "Very"),
             (1.0, "Notably"),
             (0.0, "Somewhat")]

|z|>2 → "Extremely", >1.5 → "Very", >1 → "Notably", else "Somewhat" — the deviation's magnitude becomes the adverb.

The per-dimension adjective map (excerpt)

DESC = {
 "FULL":  {hi:"full and cinematic",
           lo:"thin and paper-like"},
 "R_CHST":{hi:"rich in deep chest resonance",
           lo:"thin in chest resonance"},
 "VULN":  {hi:"raw and vulnerable",
           lo:"guarded and armored"}, ...}

Each dim has a curated hi/lo phrase (informed by the VoiceNet rubric texts). The z-score's sign picks hi vs lo; its magnitude picks the adverb.

caption(pred_dict, REFERENCE_STATS, topk=5) scores every dim against reference_stats.json (per-dim median/mean+std over 1000 real, stratified in-the-wild Emolia clips), sorts by |z|, and returns the top-k phrases. Below are real captions it produced when run on stored predictions in pred100_v3.json (each entry = one clip's 57 VoiceNet regression outputs). No GPU — pure inference on the stored score vectors.

Real example A — a rich, cinematic voice EN_B00015

genuineness 1.38 · blend 6.64 · top-5 by |z| vs the average-Emolia baseline
VoiceNet dimreg (0–6)baseline μz→ caption phrase
FULL · Fullness5.532.29+3.33Extremely full and cinematic.
WARM · Warmth5.172.68+3.13Extremely warm and enveloping.
ESTH · Esthetics4.462.39+2.53Extremely beautiful and pleasing.
R_NASL · Nasal Res.0.972.90−2.04Extremely denasal and clear.
R_HEAD · Head Res.0.011.82−2.00Very thin in head resonance.

Real example B — an elderly storybook narrator EN_B00024

genuineness 0.73 · blend 4.49 · a textbook "calm narration" persona
VoiceNet dimregμz→ caption phrase
WARM · Warmth5.082.68+3.03Extremely warm and enveloping.
ESTH · Esthetics4.772.39+2.90Extremely beautiful and pleasing.
FULL · Fullness4.882.29+2.66Extremely full and cinematic.
S_NARR · Narrator Style5.812.44+2.26Extremely narrator-like and storybook.
AGEV · Voice Age5.432.83+2.17Extremely elderly-sounding.

Real example C — a bright, joyful, high voice DE_B00002

genuineness 1.49 · blend 3.30 · note the one below-baseline dim (noisy background)
VoiceNet dimregμz→ caption phrase
VALN · Valence6.002.33+3.74Extremely joyful and positive.
R_HEAD · Head Res.4.621.82+3.10Extremely bright in head resonance.
REGS · Register5.091.93+2.23Extremely high in vocal register.
HARM · Harmonicity4.632.61+2.16Extremely pure and tonal.
BKGN · Background1.813.10−2.16Extremely noisy in the background.

Real examples D & E — two distressed, vulnerable voices ZH_B00039 FR_B00000

both high-|z| on Vulnerability — exactly the sentences a grief clip would caption strongly

ZH_B00039 · genu 2.51 · blend 8.55

Extremely raw and vulnerable.        [VULN  z=+3.08]
Extremely collapsing in energy.      [ARSH  z=-2.54]
Extremely audibly breathing/gasping. [RESP  z=+2.27]
Extremely meek and submissive.       [STNC  z=-2.26]
Extremely thin in chest resonance.   [R_CHST z=-2.01]

blend 8.55 is very high → the pipeline would add a burst note, e.g. (shaky breath).

FR_B00000 · genu 2.96 · blend 6.29

Extremely raw and vulnerable.        [VULN  z=+3.15]
Extremely noisy and aperiodic.       [HARM  z=-2.52]
Extremely darkening in mood.         [VALS  z=-2.21]
Extremely blurry and mumbled.        [CLRT  z=-2.20]
Extremely rough and raspy.           [ROUG  z=+2.19]

"raw and vulnerable, darkening in mood, rough" is a ready-made grief caption — derived, not written.

Extending the recipe to the emotion half (Empathic-Insight)

The same z-score-vs-baseline machinery extends verbatim from the 57 VoiceNet dims to the EmoNet emotions. Build a baseline median+spread per emotion over the in-the-wild corpus; per sentence, take the top emotions by |z|; emit the dominant emotion word(s) (EmoNet gives readable names — Grief, Amusement, Malice, Relief, Contempt, Awe…, but we expand each into a synonym cluster so the same concept is expressed with varied words — see §10.6), gated by intensity. Then two scalar heads add notes:

Source-of-truth rule — no double-counting. Empathic-Insight outputs 42 dimensions: 40 emotions plus Valence + Arousal. But Valence and Arousal also exist as VoiceNet dimensions (VALN, AROU). To avoid describing the same axis twice, take Valence + Arousal from VoiceNet only and use EmoNet for its 40 emotions only (drop EmoNet's Valence/Arousal). So the emotion half contributes exactly the 40 named emotions; the affective dimensions live on the VoiceNet side.
General principle — discard below-baseline noise. Only salient above-baseline deviations become caption terms. A dimension or emotion sitting far below its baseline median carries no useful information for a director note (a sentence that is simply "not sad" and "not bright" should not be captioned "un-sad, un-bright") — it is discarded, not captioned. The exceptions are the handful of VoiceNet dims whose low end is itself a describable trait (e.g. a very low register, a meek stance), which the curated DESC map handles explicitly; raw emotion heads and quality dims are kept only on their high side.

A full sentence caption is thus the concatenation, in a fixed order, of: [VoiceNet delivery phrases] + [EmoNet emotion words] + [genuineness note] + [burst note], each contributed only when it clears its |z| / value threshold, capped to a small keyword budget.

8 The genuineness gate — in detail

The single most important trust mechanism in the emotion half of the pipeline. It decides whether a salient emotion is described at all, and how strongly. Because it is the piece that keeps the caption→delivery mapping clean, it gets its own section.

Definition

genuineness
(0–6)
laion/voiceclap-commercial-genuineness measures "lived-in vs performed": 0 = read / scripted / detached, 6 = a genuine, unplanned, real moment. It scores how genuinely an emotion is embodiednot which emotion. It is orthogonal to Empathic-Insight, which says what the emotion is; genuineness says whether you believe it.

The problem it solves

When we derive a per-sentence emotion caption from Empathic-Insight's top-|z| emotions, some high emotion scores are untrustworthy, in two distinct ways:

Captioning either case as "very sad" teaches the TTS a noisy caption→delivery mapping: it learns to attach a strong emotion word to audio that does not actually deliver that emotion. We want a strong emotion word only when the emotion is BOTH salient (high |z| on Empathic-Insight) AND genuinely embodied (high genuineness). Genuineness is the AND-term.

The rule in one line. keep_strong_emotion = (|z|emotion high) AND (genuineness high) AND (quality ok)  — salience alone is not enough.

The concrete rule — median-anchored

"High genuineness" is made concrete the same robust way every other threshold in this pipeline is: against the genuineness baseline median computed over the in-the-wild corpus. The intensity a sentence's emotion word is allowed to reach is capped by where its genuineness sits relative to that median:

Sentence genuineness vs baselineEmotion-word intensity allowedExample (Sadness, salient)
at / above the medianfull — "extremely / very <emotion>" permitted"deeply, genuinely sorrowful"
below the mediancapped — no "extremely/very"; mild word only"somewhat downcast"
well below the mediandropped — emotion term removed / not captioned"flat, measured read"

So extreme emotion words ("extremely", "very") are allowed only when genuineness is above-median; below-median genuineness caps the intensity, and well-below-median drops the emotion entirely. This is the same "only salient, above-baseline deviations become caption terms" principle from §6 — applied to the trust axis: a value far below its baseline median is discarded, not described.

Genuineness as a graded descriptor (its own ladder)

Genuineness is not only a gate — laddered against its own baseline it is also a describable trait, so it can be added to the caption directly (this is implementation 3 below):

Genuineness vs baselineDescriptor added
well above median"deeply / genuinely <emotion>" (prefix that intensifies the emotion word)
around the median"genuine" / "slightly genuine" (light note, or nothing)
below median"measured" / "controlled, performed" — and the emotion word is capped or dropped per the rule above

Three implementations (pick per corpus)

1 · Hard gate

If genuineness < τ (e.g. <2/6) OR quality is low, drop the emotion terms entirely and keep only the neutral acoustic traits — or explicitly label the sentence a "flat / measured read." Simple, auditable, and the safest default for noisy in-the-wild corpora.

if genuineness < 2.0 or quality < q_min:
    emotion_terms = []                 # suppress
    caption = acoustic_terms or ["flat, measured read"]
else:
    caption = acoustic_terms + emotion_terms

2 · Soft gate (scaling)

Scale the |z|-derived intensity by genuineness, so the emotion word fades out gracefully instead of snapping off: effective_intensity = |z| × f(genuineness), where f rises from 0 to 1 over the genuineness range. A high emotion-z with low genuineness lands on "somewhat sad" — or nothing — rather than "extremely sad."

# f rises through 1.0 at the genuineness median (g_med): above-median -> full intensity
f          = clamp(genuineness / g_med, 0, 1)
eff_z      = abs(z) * f
adverb     = intensity_word(eff_z)     # Extremely / Very / Notably / Somewhat / (drop)

This reuses caption.py's existing INTENSITY ladder unchanged — genuineness simply attenuates the |z| that is fed into it, and it is a continuous version of the median-anchored cap above (at/above median → f≈1 → full intensity).

3 · Genuineness as its own caption dimension (+ a quality gate)

Give genuineness double duty: besides gating the emotion, describe it directly — high"genuine, lived-in delivery"; low"measured, controlled, performed." This is honest (it captions the flat read as flat rather than hiding it) and it teaches the model the genuine↔performed axis itself. Pair it with a quality gate using Empathic-Insight's own quality scores: if quality is poor (noise/clipping/bad cut), suppress emotion terms regardless of genuineness — this is what catches the artifact case (a) before genuineness even applies.

Worked example — same emotion, opposite treatment

SentenceSadness zgenuinenessGate decision→ caption
A+2.55.2salient AND genuine → full intensity"deeply, genuinely sorrowful"
B+2.51.1salient but NOT genuine → gated"flat, measured read" (emotion word suppressed)

Identical emotion salience (z = +2.5 in both); genuineness is the only difference, and it flips the caption from a full-intensity emotion phrase to a suppressed, honestly-labelled flat read.

Why it fits in-the-wild data

The two corpora we fine-tune on have opposite failure modes, and the gate fixes both:

Net effect: the model learns emotion from real emotional moments, not from noise or overacting.

Why a measured axis works where a prompt cannot. In our earlier prompting experiments, genuineness could not be prompted for — asking the model to "be genuine / sound authentic" actually scored lower. Genuineness is not a dial you can request; it is a real, measured property of the audio. That is exactly why it works as a trust filter on the training captions: we cannot manufacture it in the prompt, but we can measure it in the data and refuse to caption emotion where it is absent.

9 Deriving the general speaker caption

The global instruction caption is the aggregate of what is stable across the clip. After scoring every sentence, take the median over sentences of the speaker-identity dimensions — the ones that should not change within one clip:

Trait familyVoiceNet dims (median over sentences)
Age / genderAGEV, GEND
Timbre / qualityBRGT, ROUG, WARM, FULL, HARM, METL, ESTH
Resonance cavitiesR_CHST, R_THRT, R_NASL, R_HEAD, R_MASK, R_ORAL, R_MIXD
Register / pitchREGS, RANG
Overall emotional registermedian VALN / AROU + the most persistent EmoNet emotion across sentences

Run the same caption() on this per-clip median vector (against the same baseline) to get the stable-trait phrases, then compose them into the instruction with the overall emotional register and any scene direction. Real example B above is exactly this shape when treated as a whole-clip aggregate: "warm, beautiful, full, narrator-like, elderly" → a global instruction like "An elderly man, warm and full, rich resonance, storybook narrator delivery."

Division of labour: dims with low sentence-to-sentence variance (timbre, resonance, age, gender) → the instruction caption. Dims with high variance (arousal, valence, emphasis, tempo, style shifts, vulnerability) → the inline per-sentence captions. The variance itself tells you which field a trait belongs in.

10 Design recommendations — what I think

1. Deviation-vs-baseline beats absolute captioning

Caption only what is salient — the dims/emotions where the sentence departs from ordinary speech. This yields compact captions, keeps them on-topic, and makes flat sentences produce short or empty captions automatically. Absolute captioning would drown the signal in shared, uninformative traits and over-constrain the model.

2. Use robust statistics (median + MAD), not mean + std

In-the-wild score distributions are skewed and heavy-tailed (most speech is flat, a few clips are extreme). Median + MAD resists those outliers far better than mean + std, giving a more honest "typical voice" reference. (The current reference_stats.json ships mean/std; I recommend switching the production baseline to median + MAD, or at least winsorising before computing std.)

3. Keep captions short — a few keywords

Long captions over-constrain generation and, worse, drift from the data density: fine-tuning works best when the caption distribution the model sees at train time matches what it must interpret at inference. Cap each caption at top-3–5 dims + 1–2 emotion words + at most one note. Short, salient, consistent.

4. Put STABLE traits in instruction, DYNAMIC ones inline

This is the core architectural choice, and §9 gives a principled test for it: sentence-to-sentence variance. Low-variance traits (identity) belong in the global instruction; high-variance traits (performance) belong inline. Do not repeat the speaker's timbre in every sentence caption — it wastes the budget and teaches the model nothing new per sentence.

5. A genuineness gate on strong emotion words

Only add strong emotion words when genuineness (and overall quality) is high — otherwise the model learns to attach "grief-stricken" to unconvincing acting or to noise artifacts. This is important enough that it has its own section: see §8 — the genuineness gate for the definition, the three implementations (hard gate / soft scaling / own dimension + quality gate), and a worked example.

6. A controlled lexicon — with deliberate synonym variety

Draw every caption phrase from a controlled vocabulary — the VoiceNet rubric adjectives (caption.py's DESC) + the EmoNet emotion taxonomy + a small burst set. This is what lets a caption→delivery mapping be learnable at all: free-form LLM captioning would inject uncontrolled, inconsistent phrasing and blur the mapping.

But "controlled" must not mean "one canonical word per concept." If every sad sentence in training is captioned with the single token sad, the fine-tuned model learns to respond to that exact token — and an inexperienced in-the-wild user who types "he sounds really pissed off" or "melancholic" at inference gets nothing, because those words were never bound to the delivery. The fix is to vary the words within each concept, drawing from the EmoNet synonym clusters: each of the 40 emotions ships a cluster of near-synonyms. We rotate / sample among them across the dataset so the same measured emotion is expressed with varied surface words.

EmoNet emotionSynonym cluster (from the EmoNet taxonomy)Sample caption phrasings (rotated)
Angeranger, rage, fury, wrath, hate, irascibility, vexation, annoyance, peevishness"seething with rage" · "furious" · "wrathful" · "bitterly annoyed"
Sadnesssadness, sorrow, grief, melancholy, dejection, despair, heartache, mournfulness, misery"deeply sorrowful" · "grief-stricken" · "melancholic" · "dejected"
Amusementmirth, joviality, playfulness, silliness, jesting, lighthearted fun, laughter"full of mirth" · "jovial" · "playful and silly" · "lighthearted"
Fearfear, terror, dread, apprehension, alarm, horror, panic, nervousness"terrified" · "dread-filled" · "panicked" · "nervous and apprehensive"
Contemptcontempt, disapproval, scorn, disdain, loathing, detestation"scornful" · "full of disdain" · "contemptuous" · "loathing"
Elationhappiness, excitement, joy, exhilaration, delight, jubilation, bliss, cheerfulness"jubilant" · "exhilarated" · "delighted" · "cheerful and bright"
Rationale: teach the concept, not the token. Rotating synonyms within a fixed cluster keeps the mapping anchored to the measured emotion (the cluster is closed, so it stays learnable) while exposing the model to the many words a real user might use for it. The result generalises: at inference "pissed off", "furious", and "wrath" all land on the Anger delivery because all of them appeared, bound to it, in training. Controlled at the concept level, varied at the word level.

7. Use blend for burst director-notes

The vocal-burst-blend head cheaply flags laughs/gasps/sighs/moans. Turn a high blend into one matched burst note (the character study found one burst helps, a second usually hurts), placed as a (round-bracket) note outside the spoken words.

8. Real pauses teach real timing; per-sentence tokens teach pacing

[pause Xs] taken from the measured inter-sentence gaps teaches natural phrasing and the timing of emotion (the 1.4 s beat before "He's not coming back."). A per-sentence ${token:N} can additionally teach the model the pacing of a slow, heavy line vs a quick one. Both are free by-products of the alignment step.

9. Quality-control the corpus

Drop sentences with bad ASR (low alignment confidence, high WER on re-decode) or low recording quality (RCQL/BKGN off) before captioning — a wrong transcript or a noisy segment produces a misleading caption that teaches the model the wrong binding.

10. Double-quotes are optional — and markup stays outside them

Wrapping the words in "double quotes" is optional. In our earlier experiments, quotes-in-text raised WER, so if quotes are used, keep all bursts and markup outside the quotes (the model must never read a bracket aloud — inline bursts-in-text cost ~5× WER). The safest default is no quotes; the format supports both.

Net recommendation. A procedural, deviation-based, closed-lexicon captioner with a median+MAD baseline, a genuineness gate, one blend-driven burst, measured pauses, and a strict stable-vs-dynamic field split. It is cheap (two encodes/sentence), consistent (fixed vocabulary), self-thinning (empty on boring sentences), and it reuses code that already runs.

11 Open questions

12 Links & resources

The four scoring models

ASR / sentence-timestamping (recommended options)

The existing procedural-caption work & the model

LAION · MOSS-TTS-Local 4.55B voice-acting · fine-tuning prompt-format proposal (design document). Real caption examples produced by voicenet_v3grid/caption.py on stored VoiceNet predictions (pred100_v3.json), baseline reference_stats.json (1000 stratified in-the-wild Emolia clips). Verified MOSS markup ([pause Xs], ${token:N}) per the model's serving guide. Docs CC-BY-4.0.