kokoro_tts: complete the upstream eSpeak frontend port - #13
Closed
christopherthompson81 wants to merge 1 commit into
Closed
christopherthompson81 wants to merge 1 commit into
christopherthompson81 wants to merge 1 commit into
Conversation
Upstream misaki has TWO eSpeak arms: EspeakFallback for English, with a rich
mapping table, and EspeakG2P for everything else, with only the tie-bar
digraphs. This port had the second one and used it for both, so English was
phonemized with the generic table.
Measured against upstream over 60 sentences of ordinary English, that agreed on
NONE of them:
this file emitted ː in 60 sentences, ɚ in 50, ɐ in 39, ɾ in 39
upstream emits none of those, and ᵊ in 31
Every one of those symbols IS in Kokoro's vocabulary, so nothing ever failed and
no test ever went red. The model was simply handed tokens it had not been
trained on, on every English sentence. With the English arm restored the two
agree on 60/60.
Two normalisations were missing outright, and each was losing whole words:
· A SYLLABIC CONSONANT has no token. eSpeak writes "button" as bˈʌʔn̩, and
U+0329 is not in the vocabulary, so the request threw. Upstream rewrites it
as schwa + consonant and then maps the glottal stop to the /t/ it stands
for, giving bˈʌtn. That is ordinary English, not exotica -- kitten, written,
forgotten, and every -tten/-tton word behaves the same way.
· GUILLEMETS are not in the vocabulary either, but the curly quotes upstream
turns them into are. Spanish and French prose carries « » as ordinary
quotation marks; passing them through as punctuation put an untokenizable
symbol in front of the model for ~12% of Spanish and ~15% of French
sentences of real corpus text.
Verified with the ORIGINAL throw still in place, so these are fixed at the
source rather than masked: button/kitten/written, Rustenburg, and Spanish and
French quoted text all synthesize, and Spanish that already worked is unchanged.
espeak_text() is split into espeak_raw() plus the two mapping arms, because both
tables must see eSpeak's output with its tie characters intact -- ə^l is a unit,
and the old function had already collapsed it. espeak_text() itself keeps its
signature and behaviour for every existing caller.
⚠ The generic arm stays narrow on purpose. Applying the English table to another
language is destructive rather than approximate: r→ɹ flattens the Spanish and
Italian trill, x→k flattens the jota, and stripping the nasalisation tilde
deletes the French nasal vowels. English can afford those because it has no
trill and its ɾ really is an allophone of /t/.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Review copy on the fork. Independent of #10/#11/#12 — this touches only
g2p_multilingual.cppand branches frommain.The gap
Upstream misaki has two eSpeak arms:
EspeakFallback— English, with a rich mapping tableEspeakG2P— everything else, with only the tie-bar digraphsThis port has the second and uses it for both, so English is phonemized with the generic table.
Measured against upstream over 60 sentences of ordinary English, that agreed on none of them:
ːin 60 sentences,ɚin 50,ɐin 39,ɾin 39ᵊin 31Every one of those symbols is in Kokoro's vocabulary, so nothing ever failed and no test ever went red. The model was simply handed tokens it had not been trained on, on every English sentence.
With the English arm restored: 60/60 identical.
Two normalisations missing outright
Each was losing whole words, and both produced hard failures rather than quiet drift.
A syllabic consonant has no token. eSpeak writes
buttonasbˈʌʔn̩, and U+0329 is not in the vocabulary, so the request throws. Upstream rewrites it as schwa + consonant, then maps the glottal stop to the /t/ it stands for:That is ordinary English —
kitten,written,forgotten, and every-tten/-ttonword behaves the same way.Guillemets are not in the vocabulary either, but the curly quotes upstream turns them into are. Spanish and French prose carries
« »as ordinary quotation marks; passing them through as punctuation put an untokenizable symbol in front of the model for roughly 12% of Spanish and 15% of French sentences of real corpus text.Verification
Run with the original throw still in place, so these are fixed at the source rather than masked by tolerating a bad symbol:
Spanish that already worked is unchanged.
Structure
espeak_text()is split intoespeak_raw()plus the two mapping arms, because both tables must see eSpeak's output with its tie characters intact —ə^lis a unit, and the old function had already collapsed it before any English rule could match.espeak_text()keeps its signature and behaviour for every existing caller.⚠ The generic arm stays narrow on purpose, and the comment says why. Applying the English table to another language is destructive rather than approximate:
r→ɹflattens the Spanish and Italian trill,x→kflattens the jota, and stripping the nasalisation tilde deletes the French nasal vowels. English can afford those because it has no trill and itsɾreally is an allophone of /t/. (I made exactly that mistake in my own renderer earlier and it cost a day, so the warning is written from experience rather than theory.)Relationship to the other PRs
This is a different bug from #10, and both are worth having. #10 stops an unknown symbol aborting a whole request, which remains correct as a backstop for anything neither table anticipates. This PR removes the two reasons that backstop currently fires — and does it by producing the readings Kokoro was trained on, where dropping a symbol only produces one that tokenizes.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EkxqpYvUbjCpRDnFiNiVfx