Skip to content

Commit

Permalink
Merge pull request #897 from wolfgitpr/master
Browse files Browse the repository at this point in the history
add mandarin/cantonese g2p
  • Loading branch information
stakira authored Nov 25, 2023
2 parents 786c09a + 1c6254b commit 537f748
Show file tree
Hide file tree
Showing 14 changed files with 266 additions and 33 deletions.
25 changes: 4 additions & 21 deletions OpenUtau.Core/BaseChinesePhonemizer.cs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -25,25 +19,14 @@ public static Note[] ChangeLyric(Note[] group, string lyric) {
}

public static string[] Romanize(IEnumerable<string> 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);
}
Expand Down
13 changes: 13 additions & 0 deletions OpenUtau.Core/DiffSinger/DiffSingerJyutpingPhonemizer.cs
Original file line number Diff line number Diff line change
@@ -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<string> lyrics) {
return ZhG2p.CantoneseInstance.Convert(lyrics.ToList(), false, true).Split(" ");
}
}
}
2 changes: 1 addition & 1 deletion OpenUtau.Core/DiffSinger/DiffSingerPhonemizer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;

using OpenUtau.Api;

Expand Down
7 changes: 2 additions & 5 deletions OpenUtau.Core/Editing/NoteBatchEdits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,9 @@ public HanziToPinyin() {
}

public void Run(UProject project, UVoicePart part, List<UNote> 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));
}
Expand Down
1 change: 0 additions & 1 deletion OpenUtau.Core/Enunu/EnunuEnglishPhonemizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
1 change: 0 additions & 1 deletion OpenUtau.Core/Enunu/EnunuPhonemizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
20 changes: 20 additions & 0 deletions OpenUtau.Core/G2p/Data/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions OpenUtau.Core/G2p/Data/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@
<data name="g2p-it" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>g2p-it.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="g2p-jyutping" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>g2p-jyutping.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="g2p-man" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>g2p-man.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="g2p-pt" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>g2p-pt.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
Expand Down
Binary file added OpenUtau.Core/G2p/Data/g2p-jyutping.zip
Binary file not shown.
Binary file added OpenUtau.Core/G2p/Data/g2p-man.zip
Binary file not shown.
211 changes: 211 additions & 0 deletions OpenUtau.Core/G2p/ZhG2p.cs
Original file line number Diff line number Diff line change
@@ -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<ZhG2p> mandarinInstance = new Lazy<ZhG2p>(() => new ZhG2p("mandarin"));
private static Lazy<ZhG2p> cantoneseInstance = new Lazy<ZhG2p>(() => new ZhG2p("cantonese"));

public static ZhG2p MandarinInstance => mandarinInstance.Value;
public static ZhG2p CantoneseInstance => cantoneseInstance.Value;

private Dictionary<string, string> PhrasesMap = new Dictionary<string, string>();
private Dictionary<string, string> TransDict = new Dictionary<string, string>();
private Dictionary<string, string> WordDict = new Dictionary<string, string>();
private Dictionary<string, string> PhrasesDict = new Dictionary<string, string>();

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<string, string> 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<string, string> NumMap = new Dictionary<string, string>
{
{"0", "零"},
{"1", "一"},
{"2", "二"},
{"3", "三"},
{"4", "四"},
{"5", "五"},
{"6", "六"},
{"7", "七"},
{"8", "八"},
{"9", "九"}
};

static List<string> SplitString(string input) {
List<string> res = new List<string>();

// 正则表达式模式
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<string> input, List<string> res, List<int> 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<string> res) {
var temp = text.Split(' ');
res.AddRange(temp);
}

private static void RemoveElements(List<string> 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<string> input, List<string> res, List<int> 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<string> input, bool tone, bool convertNum) {
var inputList = new List<string>();
var inputPos = new List<int>();

ZhPosition(input, inputList, inputPos, convertNum);
var result = new List<string>();
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;
}

}
}
2 changes: 0 additions & 2 deletions OpenUtau.Core/OpenUtau.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
<PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.IO.Packaging" Version="7.0.0" />
<PackageReference Include="TinyPinyin.Net" Version="1.0.2" />
<PackageReference Include="ToolGood.Words.Pinyin" Version="3.1.0" />
<PackageReference Include="UTF.Unknown" Version="2.5.1" />
<PackageReference Include="Vortice.DXGI" Version="2.4.2" />
<PackageReference Include="WanaKana-net" Version="1.0.0" />
Expand Down
10 changes: 9 additions & 1 deletion OpenUtau.Core/Util/LyricsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public Type GetPreferred() {
public readonly List<Type> Available = new List<Type>() {
typeof(HiraganaLyricsHelper),
typeof(PinyinLyricsHelper),
typeof(JyutpingLyricsHelper),
typeof(ArpabetG2pLyricsHelper),
typeof(FrenchG2pLyricsHelper),
typeof(GermanG2pLyricsHelper),
Expand All @@ -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);
}
}

Expand Down
1 change: 0 additions & 1 deletion OpenUtau.Core/Vogen/VogenMandarinPhonemizer.cs
Original file line number Diff line number Diff line change
@@ -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")]
Expand Down

0 comments on commit 537f748

Please sign in to comment.