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.
(round-bracket caption), the words, and a [pause Xs] marker for real
inter-sentence silence.text = (caption) words [pause Xs] (caption) words [pause Xs] …
— a compact per-sentence director note carried in the words channel, where MOSS's verified inline
markup ([pause 0.5s], ${token:N}) already lives.voicenet_v3grid/caption.py
already does; this document includes real captions it produced on stored predictions
(e.g. "Extremely full and cinematic.", "Extremely narrator-like and storybook.").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.
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:
(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 5×.${token:N} and
inline markup ([pause 0.5s], IPA…)". This is the field we extend with per-sentence
captions.tokens ≈
words × 6). Set inline as ${token:150}… or via the token_count
parameter. It is stripped before synthesis. Larger N → longer/slower audio.| Marker | Meaning | Where it lives | Status |
|---|---|---|---|
[pause 0.5s] | A timed inter-word/inter-sentence silence, in seconds | inline in text | native |
${token:N} | Target duration = N codec frames @ 12.5 Hz; stripped before synthesis | inline in text (or token_count) | native |
| IPA | Phonetic pronunciation hints | inline in text | native |
(round-bracket) | Performance / burst director note | instruction (training convention) | native |
reference=[codes] | Voice-clone reference clip + transcript (separate path) | references field | separate |
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.
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.
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
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 } ;
unit: an optional
(caption) immediately followed by its words.(caption) describes only that sentence's dominant delivery — its salient
emotions + speaking-style shifts + a genuineness/burst note when warranted. It is short (a few
keywords), and empty/omitted for flat, unremarkable sentences.[pause Xs] iff a real inter-sentence silence of
≈X seconds was measured in the source audio. No silence → no marker (just a space).(sigh), (soft laugh)) and all markup stay outside
any double-quotes, so the model never reads brackets aloud.${token:N} may be placed once at the front (whole-clip pacing) or per unit (to teach
sentence pacing on slow/fast deliveries).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.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.
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
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.
Four full instruction+text pairs across different content. The captions shown
are in the compact keyword style the pipeline (§6–7) produces.
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.
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.
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!
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.
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.
| Model | Encoder → heads | Output | Role 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.
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.[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).[start, end] from the audio.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.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 samples | What it does — and why |
|---|---|---|
| Pause dropout | keep 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 mix | mixed | Sometimes 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 dropout | keep 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. |
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:
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.
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.
| VoiceNet dim | reg (0–6) | baseline μ | z | → caption phrase |
|---|---|---|---|---|
| FULL · Fullness | 5.53 | 2.29 | +3.33 | Extremely full and cinematic. |
| WARM · Warmth | 5.17 | 2.68 | +3.13 | Extremely warm and enveloping. |
| ESTH · Esthetics | 4.46 | 2.39 | +2.53 | Extremely beautiful and pleasing. |
| R_NASL · Nasal Res. | 0.97 | 2.90 | −2.04 | Extremely denasal and clear. |
| R_HEAD · Head Res. | 0.01 | 1.82 | −2.00 | Very thin in head resonance. |
| VoiceNet dim | reg | μ | z | → caption phrase |
|---|---|---|---|---|
| WARM · Warmth | 5.08 | 2.68 | +3.03 | Extremely warm and enveloping. |
| ESTH · Esthetics | 4.77 | 2.39 | +2.90 | Extremely beautiful and pleasing. |
| FULL · Fullness | 4.88 | 2.29 | +2.66 | Extremely full and cinematic. |
| S_NARR · Narrator Style | 5.81 | 2.44 | +2.26 | Extremely narrator-like and storybook. |
| AGEV · Voice Age | 5.43 | 2.83 | +2.17 | Extremely elderly-sounding. |
| VoiceNet dim | reg | μ | z | → caption phrase |
|---|---|---|---|---|
| VALN · Valence | 6.00 | 2.33 | +3.74 | Extremely joyful and positive. |
| R_HEAD · Head Res. | 4.62 | 1.82 | +3.10 | Extremely bright in head resonance. |
| REGS · Register | 5.09 | 1.93 | +2.23 | Extremely high in vocal register. |
| HARM · Harmonicity | 4.63 | 2.61 | +2.16 | Extremely pure and tonal. |
| BKGN · Background | 1.81 | 3.10 | −2.16 | Extremely noisy in the background. |
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).
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.
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:
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.DESC map handles explicitly; raw
emotion heads and quality dims are kept only on their high side.(soft laugh), (gasp), (sigh), (shaky
breath) — matched to the dominant emotion, placed outside the words.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.
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.
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 embodied — not which emotion.
It is orthogonal to Empathic-Insight, which says what the emotion is; genuineness says whether you
believe it.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.
keep_strong_emotion = (|z|emotion high) AND (genuineness high) AND (quality ok)
— salience alone is not enough."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 baseline | Emotion-word intensity allowed | Example (Sadness, salient) |
|---|---|---|
| at / above the median | full — "extremely / very <emotion>" permitted | "deeply, genuinely sorrowful" |
| below the median | capped — no "extremely/very"; mild word only | "somewhat downcast" |
| well below the median | dropped — 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 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 baseline | Descriptor 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 |
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
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).
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.
| Sentence | Sadness z | genuineness | Gate decision | → caption |
|---|---|---|---|---|
| A | +2.5 | 5.2 | salient AND genuine → full intensity | "deeply, genuinely sorrowful" |
| B | +2.5 | 1.1 | salient 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.
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.
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 family | VoiceNet dims (median over sentences) |
|---|---|
| Age / gender | AGEV, GEND |
| Timbre / quality | BRGT, ROUG, WARM, FULL, HARM, METL, ESTH |
| Resonance cavities | R_CHST, R_THRT, R_NASL, R_HEAD, R_MASK, R_ORAL, R_MIXD |
| Register / pitch | REGS, RANG |
| Overall emotional register | median 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."
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.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.
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.)
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.
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.
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.
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 emotion | Synonym cluster (from the EmoNet taxonomy) | Sample caption phrasings (rotated) |
|---|---|---|
| Anger | anger, rage, fury, wrath, hate, irascibility, vexation, annoyance, peevishness | "seething with rage" · "furious" · "wrathful" · "bitterly annoyed" |
| Sadness | sadness, sorrow, grief, melancholy, dejection, despair, heartache, mournfulness, misery | "deeply sorrowful" · "grief-stricken" · "melancholic" · "dejected" |
| Amusement | mirth, joviality, playfulness, silliness, jesting, lighthearted fun, laughter | "full of mirth" · "jovial" · "playful and silly" · "lighthearted" |
| Fear | fear, terror, dread, apprehension, alarm, horror, panic, nervousness | "terrified" · "dread-filled" · "panicked" · "nervous and apprehensive" |
| Contempt | contempt, disapproval, scorn, disdain, loathing, detestation | "scornful" · "full of disdain" · "contemptuous" · "loathing" |
| Elation | happiness, excitement, joy, exhilaration, delight, jubilation, bliss, cheerfulness | "jubilant" · "exhilarated" · "delighted" · "cheerful and bright" |
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.
[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.
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.
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.
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.