Repository navigation
tools: native misaki English G2P (misaki-g2p) for Paradee-8M, with agreement gate - #63
Merged
lalalune merged 4 commits intoOct 14, 2026
Merged
Conversation
Paradee-8M only saw misaki 0.9.4 phonemes in training. This ports misaki's American English pipeline as paradee/tts.py configures it (spaCy tokenizer, lexicon lookup, stemming, stress, numbers, eSpeak NG fallback with misaki's E2M table, paradee's 510-symbol chunking) to dependency-free C++17 with a C API, so speech can run without Python or ONNX. - convert_misaki_lexicon.py: pinned us_gold/us_silver -> deterministic, mmap-able misaki-en-us.lex (sha in pins.json) - rule-based tagger in place of en_core_web_sm; English number words written independently of num2words (LGPL) - optional libespeak-ng 1.52.0 (GPL-3.0-or-later, flagged in LICENSES/); without it unknown words are spelled out and fallback_kind() is 0 - agreement gate against the pinned Python misaki: 99.97% of words and 98.26% of sentences identical on the 230-sentence gate corpus Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…calls misaki_phonemize returns paradee's chunks joined by spaces; misaki_phonemize_chunks keeps them separate, matching one forward pass per chunk in paradee/tts.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…handle - misaki_g2p_phonemize's out-of-vocabulary error no longer embeds the offending chunk. The chunk is the caller's text in phonemes, and libelizainference passes this error up to callers that log it. - Each eSpeak fallback remembers its data directory and re-initialises libespeak-ng under the shared mutex when another handle switched it to a different directory, instead of silently phonemizing with the other handle's data. - The C entry points and the noexcept C++ wrappers catch every exception, not only std::exception, so nothing unwinds across the C ABI; an allocation failure in misaki_load_lexicon now sets err_out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The table generators and the agreement-reference script import the pinned Python misaki/spaCy/phonemizer env, which CI does not install, so the python type-check job could not resolve them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 14, 2026
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.
Adds
tools/misaki-g2p: a C++17 port of misaki 0.9.4's American English G2P. It is set up the wayparadee/tts.pyuses it, because Paradee-8M was trained only on misaki phonemes:en.G2P(trf=False, british=False, fallback=espeak.EspeakFallback(british=False), unk=''), plus paradee's_chunks.It has no ggml dependency and builds standalone or through
add_subdirectory. The root andtools/CMake hooks belong to the paradee-native change, so this PR does not touch them.What's in it
convert_misaki_lexicon.pyconverts the pinnedus_gold.json/us_silver.json(hexgrad/misaki@fba1236, the same bytes as the 0.9.4 wheel) intomisaki-en-us.lex. Before converting, it checks the sha256 of both inputs.grow_dictionary.fbf09090…3925, pinned inpins.json.merge_tokens,resolve_tokens, and the markdown[word](/phonemes/)features.web/misaki.js.misaki_g2p_open,misaki_g2p_phonemize(returns'\n'-separated chunks; fails on any symbol outside the Paradee vocab),misaki_g2p_fallback_kind(1 = eSpeak+E2M, 0 = spell-out),misaki_g2p_free,misaki_g2p_close, andmisaki_g2p_espeak_linked.eliza_misaki::misaki_load_lexicon,misaki_get_fallback_kind,misaki_phonemize,misaki_phonemize_chunks) for the ABI v17 bridge already merged in libelizainference ABI v17: Paradee + misaki TTS entries, ASR cancel/supported, TTS cancel and evict for Kokoro/Paradee #59. The declarations matchtools/omnivoice/tests/paradee-contract/misaki-g2p.h.Agreement gate (CTest
misaki_g2p_agreement)The reference is pinned Python misaki 0.9.4 with spaCy 3.8.16, en_core_web_sm 3.8.0, phonemizer-fork 3.3.2 and espeakng-loader 0.2.4 (libespeak-ng 1.52.0). Its output is committed as
tests/fixtures/reference.txtwith a.sha256file that the gate verifies.sentences.txt(25)The gate's report lists every mismatch. The four remaining gate failures all come from spaCy's statistical tags, which the rules can't reproduce:
" spaced "quotes;.;The gate passes with one sentence of margin (226 against the 225.4 the threshold needs). The not-gated rows show where the rules stop generalising.
Unit tests cover the tokenizer, subtokenizer, stress, E2M and numbers against expectation tables generated from the reference implementations. They also cover the C and C++ APIs, chunking, vocabulary validation, and bounded behaviour on hostile input.
The library builds cleanly with
-Wall -Wextra -Wpedantic -Wshadow -Wmissing-declarations -Werroron macOS, and for arm64-v8a with NDK r28 (-Werror, no eSpeak).Licence flags (flagged, not blocking)
MISAKI_G2P_ESPEAK=ONand the library is found. Without it, unknown words are spelled out with the lexicon's letter pronunciations,fallback_kindis 0, and those words differ from what Paradee was trained on. SeeLICENSES/eSpeak-NG-NOTICE.txt.tests/corpusare CC-BY-SA-3.0 test data.LICENSES/spaCy-MIT.txt).LICENSES/misaki-*).Notes for the sibling changes
add_subdirectory(tools/misaki-g2p)in front of omnivoice, plus theespeak-ngtarget or library and the en data fromkokoro-voice/inputs.jsonfor Android. libespeak-ng keeps its data path in a 160-byte buffer, so the on-deviceespeak-ng-datapath must stay under about 135 bytes.eliza_paradee_chunkpacks several sentences into one forward pass. paradee runs one pass per_chunksitem, which is whatmisaki_phonemize_chunks/misaki_g2p_phonemizereturn. Synthesizing each chunk separately would match the reference inference.AI assistance: Claude wrote this change and its tests. The reference outputs come from the pinned Python misaki.
🤖 Generated with Claude Code