Skip to content

Commit 2493d1f

Browse files
authored
Fix the English part for Matcha TTS. (k2-fsa#2853)
1 parent a60d6d0 commit 2493d1f

1 file changed

Lines changed: 112 additions & 35 deletions

File tree

sherpa-onnx/csrc/matcha-tts-lexicon.cc

Lines changed: 112 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#include "sherpa-onnx/csrc/matcha-tts-lexicon.h"
66

77
#include <algorithm>
8-
#include <codecvt>
98
#include <fstream>
109
#include <memory>
1110
#include <regex> // NOLINT
@@ -38,6 +37,107 @@
3837

3938
namespace sherpa_onnx {
4039

40+
namespace {
41+
// code in this anonymous namespace is written by ChatGPT
42+
//
43+
// Please see https://github.com/k2-fsa/sherpa-onnx/pull/2853
44+
// for why we need to do the replacement
45+
static const std::vector<std::pair<std::string, std::string>> kReplacements = {
46+
{"ɝ", "ɜɹ"}, {"ɚ", "əɹ"},
47+
48+
{"", "A"}, {"", "I"}, {"ɔɪ", "Y"},
49+
{"", "O"}, {"əʊ", "O"}, {"", "W"},
50+
51+
{"", "ʧ"}, {"", "ʤ"},
52+
53+
{"ː", ""},
54+
55+
{"g", "ɡ"}, {"r", "ɹ"},
56+
57+
{"e", "ɛ"},
58+
};
59+
60+
std::string Utf32ToUtf8(char32_t cp) {
61+
std::string out;
62+
63+
if (cp <= 0x7F) {
64+
out.push_back(static_cast<char>(cp));
65+
} else if (cp <= 0x7FF) {
66+
out.push_back(static_cast<char>(0xC0 | (cp >> 6)));
67+
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
68+
} else if (cp <= 0xFFFF) {
69+
out.push_back(static_cast<char>(0xE0 | (cp >> 12)));
70+
out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
71+
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
72+
} else {
73+
out.push_back(static_cast<char>(0xF0 | (cp >> 18)));
74+
out.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3F)));
75+
out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
76+
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
77+
}
78+
79+
return out;
80+
}
81+
82+
std::vector<std::string> ConvertPhonemesToUTF8(
83+
const std::vector<std::vector<char32_t>> &phonemes) {
84+
std::vector<std::string> out;
85+
86+
for (const auto &word : phonemes) {
87+
for (char32_t cp : word) {
88+
out.push_back(Utf32ToUtf8(cp));
89+
}
90+
}
91+
92+
return out;
93+
}
94+
95+
std::string JoinTokensNoSpace(const std::vector<std::string> &tokens) {
96+
std::string out;
97+
for (const auto &t : tokens) {
98+
out += t;
99+
}
100+
return out;
101+
}
102+
103+
std::string ApplyReplacements(std::string s) {
104+
for (const auto &p : kReplacements) {
105+
const std::string &from = p.first;
106+
const std::string &to = p.second;
107+
108+
size_t pos = 0;
109+
while ((pos = s.find(from, pos)) != std::string::npos) {
110+
s.replace(pos, from.size(), to);
111+
pos += to.size();
112+
}
113+
}
114+
return s;
115+
}
116+
117+
std::vector<std::string> SplitTokensUTF8(const std::string &s) {
118+
std::vector<std::string> out;
119+
120+
for (size_t i = 0; i < s.size();) {
121+
unsigned char c = s[i];
122+
size_t len = (c < 0x80) ? 1 : (c < 0xE0) ? 2 : (c < 0xF0) ? 3 : 4;
123+
124+
out.push_back(s.substr(i, len));
125+
i += len;
126+
}
127+
128+
return out;
129+
}
130+
131+
std::vector<std::string> ProcessPhonemes(
132+
const std::vector<std::vector<char32_t>> &phonemes) {
133+
auto tokens = ConvertPhonemesToUTF8(phonemes);
134+
std::string joined = JoinTokensNoSpace(tokens);
135+
std::string replaced = ApplyReplacements(joined);
136+
return SplitTokensUTF8(replaced);
137+
}
138+
139+
} // namespace
140+
41141
void CallPhonemizeEspeak(const std::string &text,
42142
piper::eSpeakPhonemeConfig &config, // NOLINT
43143
std::vector<std::vector<piper::Phoneme>> *phonemes);
@@ -246,21 +346,22 @@ class MatchaTtsLexicon::Impl {
246346
}
247347
}
248348
} else {
249-
SHERPA_ONNX_LOGE("use espeak for %s", w.c_str());
349+
if (debug_) {
350+
SHERPA_ONNX_LOGE("use espeak for %s", w.c_str());
351+
}
250352
// use espeak
251353
piper::eSpeakPhonemeConfig config;
252354
config.voice = "en-us";
253355
std::vector<std::vector<piper::Phoneme>> phonemes;
254356
CallPhonemizeEspeak(w, config, &phonemes);
255-
for (const auto &ps : phonemes) {
256-
for (const auto &p : ps) {
257-
if (phoneme2id_.count(p)) {
258-
ans.push_back(phoneme2id_.at(p));
259-
} else {
260-
SHERPA_ONNX_LOGE(
261-
"Skip unknown phonemes. Unicode codepoint: \\U+%04x. for %s",
262-
static_cast<uint32_t>(p), w.c_str());
263-
}
357+
358+
auto pp = ProcessPhonemes(phonemes);
359+
360+
for (const auto &p : pp) {
361+
if (token2id_.count(p)) {
362+
ans.push_back(token2id_.at(p));
363+
} else {
364+
SHERPA_ONNX_LOGE("Skip token: %s", p.c_str());
264365
}
265366
}
266367
}
@@ -290,29 +391,6 @@ class MatchaTtsLexicon::Impl {
290391
id2token_[p.second] = p.first;
291392
}
292393
}
293-
294-
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> conv;
295-
std::u32string s;
296-
for (const auto &p : token2id_) {
297-
if ((p.first.front() == '<' && p.first.back() == '>') ||
298-
p.first.back() == '1' || p.first.back() == '2' ||
299-
p.first.back() == '3' || p.first.back() == '4' ||
300-
p.first.back() == '5') {
301-
continue;
302-
}
303-
s = conv.from_bytes(p.first);
304-
305-
if (s.size() != 1) {
306-
SHERPA_ONNX_LOGE("Error for token %s with id %d", p.first.c_str(),
307-
p.second);
308-
SHERPA_ONNX_EXIT(-1);
309-
}
310-
311-
char32_t c = s[0];
312-
if (!phoneme2id_.count(c)) {
313-
phoneme2id_.insert({c, p.second});
314-
}
315-
}
316394
}
317395

318396
void InitLexicon(const std::string &lexicon) {
@@ -390,7 +468,6 @@ class MatchaTtsLexicon::Impl {
390468

391469
// tokens.txt is saved in token2id_
392470
std::unordered_map<std::string, int32_t> token2id_;
393-
std::unordered_map<char32_t, int32_t> phoneme2id_;
394471

395472
std::unordered_map<int32_t, std::string> id2token_;
396473

0 commit comments

Comments
 (0)