| |
| |
| |
| |
| |
| |
| |
| |
| |
| #include "g2p/ZhJiebaG2P.hpp" |
| #include <regex> |
| #include <sstream> |
| #include <algorithm> |
| #include <unordered_set> |
|
|
| |
| |
| |
|
|
| static const std::unordered_map<std::string, std::vector<std::string>> INITIAL_MAPPING = { |
| {"b", {"p"}}, {"c", {"ʦʰ"}}, {"ch", {"ʈʂʰ"}}, {"d", {"t"}}, |
| {"f", {"f"}}, {"g", {"k"}}, {"h", {"x"}}, {"j", {"ʨ"}}, |
| {"k", {"kʰ"}}, {"l", {"l"}}, {"m", {"m"}}, {"n", {"n"}}, |
| {"p", {"pʰ"}}, {"q", {"ʨʰ"}}, {"r", {"ɻ"}}, {"s", {"s"}}, |
| {"sh", {"ʂ"}}, {"t", {"tʰ"}}, {"x", {"ɕ"}}, {"z", {"ʦ"}}, |
| {"zh", {"ʈʂ"}} |
| }; |
|
|
| static const std::unordered_map<std::string, std::vector<std::string>> FINAL_MAPPING = { |
| {"a", {"a0"}}, {"ai", {"ai̯0"}}, {"an", {"a0", "n"}}, {"ang", {"a0", "ŋ"}}, |
| {"ao", {"au̯0"}}, {"e", {"ɤ0"}}, {"ei", {"ei̯0"}}, {"en", {"ə0", "n"}}, |
| {"eng", {"ə0", "ŋ"}}, {"er", {"ɚ0"}}, {"i", {"i0"}}, {"ia", {"j", "a0"}}, |
| {"ian", {"j", "ɛ0", "n"}}, {"iang", {"j", "a0", "ŋ"}}, {"iao", {"j", "au̯0"}}, |
| {"ie", {"j", "e0"}}, {"in", {"i0", "n"}}, {"ing", {"i0", "ŋ"}}, |
| {"iong", {"j", "ʊ0", "ŋ"}}, {"iou", {"j", "ou̯0"}}, {"ong", {"ʊ0", "ŋ"}}, |
| {"ou", {"ou̯0"}}, {"o", {"w", "o0"}}, {"u", {"u0"}}, {"ua", {"w", "a0"}}, |
| {"uai", {"w", "ai̯0"}}, {"uan", {"w", "a0", "n"}}, {"uang", {"w", "a0", "ŋ"}}, |
| {"ui", {"w", "ei̯0"}}, {"un", {"w", "ə0", "n"}}, {"ueng", {"w", "ə0", "ŋ"}}, |
| {"uo", {"w", "o0"}}, {"ue", {"ɥ", "e0"}}, {"uen", {"w", "ə0", "n"}}, {"uei", {"w", "ei̯0"}}, |
| {"ü", {"y0"}}, {"üe", {"ɥ", "e0"}}, {"üan", {"ɥ", "ɛ0", "n"}}, {"ün", {"y0", "n"}}, |
| {"van", {"ɥ", "ɛ0", "n"}}, {"vn", {"y0", "n"}}, {"ve", {"ɥ", "e0"}}, {"v", {"y0"}}, |
| |
| {"ii", {"ɹ̩0"}}, |
| {"iii", {"ɻ̩0"}} |
| }; |
|
|
| static const std::unordered_map<std::string, std::vector<std::string>> FINAL_MAPPING_ZH_CH_SH_R = { |
| {"i", {"ɻ̩0"}} |
| }; |
|
|
| static const std::unordered_map<std::string, std::vector<std::string>> FINAL_MAPPING_Z_C_S = { |
| {"i", {"ɹ̩0"}} |
| }; |
|
|
| static const std::unordered_map<int, std::string> TONE_MAPPING = { |
| {1, "˥"}, {2, "˧˥"}, {3, "˧˩˧"}, {4, "˥˩"}, {5, ""} |
| }; |
|
|
| static const std::unordered_map<std::string, std::pair<std::string, int>> TONE_VOWELS = { |
| {u8"ā", {u8"a", 1}}, {u8"á", {u8"a", 2}}, {u8"ǎ", {u8"a", 3}}, {u8"à", {u8"a", 4}}, |
| {u8"ē", {u8"e", 1}}, {u8"é", {u8"e", 2}}, {u8"ě", {u8"e", 3}}, {u8"è", {u8"e", 4}}, |
| {u8"ī", {u8"i", 1}}, {u8"í", {u8"i", 2}}, {u8"ǐ", {u8"i", 3}}, {u8"ì", {u8"i", 4}}, |
| {u8"ō", {u8"o", 1}}, {u8"ó", {u8"o", 2}}, {u8"ǒ", {u8"o", 3}}, {u8"ò", {u8"o", 4}}, |
| {u8"ū", {u8"u", 1}}, {u8"ú", {u8"u", 2}}, {u8"ǔ", {u8"u", 3}}, {u8"ù", {u8"u", 4}}, |
| {u8"ǖ", {u8"v", 1}}, {u8"ǘ", {u8"v", 2}}, {u8"ǚ", {u8"v", 3}}, {u8"ǜ", {u8"v", 4}}, |
| {u8"ń", {u8"n", 2}}, {u8"ň", {u8"n", 3}}, {u8"ǹ", {u8"n", 4}}, |
| {u8"ḿ", {u8"m", 2}}, {u8"m̀", {u8"m", 4}} |
| }; |
|
|
| |
| |
| |
|
|
| static const std::unordered_map<char, std::string> LETTER_TO_IPA = { |
| {'A', "ei̯"}, {'B', "pi"}, {'C', "si"}, {'D', "ti"}, {'E', "i"}, |
| {'F', "ef"}, {'G', "tʂi"}, {'H', "ei̯tʂ"}, {'I', "ai̯"}, {'J', "tʂei̯"}, |
| {'K', "kʰei̯"}, {'L', "el"}, {'M', "em"}, {'N', "en"}, {'O', "ou̯"}, |
| {'P', "pʰi"}, {'Q', "kʰju"}, {'R', "aɻ"}, {'S', "es"}, {'T', "tʰi"}, |
| {'U', "ju"}, {'V', "vi"}, {'W', "tʌplju"}, {'X', "eks"}, {'Y', "wai̯"}, |
| {'Z', "zi"}, |
| {'a', "ei̯"}, {'b', "pi"}, {'c', "si"}, {'d', "ti"}, {'e', "i"}, |
| {'f', "ef"}, {'g', "tʂi"}, {'h', "ei̯tʂ"}, {'i', "ai̯"}, {'j', "tʂei̯"}, |
| {'k', "kʰei̯"}, {'l', "el"}, {'m', "em"}, {'n', "en"}, {'o', "ou̯"}, |
| {'p', "pʰi"}, {'q', "kʰju"}, {'r', "aɻ"}, {'s', "es"}, {'t', "tʰi"}, |
| {'u', "ju"}, {'v', "vi"}, {'w', "tʌplju"}, {'x', "eks"}, {'y', "wai̯"}, |
| {'z', "zi"} |
| }; |
|
|
|
|
| std::string ZhJiebaG2P::retone(std::string p) { |
| p = utils::replace_all(p, "˧˩˧", "↓"); |
| p = utils::replace_all(p, "˧˥", "↗"); |
| p = utils::replace_all(p, "˥˩", "↘"); |
| p = utils::replace_all(p, "˥", "→"); |
| |
| |
| p = utils::replace_all(p, "\u027B\u0329", "ɨ"); |
| p = utils::replace_all(p, "\u0279\u0329", "ɨ"); |
| p = utils::replace_all(p, "ɻ̩", "ɨ"); |
| p = utils::replace_all(p, "ɹ̩", "ɨ"); |
| |
| |
|
|
| return p; |
| } |
|
|
| ZhJiebaG2P::PinyinParts ZhJiebaG2P::parse_pinyin(const std::string& raw_pinyin) { |
| PinyinParts parts; |
| parts.tone = 5; |
| std::string pinyin = utils::trim(raw_pinyin); |
| |
| if (pinyin.empty()) return parts; |
|
|
| |
| std::string base = ""; |
| int detected_tone = 5; |
| |
| for (size_t i = 0; i < pinyin.length(); ) { |
| bool matched = false; |
| |
| |
| for (const auto& kv : TONE_VOWELS) { |
| if (pinyin.compare(i, kv.first.length(), kv.first) == 0) { |
| if (detected_tone == 5) detected_tone = kv.second.second; |
| base += kv.second.first; |
| i += kv.first.length(); |
| matched = true; |
| break; |
| } |
| } |
| if (!matched) { |
| base += pinyin[i]; |
| i++; |
| } |
| } |
|
|
| |
| if (!base.empty()) { |
| char last = base.back(); |
| if (isdigit(static_cast<unsigned char>(last))) { |
| parts.tone = last - '0'; |
| base.pop_back(); |
| } else { |
| parts.tone = detected_tone; |
| } |
| } |
|
|
| base = utils::replace_all(base, "v", "ü"); |
|
|
| |
| if (base.rfind("yi", 0) == 0) { |
| base = base.substr(1); |
| } else if (base.rfind("y", 0) == 0) { |
| if (base.length() > 1 && base[1] == 'u') { |
| base = "ü" + base.substr(2); |
| } else { |
| base = "i" + base.substr(1); |
| } |
| } else if (base.rfind("wu", 0) == 0) { |
| base = base.substr(1); |
| } else if (base.rfind("w", 0) == 0) { |
| base = "u" + base.substr(1); |
| } |
|
|
| std::string p_initial = ""; |
| |
| static const std::vector<std::string> multi_initials = {"zh", "ch", "sh"}; |
| for (const auto& ini : multi_initials) { |
| if (base.rfind(ini, 0) == 0) { |
| p_initial = ini; |
| break; |
| } |
| } |
| if (p_initial.empty()) { |
| std::string first_char = base.substr(0, 1); |
| if (INITIAL_MAPPING.count(first_char)) { |
| p_initial = first_char; |
| } |
| } |
|
|
| parts.initial = p_initial; |
| parts.final = base.substr(p_initial.length()); |
| |
| |
| if (!parts.initial.empty()) { |
| if (parts.final == "iu") { |
| parts.final = "iou"; |
| } else if (parts.final == "ui") { |
| parts.final = "uei"; |
| } else if (parts.final == "un") { |
| |
| if (parts.initial != "j" && parts.initial != "q" && parts.initial != "x") { |
| parts.final = "uen"; |
| } |
| } |
| } |
| |
| if (parts.initial == "j" || parts.initial == "q" || parts.initial == "x") { |
| if (parts.final == "u") { |
| parts.final = "ü"; |
| } else if (parts.final == "ue") { |
| parts.final = "üe"; |
| } else if (parts.final == "uan") { |
| parts.final = "üan"; |
| } else if (parts.final == "un") { |
| parts.final = "ün"; |
| } |
| } |
| |
| return parts; |
| } |
|
|
| std::string ZhJiebaG2P::map_punctuation(std::string text) { |
| |
| text = utils::replace_all(text, u8"、", ", "); |
| text = utils::replace_all(text, u8",", ", "); |
| text = utils::replace_all(text, u8"。", ". "); |
| text = utils::replace_all(text, u8".", ". "); |
| text = utils::replace_all(text, u8"!", "! "); |
| text = utils::replace_all(text, u8":", ": "); |
| text = utils::replace_all(text, u8";", "; "); |
| text = utils::replace_all(text, u8"?", "? "); |
| text = utils::replace_all(text, u8"«", u8" “"); |
| text = utils::replace_all(text, u8"»", u8"” "); |
| text = utils::replace_all(text, u8"《", u8" “"); |
| text = utils::replace_all(text, u8"》", u8"” "); |
| text = utils::replace_all(text, u8"「", u8" “"); |
| text = utils::replace_all(text, u8"」", u8"” "); |
| text = utils::replace_all(text, u8"【", u8" “"); |
| text = utils::replace_all(text, u8"】", u8"” "); |
| text = utils::replace_all(text, u8"(", " ("); |
| text = utils::replace_all(text, u8")", ") "); |
| |
| size_t first = text.find_first_not_of(" \t\n\r"); |
| if (std::string::npos == first) return text; |
| size_t last = text.find_last_not_of(" \t\n\r"); |
| return text.substr(first, (last - first + 1)); |
| } |
|
|
| std::string ZhJiebaG2P::pinyin_to_ipa_convert(const std::string& pinyin) { |
| auto parts = parse_pinyin(pinyin); |
| std::vector<std::string> ipa_segments; |
|
|
| |
| if (!parts.initial.empty() && INITIAL_MAPPING.count(parts.initial)) { |
| ipa_segments.push_back(INITIAL_MAPPING.at(parts.initial)[0]); |
| } |
|
|
| |
| std::vector<std::string> final_phonemes; |
| bool handled = false; |
| bool is_erhua = false; |
|
|
| if (!parts.final.empty() && parts.final.back() == 'R') { |
| is_erhua = true; |
| parts.final.pop_back(); |
| } |
|
|
| if ((parts.initial == "zh" || parts.initial == "ch" || parts.initial == "sh" || parts.initial == "r") |
| && FINAL_MAPPING_ZH_CH_SH_R.count(parts.final)) { |
| final_phonemes = FINAL_MAPPING_ZH_CH_SH_R.at(parts.final); |
| handled = true; |
| } else if ((parts.initial == "z" || parts.initial == "c" || parts.initial == "s") |
| && FINAL_MAPPING_Z_C_S.count(parts.final)) { |
| final_phonemes = FINAL_MAPPING_Z_C_S.at(parts.final); |
| handled = true; |
| } |
|
|
| if (!handled && FINAL_MAPPING.count(parts.final)) { |
| final_phonemes = FINAL_MAPPING.at(parts.final); |
| } |
|
|
| if (final_phonemes.empty() && !parts.final.empty()) { |
| final_phonemes.push_back(parts.final); |
| } |
|
|
| |
| std::string tone_mark = (TONE_MAPPING.count(parts.tone)) ? TONE_MAPPING.at(parts.tone) : ""; |
| |
| for (const auto& ph : final_phonemes) { |
| std::string processed = ph; |
| processed = utils::replace_all(processed, "0", tone_mark); |
| ipa_segments.push_back(processed); |
| } |
|
|
| if (is_erhua) { |
| ipa_segments.push_back("ɚ"); |
| } |
|
|
| return utils::join(ipa_segments, ""); |
| } |
|
|
| std::string ZhJiebaG2P::py2ipa(const std::string& py) { |
| std::string ipa = pinyin_to_ipa_convert(py); |
| return retone(ipa); |
| } |
|
|
| std::string ZhJiebaG2P::run(const std::string& input_text, int& err) { |
| err = 0; |
|
|
| std::string result = ""; |
| |
| auto words = jieba_->cut(input_text); |
| for (const auto& pair : words) { |
| std::string w = pair.first; |
| ALOGD("word: %s", w.c_str()); |
| if (utils::is_chinese(w)) { |
| auto pinyins = jieba_->word_to_pinyin(w); |
| for (const auto& py : pinyins) { |
| result += py2ipa(py); |
| } |
| |
| |
| result += " "; |
| } else { |
| result += w; |
| } |
| } |
| |
| |
| result = utils::trim(result); |
| return result; |
| } |
|
|