|
5 | 5 | #include "sherpa-onnx/csrc/matcha-tts-lexicon.h" |
6 | 6 |
|
7 | 7 | #include <algorithm> |
8 | | -#include <codecvt> |
9 | 8 | #include <fstream> |
10 | 9 | #include <memory> |
11 | 10 | #include <regex> // NOLINT |
|
38 | 37 |
|
39 | 38 | namespace sherpa_onnx { |
40 | 39 |
|
| 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 | + {"eɪ", "A"}, {"aɪ", "I"}, {"ɔɪ", "Y"}, |
| 49 | + {"oʊ", "O"}, {"əʊ", "O"}, {"aʊ", "W"}, |
| 50 | + |
| 51 | + {"tʃ", "ʧ"}, {"dʒ", "ʤ"}, |
| 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 | + |
41 | 141 | void CallPhonemizeEspeak(const std::string &text, |
42 | 142 | piper::eSpeakPhonemeConfig &config, // NOLINT |
43 | 143 | std::vector<std::vector<piper::Phoneme>> *phonemes); |
@@ -246,21 +346,22 @@ class MatchaTtsLexicon::Impl { |
246 | 346 | } |
247 | 347 | } |
248 | 348 | } 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 | + } |
250 | 352 | // use espeak |
251 | 353 | piper::eSpeakPhonemeConfig config; |
252 | 354 | config.voice = "en-us"; |
253 | 355 | std::vector<std::vector<piper::Phoneme>> phonemes; |
254 | 356 | 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()); |
264 | 365 | } |
265 | 366 | } |
266 | 367 | } |
@@ -290,29 +391,6 @@ class MatchaTtsLexicon::Impl { |
290 | 391 | id2token_[p.second] = p.first; |
291 | 392 | } |
292 | 393 | } |
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 | | - } |
316 | 394 | } |
317 | 395 |
|
318 | 396 | void InitLexicon(const std::string &lexicon) { |
@@ -390,7 +468,6 @@ class MatchaTtsLexicon::Impl { |
390 | 468 |
|
391 | 469 | // tokens.txt is saved in token2id_ |
392 | 470 | std::unordered_map<std::string, int32_t> token2id_; |
393 | | - std::unordered_map<char32_t, int32_t> phoneme2id_; |
394 | 471 |
|
395 | 472 | std::unordered_map<int32_t, std::string> id2token_; |
396 | 473 |
|
|
0 commit comments