eSpeak NG 词典与发音规则详解:从文本到音素的完整翻译体系
【免费下载链接】espeak-ngeSpeak NG is an open source speech synthesizer that supports more than hundred languages and accents.项目地址: https://gitcode.com/GitHub_Trending/es/espeak-ng
本文系统讲解 eSpeak NG 文本转语音引擎中"文本 → 音素"翻译层的完整实现:每种语言如何通过发音规则(*_rules)与查询词典(*_list/*_extra)把拼写转换为音素串,以及这些规则文件的语法、特殊字符、标记(Flags)、条件规则、数字处理和字符替换机制。读完本文,你将掌握dictsource/目录下各语言规则文件的编写规范,能够阅读、调试甚至扩展任一语言的发音数据,并理解其背后的源码实现。
Translation Files:翻译文件的两种来源
eSpeak NG 为每种语言维护一组独立的发音文件,文件名以语言名开头,统一存放在仓库的dictsource/目录下。把单词翻译成音素有两条并行的路径:
- 发音规则(Pronunciation Rules):尝试为整门语言定义拼读到发音的规则,源文件为
<language>_rules(例如dictsource/en_rules,当前仓库中约 7131 行)。规则按字母(或字母组合)分组,逐字母扫描单词并套用匹配规则。 - 查询词典(Lookup Dictionary):列出个别单词及其发音和/或各种属性的清单,源文件为
<language>_list(例如dictsource/en_list,约 5789 行),以及可选的<language>_extra(例如en_extra)。
这些源文件被编译为espeak-ng-data目录下的<language>_dict文件(例如espeak-ng-data/en_dict)。编译过程由 Makefile 驱动,在 Makefile.am 中可以看到完整规则:
espeak-ng-data/%_dict: src/espeak-ng phsource/phonemes.stamp @echo " DICT $@" rm -f $@ cd dictsource && ESPEAK_DATA_PATH=$(CURDIR) ../src/espeak-ng --compile="$(*F)"即:用刚构建好的src/espeak-ng程序自身执行--compile=<语言名>来生成*_dict二进制数据。这也解释了为何 docs/building.md 强调"构建过程使用刚构建出的程序来编译语言与语音数据",因此必须先完成本地构建。如需只编译某一种语言,可运行make <LANG>(LANG为语言代码)。实际编译逻辑位于 compiledict.c,其中compile_dictrules()(compiledict.c)负责把规则文件解析成二进制规则数据。
翻译词条时,两条路径的分工与调用关系体现在 translate.c 的TranslateWord()→TranslateWord3()链路中:先查词典(LookupDictList(),见 dictionary.c),未命中再用规则翻译(TranslateRules(),见 dictionary.c);若规则识别出标准前后缀,还会去掉前后缀后重新查词典。整句翻译由TranslateClause()(translate.c)逐词调用TranslateWord2()完成。
Phoneme names:音素名称
每种语言的每个音素用一个 1~4 个字符的助记符表示,连同若干工具码(如重音标记和停顿)一起定义在音素数据文件中。工具音素(utility phonemes)包括:
| 符号 | 说明 |
|---|---|
' | 主重音(primary stress) |
, | 次重音(secondary stress) |
% | 非重音音节(unstressed syllable) |
= | 把主重音放到前一个音节上 |
_: | 短停顿(short pause) |
_ | 更短的停顿 |
| | 用于分隔两个相邻字符,防止它们被当作一个多字符音素 |
|| | 表示一个音素串内部的词边界(word boundary) |
注意,不必为每个音节都标注重音——重音标记只在需要改变语言默认重音规则效果时才需要。例如 dictsource/en_list 头部注释明确写道:"stress symbols ' primary , secondary % unstressed"。
各语言用于表示语音的音素,松散地基于 Kirshenbaum(ASCII-IPA)表示法 对国际音标的转写。常用音素的完整清单见phsource/phonemes文件。在词典/规则文件中书写音素时,重音标记紧跟其作用的元音音素之前(如t'Ent2i表示重音在第一个音节上)。
Pronunciation Rules:发音规则
<language>_rules文件中的规则规定了每个字母或字母序列对应哪些音素。有些规则仅在该字母(序列)被其他指定字母前置或后随时才生效。
匹配算法:为单词寻找发音时,规则被逐一搜索,任何匹配当前单词位置字母的规则都会根据其匹配的字母数量获得一个分数,得分最高的匹配规则被选中;随后指针越过已匹配的字母,重复该过程直至处理完单词的所有字母。该算法在TranslateRules()(dictionary.c)中实现:函数以空格为边界截取单词副本(word_copy),逐字符推进,对每个位置搜索字符组并选取最高分规则;若启用了espeakPHONEMES_TRACE(配合--phonemes --trace等选项),还会输出每条规则的匹配轨迹,便于调试规则文件。
Rule Groups:规则组
规则按组组织,每组以.group行开始:
.group <character>:为每个字母或字符设一组。.group <2 characters>:为某些常见的两字母组合设的可选组。仅当某字母的规则特别多时才需要(出于效率考虑);拼写规则规律的语言则不需要。两字母组的第一个字符只能是 ASCII 字符(小于 0x80)。
匹配单词时,先搜索当前位置两个字母对应的两字母组(如果存在),再搜索单字母组;两个组中得分最高的规则被采用。
.group:为没有自己组的其他字符设的组。.replace:见下文"字符替换"一节。
Letter groups:字母组
字母组用于声明一组具有某种共同特征的字母序列,可作为前规则(prerules)中词首/词中前缀的占位符,或后规则(postrules)中词中/词尾后缀的占位符。
.L<nn>:定义一个字母序列组,组内任意序列都可以在前/后规则中以Lnn匹配。nn是 01 到 94 的两位十进制数。例如:.L01 b bl br pl pr。
关于字母组的注意点:
- 一个字母组最多可有200 项(源码中定义为
#define N_LETTERGP_ITEMS 200,见 compiledict.c)。 - 匹配单词时,先检查当前位置包含字母最多的组(如果存在),再依次检查更短的组,直到单字母组;匹配组中得分最高的规则被采用。
- 字母组中的
~表示:在前规则或后规则的这个位置上不能有任何字母。
前规则字母组示例:
.L01 ~ b c .group a L01) a i // A以下单词都会匹配:base→bice、case→cice、ace→ice(因为~允许 a 前面没有字母)。
| 单词 | 音标输出 |
|---|---|
| base | bice |
| case | cice |
| ace | ice |
后规则字母组示例:
.L01 ~ b c .group a a (L01 u匹配结果:tab→tub、mac→muc、tea→teu(tea的 a 后没有字母,~匹配)。
| 单词 | 音标输出 |
|---|---|
| tab | tub |
| mac | muc |
| tea | teu |
真实语言中字母组的用法可以参考 dictsource/en_rules 开头的定义,例如.L01 l r、.L02 i y、.L05 c g k、.L06 s z等,它们服务于英语后缀处理的各类场景。
Rules:规则语法
每条规则独占一行,语法为:
[<pre>)] <match> [(<post>] <phoneme string><pre>)中的字符已被拼出并被"消耗"(不参与本次发音输出,仅作为匹配条件)。<match>中的字符将被拼出并被"消耗"——由得分最高的规则决定其发音。(<post>中的字符不会被拼出或产生发音,但可作为选择匹配规则的参考。
注意:<match>可以比字符组名更长,但不能更短。
示例:
.group o o 0 // "o" 读作 [0],消耗一个字母 oo u: // 但 "oo" 读作 [u:],消耗两个字母 b) oo (k U // 读作 [U],消耗两个字母oo通常读作[u:],但当它同时前接b且后随k时读作[U]。对于单字母组,<match>的第一个字符必须是组字母;对于两字母组,<match>的前两个字符必须是组字母。上面第二、三条规则既可以放在.group o中,也可以放在.group oo中。
<pre>、<match>、<post>中的字母必须小写,匹配时不区分大小写。一些大写字母在<pre>和<post>中有特殊含义(见下文)。
<phoneme string> 中的特殊字符
_^_<language code>:改用另一种语言翻译。若翻译单词时选中了这条规则,则本次翻译中止,改用指定的语言重新翻译整个单词。<language code>可大写或小写。这可用于识别某些字母组合是外来词并采用外来语发音,例如:
th (_ _^_EN表示以th结尾的单词改用英语翻译规则、以英语音素读出。
同时出现在 <pre> 和 <post> 中的特殊字符
| 符号 | 说明 |
|---|---|
_ | 词首或词尾(或连字符处) |
- | 连字符 |
A | 任意元音1 |
C | 任意辅音1 |
B H F G Y | 可表示其他字符集合1 |
L<nn> | 字母组nn中定义的任一字母序列 |
D | 任意数字 |
K | 非元音(即辅音或词边界或非字母字符) |
X | 直到词边界都没有元音 |
Z | 非字母字符 |
% | 双写(在 <pre> 中置于字符前、在 <post> 中置于字符后) |
/ | 其后字符按字面处理 |
\xxx | 以 xxx 的三位八进制值表示的字符 |
@ | 一个音节(即至少一个元音或双元音) |
规则示例:
_) a A // 词首的 "a" a (CC A // "a" 后跟两个辅音 a (C% A // "a" 后跟双写辅音(同一字母出现两次) a (/% A // "a" 后跟百分号 %C) a A // "a" 前是双写辅音 @@) bi bI // "bi" 前至少有 2 个音节 @@a) bi bI // "bi" 前至少有 2 个音节且后随 'a' @) ly (_S2 lI // 词尾且有至少一个其他音节的 "ly" 是发 [lI] 的后缀; // 移除它并重译整个单词 _) un (@P2 %Vn // 词首的 "un" 是非重读前缀,发 [Vn]注意事项:
- <pre> 部分匹配的字符不参与音节计数。
- 词尾标记不能与音节标记连用,例如
_@)或@_无效;但可以用@) ... (+或... (@+构造"仅一个音节"规则来压过"多个音节"规则。
仅出现在 <pre> 中的特殊字符
| 符号 | 说明 |
|---|---|
& | 可能重读的音节(即未被定义为非重读) |
V | 仅当上一个词提示了预期为动词形式时匹配 |
xxJ | 跳字母直到xx。单独的xx表示当前词开头;xx_yy表示xx是上一词结尾、yy是当前词开头。必要时可用多个J,也支持Lxx组作为字母标记 |
例如:
get_J) a i // 对 "get ada" 会说 "get adi" setJ) a o // 对 "set ada" 会说 "set oda" ge_tJ) a i: // 对 "ge tada" 会说 "get adi:"仅出现在 <post> 中的特殊字符
| 符号 | 说明 |
|---|---|
+ | 强制本条规则得分增加 20 分(可重复以获得更强效果) |
< | 强制本条规则得分减少 20 分(可重复) |
Jxx | 跳字母直到xx。单独的xx表示当前词末尾;xx_yy表示xx是当前词尾、yy是下一词开头。必要时可用多个J,也支持Lxx组 |
S<number> | 匹配到的这些字符是标准后缀:移除它们并重译单词 |
P<number> | 匹配到的这些字符是标准前缀:移除它们并重译单词 |
Lnn | nn是 01 到 20 的两位十进制数;匹配字母组nn中定义的任一字母序列 |
N | 仅当单词不是"移除后缀后的重译"时使用此规则 |
# | (仅英语)把下一个 "e" 变成特殊字符 "E" |
$noprefix | 仅当单词不是"移除前缀后的重译"时使用此规则 |
$w_alt | 仅当整个词($w_alt2)以$w_alt3、$alt、$alt2或$alt3属性出现在*_list文件中时使用此规则 |
$p_alt | 仅当本条规则的$p_alt2(含 pre)到$p_alt3匹配部分构成的部分词以$alt、$alt2或$alt3属性出现在*_list文件中时使用此规则 |
示例:
_) un (i ju: // ... 除了以 "uni" 开头的词 _) un (inP2 ,Vn // ... 但对以 "unin" 开头的词成立 a (J_get u // 对 "ada get" 会说 "uda get" a (Jset e // 对 "ada set" 会说 "ade set" a (Jg_et u: // 对 "adag et" 会说 "u:dag et"S和P必须位于 <post> 串的末尾。
S<number>之后可跟附加字母(如S2ei),P<number>之后同样可跟附加字母(如P3v)。其中部分可能为英语专用,但其他语言可以建立类似机制:
| 符号 | 说明 |
|---|---|
q | 查询*_list文件以找到词干的词重音位置或其他属性,但不用去掉后缀的词重译 |
t | 在加后缀之前确定单词的重音模式 |
d | 加后缀时前一个字母可能被双写 |
e | 可能被删除了一个e |
i | y可能被改成了i |
v | 该后缀意味着应使用动词形式的发音 |
f | 该后缀意味着下一个词很可能是动词 |
m | 移除此后缀后,还可以继续移除更多后缀 |
t | 在加前缀之前确定单词的重音模式 |
Pronunciation Dictionary List:发音词典列表
<language>_list文件包含一批发音被显式给出的单词(而不是由发音规则推导)。若存在<language>_extra文件,其内容被视作排在<language>_list之后(即优先级更高,因为词典是自底向上搜索的)。除发音外,列表还可以用来指定单词的重音模式或其他属性。
一个重要的交互机制:如果发音规则应用到某词并识别出标准前缀或后缀,那么去掉前后缀后,该词会再次在发音词典列表中查找(这正是S/P标记的实现效果)。
词典列表每行的形式:
<word> [<phoneme string>] [<flags>]例如:
book bUk也可以只给出重音而不给完整发音,用来改变发音规则原本会放置的重音位置:
berlin $2 // 重音在第二音节 absolutely $3 // 重音在第三音节 for $u // 非重读的词Multiple Words:多词组合
也可以为一组同时出现的词指定发音,最多四个词,用括号括起来。用途包括:改变这些词一起出现时的发音或重音模式——
(de jure) deI||dZ'U@rI2 // 注意:音素串中用 || 作词分隔或把它们连读、当作一个词发音——
(of a) @v@或给一起出现的组合附加标记——
(such as) sVtS||a2z $pause // 前面加停顿连字符词在<language>_list文件中必须用括号括起来,因为其两部分被视为两个独立的词。
<phoneme string> 中的特殊字符
| 符号 | 说明 |
|---|---|
_^_<language code> | 改用另一种语言翻译(见上文解释) |
Flags:标记
一个词(或词组)可以带一个或多个标记,可以代替音标或与音标并用:
| 符号 | 说明 |
|---|---|
$1~$7 | 主重音分别在第 1~7 个音节上 |
$u | 该词非重读。对于多音节词,按默认重音规则施加轻微重音 |
$u1$u2$u3 | 非重读,但在第 1/2/3 音节上带轻微重音 |
$u+$u1+$u2+$u3+ | 同上,但如果该词位于子句末尾则带完整重音 |
$pause | 确保在该词之前有短暂停顿(如 "and" 等连词、某些介词)。不适用于句子的第 1、2 个词或最后一个词 |
$brk | 确保该词前有非常短的停顿,比$pause更短(如某些介词)。不适用于句子最后一个词 |
$only | 如果已经移除了前缀或后缀,则此规则不适用 |
$onlys | 同$only,但允许标准复数结尾 |
$stem | 仅当已经移除了后缀(即原词必须带后缀)时此规则才适用 |
$strend | 位于子句末尾时该词带完整重音 |
$strend2 | 同$strend,但后面只跟着非重读词时也重读 |
$unstressend | 位于子句末尾时该词不重读 |
$abbrev | 有两种含义。若无音素串:即使词含元音也按单个字母逐个读出(如 "abc" 应读作 "a" "b" "c")。若有音素串:该词因是缩写而大写,且大写不表示强调(当"强调全大写"开启时) |
$double | 使下一个词的词首辅音双写(如意大利语、芬兰语使用) |
$alt$alt2..$alt7 | 这 7 组为语言相关标记,其用法应在该语言的*_list文件中说明。$alt与$alt1同义 |
$combine | 该词被当作与下一个词以连字符组合 |
$dot | 忽略该词后面的.,即使后面跟着大写字母(如Mr.、Dr.) |
$hasdot | 若该词后跟句点则使用此发音(同时隐含$dot) |
$max3 | 发音中最多重复 3 次 |
$text | 该词翻译为替换文本而非音素 |
$verbf | 下一个词很可能是动词 |
$verbsf | 下一个词若带 "s" 后缀则很可能是动词 |
$nounf | 下一个词很可能是名词 |
$pastf | 下一个词很可能是过去时 |
$verb | 若为动词则用此发音,即前一个已处理词设置了$verbf或$verbsf |
$noun | 若为名词则用此发音,即前一个已处理词设置了$nounf |
$past | 若为过去时则用此发音,即前一个已处理词设置了$pastf |
$verbextend | 将$verbf与$verbsf的影响延伸到遇到带$verb或$nounf的词为止 |
$capital | 若该词首字母大写则用此发音(如 polish 与 Polish) |
$allcaps | 若该词全大写则用此发音 |
$accent | 用于单个字母字符的发音:字符名读作"基字母名 + 重音(变音符号)名"。例如可指定 "â" 读作 "a" "circumflex" |
$accent_before | 与重音名连用:在字母名之前说这个重音名 |
$atstart | 位于子句开头时用此发音 |
$atend | 位于子句末尾时用此发音 |
$sentence | 该规则仅当子句包含句子终结符(即{ . ? ! }而非{ , ; : })时适用。例如$atend $sentence表示规则仅在句子末尾适用 |
$native | 若切换到其他翻译则不用 |
最后一组标记($verbf家族)可能为英语专用,但其他语言也可建立类似机制。它们是对类似 ob'ject(动词)与 'object(名词)、read(现在时)与 read(过去时)这类词对的粗略改进尝试。
词典是自底向上搜索的:第一个满足所有条件的匹配(即列表中最靠下的那条)被采用。所以如果有:
to t@ // 非重读版本 to tu: $atend // 重读版本那么当to位于子句末尾时输出[tu:],否则输出[t@]。这种"通用条目在上、特殊条目在下"的布局正是 dictsource/en_list 的常见写法,例如:
a $nounf a eI $atend第一行给 "a" 打上"下词可能是名词"的标记,第二行规定句末的 "a" 读[eI]。实际查看 dictsource/en_list 还能看到$alt1~$alt7的英文专用含义说明(如$alt1用强结尾、$alt2用弱结尾等),这印证了$alt系列是"语言相关、用法由该语言*_list文件说明"的设计。
Translating a Word to Another Word:把词翻译成另一个词
除了用音素串指定发音,还可以指定一个"听起来像"的别的词。用$text属性,例如:
cough coff $text或者用单独一行$textmode开启此模式,对文件中其后的所有条目生效,直到被$phonememode关闭:
$textmode cough coff through threw $phonememode注意限制:
- 此功能不能用于
*_list文件中以_开头的特殊条目(如数字); - 目前
textmode条目只对完整单词识别,对移除前后缀后的词干不生效(例如上面的例子无法匹配 "coughs")。
Conditional Rules:条件规则
*_rules文件中的规则和*_list文件中的条目都可以做成条件式的——它们只对某些语音(voice)生效。这对为同一语言的不同变体(方言或口音)指定不同发音非常有用。
条件规则在*_rules或*_list文件的行首加上?和一个条件编号,表示该规则仅在 voice 文件的dictrules行中指定了该条件编号时才生效(见 voice 文件说明)。如果规则以?!开头,则该规则仅在 voice 文件没有指定该条件编号时生效。例如:
?3 can't kant // 仅当 voice 有: dictrules 3 ?!3 rather rA:D3 // 仅当 voice 没有: dictrules 3Voice 文件中对应的写法(详见 docs/voices.md):
dictrules <list of rule numbers>它给出该 voice 应用的条件字典规则编号列表,编号范围 0~31,具体含义由各语言的字典文件定义。作为实例,dictsource/en_rules 开头就声明了?3 General American、?!3 Not General American、?2用清音 [w#] 读 "wh"、?5拆分 [3:] 等英语方言条件。
Numbers and Character Names:数字与字符名
Letter names:字母名
单个字母的名字既可以在*_rules中给出,也可以在*_list中给出。有时某个字母本身也是语言中的一个词,且作为词的发音与作为字母名不同;此时应在*_list中用下划线前缀列出字母名(区别于其作为词的发音)。例如英语:
_a eI在 dictsource/en_list 中可以看到完整的字母名表(a eI、b bi:、c si:……以及_s E2s这类带下划线的特殊条目),还有?3 z zi:这样的方言条件条目,以及用$accent处理的带变音符号字符(à、â、ä……读作基字母名加重音名)。
Numbers:数字
数字的口语化由TranslateNumber()函数控制,而该函数的行为由语言在langopts.numbers选项(设置于tr_languages.c的NewTranslator()中)决定。它按各语言可设置的各种选项,用数字片段拼接出朗读形式;数字片段在*_list文件中给出:
| 符号 | 说明 |
|---|---|
_0到_9 | 数字 0 到 9 |
_13等 | 在_10到_99范围内需要特殊发音的具体数字 |
_2X_3X | 二十、三十等,用于构成 10 到 99 |
_0C | "百"(hundred)一词 |
_1C_2C | 需要时,"一百、二百"等的特殊发音 |
_1C0 | 需要时,正好 100 的特殊发音 |
_0M1 | "千"(thousand)一词 |
_1M1_2M1 | 需要时,"1 千、2 千"等的特殊发音 |
_0M2 | "百万"(million)一词 |
_0M3 | 1,000,000,000 一词 |
_0MX | 数字10^3X的词 |
_0and | 朗读数字时的 "and"(如 "two hundred and twenty") |
_dpt | 小数点/小数逗号的读法 |
_dpt2 | 小数点后所有数字读完后(若有)的收尾词 |
这些片段在真实词典中随处可见。例如英语 dictsource/en_list 中有:
_2X tw'Ent2i|| _0M1 T'aUz@nd _0M10 n0n'Ili@n ?!3 _0and @n _dpt pOInt汉语普通话 dictsource/cmn_list 中则有:
_0 liN35 _2X @r51s.i.35_| _0M1 tS;h'iE55n_| // 1,000 _0M2 w'A51n_| // 10,000 _dpt t'iE35n_|关于序数词:要启用序数朗读,需要两步:
- 在
tr_languages.c中设置langopts.numbers为NUM_ORDINAL_DOT(该宏定义为0x00010000,注释为 "dot after number indicates ordinal",见 translate.h)——它让"数字后的点号表示序数"; - 为上文每个数字符号加上后缀
o的条目,例如_1o表示 first,_51o表示 fifty first,依此类推。
TranslateNumber()的实现位于 numbers.c(内部由TranslateNumber_1()处理),序数判定逻辑见 numbers.c:当NUM_ORDINAL_DOT开启且词后紧跟.或词带FLAG_HAS_DOT时按序数处理。langopts.numbers的全部NUM_*位标志定义在 translate.h,包括NUM_DECIMAL_COMMA(逗号作小数分隔符)、NUM_SWAP_TENS("three-and-twenty" 式十位与个位互换)、NUM_HUNDRED_AND(百/千后加 "and")、NUM_OMIT_1_HUNDRED("hundred" 前省略 "one")、NUM_1900(19** 读作 nineteen hundred)、NUM_ROMAN(识别罗马数字)等——各语言在 tr_languages.c 中按需组合这些标志。
另外,一个数字是"按整数读出"还是"逐位拼读"的分界线由tr->langopts.max_digits决定:默认值为 14(见 tr_languages.c,注释 "max number of digits which can be spoken as an integer number"),各语言可在NewTranslator()中覆盖(例如日语设为 33、部分语言设为 20/32 等)。实际判定在 translate.c:n_digits > tr->langopts.max_digits时改为逐位读。
Character Substitution:字符替换
字符替换通过在*_rules文件开头放一个.replace区段来指定。每行可以把多个源字符替换为一到两个字符。此替换在单词被搜索于*_list/*_listx文件并用拼写到音素规则翻译之前完成。只需指定字符的小写形式。例如:
.replace ô ő // (匈牙利语)允许用 o-抑扬符 代替 o-双锐音符 û ű cx ĉ // (世界语)允许用 "cx" 作为 c-抑扬符 的替代 fi fi // 用两个字符替换单个连字字符在 dictsource/en_rules 中可以看到实际应用的.replace区段:
.replace æ ae œ oe ff ff // ligatures fi fi fl fl注意文档中.replace的源字符列表可能包含多个源字符(如世界语示例中的cx),这说明替换支持把双字母序列折叠为单个字母,这正是某些替代拼写方案(如 x-system 转写)得以支持的关键。
(Re)definition of character groups:字符组的(重新)定义
A组中的元音字符集和C组中的辅音字符集可以针对特定语言重新定义;由B、E、F、G指示的其他字母集合通常对每种语言都有特定含义。
字符组的(重新)定义在 tr_languages.c 中完成:通常在NewTranslator()里调用SetLetterBits()函数。注意,字母应存储为字符数组,因此多字节 Unicode 字母应通过特定Translator结构的transpose_min和transpose_max参数转置,或使用SetLetterBitsUTF8()函数。
源码中这三个函数定义在 tr_languages.c:
SetLetterBits():对字符串中每个单字节字符设置指定位组;SetLetterBitsRange():对某个连续码位范围批量设置;SetLetterBitsUTF8():按 UTF-8 逐字符解码(通过utf8_in())后设置,支持多字节字符。
默认分组(即拉丁字母语言的基础定义)在 tr_languages.c:
SetLetterBits(tr, 0, "aeiou"); // A vowels, except y SetLetterBits(tr, 1, "bcdfgjklmnpqstvxz"); // B hard consonants, excluding h,r,w SetLetterBits(tr, 2, "bcdfghjklmnpqrstvwxz"); // C all consonants SetLetterBits(tr, 3, "hlmnr"); // H 'soft' consonants SetLetterBits(tr, 4, "cfhkpqstx"); // F voiceless consonants SetLetterBits(tr, 5, "bdgjlmnrvwyz"); // G voiced SetLetterBits(tr, 6, "eiy"); // Letter group Y, front vowels SetLetterBits(tr, 7, "aeiouy"); // vowels, including y其中 0~6 号组正好对应规则中A B C H F G Y这 7 个特殊字母所匹配的字符集合。每种语言随后可以覆盖这些定义,例如俄语用SetLetterBits(tr, LETTERGP_A, ru_vowels)等重新定义(tr_languages.c),阿拉伯语则用SetLetterBitsUTF8()按 UTF-8 定义元音/辅音/重音字母(tr_languages.c),天城文等用SetLetterBitsRange()覆盖连续码位区间(tr_languages.c)。
小结
eSpeak NG 的文本到音素翻译由三层组成:*_rules规则文件定义"拼读到发音"的通用规则并带计分匹配与前后缀处理;*_list/*_extra词典文件为个别词、多词组合提供显式发音、重音与词法标记,自底向上搜索且支持条件规则与$text同音词替换;langopts与SetLetterBits()家族则按语言定制数字朗读与字符分组语义。理解这套体系后,无论是修正某个单词的发音、为新方言添加条件规则,还是为全新语言编写*_rules与*_list,你都可以直接对照 docs/add_language.md 与仓库中的dictsource/实际数据动手实践。
1相关字符集合可由语言自定义,见"字符组的(重新)定义"一节;数字"按整数读 vs 逐位读"的默认分界线为 14 位,由tr->langopts.max_digits控制。
【免费下载链接】espeak-ngeSpeak NG is an open source speech synthesizer that supports more than hundred languages and accents.项目地址: https://gitcode.com/GitHub_Trending/es/espeak-ng
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考