Skip to content

Commit 9eb1fb8

Browse files
Chinese CVV: custom dictionary support
1 parent eea982e commit 9eb1fb8

File tree

5 files changed

+302
-391
lines changed

5 files changed

+302
-391
lines changed

OpenUtau.Plugin.Builtin/ChineseCVVMonophonePhonemizer.cs

Lines changed: 0 additions & 117 deletions
This file was deleted.
Lines changed: 69 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,62 @@
1+
using System;
12
using System.Collections.Generic;
3+
using System.IO;
24
using System.Linq;
5+
using System.Threading.Tasks;
36
using OpenUtau.Api;
4-
using OpenUtau.Core;
5-
using OpenUtau.Core.Ustx;
7+
using Serilog;
68

7-
namespace OpenUtau.Plugin.Builtin {
9+
namespace OpenUtau.Plugin.Builtin
10+
{
811
/// <summary>
912
/// Chinese 十月式整音扩张 CVV Phonemizer.
1013
/// <para>It works by spliting "duang" to "duang" + "_ang", to produce the proper tail sound.</para>
1114
/// </summary>
1215
[Phonemizer("Chinese CVV (十月式整音扩张) Phonemizer", "ZH CVV", language: "ZH")]
13-
public class ChineseCVVPhonemizer : BaseChinesePhonemizer {
16+
public class ChineseCVVMonophonePhonemizer : MonophonePhonemizer
17+
{
18+
static readonly string pinyins = "a,ai,an,ang,ao,ba,bai,ban,bang,bao,bei,ben,beng,bi,bian,biao,bie,bin,bing,bo,bu,ca,cai,can,cang,cao,ce,cei,cen,ceng,cha,chai,chan,chang,chao,che,chen,cheng,chi,chong,chou,chu,chua,chuai,chuan,chuang,chui,chun,chuo,ci,cong,cou,cu,cuan,cui,cun,cuo,da,dai,dan,dang,dao,de,dei,den,deng,di,dia,dian,diao,die,ding,diu,dong,dou,du,duan,dui,dun,duo,e,ei,en,eng,er,fa,fan,fang,fei,fen,feng,fo,fou,fu,ga,gai,gan,gang,gao,ge,gei,gen,geng,gong,gou,gu,gua,guai,guan,guang,gui,gun,guo,ha,hai,han,hang,hao,he,hei,hen,heng,hong,hou,hu,hua,huai,huan,huang,hui,hun,huo,ji,jia,jian,jiang,jiao,jie,jin,jing,jiong,jiu,ju,jv,juan,jvan,jue,jve,jun,jvn,ka,kai,kan,kang,kao,ke,kei,ken,keng,kong,kou,ku,kua,kuai,kuan,kuang,kui,kun,kuo,la,lai,lan,lang,lao,le,lei,leng,li,lia,lian,liang,liao,lie,lin,ling,liu,lo,long,lou,lu,luan,lun,luo,lv,lve,ma,mai,man,mang,mao,me,mei,men,meng,mi,mian,miao,mie,min,ming,miu,mo,mou,mu,na,nai,nan,nang,nao,ne,nei,nen,neng,ni,nian,niang,niao,nie,nin,ning,niu,nong,nou,nu,nuan,nun,nuo,nv,nve,o,ou,pa,pai,pan,pang,pao,pei,pen,peng,pi,pian,piao,pie,pin,ping,po,pou,pu,qi,qia,qian,qiang,qiao,qie,qin,qing,qiong,qiu,qu,qv,quan,qvan,que,qve,qun,qvn,ran,rang,rao,re,ren,reng,ri,rong,rou,ru,rua,ruan,rui,run,ruo,sa,sai,san,sang,sao,se,sen,seng,sha,shai,shan,shang,shao,she,shei,shen,sheng,shi,shou,shu,shua,shuai,shuan,shuang,shui,shun,shuo,si,song,sou,su,suan,sui,sun,suo,ta,tai,tan,tang,tao,te,tei,teng,ti,tian,tiao,tie,ting,tong,tou,tu,tuan,tui,tun,tuo,wa,wai,wan,wang,wei,wen,weng,wo,wu,xi,xia,xian,xiang,xiao,xie,xin,xing,xiong,xiu,xu,xv,xuan,xvan,xue,xve,xun,xvn,ya,yan,yang,yao,ye,yi,yin,ying,yo,yong,you,yu,yv,yuan,yvan,yue,yve,yun,yvn,za,zai,zan,zang,zao,ze,zei,zen,zeng,zha,zhai,zhan,zhang,zhao,zhe,zhei,zhen,zheng,zhi,zhong,zhou,zhu,zhua,zhuai,zhuan,zhuang,zhui,zhun,zhuo,zi,zong,zou,zu,zuan,zui,zun";
19+
static readonly string tails = "_vn,_ing,_ong,_an,_ou,_er,_ao,_eng,_ang,_en,_en2,_ai,_iong,_in,_ei";
20+
21+
static readonly string[] pinyinList = pinyins.Split(',');
22+
static readonly string[] tailList = tails.Split(',');
23+
24+
public ChineseCVVMonophonePhonemizer() {
25+
ConsonantLength = 120;
26+
}
27+
28+
protected override IG2p LoadG2p() {
29+
var g2ps = new List<IG2p>();
30+
31+
// Load dictionary from plugin folder.
32+
string path = Path.Combine(PluginDir, "zhcvv.yaml");
33+
if (File.Exists(path)) {
34+
g2ps.Add(G2pDictionary.NewBuilder().Load(File.ReadAllText(path)).Build());
35+
}
36+
37+
// Load dictionary from singer folder.
38+
if (singer != null && singer.Found && singer.Loaded) {
39+
string file = Path.Combine(singer.Location, "zhcvv.yaml");
40+
if (File.Exists(file)) {
41+
try {
42+
g2ps.Add(G2pDictionary.NewBuilder().Load(File.ReadAllText(file)).Build());
43+
} catch (Exception e) {
44+
Log.Error(e, $"Failed to load {file}");
45+
}
46+
}
47+
}
48+
g2ps.Add(new ChineseCVVG2p());
49+
return new G2pFallbacks(g2ps.ToArray());
50+
}
51+
52+
protected override Dictionary<string, string[]> LoadVowelFallbacks() {
53+
return "_un=_en".Split(';')
54+
.Select(entry => entry.Split('='))
55+
.ToDictionary(parts => parts[0], parts => parts[1].Split(','));
56+
}
57+
}
58+
59+
class ChineseCVVG2p : IG2p{
1460
/// <summary>
1561
/// The consonant table.
1662
/// </summary>
@@ -22,26 +68,22 @@ public class ChineseCVVPhonemizer : BaseChinesePhonemizer {
2268

2369
static HashSet<string> cSet;
2470
static Dictionary<string, string> vDict;
25-
26-
static ChineseCVVPhonemizer() {
71+
72+
static ChineseCVVG2p() {
2773
cSet = new HashSet<string>(consonants.Split(','));
2874
vDict = vowels.Split(',')
2975
.Select(s => s.Split('='))
3076
.ToDictionary(a => a[0], a => a[1]);
3177
}
3278

33-
private USinger singer;
34-
35-
// Simply stores the singer in a field.
36-
public override void SetSinger(USinger singer) => this.singer = singer;
79+
public bool IsVowel(string phoneme){
80+
return !phoneme.StartsWith("_");
81+
}
3782

38-
public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevNeighbour, Note? nextNeighbour, Note[] prevNeighbours) {
83+
public string[] Query(string lyric){
3984
// The overall logic is:
4085
// 1. Remove consonant: "duang" -> "uang".
4186
// 2. Lookup the trailing sound in vowel table: "uang" -> "_ang".
42-
// 3. Split the total duration and returns "duang" and "_ang".
43-
var lyric = notes[0].lyric;
44-
var note = notes[0];
4587
string consonant = string.Empty;
4688
string vowel = string.Empty;
4789
if (lyric.Length > 2 && cSet.Contains(lyric.Substring(0, 2))) {
@@ -63,62 +105,20 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN
63105
if ((vowel == "an") && (consonant == "y")) {
64106
vowel = "ian";
65107
}
66-
string phoneme0 = lyric;
67-
// Get color
68-
string color = string.Empty;
69-
int toneShift = 0;
70-
if (note.phonemeAttributes != null) {
71-
var attr = note.phonemeAttributes.FirstOrDefault(attr => attr.index == 0);
72-
color = attr.voiceColor;
73-
toneShift = attr.toneShift;
108+
if(vDict.TryGetValue(vowel, out var tail)){
109+
return new string[] { lyric, tail };
110+
}else{
111+
return new string[] { lyric };
74112
}
75-
// We will need to split the total duration for phonemes, so we compute it here.
76-
int totalDuration = notes.Sum(n => n.duration);
77-
// Lookup the vowel split table. For example, "uang" will match "_ang".
78-
if (vDict.TryGetValue(vowel, out var phoneme1)) {
79-
// Now phoneme0="duang" and phoneme1="_ang",
80-
// try to give "_ang" 120 ticks, but no more than half of the total duration.
81-
int length1 = 120;
82-
if (length1 > totalDuration / 2) {
83-
length1 = totalDuration / 2;
84-
}
85-
if (singer.TryGetMappedOto(phoneme0, note.tone + toneShift, color, out var oto0)) {
86-
phoneme0 = oto0.Alias;
87-
}
88-
89-
if (singer.TryGetMappedOto(phoneme1, note.tone + toneShift, color, out var oto1)) {
90-
phoneme1 = oto1.Alias;
91-
}
92-
93-
if (phoneme1.Contains("_un") && !singer.TryGetMappedOto(phoneme1, note.tone + toneShift, color, out var oto2)) {
94-
phoneme1 = "_en";
95-
} else if (phoneme1.Contains("_un") && singer.TryGetMappedOto(phoneme1, note.tone + toneShift, color, out var oto3)) {
96-
phoneme1 = oto3.Alias;
97-
}
113+
114+
}
115+
public bool IsValidSymbol(string symbol){
116+
return true;
117+
}
98118

99-
return new Result {
100-
phonemes = new Phoneme[] {
101-
new Phoneme() {
102-
phoneme = phoneme0,
103-
},
104-
new Phoneme() {
105-
phoneme = phoneme1,
106-
position = totalDuration - length1,
107-
}
108-
},
109-
};
110-
}
111-
if (singer.TryGetMappedOto(phoneme0, note.tone + toneShift, color, out var oto)) {
112-
phoneme0 = oto.Alias;
113-
}
114-
// Not spliting is needed. Return as is.
115-
return new Result {
116-
phonemes = new Phoneme[] {
117-
new Phoneme() {
118-
phoneme = phoneme0,
119-
}
120-
},
121-
};
119+
public string[] UnpackHint(string hint, char separator = ' ') {
120+
return hint.Split(separator)
121+
.ToArray();
122122
}
123123
}
124-
}
124+
}

0 commit comments

Comments
 (0)