Skip to content

Language support

This page covers the default std phoneme set and its mbfa-* models: which languages they align, the code to pass for each, and what changes for tonal and unspaced languages. The espeak-ng ph66 set is summarised at the end.

How a language picks its model

lang code ──► canonical BCP 47 code ──► language group ──► mbfa model
  "pt-br"          "pt-BR"                  latin            mbfa-latin
  "cmn"            "zh"                     cjk              mbfa-cjk
  "hr"             "hbs"                    cyrillic         mbfa-cyrillic

Languages are grouped by writing system, not by language family. Each language group has its own token list (the model's phoneme head) and its own model. Within a group, switching language only changes the G2P's language: aligner.phonemizer.set_backend(language="fr"). Across groups, the model has to change too: aligner.load_model(preset="ru").

Codes are accepted in any case and in several spellings: BCP 47 (pt-BR), ISO 639-3 (deu, cmn), and the espeak-ng spellings the ph66 set uses (en-us, fr-be, en-gb-x-rp). A regional variant the G2P does not have falls back to its base language with a warning. aligner.lang holds the canonical code.

A language outside the nine groups below raises UnsupportedLanguageError, and the message names phoneme_set="ph66" as the alternative. BFA never falls back to the other phoneme set silently.

balign --list-presets prints the live list.

Supported: 82 languages in 9 language groups

Trained means the language was in the model's FLEURS training data; the hours are the FLEURS training audio for that language. mbfa-latin and the models of the newer groups (vietnamese, thai_khmer, abjad, brahmic, other_alphabetic) used all of it. mbfa-cyrillic and mbfa-cjk used a 62% sample. A language the G2P covers but the model never heard is still aligned, through the phonemes it shares with the trained ones. Expect it to be less accurate.

latin → mbfa-latin (208 tokens)

The default group. Latin script, words separated by spaces, no tone. Two more latin models cover the same languages and are selected by name: mbfa-latin-benchmark (best on clean speech) and mbfa-latin-noEnglish (the same recipe trained without English, for transfer tests).

code language trained (h)
af Afrikaans 3.2
az Azerbaijani 8.5
bs Bosnian 9.5
ca Catalan 7.0
cs Czech 8.2
cy Welsh (North); cy-sw South 6.8
da Danish 7.3
de German 8.8
en English (US); en-GB British 7.1
es Spanish (Castilian); es-419 Latin American, es-MX Mexican 8.5 (es-419)
et Estonian 7.0
fi Finnish 8.2
fr French; fr-CA Quebec 9.7
ga Irish 7.3
gl Galician 6.5
hu Hungarian 9.0
id Indonesian 8.1
is Icelandic 2.7
it Italian 8.8
lb Luxembourgish 7.3
lt Lithuanian 8.9
mi Māori 4.3
ms Malay 8.3
mt Maltese 9.1
nb Norwegian Bokmål (no and nn resolve here) 8.8
nl Dutch 7.6
pl Polish 8.8
pt Portuguese (European); pt-BR Brazilian 8.7 (pt-BR)
ro Romanian 9.5
sk Slovak 5.7
sl Slovenian 7.3
sv Swedish 7.3
sw Swahili 6.8
tl Tagalog / Filipino (fil) 6.0
tr Turkish 7.7
uz Uzbek 9.1
arg (an) Aragonese —
eo Esperanto —
eu Basque —
ia Interlingua —
io Ido —
la Latin (classical); la-eccl ecclesiastical —
pap Papiamento —
se Northern Sami —
sq Albanian —
tk Turkmen —
ang Old English —
enm Middle English —
egy Egyptian (transliteration) —

cyrillic → mbfa-cyrillic (127 tokens)

Cyrillic script, words separated by spaces, no tone.

code language trained (h)
be Belarusian 7.7
bg Bulgarian 8.9
hbs Serbo-Croatian, Latin script (hr resolves here); hbs-Cyrl Cyrillic 9.3 (hr)
kk Kazakh 8.5
mk Macedonian 6.5
ru Russian 7.6
sr Serbian 8.3
uk Ukrainian 8.5
ady Adyghe —
ba Bashkir —
tt Tatar —

Croatian is in this group even though it is written in Latin script. The G2P has one Serbo-Croatian language with a tag for each script, and the language takes the group of the majority. So BFA(preset="hr") loads mbfa-cyrillic, which was trained on Croatian. Bosnian (bs) is its own G2P language and stays in latin.

cjk → mbfa-cjk (48 tokens)

Han script. These languages need word segmentation, and they carry lexical tone.

code language trained (h) segmenter
zh Mandarin, simplified script (cmn, zh-cn resolve here) 9.0 built in (longest match on the bundled dictionary keys)
zh-Hant Mandarin, traditional script (zh-tw) (as zh) built in
yue Cantonese 5.9 pycantonese: pip install "bournemouth-forced-aligner[yue]"

japanese → mbfa-japanese (28 tokens)

code language trained (h) segmenter
ja Japanese 7.0 fugashi + unidic-lite: pip install "bournemouth-forced-aligner[ja]"

BFA(preset="ja") raises ImportError at construction until the segmenter is installed.

vietnamese → mbfa-vietnamese (45 tokens)

Latin script with tone letters. Kept out of latin because it is tonal.

code language trained (h)
vi Vietnamese (Northern); vi-c Central, vi-s Southern 8.7

thai_khmer → mbfa-thai (38 tokens)

Thai script. Needs word segmentation, and carries lexical tone. Khmer shares the token list but is excluded (see below), so the model was trained on Thai only.

code language trained (h) segmenter
th Thai 7.8 pythainlp: pip install "bournemouth-forced-aligner[th]"

abjad → mbfa-abjad (90 tokens)

Arabic script and Syriac. The orthography leaves most vowels out, so the G2P supplies them.

code language trained (h)
ar Arabic 5.8
ku Kurdish (Central, ckb) 9.0
fa Persian 8.2
sd Sindhi 8.8
ur Urdu 6.3
ug Uyghur —
syc Syriac —

brahmic → mbfa-brahmic (96 tokens)

Indic abugidas (Devanagari, Odia, Tamil scripts).

code language trained (h)
hi Hindi 6.2
or Odia 3.2
ta Tamil 7.4
sa Sanskrit —

other_alphabetic → mbfa-other-alphabetic (104 tokens)

The remaining alphabets: Greek, Armenian, Georgian, Hangul and Ethiopic. Words are separated by spaces, and no language in the group has tone.

code language trained (h)
am Amharic 9.0
el Greek 9.6
hy Armenian (Eastern); hy-west Western 9.3
ka Georgian 4.5
ko Korean 7.4
grc Ancient Greek —

The models of these five groups were warm-started from the latin model's trunk and selected on FLEURS validation loss; no boundary labels exist for them, so they have no boundary evaluation yet.

Not supported yet

language group languages why not yet
excluded km Khmer, my Burmese, nan Min Nan, tts Isan the G2P output is not usable yet (see below)

The excluded languages have these problems: Khmer folds 20% of its tokens to <unk>. Burmese writes tone as vowel diacritics, and its group has no phoneme tokens at all. Min Nan has no maintained segmenter, and most of its dictionary carries tone sandhi. Isan's dictionary is a romanisation, not IPA.

For any of these, phoneme_set="ph66" runs if espeak-ng knows the language, with the caveats below.

Nuances

No text normalisation

The G2P reads one dictionary word at a time. Digits, symbols and abbreviations are not expanded: write "twenty five", not "25", and "doctor", not "Dr.". espeak-ng (the ph66 set) does expand them.

Silence and punctuation

By default a SIL is written at every pause mark (, . ; : ! ? …, dashes and the CJK marks ,。!?;:、). Without one, a pause has nowhere to go and is swallowed by the phone next to it. The aligner can skip any SIL at no cost, so this only gives the path a place to put a pause. punctuation_sil=False (--no-punctuation-sil) turns it off: the mbfa models were trained on sequences without these silences, and the paper's benchmarks were run that way. An explicit <SIL> in the text is always honoured.

Tone

Mandarin, Cantonese, Vietnamese and Thai are the tonal languages with a model. Tone is a separate layer, not part of the phoneme: the vowel /a/ with tone 35 and with tone 21 is the same token, so the phoneme inventory stays small and the phonemes are shared with other languages.

  • Each phone carries a tone index into a fixed vocabulary of 22 Chao contours ('' 35 21 33 45 51 32 214 55 24 3 312 5 13 2 212 42 44 53 22 4 114). 0 means no tone. The tone sits on the syllable's vowel, on the last token when the vowel is split into two (知道 → ʈ ʂ ɚ⁵⁵ t ɑ ʊ⁵¹), and consonants carry 0.
  • The tone comes from the G2P's dictionary pronunciation, not from the audio. It is the word's lexical tone, with tone sandhi not applied, so a speaker's realised contour can differ from it. The tone's timing is the timing of the vowel it sits on. The tonal models have a trained tone head, but BFA does not read it yet.
  • Atonal languages have 0 everywhere. Burmese tone is written as vowel diacritics, so it would not reach this layer (Burmese is excluded anyway).
  • Pitch accent is not tone. Serbo-Croatian and Slovenian mark rising and falling accents with the same diacritics, and the G2P keeps them as part of the vowel. Accented vowels are therefore separate tokens in those languages (dobar dan → d ô b a r d â n), and tone stays 0.

Stress and length

Two more layers, one value per phone, filled for every language where the G2P writes them:

layer values
stress 0 none, 1 primary (ˈ), 2 secondary (ˌ), on the syllable nucleus
length 0 short, 1 half-long (ˑ), 2 long (ː)

Japanese long vowels and other length contrasts are written this way: aː is the token a with length 2. Length has three values, not two, because the half-long mark is rare but real.

Two tokens for one phoneme

The token lists are small, so a phoneme that is not in its group's list backs off: to its nearest neighbour (phoneme_label then differs from ipa_label), to two tokens, or as a last resort to <unk>. Affricates and diphthongs are the usual two-token cases: Mandarin ʈ͡ʂ → ʈ ʂ, English /aɪ/ → a ɪ, /tʃ/ → t ʃ. Both tokens get their own timestamp, and both carry the phoneme's tone, stress, length and word. If you need one interval per phoneme, merge them by target_seq_idx and word. (This is also why the std models place more onsets than a corpus labelled phone by phone: see Evaluation.)

phonemize_sentence(text)["n_folded"] counts the tokens that fell back to <unk>.

Word segmentation

The G2P takes one word in and gives one IPA string out, so a script that does not put spaces between words must be segmented first. BFA does this automatically:

language segmenter install
Mandarin (zh, zh-Hant) longest match on the bundled dictionary keys nothing
Cantonese (yue) pycantonese (HKCanCor) [yue]
Japanese (ja) fugashi with UniDic morphemes, fed to the G2P as katakana [ja]
Thai (th) pythainlp newmm [th]

Each language has exactly one segmenter, so the same text always produces the same words on every machine. A missing segmenter package raises once, when the language is set, and the error includes the install line.

Already-phonemized input

A segment that carries gold_ph (a standard_g2p .gs.json segment) is aligned from those phonemes and is not phonemized again, so the G2P model is never loaded for it:

gs = json.load(open("clip.gs.json"))
result = aligner.process_segments([{"segments": gs["segments"]}], [audio])

The ph66 set

phoneme_set="ph66" phonemizes with espeak-ng (pip install "bournemouth-forced-aligner[ph66]" plus the system espeak-ng package) into one 66-phoneme inventory shared by every language. The language picks one of two models:

  • English codes (en-us, en, en-gb, en-029, en-gb-x-rp, en-gb-scotland, en-gb-x-gbclan, en-gb-x-gbcwmd) → CUPE-3-en, the most accurate English model BFA has.
  • Every other code → CUPE-3-mls7, trained on English plus German, Spanish, French, Italian, Dutch, Polish and Portuguese. For any other language, its phonemes are aligned by their nearest sounds in those, with a warning.

espeak-ng can phonemize 80+ languages into ph66. These are the codes the ph66 mapper covers:

Germanic   de nl af lb da sv nb is
Romance    fr fr-be fr-ch es es-419 it pt pt-br ca an ro la
Slavic     pl cs sk sl hr bs sr mk bg ru ru-lv uk be
Celtic     ga gd cy        Baltic  lv lt        Uralic  et hu fi
Indic      hi bn ur pa gu mr ne as or si kok sd
Iranian    fa fa-latn ku   Hellenic el          Armenian hy hyw     Albanian sq
Turkic     tr az kk ky uz tt tk ba
Semitic    ar he am mt     Dravidian ta te kn ml
Other      ka eu qu id ms eo ia io lfn

ph66 has no tone, so it does not cover tonal languages (Chinese, Vietnamese, Thai, Burmese) or Japanese and Korean. That is a limit of the inventory, and a different model would not fix it. Use the std set for Mandarin, Cantonese and Japanese. The full espeak-ng code list is in espeak-ng's docs.

ph66 output uses the keys ph66 / pg16 where std uses local_ph / gold_phg, and it has no tone, stress or length. A compound phoneme such as /ɑːɹ/ is split into two entries, and the second one has ipa_label: "-".