From 6b199c94991cbbd832ae7e7b39b470314caa38a5 Mon Sep 17 00:00:00 2001 From: IsE333 Date: Wed, 7 May 2025 16:07:03 +0300 Subject: [PATCH 1/4] Add TurkishCVVCPhonemizer. --- .../TurkishCVVCPhonemizer.cs | 370 ++++++++++++++++++ OpenUtau/Strings/Strings.axaml | 1 + 2 files changed, 371 insertions(+) create mode 100644 OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs diff --git a/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs b/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs new file mode 100644 index 000000000..92f58f2c1 --- /dev/null +++ b/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs @@ -0,0 +1,370 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using OpenUtau.Api; +using OpenUtau.Core.Ustx; + +namespace OpenUtau.Plugin.Builtin { + [Phonemizer("Turkish CVVC Phonemizer", "TR CVVC", "IsE & comorybxto", language: "TR")] + public class TurkishCVVCPhonemizer : Phonemizer { + static readonly string[] glottalStops = new string[] { "?", "q" }; + static readonly string[] vowels = new string[] { "a", "e", "ae", "eu", "i", "o", "oe", "u", "ue" }; + static readonly string[] sustainedConsonants = new string[] { "Y", "L", "LY", "M", "N", "NG" }; + static readonly string[] consonants = "9,b,c,ch,d,f,g,h,j,k,l,m,n,ng,p,r,rr,r',s,sh,t,v,w,y,z,by,dy,gy,hy,ky,ly,my,ny,py,ry,ty,Y,L,LY,M,N,NG,-,?,q".Split(','); + + static TurkishCVVCPhonemizer() { + } + + // Store singer in field, will try reading presamp.ini later + private USinger singer; + public override void SetSinger(USinger singer) => this.singer = singer; + + // make it quicker to check multiple oto occurrences at once rather than spamming if else if + private bool checkOtoUntilHit(string[] input, Note note, out UOto oto) { + oto = default; + var attr = note.phonemeAttributes?.FirstOrDefault(attr => attr.index == 0) ?? default; + + var otos = new List(); + foreach (string test in input) { + if (singer.TryGetMappedOto(test + attr.alternate, note.tone + attr.toneShift, attr.voiceColor, out var otoAlt)) { + otos.Add(otoAlt); + } else if (singer.TryGetMappedOto(test, note.tone + attr.toneShift, attr.voiceColor, out var otoCandidacy)) { + otos.Add(otoCandidacy); + } + } + + string color = attr.voiceColor ?? ""; + if (otos.Count > 0) { + if (otos.Any(oto => (oto.Color ?? string.Empty) == color)) { + oto = otos.Find(oto => (oto.Color ?? string.Empty) == color); + return true; + } else { + oto = otos.First(); + return true; + } + } + return false; + } + + private string[] getNoteStart(SegmentedLyric phonemesCurrent, SegmentedLyric phonemesPrev) { + string noteStart = phonemesCurrent.StartC1 + phonemesCurrent.StartC2 + phonemesCurrent.Vow; + bool hasNoPrevNeighbour = (phonemesPrev.StartC1 == "") && (phonemesPrev.Vow == ""); + string[] result = new string[] { "- " + noteStart, noteStart, phonemesCurrent.Lyric }; + + if (hasNoPrevNeighbour) { + return result; + } + + if (phonemesCurrent.StartC1 == "") { + if (phonemesPrev.hasConsonantAfterVowel()) { //vc + V + if (sustainedConsonants.Contains(phonemesPrev.EndC1.ToUpper())) { + result[0] = phonemesPrev.EndC1.ToUpper() + " " + phonemesCurrent.Vow; + } + } else if (phonemesPrev.Vow != "") { //v + V + if (phonemesCurrent.Has9BeforeVow) { + result[0] = phonemesPrev.Vow + " 9" + phonemesCurrent.Vow; + } else { + result[0] = phonemesPrev.Vow + " " + phonemesCurrent.Vow; + } + } + return result; + } + + return new string[] { noteStart, phonemesCurrent.Lyric }; + } + + private string getAlternativeConsonant(string consonant, string vow) { + if (vow == "e" || vow == "i" || vow == "ue" || vow == "oe") { + string y = "y"; + if (consonant.ToUpper() == consonant) { + y = "Y"; + } + if (consonants.Contains(consonant + y)) { + return consonant + y; + } + } + return consonant; + } + + private string[] getConsonantEnding(SegmentedLyric current, bool hasNext, SegmentedLyric next) { + string v_ = current.Vow + " "; + + if (glottalStops.Contains(current.EndC1)) + return new string[] { v_ + current.EndC1 }; + + if (hasNext) { + //if (sustainedConsonants.Contains(current.EndC1.ToUpper())) + // return new string[] { v_ + current.EndC1.ToUpper() }; + if (current.EndC1 != "" && current.EndC1 == next.StartC1) + return new string[] { v_ + getAlternativeConsonant(current.EndC1, current.Vow) }; + + if (next.StartC1 == "g" || next.StartC1 == "k") { + if (current.EndC1 == "n") { + current.EndC1 = "ng"; + } else if (current.EndC1 == "N") { + current.EndC1 = "NG"; + } + } + } + + if (!current.hasConsonantAfterVowel()) { + if (!hasNext) { + return new string[] { v_ + "-" }; + } else if (next.hasConsonantBeforeVowel()) { // V + c + return new string[] { v_ + getAlternativeConsonant(next.StartC1, next.Vow) }; + } else {//V + v + return new string[] { "" }; + } + } + + return new string[] { + v_ + current.EndC1 + "-", + v_ + current.EndC1 }; + } + + private string getIfPChTK(string c) { + if (c == "p" || c == "ch" || c == "t" || c == "k") { + return ""; + } + return "-"; + } + + private string convertToOtoStyledLyric(string lyric) { + lyric = lyric.Replace("ç", "ch"); + lyric = lyric.Replace("ş", "sh"); + lyric = lyric.Replace("ğ", "9"); + lyric = lyric.Replace("æ", "ae"); + lyric = lyric.Replace("E", "ae"); + lyric = lyric.Replace("ı", "eu"); + lyric = lyric.Replace("ö", "oe"); + lyric = lyric.Replace("ü", "ue"); + return lyric; + } + + private struct SegmentedLyric { + public string Lyric; + public string StartC1; + public string StartC2; + public string Vow; + public string EndC1; + public string EndC2; + public bool Has9BeforeVow; + public SegmentedLyric(string originalLyric, string[] phonemes, bool has9BeforeVow) { + Lyric = originalLyric; + StartC1 = phonemes[0]; + StartC2 = phonemes[1]; + Vow = phonemes[2]; + EndC1 = phonemes[3]; + EndC2 = phonemes[4]; + Has9BeforeVow = has9BeforeVow; + } + public SegmentedLyric(bool has9BeforeVow) { + Lyric = ""; + StartC1 = StartC2 = Vow = EndC1 = EndC2 = ""; + Has9BeforeVow = has9BeforeVow; + } + public bool hasConsonantBeforeVowel() { + return StartC1 != ""; + } + public bool hasConsonantAfterVowel() { + return EndC1 != ""; + } + } + + private SegmentedLyric getSegmentedPhonemes(string lyric) { //CCVCC + lyric = convertToOtoStyledLyric(lyric); + string[] phonemes = new string[] { "", "", "", "", "" }; + bool has9BeforeVow = false; + int charIndex = 0; + + for (int i = 0; i < 5; i++) { + string twoCharPhoneme = ""; + if (charIndex + 2 <= lyric.Length) { + twoCharPhoneme = lyric.Substring(charIndex, 2); + } + string oneCharPhoneme = lyric.Substring(charIndex, 1); + + if (i < 2 && oneCharPhoneme == "9") { + has9BeforeVow |= true; + charIndex += 1; + } else if (vowels.Contains(twoCharPhoneme)) { + i = 2; + phonemes[i] = twoCharPhoneme; + charIndex += 2; + } else if (vowels.Contains(oneCharPhoneme)) { + i = 2; + phonemes[i] = oneCharPhoneme; + charIndex += 1; + } else if (consonants.Contains(twoCharPhoneme) && i != 2) { + phonemes[i] = twoCharPhoneme; + charIndex += 2; + } else if (consonants.Contains(oneCharPhoneme) && i != 2) { + phonemes[i] = oneCharPhoneme; + charIndex += 1; + } else { // not found + i -= 1; + charIndex += 1; + } + + if (charIndex == lyric.Length) { + break; + } + } + return new SegmentedLyric(lyric, phonemes, has9BeforeVow); + } + + public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevNeighbour, Note? nextNeighbour, Note[] prevNeighbours) { + var note = notes[0]; + var currentLyric = note.lyric.Normalize(); + + /* + if (currentLyric[0] == ',') + basicMode = !basicMode; + if (basicMode) { + return new Result { + phonemes = new Phoneme[] { + new Phoneme() { + phoneme = currentLyric, + } + }, + }; + }*/ + if (currentLyric[0] == '.') { + return new Result { + phonemes = new Phoneme[] { + new Phoneme() { + phoneme = currentLyric.Substring(1), + } + }, + }; + } + + SegmentedLyric phonemesCurrent = getSegmentedPhonemes(currentLyric); + SegmentedLyric phonemesPrev = new SegmentedLyric(false); + SegmentedLyric phonemesNext = new SegmentedLyric(false); + + if (prevNeighbour != null) { + phonemesPrev = getSegmentedPhonemes(prevNeighbour.Value.lyric.Normalize()); + } + if (nextNeighbour != null) { + phonemesNext = getSegmentedPhonemes(nextNeighbour.Value.lyric.Normalize()); + } + + string[] noteStartInput = getNoteStart(phonemesCurrent, phonemesPrev); + string[] noteEndInput = getConsonantEnding(phonemesCurrent, nextNeighbour.HasValue, phonemesNext);//phonemesCurrent.EndC1 + "-"; + string noteStart = "", noteEnd = "", noteEndCC = ""; + + if (phonemesCurrent.EndC2 != "") { // + VCC + noteEndInput = new string[] { phonemesCurrent.Vow + " " + phonemesCurrent.EndC1 + getIfPChTK(phonemesCurrent.EndC1) }; + noteEndCC = phonemesCurrent.EndC1 + phonemesCurrent.EndC2 + " -"; + } + + + if (checkOtoUntilHit(noteStartInput, note, out var o1)) { + noteStart = o1.Alias; + } + + if (checkOtoUntilHit(noteEndInput, note, out var o2)) { + noteEnd = o2.Alias; + } else { + noteEnd = ""; + } + + var input = new string[] { noteEndCC }; + if (checkOtoUntilHit(input, note, out var o3)) { + noteEndCC = o3.Alias; + } else { + noteEndCC = ""; + } + + if (noteStart != "" && noteEnd == "" && noteEndCC == "") { + return new Result { + phonemes = new Phoneme[] { + new Phoneme() { + phoneme = noteStart, + } + }, + }; + } else if (noteStart != "" && noteEnd != "") { + int totalDuration = notes.Sum(n => n.duration); + int lastLengthFromOto = 120; + double isCVCoeff = 1; + if (phonemesCurrent.hasConsonantAfterVowel()) + isCVCoeff = 1.8; + int isEndCoeff = 2; + if (nextNeighbour != null) { + isEndCoeff = 1; + var attr0 = nextNeighbour.Value.phonemeAttributes?.FirstOrDefault(attr => attr.index == 0) ?? default; //next first + if (singer.TryGetMappedOto(getNoteStart(phonemesNext, phonemesCurrent)[0], note.tone + attr0.toneShift, attr0.voiceColor, out var oto0)) { + // If overlap is a negative value, vcLength is longer than Preutter + if (oto0.Overlap < 0) + lastLengthFromOto = timeAxis.MsPosToTickPos(oto0.Preutter - oto0.Overlap); + else + lastLengthFromOto = timeAxis.MsPosToTickPos(oto0.Preutter); + } + } + // vcLength depends on the Vel of the note + var attr1 = note.phonemeAttributes?.FirstOrDefault(attr => attr.index == 1) ?? default; // current last (noteEnd) + var vcLength = Convert.ToInt32(Math.Min(totalDuration / (2 * isEndCoeff), lastLengthFromOto * isCVCoeff * (attr1.consonantStretchRatio ?? 1))); + + if (noteEndCC == "") { + return new Result { + phonemes = new Phoneme[] { + new Phoneme() { + phoneme = noteStart, + }, + new Phoneme() { + phoneme = noteEnd, + position = totalDuration - vcLength, + }, + }, + }; + } else { + int ccLengthFromOto = 60; + var attr2 = note.phonemeAttributes?.FirstOrDefault(attr => attr.index == 2) ?? default; + if (nextNeighbour != null) { + if (singer.TryGetMappedOto(noteEndCC, note.tone + attr2.toneShift, attr2.voiceColor, out var oto1)) { + // If overlap is a negative value, vcLength is longer than Preutter + if (oto1.Overlap < 0) { + ccLengthFromOto = timeAxis.MsPosToTickPos(oto1.Preutter - oto1.Overlap); + } else { + ccLengthFromOto = timeAxis.MsPosToTickPos(oto1.Preutter); + } + } + } + vcLength = Convert.ToInt32(Math.Min(totalDuration / 3, ccLengthFromOto * (attr1.consonantStretchRatio ?? 1))); + var ccLength = Convert.ToInt32(Math.Min(totalDuration / 3, lastLengthFromOto * (attr2.consonantStretchRatio ?? 1))); + + List exp = new List(); + PhonemeExpression e = new PhonemeExpression() { abbr = Core.Format.Ustx.VOL, value = 70 }; + exp.Add(e); + + return new Result { + phonemes = new Phoneme[] { + new Phoneme() { + phoneme = noteStart, + }, + new Phoneme() { + phoneme = noteEnd, + position = totalDuration - vcLength - ccLength, + }, + new Phoneme() { + phoneme = noteEndCC, + position = totalDuration - ccLength, + expressions = exp, + }, + }, + }; + } + } + + return new Result { + phonemes = new Phoneme[] { + new Phoneme() { + phoneme = currentLyric, + } + }, + }; + } + } +} diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index ba1a9d27f..bf771ea1e 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -123,6 +123,7 @@ Portuguese Russian Thai + Turkish Vietnamese Chinese Cantonese From bd8eee18a584c2d228d8745257898f3d6a6628e5 Mon Sep 17 00:00:00 2001 From: IsE333 Date: Fri, 9 May 2025 15:29:56 +0300 Subject: [PATCH 2/4] Update TurkishCVVCPhonemizer and clean up code --- .../TurkishCVVCPhonemizer.cs | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs b/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs index 92f58f2c1..5d25be093 100644 --- a/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs @@ -5,16 +5,14 @@ using OpenUtau.Core.Ustx; namespace OpenUtau.Plugin.Builtin { - [Phonemizer("Turkish CVVC Phonemizer", "TR CVVC", "IsE & comorybxto", language: "TR")] + [Phonemizer("Turkish CVVC Phonemizer", "TR CVVC", "ise", language: "TR")] + // Contributed by ise with the help of Japanese CVVC phonemizer by TUBS public class TurkishCVVCPhonemizer : Phonemizer { static readonly string[] glottalStops = new string[] { "?", "q" }; static readonly string[] vowels = new string[] { "a", "e", "ae", "eu", "i", "o", "oe", "u", "ue" }; static readonly string[] sustainedConsonants = new string[] { "Y", "L", "LY", "M", "N", "NG" }; static readonly string[] consonants = "9,b,c,ch,d,f,g,h,j,k,l,m,n,ng,p,r,rr,r',s,sh,t,v,w,y,z,by,dy,gy,hy,ky,ly,my,ny,py,ry,ty,Y,L,LY,M,N,NG,-,?,q".Split(','); - static TurkishCVVCPhonemizer() { - } - // Store singer in field, will try reading presamp.ini later private USinger singer; public override void SetSinger(USinger singer) => this.singer = singer; @@ -93,8 +91,6 @@ private string[] getConsonantEnding(SegmentedLyric current, bool hasNext, Segmen return new string[] { v_ + current.EndC1 }; if (hasNext) { - //if (sustainedConsonants.Contains(current.EndC1.ToUpper())) - // return new string[] { v_ + current.EndC1.ToUpper() }; if (current.EndC1 != "" && current.EndC1 == next.StartC1) return new string[] { v_ + getAlternativeConsonant(current.EndC1, current.Vow) }; @@ -122,7 +118,7 @@ private string[] getConsonantEnding(SegmentedLyric current, bool hasNext, Segmen v_ + current.EndC1 }; } - private string getIfPChTK(string c) { + private string checkPChTK(string c) { if (c == "p" || c == "ch" || c == "t" || c == "k") { return ""; } @@ -217,18 +213,6 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN var note = notes[0]; var currentLyric = note.lyric.Normalize(); - /* - if (currentLyric[0] == ',') - basicMode = !basicMode; - if (basicMode) { - return new Result { - phonemes = new Phoneme[] { - new Phoneme() { - phoneme = currentLyric, - } - }, - }; - }*/ if (currentLyric[0] == '.') { return new Result { phonemes = new Phoneme[] { @@ -255,7 +239,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN string noteStart = "", noteEnd = "", noteEndCC = ""; if (phonemesCurrent.EndC2 != "") { // + VCC - noteEndInput = new string[] { phonemesCurrent.Vow + " " + phonemesCurrent.EndC1 + getIfPChTK(phonemesCurrent.EndC1) }; + noteEndInput = new string[] { phonemesCurrent.Vow + " " + phonemesCurrent.EndC1 + checkPChTK(phonemesCurrent.EndC1) }; noteEndCC = phonemesCurrent.EndC1 + phonemesCurrent.EndC2 + " -"; } From 2f89200c3648cd00cf998976a6e6dcd0f7f3edea Mon Sep 17 00:00:00 2001 From: IsE333 <77530401+IsE333@users.noreply.github.com> Date: Mon, 21 Jul 2025 19:42:31 +0300 Subject: [PATCH 3/4] Update TurkishCVVCPhonemizer for improved phonemization --- .../TurkishCVVCPhonemizer.cs | 102 +++++++++++++++--- 1 file changed, 88 insertions(+), 14 deletions(-) diff --git a/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs b/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs index 5d25be093..2ceca0085 100644 --- a/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs @@ -9,10 +9,31 @@ namespace OpenUtau.Plugin.Builtin { // Contributed by ise with the help of Japanese CVVC phonemizer by TUBS public class TurkishCVVCPhonemizer : Phonemizer { static readonly string[] glottalStops = new string[] { "?", "q" }; - static readonly string[] vowels = new string[] { "a", "e", "ae", "eu", "i", "o", "oe", "u", "ue" }; + static string[] vowels = new string[] { "a", "e", "i", "o", "u", "", "", "", "" }; + static readonly string[,] vowelAlternatives = new string[,] { + { "E", "æ", "ae" }, + { "I", "ı", "eu" }, + { "O", "ö", "oe" }, + { "U", "ü", "ue" } + }; + static bool[] consonantReplace = new bool[11]; + static readonly string[,] consonantAlternatives = new string[,] { + { "Y", "y" }, + { "L", "l" }, + { "LY", "l'" }, + { "ly", "l'" }, + { "M", "m" }, + { "N", "n" }, + { "NG", "ng" }, + { "?", "q" }, + { "l'", "l" }, //not ideal + { "k'", "ky" }, + { "g'", "gy" } + }; static readonly string[] sustainedConsonants = new string[] { "Y", "L", "LY", "M", "N", "NG" }; - static readonly string[] consonants = "9,b,c,ch,d,f,g,h,j,k,l,m,n,ng,p,r,rr,r',s,sh,t,v,w,y,z,by,dy,gy,hy,ky,ly,my,ny,py,ry,ty,Y,L,LY,M,N,NG,-,?,q".Split(','); + static readonly string[] consonants = "9,b,c,ch,d,f,g,h,j,k,l,m,n,ng,p,r,rr,r',s,sh,t,v,w,y,z,by,dy,gy,g',hy,ky,k',ly,l',my,ny,py,ry,ty,Y,L,LY,M,N,NG,-,?,q".Split(','); + private string singerName = ""; // for detecting singer changes // Store singer in field, will try reading presamp.ini later private USinger singer; public override void SetSinger(USinger singer) => this.singer = singer; @@ -55,8 +76,9 @@ private string[] getNoteStart(SegmentedLyric phonemesCurrent, SegmentedLyric pho if (phonemesCurrent.StartC1 == "") { if (phonemesPrev.hasConsonantAfterVowel()) { //vc + V - if (sustainedConsonants.Contains(phonemesPrev.EndC1.ToUpper())) { - result[0] = phonemesPrev.EndC1.ToUpper() + " " + phonemesCurrent.Vow; + if (sustainedConsonants.Contains(phonemesPrev.EndC1)) { + result[1] = result[0]; + result[0] = phonemesPrev.EndC1 + " " + phonemesCurrent.Vow; } } else if (phonemesPrev.Vow != "") { //v + V if (phonemesCurrent.Has9BeforeVow) { @@ -74,6 +96,11 @@ private string[] getNoteStart(SegmentedLyric phonemesCurrent, SegmentedLyric pho private string getAlternativeConsonant(string consonant, string vow) { if (vow == "e" || vow == "i" || vow == "ue" || vow == "oe") { string y = "y"; + if (consonantReplace[3] && consonant == "l") { //if ly does not exist + return consonant; + } + /* if (singer.TryGetOto("e " + consonant + "'", out UOto oto)) {}*/ + if (consonant.ToUpper() == consonant) { y = "Y"; } @@ -125,15 +152,48 @@ private string checkPChTK(string c) { return "-"; } + private void syncPhonemesFromSinger() { + vowels[5] = ""; + vowels[6] = ""; + vowels[7] = ""; + vowels[8] = ""; + for (int i = 5; i < 9; i++) { + for (int j = 0; j < 3; j++) { + string v = vowelAlternatives[i - 5, j]; + if (singer.TryGetOto("- " + v, out UOto oto)) { + vowels[i] = v; + break; + } + } + } + for (int i = 0; i < consonantAlternatives.GetLength(0); i++) { + if (!singer.TryGetOto("a " + consonantAlternatives[i, 0], out UOto oto)) + consonantReplace[i] = true; + else + consonantReplace[i] = false; + } + + } + private string convertToOtoStyledLyric(string lyric) { + if (singerName != singer.Name) { + singerName = singer.Name; + syncPhonemesFromSinger(); + } + lyric = lyric.Replace("ç", "ch"); lyric = lyric.Replace("ş", "sh"); lyric = lyric.Replace("ğ", "9"); - lyric = lyric.Replace("æ", "ae"); - lyric = lyric.Replace("E", "ae"); - lyric = lyric.Replace("ı", "eu"); - lyric = lyric.Replace("ö", "oe"); - lyric = lyric.Replace("ü", "ue"); + + for (int i = 0; i < consonantAlternatives.GetLength(0); i++) + if (consonantReplace[i]) + lyric = lyric.Replace(consonantAlternatives[i, 0], consonantAlternatives[i, 1]); + + for (int i = 5; i < 9; i++) + for (int j = 0; j < 3; j++) + if (vowelAlternatives[i - 5, j] != vowels[i]) + lyric = lyric.Replace(vowelAlternatives[i - 5, j], vowels[i]); + return lyric; } @@ -180,6 +240,18 @@ private SegmentedLyric getSegmentedPhonemes(string lyric) { //CCVCC } string oneCharPhoneme = lyric.Substring(charIndex, 1); + if (phonemes[0] == "s" && i == 1) { // for CCCV exceptions + if (twoCharPhoneme == "pr") { + phonemes[i] = "pr"; + charIndex += 2; + continue; + } else if (twoCharPhoneme == "tr") { + phonemes[i] = "tr"; + charIndex += 2; + continue; + } + } + if (i < 2 && oneCharPhoneme == "9") { has9BeforeVow |= true; charIndex += 1; @@ -239,19 +311,21 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN string noteStart = "", noteEnd = "", noteEndCC = ""; if (phonemesCurrent.EndC2 != "") { // + VCC - noteEndInput = new string[] { phonemesCurrent.Vow + " " + phonemesCurrent.EndC1 + checkPChTK(phonemesCurrent.EndC1) }; - noteEndCC = phonemesCurrent.EndC1 + phonemesCurrent.EndC2 + " -"; + noteEndInput = new string[] { phonemesCurrent.Vow + " " + phonemesCurrent.EndC1 + phonemesCurrent.EndC2 + "-" }; } - if (checkOtoUntilHit(noteStartInput, note, out var o1)) { noteStart = o1.Alias; } if (checkOtoUntilHit(noteEndInput, note, out var o2)) { noteEnd = o2.Alias; - } else { - noteEnd = ""; + } else if (phonemesCurrent.EndC2 != "") { //if oto has no vcc, try vc+c + noteEndInput = new string[] { phonemesCurrent.Vow + " " + phonemesCurrent.EndC1 + checkPChTK(phonemesCurrent.EndC1) }; + if (checkOtoUntilHit(noteEndInput, note, out var o2_)) { + noteEnd = o2_.Alias; + noteEndCC = phonemesCurrent.EndC1 + phonemesCurrent.EndC2 + " -"; + } } var input = new string[] { noteEndCC }; From 29c85b5ca872fb5b126a1891d987c3129cbb6217 Mon Sep 17 00:00:00 2001 From: IsE333 <77530401+IsE333@users.noreply.github.com> Date: Tue, 22 Jul 2025 20:20:41 +0300 Subject: [PATCH 4/4] Delete unnecessary lines and add default values to vowels --- OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs b/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs index 2ceca0085..1e8235791 100644 --- a/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/TurkishCVVCPhonemizer.cs @@ -9,7 +9,7 @@ namespace OpenUtau.Plugin.Builtin { // Contributed by ise with the help of Japanese CVVC phonemizer by TUBS public class TurkishCVVCPhonemizer : Phonemizer { static readonly string[] glottalStops = new string[] { "?", "q" }; - static string[] vowels = new string[] { "a", "e", "i", "o", "u", "", "", "", "" }; + static string[] vowels = new string[] { "a", "e", "i", "o", "u", "æ", "ı", "ö", "ü" }; static readonly string[,] vowelAlternatives = new string[,] { { "E", "æ", "ae" }, { "I", "ı", "eu" }, @@ -26,7 +26,7 @@ public class TurkishCVVCPhonemizer : Phonemizer { { "N", "n" }, { "NG", "ng" }, { "?", "q" }, - { "l'", "l" }, //not ideal + { "l'", "l" }, { "k'", "ky" }, { "g'", "gy" } }; @@ -99,7 +99,6 @@ private string getAlternativeConsonant(string consonant, string vow) { if (consonantReplace[3] && consonant == "l") { //if ly does not exist return consonant; } - /* if (singer.TryGetOto("e " + consonant + "'", out UOto oto)) {}*/ if (consonant.ToUpper() == consonant) { y = "Y"; @@ -153,10 +152,6 @@ private string checkPChTK(string c) { } private void syncPhonemesFromSinger() { - vowels[5] = ""; - vowels[6] = ""; - vowels[7] = ""; - vowels[8] = ""; for (int i = 5; i < 9; i++) { for (int j = 0; j < 3; j++) { string v = vowelAlternatives[i - 5, j]; @@ -166,13 +161,13 @@ private void syncPhonemesFromSinger() { } } } + for (int i = 0; i < consonantAlternatives.GetLength(0); i++) { if (!singer.TryGetOto("a " + consonantAlternatives[i, 0], out UOto oto)) consonantReplace[i] = true; else consonantReplace[i] = false; } - } private string convertToOtoStyledLyric(string lyric) {