Environment
- Model: Supertonic-3-LiteRT
- Component:
speech_core::SupertonicTokenizer / LiteRTSupertonicTts
- Target Platform: Android (LiteRT / C++)
Description
When synthesizing text using LiteRTSupertonicTts, sentences that slightly exceed the per-chunk token limit (cap / max_codepoints, typically ~50–60 codepoints) suffer from unnatural pauses mid-sentence, frequently isolating the final 1 or 2 words into a standalone synthesis chunk.
This makes the voice sound unnatural, breaking phrases right before the last word (e.g., "... sous les [pause] arbres." or "... dans le [pause] jardin.").
Root Cause Analysis
After inspecting supertonic_tokenizer.cpp, the issue appears to stem from the interaction of three mechanisms:
-
Greedy Chunk Packing in SupertonicTokenizer::chunk:
When an oversize sentence is split on word boundaries, words are greedily accumulated until the capacity (cap) is reached:
for (char32_t c : sent) {
if (is_ws(c)) { push_word(); continue; }
word.push_back(c);
if (static_cast<int>(word.size()) >= cap) push_word();
}
For a typical sentence of ~58–62 codepoints and a cap of ~50, the first chunk consumes ~50 codepoints, leaving a tiny remnant of 7–9 codepoints (almost always exactly the final word) in the second chunk.
-
Forced Terminal Period in SupertonicTokenizer::preprocess:
if (!ends_with_terminal(out)) out.push_back('.');
When a sentence is severed mid-phrase (e.g. "... dans le"), preprocess() appends a terminal period .. This forces the non-autoregressive Flow-Matching model to synthesize the fragment with falling terminal intonation, followed by generating "jardin." as a completely new sentence with independent acoustic boundaries.
-
Sub-sentence Punctuation Ignored in kTerm:
kTerm currently only splits at U".!?…。!?". Clauses separated by commas ,, colons :, or semicolons ; are not treated as valid chunk boundaries, causing long multi-clause sentences to bypass clause-level splitting and fall straight into the greedy word-packer.
Steps to Reproduce
Synthesize the following French paragraph with LiteRTSupertonicTts:
Bien sûr ! Voici une petite histoire de vacances d'été en 10 phrases :
Cet été, j'ai passé mes vacances à la campagne. J'ai loué une petite maison près d'un lac magnifique. Chaque matin, je me levais tôt pour faire une longue promenade. J'ai beaucoup nagé dans l'eau fraîche et claire. J'ai aussi passé du temps à lire des livres sous les arbres. Un après-midi, j'ai aidé mes voisins à jardiner. Nous avons mangé des fruits frais cueillis dans le jardin. Le soir, je regardais les étoiles depuis ma terrasse. C'était un été très calme et reposant. Je suis revenu à la ville avec beaucoup de souvenirs heureux.
Est-ce que tu veux que je traduise ce texte ou que je te raconte une autre histoire ?
Observed Behavior
Noticeable pauses and intonation breaks occur at:
"... de vacances d'été en 10" [pause] "phrases :" (~50 chars + 9 chars)
"... à lire des livres sous les" [pause] "arbres." (~52 chars + 7 chars)
"... cueillis dans le" [pause] "jardin." (~50 chars + 7 chars)
"... beaucoup de souvenirs" [pause] "heureux." (~52 chars + 8 chars)
"... ce texte ou que je te" [pause] "raconte une autre histoire ?" (~56 chars + 28 chars)
Sentences under ~48 codepoints (e.g., "Cet été, j'ai passé mes vacances à la campagne.") synthesize cleanly without any pauses.
Environment
speech_core::SupertonicTokenizer/LiteRTSupertonicTtsDescription
When synthesizing text using
LiteRTSupertonicTts, sentences that slightly exceed the per-chunk token limit (cap/max_codepoints, typically ~50–60 codepoints) suffer from unnatural pauses mid-sentence, frequently isolating the final 1 or 2 words into a standalone synthesis chunk.This makes the voice sound unnatural, breaking phrases right before the last word (e.g.,
"... sous les [pause] arbres."or"... dans le [pause] jardin.").Root Cause Analysis
After inspecting
supertonic_tokenizer.cpp, the issue appears to stem from the interaction of three mechanisms:Greedy Chunk Packing in
SupertonicTokenizer::chunk:When an oversize sentence is split on word boundaries, words are greedily accumulated until the capacity (
cap) is reached:For a typical sentence of ~58–62 codepoints and a
capof ~50, the first chunk consumes ~50 codepoints, leaving a tiny remnant of 7–9 codepoints (almost always exactly the final word) in the second chunk.Forced Terminal Period in
SupertonicTokenizer::preprocess:When a sentence is severed mid-phrase (e.g.
"... dans le"),preprocess()appends a terminal period.. This forces the non-autoregressive Flow-Matching model to synthesize the fragment with falling terminal intonation, followed by generating"jardin."as a completely new sentence with independent acoustic boundaries.Sub-sentence Punctuation Ignored in
kTerm:kTermcurrently only splits atU".!?…。!?". Clauses separated by commas,, colons:, or semicolons;are not treated as valid chunk boundaries, causing long multi-clause sentences to bypass clause-level splitting and fall straight into the greedy word-packer.Steps to Reproduce
Synthesize the following French paragraph with
LiteRTSupertonicTts:Observed Behavior
Noticeable pauses and intonation breaks occur at:
"... de vacances d'été en 10"[pause]"phrases :"(~50 chars + 9 chars)"... à lire des livres sous les"[pause]"arbres."(~52 chars + 7 chars)"... cueillis dans le"[pause]"jardin."(~50 chars + 7 chars)"... beaucoup de souvenirs"[pause]"heureux."(~52 chars + 8 chars)"... ce texte ou que je te"[pause]"raconte une autre histoire ?"(~56 chars + 28 chars)Sentences under ~48 codepoints (e.g., "Cet été, j'ai passé mes vacances à la campagne.") synthesize cleanly without any pauses.