diff --git a/OpenUtau.Core/BaseChinesePhonemizer.cs b/OpenUtau.Core/BaseChinesePhonemizer.cs index 8db743ebb..f370598e0 100644 --- a/OpenUtau.Core/BaseChinesePhonemizer.cs +++ b/OpenUtau.Core/BaseChinesePhonemizer.cs @@ -1,16 +1,10 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using OpenUtau.Core.G2p; using OpenUtau.Api; -using ToolGood.Words.Pinyin; namespace OpenUtau.Core { public abstract class BaseChinesePhonemizer : Phonemizer { - public static bool IsHanzi(string lyric) { - return lyric.Length == 1 && WordsHelper.IsAllChinese(lyric); - } - public static Note[] ChangeLyric(Note[] group, string lyric) { var oldNote = group[0]; group[0] = new Note { @@ -25,25 +19,14 @@ public static Note[] ChangeLyric(Note[] group, string lyric) { } public static string[] Romanize(IEnumerable lyrics) { - var lyricsArray = lyrics.ToArray(); - var hanziLyrics = String.Join("", lyricsArray - .Where(IsHanzi)); - var pinyinResult = WordsHelper.GetPinyin(hanziLyrics, " ").ToLower().Split(); - var pinyinIndex = 0; - for(int i=0; i < lyricsArray.Length; i++) { - if (lyricsArray[i].Length == 1 && WordsHelper.IsAllChinese(lyricsArray[i])) { - lyricsArray[i] = pinyinResult[pinyinIndex]; - pinyinIndex++; - } - } - return lyricsArray; + return ZhG2p.MandarinInstance.Convert(lyrics.ToList(), false, true).Split(" "); } public static void RomanizeNotes(Note[][] groups) { var ResultLyrics = Romanize(groups.Select(group => group[0].lyric)); Enumerable.Zip(groups, ResultLyrics, ChangeLyric).Last(); } - + public override void SetUp(Note[][] groups) { RomanizeNotes(groups); } diff --git a/OpenUtau.Core/DiffSinger/DiffSingerJyutpingPhonemizer.cs b/OpenUtau.Core/DiffSinger/DiffSingerJyutpingPhonemizer.cs new file mode 100644 index 000000000..b4a4d01c3 --- /dev/null +++ b/OpenUtau.Core/DiffSinger/DiffSingerJyutpingPhonemizer.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using OpenUtau.Api; +using OpenUtau.Core.G2p; +using System.Linq; + +namespace OpenUtau.Core.DiffSinger { + [Phonemizer("DiffSinger Jyutping Phonemizer", "DIFFS ZH-YUE", language: "ZH")] + public class DiffSingerJyutpingPhonemizer : DiffSingerBasePhonemizer { + protected override string[] Romanize(IEnumerable lyrics) { + return ZhG2p.CantoneseInstance.Convert(lyrics.ToList(), false, true).Split(" "); + } + } +} diff --git a/OpenUtau.Core/DiffSinger/DiffSingerPhonemizer.cs b/OpenUtau.Core/DiffSinger/DiffSingerPhonemizer.cs index 7733089d6..b230c65f8 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerPhonemizer.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerPhonemizer.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using OpenUtau.Api; diff --git a/OpenUtau.Core/Editing/NoteBatchEdits.cs b/OpenUtau.Core/Editing/NoteBatchEdits.cs index eef640481..cc020d173 100644 --- a/OpenUtau.Core/Editing/NoteBatchEdits.cs +++ b/OpenUtau.Core/Editing/NoteBatchEdits.cs @@ -192,12 +192,9 @@ public HanziToPinyin() { } public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { - var pinyinNotes = selectedNotes - .Where(note => BaseChinesePhonemizer.IsHanzi(note.lyric)) - .ToArray(); - var pinyinResult = BaseChinesePhonemizer.Romanize(pinyinNotes.Select(note=>note.lyric)); + var pinyinResult = BaseChinesePhonemizer.Romanize(selectedNotes.Select(note=>note.lyric)); docManager.StartUndoGroup(true); - foreach(var t in Enumerable.Zip(pinyinNotes, pinyinResult, + foreach(var t in Enumerable.Zip(selectedNotes, pinyinResult, (note, pinyin) => Tuple.Create(note, pinyin))) { docManager.ExecuteCmd(new ChangeNoteLyricCommand(part, t.Item1, t.Item2)); } diff --git a/OpenUtau.Core/Enunu/EnunuEnglishPhonemizer.cs b/OpenUtau.Core/Enunu/EnunuEnglishPhonemizer.cs index 2f685b4b5..9c105e233 100644 --- a/OpenUtau.Core/Enunu/EnunuEnglishPhonemizer.cs +++ b/OpenUtau.Core/Enunu/EnunuEnglishPhonemizer.cs @@ -9,7 +9,6 @@ using OpenUtau.Core.Ustx; using OpenUtau.Core.G2p; using Serilog; -using TinyPinyin; namespace OpenUtau.Core.Enunu { [Phonemizer("Enunu English Phonemizer", "ENUNU EN", "O3", language:"EN")] diff --git a/OpenUtau.Core/Enunu/EnunuPhonemizer.cs b/OpenUtau.Core/Enunu/EnunuPhonemizer.cs index 3a7562b10..49f575f12 100644 --- a/OpenUtau.Core/Enunu/EnunuPhonemizer.cs +++ b/OpenUtau.Core/Enunu/EnunuPhonemizer.cs @@ -6,7 +6,6 @@ using K4os.Hash.xxHash; using OpenUtau.Api; using OpenUtau.Core.Ustx; -using TinyPinyin; namespace OpenUtau.Core.Enunu { [Phonemizer("Enunu Phonemizer", "ENUNU")] diff --git a/OpenUtau.Core/G2p/Data/Resources.Designer.cs b/OpenUtau.Core/G2p/Data/Resources.Designer.cs index f1c615187..6819a28c7 100644 --- a/OpenUtau.Core/G2p/Data/Resources.Designer.cs +++ b/OpenUtau.Core/G2p/Data/Resources.Designer.cs @@ -129,5 +129,25 @@ internal static byte[] g2p_ru { return ((byte[])(obj)); } } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] g2p_jyutping { + get { + object obj = ResourceManager.GetObject("g2p-jyutping", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] g2p_man { + get { + object obj = ResourceManager.GetObject("g2p-man", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/OpenUtau.Core/G2p/Data/Resources.resx b/OpenUtau.Core/G2p/Data/Resources.resx index 4eb6253c1..257659075 100644 --- a/OpenUtau.Core/G2p/Data/Resources.resx +++ b/OpenUtau.Core/G2p/Data/Resources.resx @@ -133,6 +133,12 @@ g2p-it.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + g2p-jyutping.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + g2p-man.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + g2p-pt.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/OpenUtau.Core/G2p/Data/g2p-jyutping.zip b/OpenUtau.Core/G2p/Data/g2p-jyutping.zip new file mode 100644 index 000000000..395d3db88 Binary files /dev/null and b/OpenUtau.Core/G2p/Data/g2p-jyutping.zip differ diff --git a/OpenUtau.Core/G2p/Data/g2p-man.zip b/OpenUtau.Core/G2p/Data/g2p-man.zip new file mode 100644 index 000000000..049f83a09 Binary files /dev/null and b/OpenUtau.Core/G2p/Data/g2p-man.zip differ diff --git a/OpenUtau.Core/G2p/ZhG2p.cs b/OpenUtau.Core/G2p/ZhG2p.cs new file mode 100644 index 000000000..b25bd82a9 --- /dev/null +++ b/OpenUtau.Core/G2p/ZhG2p.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using OpenUtau.Core.Util; + +namespace OpenUtau.Core.G2p { + public class ZhG2p { + private static Lazy mandarinInstance = new Lazy(() => new ZhG2p("mandarin")); + private static Lazy cantoneseInstance = new Lazy(() => new ZhG2p("cantonese")); + + public static ZhG2p MandarinInstance => mandarinInstance.Value; + public static ZhG2p CantoneseInstance => cantoneseInstance.Value; + + private Dictionary PhrasesMap = new Dictionary(); + private Dictionary TransDict = new Dictionary(); + private Dictionary WordDict = new Dictionary(); + private Dictionary PhrasesDict = new Dictionary(); + + private ZhG2p(string language) { + byte[] data; + if (language == "mandarin") { + data = Data.Resources.g2p_man; + } else { + data = Data.Resources.g2p_jyutping; + } + + LoadDict(data, "phrases_map.txt", PhrasesMap); + LoadDict(data, "phrases_dict.txt", PhrasesDict); + LoadDict(data, "user_dict.txt", PhrasesDict); + LoadDict(data, "word.txt", WordDict); + LoadDict(data, "trans_word.txt", TransDict); + } + + public static bool LoadDict(byte[] data, string fileName, Dictionary resultMap) { + string[] content = Zip.ExtractText(data, fileName); + + content.Select(line => line.Trim()) + .Select(line => line.Split(":")) + .Where(parts => parts.Length == 2) + .ToList() + .ForEach(parts => resultMap[parts[0]] = parts[1]); + + return true; + } + + private static readonly Dictionary NumMap = new Dictionary + { + {"0", "零"}, + {"1", "一"}, + {"2", "二"}, + {"3", "三"}, + {"4", "四"}, + {"5", "五"}, + {"6", "六"}, + {"7", "七"}, + {"8", "八"}, + {"9", "九"} + }; + + static List SplitString(string input) { + List res = new List(); + + // 正则表达式模式 + string pattern = @"(?![ー゜])([a-zA-Z]+|[+-]|[0-9]|[\u4e00-\u9fa5]|[\u3040-\u309F\u30A0-\u30FF][ャュョゃゅょァィゥェォぁぃぅぇぉ]?)"; + + // 使用正则表达式匹配 + MatchCollection matches = Regex.Matches(input, pattern); + + foreach (Match match in matches) { + res.Add(match.Value); + } + + return res; + } + + private static string ResetZH(List input, List res, List positions) { + var result = input; + for (var i = 0; i < positions.Count; i++) { + result[positions[i]] = res[i]; + } + + return string.Join(" ", result); + } + + private static void AddString(string text, List res) { + var temp = text.Split(' '); + res.AddRange(temp); + } + + private static void RemoveElements(List list, int start, int n) { + if (start >= 0 && start < list.Count && n > 0) { + int countToRemove = Math.Min(n, list.Count - start); + list.RemoveRange(start, countToRemove); + } + } + + private void ZhPosition(List input, List res, List positions, bool convertNum) { + for (int i = 0; i < input.Count; i++) { + if (WordDict.ContainsKey(input[i]) || TransDict.ContainsKey(input[i])) { + res.Add(input[i]); + positions.Add(i); + } else if (convertNum && NumMap.ContainsKey(input[i])) { + res.Add(NumMap[input[i]]); + positions.Add(i); + } + } + } + + public string Convert(string input, bool tone, bool covertNum) { + return Convert(SplitString(input), tone, covertNum); + } + + public string Convert(List input, bool tone, bool convertNum) { + var inputList = new List(); + var inputPos = new List(); + + ZhPosition(input, inputList, inputPos, convertNum); + var result = new List(); + var cursor = 0; + + while (cursor < inputList.Count) { + var rawCurrentChar = inputList[cursor]; + var currentChar = TradToSim(rawCurrentChar); + + if (!WordDict.ContainsKey(currentChar)) { + result.Add(currentChar); + cursor++; + continue; + } + + if (!IsPolyphonic(currentChar)) { + result.Add(GetDefaultPinyin(currentChar)); + cursor++; + } else { + var found = false; + for (var length = 4; length >= 2 && !found; length--) { + if (cursor + length <= inputList.Count) { + // cursor: 地, subPhrase: 地久天长 + var subPhrase = string.Join("", inputList.GetRange(cursor, length)); + if (PhrasesDict.ContainsKey(subPhrase)) { + AddString(PhrasesDict[subPhrase], result); + cursor += length; + found = true; + } + + if (cursor >= 1 && !found) { + // cursor: 重, subPhrase_1: 语重心长 + var subPhrase_1 = string.Join("", inputList.GetRange(cursor - 1, length)); + if (PhrasesDict.ContainsKey(subPhrase_1)) { + result.RemoveAt(result.Count - 1); + AddString(PhrasesDict[subPhrase_1], result); + cursor += length - 1; + found = true; + } + } + } + + if (cursor + 1 - length >= 0 && !found && cursor + 1 <= inputList.Count) { + // cursor: 好, xSubPhrase: 各有所好 + var xSubPhrase = string.Join("", inputList.GetRange(cursor + 1 - length, length)); + if (PhrasesDict.ContainsKey(xSubPhrase)) { + RemoveElements(result, cursor + 1 - length, length - 1); + AddString(PhrasesDict[xSubPhrase], result); + cursor += 1; + found = true; + } + } + + if (cursor + 2 - length >= 0 && cursor + 2 <= inputList.Count && !found) { + // cursor: 好, xSubPhrase: 叶公好龙 + var xSubPhrase_1 = string.Join("", inputList.GetRange(cursor + 2 - length, length)); + if (PhrasesDict.ContainsKey(xSubPhrase_1)) { + RemoveElements(result, cursor + 2 - length, length - 2); + AddString(PhrasesDict[xSubPhrase_1], result); + cursor += 2; + found = true; + } + } + } + + if (!found) { + result.Add(GetDefaultPinyin(currentChar)); + cursor++; + } + } + } + + if (!tone) { + for (var i = 0; i < result.Count; i++) { + result[i] = System.Text.RegularExpressions.Regex.Replace(result[i], "[^a-z]", ""); + } + } + + return ResetZH(input, result, inputPos); + } + + bool IsPolyphonic(string text) { + return PhrasesMap.ContainsKey(text); + } + + string TradToSim(string text) { + return TransDict.ContainsKey(text) ? TransDict[text] : text; + } + + string GetDefaultPinyin(string text) { + return WordDict.ContainsKey(text) ? WordDict[text] : text; + } + + } +} diff --git a/OpenUtau.Core/OpenUtau.Core.csproj b/OpenUtau.Core/OpenUtau.Core.csproj index 306b5e2e0..5360cdbd8 100644 --- a/OpenUtau.Core/OpenUtau.Core.csproj +++ b/OpenUtau.Core/OpenUtau.Core.csproj @@ -24,8 +24,6 @@ - - diff --git a/OpenUtau.Core/Util/LyricsHelper.cs b/OpenUtau.Core/Util/LyricsHelper.cs index 726b266d5..c8389bbdc 100644 --- a/OpenUtau.Core/Util/LyricsHelper.cs +++ b/OpenUtau.Core/Util/LyricsHelper.cs @@ -34,6 +34,7 @@ public Type GetPreferred() { public readonly List Available = new List() { typeof(HiraganaLyricsHelper), typeof(PinyinLyricsHelper), + typeof(JyutpingLyricsHelper), typeof(ArpabetG2pLyricsHelper), typeof(FrenchG2pLyricsHelper), typeof(GermanG2pLyricsHelper), @@ -54,7 +55,14 @@ public string Convert(string text) { public class PinyinLyricsHelper : ILyricsHelper { public string Source => "汉->han"; public string Convert(string lyric) { - return TinyPinyin.PinyinHelper.GetPinyin(lyric).ToLowerInvariant(); + return ZhG2p.MandarinInstance.Convert(lyric, false, true); + } + } + + public class JyutpingLyricsHelper : ILyricsHelper { + public string Source => "粤->jyut"; + public string Convert(string lyric) { + return ZhG2p.CantoneseInstance.Convert(lyric, false, true); } } diff --git a/OpenUtau.Core/Vogen/VogenMandarinPhonemizer.cs b/OpenUtau.Core/Vogen/VogenMandarinPhonemizer.cs index aa53bc504..6f1287cb9 100644 --- a/OpenUtau.Core/Vogen/VogenMandarinPhonemizer.cs +++ b/OpenUtau.Core/Vogen/VogenMandarinPhonemizer.cs @@ -1,6 +1,5 @@ using Microsoft.ML.OnnxRuntime; using OpenUtau.Api; -using TinyPinyin; namespace OpenUtau.Core.Vogen { [Phonemizer("Vogen Chinese Mandarin Phonemizer", "VOGEN ZH", language: "ZH")]