Skip to content

Commit

Permalink
Chinese CVV: custom dictionary support
Browse files Browse the repository at this point in the history
  • Loading branch information
oxygen-dioxide committed Jul 14, 2023
1 parent 5b5c614 commit 7faf722
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 394 deletions.
117 changes: 0 additions & 117 deletions OpenUtau.Plugin.Builtin/ChineseCVVMonophonePhonemizer.cs

This file was deleted.

138 changes: 69 additions & 69 deletions OpenUtau.Plugin.Builtin/ChineseCVVPhonemizer.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,62 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using OpenUtau.Api;
using OpenUtau.Core;
using OpenUtau.Core.Ustx;
using Serilog;

namespace OpenUtau.Plugin.Builtin {
namespace OpenUtau.Plugin.Builtin
{
/// <summary>
/// Chinese 十月式整音扩张 CVV Phonemizer.
/// <para>It works by spliting "duang" to "duang" + "_ang", to produce the proper tail sound.</para>
/// </summary>
[Phonemizer("Chinese CVV (十月式整音扩张) Phonemizer", "ZH CVV", language: "ZH")]
public class ChineseCVVPhonemizer : BaseChinesePhonemizer {
public class ChineseCVVMonophonePhonemizer : MonophonePhonemizer
{
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";
static readonly string tails = "_vn,_ing,_ong,_an,_ou,_er,_ao,_eng,_ang,_en,_en2,_ai,_iong,_in,_ei";

static readonly string[] pinyinList = pinyins.Split(',');
static readonly string[] tailList = tails.Split(',');

public ChineseCVVMonophonePhonemizer() {
ConsonantLength = 120;
}

protected override IG2p LoadG2p() {
var g2ps = new List<IG2p>();

// Load dictionary from plugin folder.
string path = Path.Combine(PluginDir, "zhcvv.yaml");
if (File.Exists(path)) {
g2ps.Add(G2pDictionary.NewBuilder().Load(File.ReadAllText(path)).Build());
}

// Load dictionary from singer folder.
if (singer != null && singer.Found && singer.Loaded) {
string file = Path.Combine(singer.Location, "zhcvv.yaml");
if (File.Exists(file)) {
try {
g2ps.Add(G2pDictionary.NewBuilder().Load(File.ReadAllText(file)).Build());
} catch (Exception e) {
Log.Error(e, $"Failed to load {file}");
}
}
}
g2ps.Add(new ChineseCVVG2p());
return new G2pFallbacks(g2ps.ToArray());
}

protected override Dictionary<string, string[]> LoadVowelFallbacks() {
return "_un=_en".Split(';')
.Select(entry => entry.Split('='))
.ToDictionary(parts => parts[0], parts => parts[1].Split(','));
}
}

class ChineseCVVG2p : IG2p{
/// <summary>
/// The consonant table.
/// </summary>
Expand All @@ -22,26 +68,22 @@ public class ChineseCVVPhonemizer : BaseChinesePhonemizer {

static HashSet<string> cSet;
static Dictionary<string, string> vDict;

static ChineseCVVPhonemizer() {
static ChineseCVVG2p() {
cSet = new HashSet<string>(consonants.Split(','));
vDict = vowels.Split(',')
.Select(s => s.Split('='))
.ToDictionary(a => a[0], a => a[1]);
}

private USinger singer;

// Simply stores the singer in a field.
public override void SetSinger(USinger singer) => this.singer = singer;
public bool IsVowel(string phoneme){
return !phoneme.StartsWith("_");
}

public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevNeighbour, Note? nextNeighbour, Note[] prevNeighbours) {
public string[] Query(string lyric){
// The overall logic is:
// 1. Remove consonant: "duang" -> "uang".
// 2. Lookup the trailing sound in vowel table: "uang" -> "_ang".
// 3. Split the total duration and returns "duang" and "_ang".
var lyric = notes[0].lyric;
var note = notes[0];
string consonant = string.Empty;
string vowel = string.Empty;
if (lyric.Length > 2 && cSet.Contains(lyric.Substring(0, 2))) {
Expand All @@ -63,62 +105,20 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN
if ((vowel == "an") && (consonant == "y")) {
vowel = "ian";
}
string phoneme0 = lyric;
// Get color
string color = string.Empty;
int toneShift = 0;
if (note.phonemeAttributes != null) {
var attr = note.phonemeAttributes.FirstOrDefault(attr => attr.index == 0);
color = attr.voiceColor;
toneShift = attr.toneShift;
if(vDict.TryGetValue(vowel, out var tail)){
return new string[] { lyric, tail };
}else{
return new string[] { lyric };
}
// We will need to split the total duration for phonemes, so we compute it here.
int totalDuration = notes.Sum(n => n.duration);
// Lookup the vowel split table. For example, "uang" will match "_ang".
if (vDict.TryGetValue(vowel, out var phoneme1)) {
// Now phoneme0="duang" and phoneme1="_ang",
// try to give "_ang" 120 ticks, but no more than half of the total duration.
int length1 = 120;
if (length1 > totalDuration / 2) {
length1 = totalDuration / 2;
}
if (singer.TryGetMappedOto(phoneme0, note.tone + toneShift, color, out var oto0)) {
phoneme0 = oto0.Alias;
}

if (singer.TryGetMappedOto(phoneme1, note.tone + toneShift, color, out var oto1)) {
phoneme1 = oto1.Alias;
}

if (phoneme1.Contains("_un") && !singer.TryGetMappedOto(phoneme1, note.tone + toneShift, color, out var oto2)) {
phoneme1 = "_en";
} else if (phoneme1.Contains("_un") && singer.TryGetMappedOto(phoneme1, note.tone + toneShift, color, out var oto3)) {
phoneme1 = oto3.Alias;
}

}
public bool IsValidSymbol(string symbol){
return true;
}

return new Result {
phonemes = new Phoneme[] {
new Phoneme() {
phoneme = phoneme0,
},
new Phoneme() {
phoneme = phoneme1,
position = totalDuration - length1,
}
},
};
}
if (singer.TryGetMappedOto(phoneme0, note.tone + toneShift, color, out var oto)) {
phoneme0 = oto.Alias;
}
// Not spliting is needed. Return as is.
return new Result {
phonemes = new Phoneme[] {
new Phoneme() {
phoneme = phoneme0,
}
},
};
public string[] UnpackHint(string hint, char separator = ' ') {
return hint.Split(separator)
.ToArray();
}
}
}
}
Loading

0 comments on commit 7faf722

Please sign in to comment.