Skip to content

kokoro_tts: complete the upstream eSpeak frontend port - #13

Closed
christopherthompson81 wants to merge 1 commit into
mainfrom
pr4-complete-misaki-espeak-port
Closed

christopherthompson81 wants to merge 1 commit into
mainfrom
pr4-complete-misaki-espeak-port

Conversation

@christopherthompson81

Copy link
Copy Markdown
Owner

Review copy on the fork. Independent of #10/#11/#12 — this touches only g2p_multilingual.cpp and branches from main.

The gap

Upstream misaki has two eSpeak arms:

  • EspeakFallback — English, with a rich mapping table
  • EspeakG2P — everything else, with only the tie-bar digraphs

This 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:

this file emits ː 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: 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 button as bˈʌʔ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:

raw espeak     button -> bˈʌʔn̩          (this port, unhandled)
upstream       button -> bˈʌtn

That is ordinary English — 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 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:

OK  button/kitten/written   2.55s    OK  en-gb           2.88s
OK  Rustenburg              2.52s    OK  es unaffected   2.25s
OK  es guillemets           1.73s
OK  fr guillemets           2.58s

Spanish that already worked is unchanged.

Structure

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 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, xk 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/. (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

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/.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant