From c33bc3a770fdb1f9adffca576f2cdd039422d919 Mon Sep 17 00:00:00 2001 From: EX3 Date: Tue, 30 Apr 2024 21:26:02 +0900 Subject: [PATCH 1/5] Added function to handle Non-Hangeul graphemes --- OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs b/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs index b1a92eabe..3871f31ea 100644 --- a/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs @@ -23,6 +23,11 @@ public abstract class BaseKoreanPhonemizer : Phonemizer { protected static readonly string[] PLAIN_VOWELS = new string[]{"ㅏ", "ㅣ", "ㅜ", "ㅔ", "ㅗ", "ㅡ", "ㅓ", "ㅢ"}; protected static readonly string[] SOFT_BATCHIMS = new string[]{"ㄴ", "ㄹ", "ㅇ"}; protected static readonly string[] HARD_BATCHIMS = new string[]{"ㄱ", "ㄷ", "ㅂ", "ㅁ"}; + + // this phonemizer will call ConvertPhonemes() when lyric is hanguel or additionalTest is true . (override to use) + protected virtual bool additionalTest(string lyric) { + return true; + } public override void SetSinger(USinger singer) => this.singer = singer; public static string? FindInOto(USinger singer, string phoneme, Note note, bool nullIfNotFound = false) { // 음소와 노트를 입력받고, 다음계 및 보이스컬러 에일리어스를 적용한다. @@ -125,6 +130,7 @@ public Result GenerateResult(String firstPhoneme, String secondPhoneme, int tota position = totalDuration - totalDuration / totalDurationDivider}, } }; + } /// @@ -292,7 +298,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN phonemes = phonemes }; } - else if (KoreanPhonemizerUtil.IsHangeul(lyric)) { + else if (KoreanPhonemizerUtil.IsHangeul(lyric) || !KoreanPhonemizerUtil.IsHangeul(lyric) && additionalTest(lyric)) { return ConvertPhonemes(notes, prev, next, prevNeighbour, nextNeighbour, prevNeighbours); } else { @@ -300,6 +306,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN } } + /// /// abstract class for Ini Management /// To use, child phonemizer should implement this class(BaseIniManager) with its own setting values! From b0dab2ab5eee7f794fe0fc835da15df9fab83a14 Mon Sep 17 00:00:00 2001 From: EX3 Date: Tue, 30 Apr 2024 21:34:04 +0900 Subject: [PATCH 2/5] Improved KoreanCVVCStandardPronunciationPhonemizer --- ...reanCVVCStandardPronunciationPhonemizer.cs | 923 ++++++++---------- 1 file changed, 418 insertions(+), 505 deletions(-) diff --git a/OpenUtau.Plugin.Builtin/KoreanCVVCStandardPronunciationPhonemizer.cs b/OpenUtau.Plugin.Builtin/KoreanCVVCStandardPronunciationPhonemizer.cs index 916e482cf..5451915ec 100644 --- a/OpenUtau.Plugin.Builtin/KoreanCVVCStandardPronunciationPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/KoreanCVVCStandardPronunciationPhonemizer.cs @@ -1,544 +1,457 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using OpenUtau.Api; using OpenUtau.Core.Ustx; +using OpenUtau.Core; namespace OpenUtau.Plugin.Builtin { - [Phonemizer("Korean CVVC Phonemizer", "KO CVVC", "RYUUSEI", language:"KO")] - public class KoreanCVVCStandardPronunciationPhonemizer : Phonemizer { - static readonly string initialConsonantsTable = "ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ"; - static readonly string vowelsTable = "ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ"; - static readonly string lastConsonantsTable = " ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ"; - static readonly ushort unicodeKoreanBase = 0xAC00; - static readonly ushort unicodeKoreanLast = 0xD79F; - - private char[] SeparateHangul(char letter) { - if (letter == 0) return new char[] { ' ', ' ', ' ' }; - var u16 = Convert.ToUInt16(letter); - - if (u16 < unicodeKoreanBase || u16 > unicodeKoreanLast) - return new char[] { letter }; - - u16 -= unicodeKoreanBase; - - var i = u16 / (21 * 28); - u16 %= 21 * 28; - var v = u16 / 28; - u16 %= 28; - var l = u16; - - return new char[] { initialConsonantsTable[i], vowelsTable[v], lastConsonantsTable[l] }; + /// Phonemizer for 'KOR CVVC' /// + [Phonemizer("Korean CVVC Phonemizer", "KO CVVC", "RYUUSEI & EX3", language: "KO")] + public class KoreanCVVCPhonemizer : BaseKoreanPhonemizer { + public override void SetSinger(USinger singer) { + if (this.singer == singer) {return;} + this.singer = singer; + if (this.singer == null) {return;} + + if (this.singer.SingerType != USingerType.Classic){return;} } - // 초성 - static readonly string[] initialConsonants = new string[] { - "g=ㄱ", - "kk=ㄲ", - "k=ㅋ", - "n=ㄴ", - "d=ㄷ", - "tt=ㄸ", - "t=ㅌ", - "r=ㄹ", - "m=ㅁ", - "b=ㅂ", - "pp=ㅃ", - "s=ㅅ", - "ss=ㅆ", - "=ㅇ, ", - "j=ㅈ", - "jj=ㅉ", - "ch=ㅊ", - "p=ㅍ", - "h=ㅎ", - }; + protected override bool additionalTest(string lyric) { + return IsENPhoneme(lyric); + } - // 일반 모음 - static readonly string[] vowels = new string[] { - "a=ㅏ", - "ya=ㅑ", - "eo=ㅓ", - "yeo=ㅕ", - "o=ㅗ", - "yo=ㅛ", - "u=ㅜ", - "yu=ㅠ", - "eu=ㅡ", - "i=ㅣ", - "e=ㅔ", - "ye=ㅖ", - "ae=ㅐ", - "yae=ㅒ", - "eui=ㅢ", - "we=ㅞ,ㅙ,ㅚ", - "weo=ㅝ", - "wa=ㅘ", - "wi=ㅟ", - }; + static readonly Dictionary FIRST_CONSONANTS = new Dictionary(){ + {"ㄱ", "g"}, + {"ㄲ", "kk"}, + {"ㄴ", "n"}, + {"ㄷ", "d"}, + {"ㄸ", "tt"}, + {"ㄹ", "r"}, + {"ㅁ", "m"}, + {"ㅂ", "b"}, + {"ㅃ", "pp"}, + {"ㅅ", "s"}, + {"ㅆ", "ss"}, + {"ㅇ", ""}, + {"ㅈ", "j"}, + {"ㅉ", "jj"}, + {"ㅊ", "ch"}, + {"ㅋ", "k"}, + {"ㅌ", "t"}, + {"ㅍ", "p"}, + {"ㅎ", "h"}, + {"null", ""}, // 뒤 글자가 없을 때를 대비 + // EN Phonemes + {"th", "th"}, + {"v", "v"}, + {"f", "f"}, + {"rr", "rr"} + }; + + static readonly Dictionary MIDDLE_VOWELS = new Dictionary(){ + {"ㅏ", new string[3]{"a", "", "a"}}, + {"ㅐ", new string[3]{"e", "", "e"}}, + {"ㅑ", new string[3]{"ya", "y", "a"}}, + {"ㅒ", new string[3]{"ye", "y", "e"}}, + {"ㅓ", new string[3]{"eo", "", "eo"}}, + {"ㅔ", new string[3]{"e", "", "e"}}, + {"ㅕ", new string[3]{"yeo", "y", "eo"}}, + {"ㅖ", new string[3]{"ye", "y", "e"}}, + {"ㅗ", new string[3]{"o", "", "o"}}, + {"ㅘ", new string[3]{"wa", "w", "a"}}, + {"ㅙ", new string[3]{"we", "w", "e"}}, + {"ㅚ", new string[3]{"we", "w", "e"}}, + {"ㅛ", new string[3]{"yo", "y", "o"}}, + {"ㅜ", new string[3]{"u", "", "u"}}, + {"ㅝ", new string[3]{"weo", "w", "eo"}}, + {"ㅞ", new string[3]{"we", "w", "e"}}, + {"ㅟ", new string[3]{"wi", "w", "i"}}, + {"ㅠ", new string[3]{"yu", "y", "u"}}, + {"ㅡ", new string[3]{"eu", "", "eu"}}, + {"ㅢ", new string[3]{"eui", "", "i"}}, + {"ㅣ", new string[3]{"i", "", "i"}}, + {"null", new string[3]{"", "", ""}} // 뒤 글자가 없을 때를 대비 + }; + static readonly Dictionary LAST_CONSONANTS = new Dictionary(){ + //ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ + {"ㄱ", new string[]{"K", ""}}, + {"ㄲ", new string[]{"K", ""}}, + {"ㄳ", new string[]{"K", ""}}, + {"ㄴ", new string[]{"n", "2"}}, + {"ㄵ", new string[]{"n", "2"}}, + {"ㄶ", new string[]{"n", "2"}}, + {"ㄷ", new string[]{"T", "1"}}, + {"ㄹ", new string[]{"l", "4"}}, + {"ㄺ", new string[]{"K", ""}}, + {"ㄻ", new string[]{"m", "1"}}, + {"ㄼ", new string[]{"l", "4"}}, + {"ㄽ", new string[]{"l", "4"}}, + {"ㄾ", new string[]{"l", "4"}}, + {"ㄿ", new string[]{"P", "1"}}, + {"ㅀ", new string[]{"l", "4"}}, + {"ㅁ", new string[]{"m", "1"}}, + {"ㅂ", new string[]{"P", "1"}}, + {"ㅄ", new string[]{"P", "1"}}, + {"ㅅ", new string[]{"T", "1"}}, + {"ㅆ", new string[]{"T", "1"}}, + {"ㅇ", new string[]{"ng", "3"}}, + {"ㅈ", new string[]{"T", "1"}}, + {"ㅊ", new string[]{"T", "1"}}, + {"ㅋ", new string[]{"L", ""}}, + {"ㅌ", new string[]{"T", "1"}}, + {"ㅍ", new string[]{"P", "1"}}, + {"ㅎ", new string[]{"T", "1"}}, + {" ", new string[]{"", ""}}, // no batchim + {"null", new string[]{"", ""}} // 뒤 글자가 없을 때를 대비 + }; - // V-V의 경우 이전 모음으로 대체 - static readonly string[] subsequentVowels = new string[] { - "a=ㅏ,ㅑ,ㅘ", - "eo=ㅓ,ㅕ,ㅝ", - "o=ㅗ,ㅛ", - "u=ㅜ,ㅠ", - "eu=ㅡ", - "e=ㅔ,ㅐ,ㅞ,ㅙ", - "i=ㅣ,ㅢ,ㅟ", + static readonly Dictionary BATCHIM_ROMAJ = new Dictionary(){ // to handle EN Phoneme's batchim, for example: theong = theo , eo ng + {"K", "ㄱ"}, + {"k", "ㄱ"}, + {"T", "ㄷ"}, + {"t", "ㄷ"}, + {"P", "ㅂ"}, + {"p", "ㅂ"}, + {"ng", "ㅇ"}, + {"n", "ㄴ"}, + {"l", "ㄹ"}, + {"m", "ㅁ"}, }; - // 끝소리일 경우에만 동작 - static readonly string[] lastConsonants = new string[] { - "k=ㄱ,ㅋ,ㄲ,ㄳ,ㄺ", - "n=ㄴ,ㄵ,ㄶ", - "t=ㄷ,ㅅ,ㅈ,ㅊ,ㅌ,ㅆ,ㅎ", - "l=ㄹ,ㄼ,ㄽ,ㄾ,ㄿ,ㅀ", - "m=ㅁ,ㄻ", - "p=ㅂ,ㅍ,ㅄ", - "ng=ㅇ", - }; + static readonly string[][] EN_PHONEMES = { + "theui=th,ㅢ theu=th,ㅡ theo=th,ㅓ tha=th,ㅏ thi=th,ㅣ thu=th,ㅜ the=th,ㅔ tho=th,ㅗ".Split(), + "thyeo=th,ㅕ thya=th,ㅑ thyu=th,ㅜ thye=th,ㅖ thyo=th,ㅛ".Split(), + "thweo=th,ㅝ thwa=th,ㅘ thwi=th,ㅟ thwe=th,ㅞ".Split(), + + "veui=v,ㅢ veu=v,ㅡ veo=v,ㅓ va=v,ㅏ vi=v,ㅣ vu=v,ㅜ ve=v,ㅔ vo=v,ㅗ".Split(), + "vyeo=v,ㅕ vya=v,ㅑ vyu=v,ㅜ vye=v,ㅖ vyo=v,ㅛ".Split(), + "vweo=v,ㅝ vwa=v,ㅘ vwi=v,ㅟ vwe=v,ㅞ".Split(), - // 표준발음법 적용 - static readonly string[] ruleOfConsonants = new string[] { - // 자음동화: (비음화, 유음화) - "ㅇㄴ=ㅇㄴ,ㄱㄴ,ㄱㄹ,ㅇㄹ", - "ㅇㄱ=ㅇㄱ,ㄱㅁ", - "ㄴㄴ=ㄴㄴ,ㄷㄴ,ㄵㄴ", - "ㄴㅁ=ㄴㅁ,ㄷㅁ,ㄵㅁ", - "ㅁㄴ=ㅁㄴ,ㅂㄴ,ㅂㄹ,ㅁㄹ", - "ㅁㅁ=ㅁㅁ,ㅂㅁ", - "ㄹㄹ=ㄹㄹ,ㄴㄹ,ㄵㄹ", - - // 구개음화 - " ㅈㅣ=ㄷㅇㅣ", - " ㅈㅓ=ㄷㅇㅓ", - " ㅈㅓ=ㄷㅇㅕ", - " ㅊㅣ=ㄷㅎㅣ", - " ㅊㅓ=ㄷㅎㅓ", - " ㅊㅓ=ㄷㅎㅕ", - " ㅊㅣ=ㅌㅇㅣ", - " ㅊㅓ=ㅌㅇㅓ", - " ㅊㅓ=ㅌㅇㅕ", - " ㅊㅣ=ㅌㅎㅣ", - " ㅊㅓ=ㅌㅎㅓ", - " ㅊㅓ=ㅌㅎㅕ", - "ㄹㅊㅣ=ㄾㅇㅣ", - "ㄹㅊㅓ=ㄾㅇㅓ", - "ㄹㅊㅓ=ㄾㅇㅕ", - "ㄹㅊㅣ=ㄾㅎㅣ", - "ㄹㅊㅓ=ㄾㅎㅓ", - "ㄹㅊㅓ=ㄾㅎㅕ", - - // 경음화 - "ㄱㄲ=ㄱㄲ,ㄱㄱ,ㄲㄱ", - "ㄱㄸ=ㄱㄸ,ㄱㄷ,ㄺㄷ,ㄺㅌ,ㄺㄸ", - "ㄱㅃ=ㄱㅃ,ㄱㅂ", - "ㄱㅆ=ㄱㅆ,ㄱㅅ", - "ㄱㅉ=ㄱㅉ,ㄱㅈ", - "ㄴㄸ=ㄴㄸ,ㄵㄷ,ㄵㄸ,ㄶㅌ,ㄶㄸ", - "ㄷㄲ=ㄷㄲ,ㄷㄱ", - "ㄷㄸ=ㄷㄸ,ㄷㄷ", - "ㄷㅃ=ㄷㅃ,ㄷㅂ", - "ㄷㅆ=ㄷㅆ,ㄷㅅ", - "ㄷㅉ=ㄷㅉ", - "ㅁㄸ=ㅁㄸ,ㄻㄷ,ㄻㅌ,ㄻㄸ", - "ㅂㄲ=ㅂㄲ,ㅂㄱ,ㅄㄲ,ㅄㄱ,ㄼㄱ,ㄼㅋ,ㄼㄲ", - "ㅂㄸ=ㅂㄸ,ㅂㄷ,ㅄㄸ,ㅄㄷ", - "ㅂㅃ=ㅂㅃ,ㅂㅂ,ㅄㅃ,ㅄㅂ", - "ㅂㅆ=ㅂㅆ,ㄼㅅ,ㄼㅆ,ㅂㅅ,ㅄㅆ,ㅄㅅ", - "ㅂㅉ=ㅂㅉ,ㅂㅈ,ㅄㅉ,ㅄㅈ", - "ㅅㄲ=ㅅㄲ,ㅅㄱ,ㅆㄲ,ㅆㄱ", - "ㅅㄸ=ㅅㄸ,ㅅㄷ,ㅆㄸ,ㅆㄷ", - "ㅅㅃ=ㅅㅃ,ㅅㅂ,ㅆㅃ,ㅆㅂ", - "ㅅㅆ=ㅅㅆ,ㅅㅅ,ㅆㅆ,ㅆㅅ", - "ㅅㅉ=ㅅㅉ,ㅅㅈ,ㅆㅉ,ㅆㅈ", - "ㅈㄲ=ㅈㄲ,ㅈㄱ", - "ㅈㄸ=ㅈㄸ,ㅈㄷ", - "ㅈㅃ=ㅈㅃ,ㅈㅂ", - "ㅈㅉ=ㅈㅉ,ㅈㅈ", - "ㅈㅆ=ㅈㅆ,ㅈㅅ", - - // 자음 축약 - " ㅋ=ㄱㅎ", - " ㅌ=ㄷㅎ", - " ㅍ=ㅂㅎ", - " ㅊ=ㅈㅎ", - "ㄴㅊ=ㄴㅊ,ㄵㅎ", - - // 탈락 - "ㄴㅌ=ㄴㅌ,ㄶㄷ", - " ㄴ=ㄶㅇ", - "ㄹㅌ=ㄹㅌ,ㄼㄷ,ㄼㅌ,ㄽㄷ,ㄾㅌ,ㄾㄷ,ㄽㅌ,ㄿㄷ,ㄿㅌ,ㅀㄷ,ㄾㅇ", - "ㄹㄸ=ㄹㄸ,ㅀㅌ,ㅀㄸ", - " ㄹ=ㅀㅇ", - - // 연음 - " ㄱ=ㄱㅇ", - " ㄲ=ㄲㅇ", - "ㄱㅅ=ㄳㅇ", - " ㄴ=ㄴㅇ", - "ㄴㅈ=ㄴㅈ,ㄵㅇ", - " ㄹ=ㄹㅇ", - "ㄹㄱ=ㄹㄱ,ㄺㅇ", - "ㄹㅁ=ㄹㅁ,ㄻㅇ", - "ㄹㅂ=ㄹㅂ,ㄼㅇ", - "ㄹㅅ=ㄹㅅ,ㄽㅇ", - "ㄹㅍ=ㄹㅍ,ㄿㅇ,ㄺㅂ,ㄻㅂ,ㄼㅂ,ㄽㅂ,ㄾㅂ,ㄿㅂ,ㅀㅂ", - " ㅁ=ㅁㅇ", - " ㅂ=ㅂㅇ", - "ㅂㅅ=ㅄㅇ", - " ㅅ=ㅅㅇ", - " ㅈ=ㅈㅇ", - " ㅊ=ㅊㅇ", - " ㅋ=ㅋㅇ", - " ㅌ=ㅌㅇ", - " ㅍ=ㅍㅇ", - " ㅎ=ㅎㅇ", - - // 이 외 - "ㄱㅋ=ㄱㅋ", - "ㄱㅌ=ㄱㅌ", - "ㄱㅊ=ㄱㅊ", - "ㄱㅍ=ㄱㅍ", - "ㄲㅂ=ㄲㅂ", - "ㄲㅃ=ㄲㅃ", - "ㄲㅈ=ㄲㅈ", - "ㄲㅉ=ㄲㅉ", - "ㄲㅅ=ㄲㅅ", - "ㄲㅆ=ㄲㅆ", - "ㄲㅁ=ㄲㅁ", - "ㄲㄴ=ㄲㄴ", - "ㄲㄹ=ㄲㄹ", - "ㄲㅋ=ㄲㅋ", - "ㄲㅌ=ㄲㅌ", - "ㄲㅊ=ㄲㅊ", - "ㄲㅍ=ㄲㅍ", - "ㄴㅂ=ㄴㅂ,ㄵㅂ,ㄶㅂ", - "ㄴㅃ=ㄴㅃ,ㄵㅃ,ㄶㅃ", - "ㄴㄷ=ㄴㄷ", - "ㄴㄱ=ㄴㄱ,ㄵㄱ,ㄶㄱ", - "ㄴㄲ=ㄴㄲ,ㄵㄲ,ㄶㄲ", - "ㄴㅅ=ㄴㅅ,ㄵㅅ,ㄶㅅ", - "ㄴㅆ=ㄴㅆ,ㄵㅆ,ㄶㅆ", - "ㄴㅎ=ㄴㅎ,ㄶㅎ", - "ㄴㅋ=ㄴㅋ,ㄵㅋ,ㄶㅋ", - "ㄴㅍ=ㄴㅍ,ㄵㅍ,ㄶㅍ", - "ㄷㄹ=ㄷㄹ", - "ㄷㅋ=ㄷㅋ", - "ㄷㅌ=ㄷㅌ", - "ㄷㅊ=ㄷㅊ", - "ㄷㅍ=ㄷㅍ", - "ㄹㅃ=ㄹㅃ,ㄺㅃ,ㄻㅃ,ㄼㅃ,ㄽㅃ,ㄾㅃ,ㄿㅃ,ㅀㅃ", - "ㄹㅈ=ㄹㅈ,ㄺㅈ,ㄻㅈ,ㄼㅈ,ㄽㅈ,ㄾㅈ,ㄿㅈ,ㅀㅈ", - "ㄹㅉ=ㄹㅉ,ㄺㅉ,ㄻㅉ,ㄼㅉ,ㄽㅉ,ㄾㅉ,ㄿㅉ,ㅀㅉ", - "ㄹㄷ=ㄹㄷ", - "ㄹㄲ=ㄹㄲ,ㄺㄲ,ㄻㄲ,ㄽㄲ,ㄾㄲ,ㄿㄲ,ㅀㄲ", - "ㄹㄴ=ㄹㄴ,ㄺㄴ,ㄻㄴ,ㄼㄴ,ㄽㄴ,ㄾㄴ,ㄿㄴ,ㅀㄴ", - "ㄹㅎ=ㄹㅎ,ㄺㅎ,ㄻㅎ,ㄼㅎ,ㄽㅎ,ㄾㅎ,ㄿㅎ,ㅀㅎ", - "ㄹㅋ=ㄹㅋ,ㄺㅋ,ㄻㅋ,ㄽㅋ,ㄾㅋ,ㄿㅋ,ㅀㅋ", - "ㄹㅊ=ㄹㅊ,ㄺㅊ,ㄻㅊ,ㄼㅊ,ㄽㅊ,ㄾㅊ,ㄿㅊ,ㅀㅊ", - "ㅁㅂ=ㅁㅂ", - "ㅁㅃ=ㅁㅃ", - "ㅁㅈ=ㅁㅈ", - "ㅁㅉ=ㅁㅉ", - "ㅁㄷ=ㅁㄷ", - "ㅁㅅ=ㅁㅅ", - "ㅁㅆ=ㅁㅆ", - "ㅁㅋ=ㅁㅋ", - "ㅁㅌ=ㅁㅌ", - "ㅁㅊ=ㅁㅊ", - "ㅁㅍ=ㅁㅍ", - "ㅂㅋ=ㅂㅋ,ㅄㅋ", - "ㅂㅌ=ㅂㅌ,ㅄㅌ", - "ㅂㅊ=ㅂㅊ,ㅄㅊ", - "ㅂㅍ=ㅂㅍ,ㅄㅍ", - "ㅅㅁ=ㅅㅁ,ㅆㅁ", - "ㅅㄴ=ㅅㄴ,ㅆㄴ", - "ㅅㄹ=ㅅㄹ,ㅆㄹ", - "ㅅㅋ=ㅅㅋ,ㅆㅋ", - "ㅅㅌ=ㅅㅌ,ㅆㅌ", - "ㅅㅊ=ㅅㅊ,ㅆㅊ", - "ㅅㅍ=ㅅㅍ,ㅆㅍ", - "ㅅㅎ=ㅅㅎ,ㅆㅎ", - "ㅇㅂ=ㅇㅂ", - "ㅇㅃ=ㅇㅃ", - "ㅇㅈ=ㅇㅈ", - "ㅇㅉ=ㅇㅉ", - "ㅇㄷ=ㅇㄷ", - "ㅇㄸ=ㅇㄸ", - "ㅇㄲ=ㅇㄲ", - "ㅇㅅ=ㅇㅅ", - "ㅇㅆ=ㅇㅆ", - "ㅇㅁ=ㅇㅁ", - "ㅇㅇ=ㅇㅇ", - "ㅇㅎ=ㅇㅎ", - "ㅇㅋ=ㅇㅋ", - "ㅇㅌ=ㅇㅌ", - "ㅇㅊ=ㅇㅊ", - "ㅇㅍ=ㅇㅍ", - "ㅈㅁ=ㅈㅁ", - "ㅈㄴ=ㅈㄴ", - "ㅈㄹ=ㅈㄹ", - "ㅈㅋ=ㅈㅋ", - "ㅈㅌ=ㅈㅌ", - "ㅈㅊ=ㅈㅊ", - "ㅈㅍ=ㅈㅍ", - "ㅊㅃ=ㅊㅃ", - "ㅊㅂ=ㅊㅂ", - "ㅊㅉ=ㅊㅉ", - "ㅊㅈ=ㅊㅈ", - "ㅊㄸ=ㅊㄸ", - "ㅊㄷ=ㅊㄷ", - "ㅊㄲ=ㅊㄲ", - "ㅊㄱ=ㅊㄱ", - "ㅊㅆ=ㅊㅆ", - "ㅊㅅ=ㅊㅅ", - "ㅊㅁ=ㅊㅁ", - "ㅊㄴ=ㅊㄴ", - "ㅊㄹ=ㅊㄹ", - "ㅊㅋ=ㅊㅋ", - "ㅊㅌ=ㅊㅌ", - "ㅊㅊ=ㅊㅊ", - "ㅊㅍ=ㅊㅍ", - "ㅋㅃ=ㅋㅃ", - "ㅋㅂ=ㅋㅂ", - "ㅋㅉ=ㅋㅉ", - "ㅋㅈ=ㅋㅈ", - "ㅋㄸ=ㅋㄸ", - "ㅋㄷ=ㅋㄷ", - "ㅋㄲ=ㅋㄲ", - "ㅋㄱ=ㅋㄱ", - "ㅋㅁ=ㅋㅁ", - "ㅋㄴ=ㅋㄴ", - "ㅋㄹ=ㅋㄹ", - "ㅋㅋ=ㅋㅋ", - "ㅋㅌ=ㅋㅌ", - "ㅋㅊ=ㅋㅊ", - "ㅋㅍ=ㅋㅍ", - "ㅌㅃ=ㅌㅃ", - "ㅌㅂ=ㅌㅂ", - "ㅌㅉ=ㅌㅉ", - "ㅌㅈ=ㅌㅈ", - "ㅌㄸ=ㅌㄸ", - "ㅌㄷ=ㅌㄷ", - "ㅌㄲ=ㅌㄲ", - "ㅌㄱ=ㅌㄱ", - "ㅌㅆ=ㅌㅆ", - "ㅌㅅ=ㅌㅅ", - "ㅌㅁ=ㅌㅁ", - "ㅌㄴ=ㅌㄴ", - "ㅌㄹ=ㅌㄹ", - "ㅌㅋ=ㅌㅋ", - "ㅌㅌ=ㅌㅌ", - "ㅌㅊ=ㅌㅊ", - "ㅌㅍ=ㅌㅍ", - "ㅍㅃ=ㅍㅃ", - "ㅍㅂ=ㅍㅂ", - "ㅍㅉ=ㅍㅉ", - "ㅍㅈ=ㅍㅈ", - "ㅍㄸ=ㅍㄸ", - "ㅍㄷ=ㅍㄷ", - "ㅍㄲ=ㅍㄲ", - "ㅍㄱ=ㅍㄱ", - "ㅍㅆ=ㅍㅆ", - "ㅍㅅ=ㅍㅅ", - "ㅍㅁ=ㅍㅁ", - "ㅍㄴ=ㅍㄴ", - "ㅍㄹ=ㅍㄹ", - "ㅍㅋ=ㅍㅋ", - "ㅍㅌ=ㅍㅌ", - "ㅍㅊ=ㅍㅊ", - "ㅍㅍ=ㅍㅍ", - "ㅎㅃ=ㅎㅃ", - "ㅎㅂ=ㅎㅂ", - "ㅎㅉ=ㅎㅉ", - "ㅎㅈ=ㅎㅈ", - "ㅎㄸ=ㅎㄸ", - "ㅎㄷ=ㅎㄷ", - "ㅎㄲ=ㅎㄲ", - "ㅎㄱ=ㅎㄱ", - "ㅎㅆ=ㅎㅆ", - "ㅎㅅ=ㅎㅅ", - "ㅎㅁ=ㅎㅁ", - "ㅎㄴ=ㅎㄴ", - "ㅎㄹ=ㅎㄹ", - "ㅎㅎ=ㅎㅎ", - "ㅎㅋ=ㅎㅋ", - "ㅎㅌ=ㅎㅌ", - "ㅎㅊ=ㅎㅊ", - "ㅎㅍ=ㅎㅍ", + "feui=f,ㅢ feu=f,ㅡ feo=f,ㅓ fa=f,ㅏ fi=f,ㅣ fu=f,ㅜ fe=f,ㅔ fo=f,ㅗ".Split(), + "fyeo=f,ㅕ fya=f,ㅑ fyu=f,ㅜ fye=f,ㅖ fyo=f,ㅛ".Split(), + "fweo=f,ㅝ fwa=f,ㅘ fwi=f,ㅟ fwe=f,ㅞ".Split(), + + "rreui=rr,ㅢ rreu=rr,ㅡ rreo=rr,ㅓ rra=rr,ㅏ rri=rr,ㅣ rru=rr,ㅜ rre=rr,ㅔ rro=rr,ㅗ".Split(), + "rryeo=rr,ㅕ rrya=rr,ㅑ rryu=rr,ㅜ rrye=rr,ㅖ rryo=rr,ㅛ".Split(), + "rrweo=rr,ㅝ rrwa=rr,ㅘ rrwi=rr,ㅟ rrwe=rr,ㅞ".Split(), }; - static readonly Dictionary initialConsonantLookup; - static readonly Dictionary vowelLookup; - static readonly Dictionary subsequentVowelsLookup; - static readonly Dictionary lastConsonantsLookup; - static readonly Dictionary ruleOfConsonantsLookup; - - - static KoreanCVVCStandardPronunciationPhonemizer() { - initialConsonantLookup = initialConsonants.ToList() - .SelectMany(line => { - var parts = line.Split('='); - return parts[1].Split(',').Select(cv => (cv, parts[0])); - }) - .ToDictionary(t => t.Item1, t => t.Item2); - vowelLookup = vowels.ToList() - .SelectMany(line => { - var parts = line.Split('='); - return parts[1].Split(',').Select(cv => (cv, parts[0])); - }) - .ToDictionary(t => t.Item1, t => t.Item2); - subsequentVowelsLookup = subsequentVowels.ToList() - .SelectMany(line => { - var parts = line.Split('='); - return parts[1].Split(',').Select(cv => (cv, parts[0])); - }) - .ToDictionary(t => t.Item1, t => t.Item2); - lastConsonantsLookup = lastConsonants.ToList() - .SelectMany(line => { - var parts = line.Split('='); - return parts[1].Split(',').Select(cv => (cv, parts[0])); - }) - .ToDictionary(t => t.Item1, t => t.Item2); - ruleOfConsonantsLookup = ruleOfConsonants.ToList() - .SelectMany(line => { - var parts = line.Split('='); - return parts[1].Split(',').Select(cv => (cv, parts[0])); - }) - .ToDictionary(t => t.Item1, t => t.Item2); + private Result ConvertForCVVC(Note[] notes, string[] prevLyric, string[] thisLyric, string[] nextLyric, Note? nextNeighbour) { + string thisMidVowelHead; + string thisMidVowelTail; + + int totalDuration = notes.Sum(n => n.duration); + Note note = notes[0]; + + string soundBeforeEndSound = thisLyric[2] == " " ? thisLyric[1] : thisLyric[2]; + string thisMidVowelForEnd; + + thisMidVowelForEnd = MIDDLE_VOWELS.ContainsKey(soundBeforeEndSound) ? MIDDLE_VOWELS[soundBeforeEndSound][2] : LAST_CONSONANTS[soundBeforeEndSound][0]; + string endSound = $"{thisMidVowelForEnd} R"; + + bool isItNeedsFrontCV; + bool isItNeedsVC; + bool isItNeedsVV; + bool isItNeedsVSv; // V + Semivowel, example) a y, a w + bool isItNeedsEndSound; + + isItNeedsFrontCV = prevLyric[0] == "null" || prevLyric[1] == "null"; + isItNeedsEndSound = (nextLyric[0] == "null" || nextLyric[1] == "null") && nextNeighbour == null; + if (thisLyric.All(part => part == null)) { + return GenerateResult(FindInOto(note.lyric, note)); + } + else { + thisMidVowelHead = $"{MIDDLE_VOWELS[thisLyric[1]][1]}"; + thisMidVowelTail = $"{MIDDLE_VOWELS[thisLyric[1]][2]}"; + } + + string CV; + if (thisLyric[0] == "ㄹ" && prevLyric[2] == "ㄹ"){ // ㄹㄹ = l + CV = $"l{MIDDLE_VOWELS[thisLyric[1]][0]}"; + } + else { + CV = $"{FIRST_CONSONANTS[thisLyric[0]]}{MIDDLE_VOWELS[thisLyric[1]][0]}"; + } - } + string frontCV; + string batchim = null; + string VC = $"{thisMidVowelTail} {FIRST_CONSONANTS[nextLyric[0]]}{MIDDLE_VOWELS[nextLyric[1]][1]}"; + string VV = $"{MIDDLE_VOWELS[prevLyric[1]][2]} {thisMidVowelHead}{thisMidVowelTail}"; + string VSv = $"{thisMidVowelTail} {MIDDLE_VOWELS[nextLyric[1]][1]}"; + string CC = null; + + isItNeedsVV = prevLyric[2] == " " && thisLyric[0] == "ㅇ"; + if (prevLyric[2] == "ㅇ" && thisLyric[0] == "ㅇ") { // + isItNeedsVV = true; + VV = $"{LAST_CONSONANTS["ㅇ"][0]} {thisMidVowelHead}{thisMidVowelTail}"; + } + isItNeedsVSv = thisLyric[2] == " " && nextLyric[0] == "ㅇ" && !PLAIN_VOWELS.Contains(nextLyric[1]) && FindInOto(VSv, note, true) != null; + isItNeedsVC = thisLyric[2] == " " && nextLyric[0] != "ㅇ" && nextLyric[0] != "null" && FindInOto(VC, note, true) != null; + + frontCV = $"- {CV}"; + if (FindInOto(frontCV, note, true) == null) { + frontCV = $"-{CV}"; + if (FindInOto(frontCV, note, true) == null) { + frontCV = CV; + } + } - // Store singer in field, will try reading presamp.ini later - private USinger singer; - public override void SetSinger(USinger singer) => this.singer = singer; + if (isItNeedsVV && FindInOto(VV, note, true) != null) { + CV = VV; + if (isItNeedsVSv) { // if use a wa, don't use a w wa + isItNeedsVSv = false; + } + } - // Legacy mapping. Might adjust later to new mapping style. - public override bool LegacyMapping => true; - - public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevNeighbour, Note? nextNeighbour, Note[] prevNeighbours) { - var prevLyric = prevNeighbour?.lyric; - char[] prevKoreanLyrics = { ' ', ' ', ' ' }; - bool isPrevEndV = true; - if (prevLyric != null && prevLyric[0] >= '가' && prevLyric[0] <= '힣') { - prevKoreanLyrics = SeparateHangul(prevLyric != null ? prevLyric[0] : '\0'); - isPrevEndV = prevKoreanLyrics[2] == ' ' && prevKoreanLyrics[0] != ' '; + + if (thisLyric[2] == " " && isItNeedsVC) { // no batchim, needs VC + if (isItNeedsFrontCV){ + return GenerateResult(FindInOto(frontCV, note), FindInOto(VC, note), totalDuration, 120, 3); + } + return GenerateResult(FindInOto(CV, note), FindInOto(VC, note), totalDuration, 120, 3); } - var currentLyric = notes[0].lyric; - if (!(currentLyric[0] >= '가' && currentLyric[0] <= '힣')) { - return new Result { - phonemes = new Phoneme[] { - new Phoneme { - phoneme = $"{currentLyric}", - } - }, - }; + if (thisLyric[2] == " " && isItNeedsVSv) { // no batchim, needs VSv + if (isItNeedsFrontCV){ + return GenerateResult(FindInOto(frontCV, note), FindInOto(VSv, note), totalDuration, 120, 3); + } + return GenerateResult(FindInOto(CV, note), FindInOto(VSv, note), totalDuration, 120, 3); } - var currentKoreanLyrics = SeparateHangul(currentLyric[0]); - var isCurrentEndV = currentKoreanLyrics[2] == ' ' && currentKoreanLyrics[0] != ' '; - var nextLyric = nextNeighbour?.lyric; - char[] nextKoreanLyrics = { ' ', ' ', ' ' }; - if (nextLyric != null && nextLyric[0] >= '가' && nextLyric[0] <= '힣') { - nextKoreanLyrics = SeparateHangul(nextLyric != null ? nextLyric[0] : '\0'); + if (thisLyric[2] == " ") { // no batchim, doesn't need VC + if (isItNeedsFrontCV){ + return isItNeedsEndSound ? + GenerateResult(FindInOto(frontCV, note), FindInOto(endSound, note), totalDuration, 8) + : GenerateResult(FindInOto(frontCV, note)); + } + return isItNeedsEndSound ? + GenerateResult(FindInOto(CV, note), FindInOto(endSound, note), totalDuration, 8) + : GenerateResult(FindInOto(CV, note)); + } + + batchim = $"{thisMidVowelTail} {LAST_CONSONANTS[thisLyric[2]][0]}"; + + if (FindInOto(batchim, note, true) == null) { + batchim = batchim.ToLower(); // try to use lower-cased batchim } - int totalDuration = notes.Sum(n => n.duration); - int vcLength = 60; - - string CV = ""; - if (prevNeighbour != null) { - // 앞문자 존재 - if (!isPrevEndV) { - // 앞문자 종결이 C - ruleOfConsonantsLookup.TryGetValue(prevKoreanLyrics[2].ToString() + currentKoreanLyrics[0].ToString(), out var CCConsonants); - vowelLookup.TryGetValue(currentKoreanLyrics[1].ToString(), out var currentVowel); - initialConsonantLookup.TryGetValue(CCConsonants == null ? currentKoreanLyrics[0].ToString() : CCConsonants[1].ToString(), out var changedCurrentConsonants); - CV = $"{changedCurrentConsonants}{currentVowel}"; - - } else { - // 앞문자 종결이 V - initialConsonantLookup.TryGetValue(currentKoreanLyrics[0].ToString(), out var currentInitialConsonants); - vowelLookup.TryGetValue(currentKoreanLyrics[1].ToString(), out var currentVowel); - - CV = $"{currentInitialConsonants}{currentVowel}"; + if (nextLyric[0] == "null" || nextLyric[0] == "ㅇ") { // batchim, doesn't need CC + if (isItNeedsFrontCV ){ + return GenerateResult(FindInOto(frontCV, note), FindInOto(batchim, note), totalDuration, 8); + } + return GenerateResult(FindInOto(CV, note), FindInOto(batchim, note), totalDuration, 8); + } + CC = $"{LAST_CONSONANTS[thisLyric[2]][0]} {FIRST_CONSONANTS[nextLyric[0]]}{MIDDLE_VOWELS[nextLyric[1]][1]}"; + + + if (FindInOto(CC, note, true) != null) { // batchim + CC + if (isItNeedsFrontCV){ + return GenerateResult(FindInOto(frontCV, note), FindInOto(batchim, note), FindInOto(CC, note), totalDuration, 120, 2, 3); } - } else { - // 앞문자 없음 - initialConsonantLookup.TryGetValue(currentKoreanLyrics[0].ToString(), out var currentInitialConsonants); - vowelLookup.TryGetValue(currentKoreanLyrics[1].ToString(), out var currentVowel); + return GenerateResult(FindInOto(CV, note), FindInOto(batchim, note), FindInOto(CC, note), totalDuration, 120, 2, 3); + } + else { // batchim + no CC + if (isItNeedsFrontCV){ + GenerateResult(FindInOto(frontCV, note), FindInOto(batchim, note), totalDuration, 120, 5); + } + return GenerateResult(FindInOto(CV, note), FindInOto(batchim, note), totalDuration, 120, 5); + } + + } + + private string? FindInOto(String phoneme, Note note, bool nullIfNotFound=false){ + return BaseKoreanPhonemizer.FindInOto(singer, phoneme, note, nullIfNotFound); + } - CV = $"{currentInitialConsonants}{currentVowel}"; + private bool IsENPhoneme(String phoneme) { + bool isENPhoneme = false; + if (phoneme.StartsWith("rr") || phoneme.StartsWith("th") || phoneme.StartsWith("f") || phoneme.StartsWith("v")){ + isENPhoneme = true; } - //System.Diagnostics.Debug.WriteLine(CV); - - - string VC = ""; - if (isCurrentEndV) { - // 이번 문자 종결이 CV - if (nextLyric == null || !(nextLyric[0] >= '가' && nextLyric[0] <= '힣')) { - // 다음 문자가 없는 경우 - } else { - // 다음 문자가 있는 경우(V + C or V) - subsequentVowelsLookup.TryGetValue(currentKoreanLyrics[1].ToString(), out var currentVowel); - initialConsonantLookup.TryGetValue(nextKoreanLyrics[0].ToString(), out var nextInitialConsonants); - if (nextInitialConsonants == "") { - // VV인 경우 - vowelLookup.TryGetValue(nextKoreanLyrics[1].ToString(), out var nextVowel); - // VC = $"{currentVowel} {nextVowel}"; - } else { - // VC인 경우 - VC = $"{currentVowel} {nextInitialConsonants}"; + return isENPhoneme; + } + + public override Result ConvertPhonemes(Note[] notes, Note? prev, Note? next, Note? prevNeighbour, Note? nextNeighbour, Note[] prevNeighbours) { + Note note = notes[0]; + bool prevIsEN = false; + bool currIsEN = false; + bool nextIsEN = false; + string[] prevENPhones = new string[3]{"", "", ""}; + string[] currENPhones = new string[3]{"", "", ""}; + string[] nextENPhones = new string[3]{"", "", ""}; + bool exitLoop = false; + + if (IsENPhoneme(note.lyric)) { + foreach (string[] _ in EN_PHONEMES) { + if (exitLoop) { + exitLoop = false; + break; + } + foreach (string p in _){ + string grapheme = p.Split("=")[0]; + + if (!note.lyric.StartsWith(grapheme)) { + continue; + } + string[] temp = p.Split("=")[1].Split(","); + currENPhones[0] = temp[0]; + currENPhones[1] = temp[1]; + + if (note.lyric.Length != grapheme.Length) { + currENPhones[2] = BATCHIM_ROMAJ[note.lyric.Substring(grapheme.Length)]; + } + else { + currENPhones[2] = " "; + } + + currIsEN = true; + exitLoop = true; + break; + } + } + } + if (prev != null && IsENPhoneme(((Note)prev).lyric)){ + foreach (string[] _ in EN_PHONEMES) { + if (exitLoop) { + exitLoop = false; + break; + } + foreach (string p in _){ + string grapheme = p.Split("=")[0]; + + if (!((Note)prev).lyric.StartsWith(grapheme)) { + continue; + } + string[] temp = p.Split("=")[1].Split(","); + prevENPhones[0] = temp[0]; + prevENPhones[1] = temp[1]; + + if (((Note)prev).lyric.Length != grapheme.Length) { + prevENPhones[2] = BATCHIM_ROMAJ[((Note)prev).lyric.Substring(grapheme.Length)]; + } + else { + prevENPhones[2] = " "; + } + + prevIsEN = true; + exitLoop = true; + break; } } - } else { - // 이번 문자 종결이 CVC - - subsequentVowelsLookup.TryGetValue(currentKoreanLyrics[1].ToString(), out var currentVowels); - if (nextLyric == null || !(nextLyric[0] >= '가' && nextLyric[0] <= '힣')) { - // 다음 문자가 없는 경우 - lastConsonantsLookup.TryGetValue(currentKoreanLyrics[2].ToString(), out var lastConsonants); - VC = $"{currentVowels} {lastConsonants}"; - } else { - // 다음 문자가 있는 경우(C + C or V) - ruleOfConsonantsLookup.TryGetValue(currentKoreanLyrics[2].ToString() + nextKoreanLyrics[0].ToString(), out var ruleVC); - if (ruleVC[0] == ' ') { - // 현재 노트가 CVC에서 CV로 바뀌는 경우 - subsequentVowelsLookup.TryGetValue(currentKoreanLyrics[1].ToString(), out var currentVowel); - initialConsonantLookup.TryGetValue(ruleVC[1].ToString(), out var nextInitialConsonants); - VC = $"{currentVowel} {nextInitialConsonants}"; - } else { - // 현재 노트가 CVC가 유지되는 경우 - lastConsonantsLookup.TryGetValue(ruleVC[0].ToString(), out var lastConsonants); - VC = $"{currentVowels} {lastConsonants}"; + } + if (next != null && IsENPhoneme(((Note)next).lyric)){ + foreach (string[] _ in EN_PHONEMES) { + if (exitLoop) { + exitLoop = false; + break; + } + foreach (string p in _){ + string grapheme = p.Split("=")[0]; + + if (!((Note)next).lyric.StartsWith(grapheme)) { + continue; + } + string[] temp = p.Split("=")[1].Split(","); + nextENPhones[0] = temp[0]; + nextENPhones[1] = temp[1]; + + if (((Note)next).lyric.Length != grapheme.Length) { + prevENPhones[2] = BATCHIM_ROMAJ[((Note)next).lyric.Substring(grapheme.Length)]; + } + else { + prevENPhones[2] = " "; + } + nextIsEN = true; + exitLoop = true; + break; } } } + + if (!KoreanPhonemizerUtil.IsHangeul(note.lyric) && !prevIsEN && !currIsEN && !nextIsEN){ + return GenerateResult(FindInOto(notes[0].lyric, notes[0])); + } + + Hashtable lyrics; + if (KoreanPhonemizerUtil.IsHangeul(note.lyric)){ + lyrics = KoreanPhonemizerUtil.Variate(prevNeighbour, note, nextNeighbour); + } + else { + // handle current phoneme which is not hangeul, but have to supported by phonemizer - tha, thi, thu, fyeo... etc. + lyrics = new Hashtable() { [0] = "null", [1] = "null", [2] = "null", [3] = "null", [4] = "null", [5] = "null", [6] = "null", [7] = "null", [8] = "null",};// init into all null + + if (prevNeighbour != null && !IsENPhoneme(((Note)prevNeighbour).lyric)) { + Hashtable t = KoreanPhonemizerUtil.Variate(null, (Note)prevNeighbour, null); + lyrics[0] = (string)t[3]; + lyrics[1] = (string)t[4]; + lyrics[2] = (string)t[5]; + } + if (nextNeighbour != null && !IsENPhoneme(((Note)nextNeighbour).lyric)) { + Hashtable t = KoreanPhonemizerUtil.Variate(null, (Note)nextNeighbour, null); + lyrics[6] = (string)t[3]; + lyrics[7] = (string)t[4]; + lyrics[8] = (string)t[5]; + } + } - if (VC == "") { - return new Result { - phonemes = new Phoneme[] { - new Phoneme { - phoneme = CV, - }, - }, + string[] prevLyric = new string[]{ // "ㄴ", "ㅑ", "ㅇ" + prevIsEN ? prevENPhones[0] : (string)lyrics[0], + prevIsEN ? prevENPhones[1]:(string)lyrics[1], + prevIsEN ? prevENPhones[2]:(string)lyrics[2] + }; + string[] thisLyric = new string[]{ // "ㄴ", "ㅑ", "ㅇ" + currIsEN ? currENPhones[0] : (string)lyrics[3], + currIsEN ? currENPhones[1] : (string)lyrics[4], + currIsEN? currENPhones[2] : (string)lyrics[5] }; + string[] nextLyric = new string[]{ // "ㄴ", "ㅑ", "ㅇ" + nextIsEN ? nextENPhones[0] : (string)lyrics[6], + nextIsEN ? nextENPhones[1] : (string)lyrics[7], + nextIsEN ? nextENPhones[2] : (string)lyrics[8] + }; + + if (thisLyric[0] == "null") { + return GenerateResult(FindInOto(notes[0].lyric, notes[0])); } - return new Result { - phonemes = new Phoneme[] { - new Phoneme { - phoneme = CV, - }, - new Phoneme { - phoneme = VC, - position = totalDuration - vcLength, - }, - }, - }; + if (prevLyric[2] != " " && prevIsEN && thisLyric[0] == "ㅇ") { // perform yeoneum when 'EN Phoneme with batchim' came + thisLyric[0] = prevLyric[2]; + prevLyric[2] = " "; + } + return ConvertForCVVC(notes, prevLyric, thisLyric, nextLyric, nextNeighbour); + } + + + public override Result GenerateEndSound(Note[] notes, Note? prev, Note? next, Note? prevNeighbour, Note? nextNeighbour, Note[] prevNeighbours) { + Note note = notes[0]; + if (prevNeighbour == null) { + return GenerateResult(FindInOto(note.lyric, note)); + } + + Note prevNeighbour_ = (Note)prevNeighbour; + Hashtable lyrics = KoreanPhonemizerUtil.Separate(prevNeighbour_.lyric); + + string[] prevLyric = new string[]{ // "ㄴ", "ㅑ", "ㅇ" + (string)lyrics[0], + (string)lyrics[1], + (string)lyrics[2] + }; + + string soundBeforeEndSound = prevLyric[2] == " " ? prevLyric[1] : prevLyric[2]; + string endSound = note.lyric; + string prevMidVowel; + + prevMidVowel = MIDDLE_VOWELS.ContainsKey(soundBeforeEndSound) ? MIDDLE_VOWELS[soundBeforeEndSound][2] : LAST_CONSONANTS[soundBeforeEndSound][0]; + + if (FindInOto($"{prevMidVowel} {endSound}", note, true) == null) { + if (FindInOto($"{prevMidVowel}{endSound}", note, true) == null) { + return GenerateResult(FindInOto($"{endSound}", note)); + } + return GenerateResult(FindInOto($"{prevMidVowel}{endSound}", note, true)); + } + return GenerateResult(FindInOto($"{prevMidVowel} {endSound}", note)); } } -} +} \ No newline at end of file From 89e71e169c8e8037dd072cb392ef0210ae85b890 Mon Sep 17 00:00:00 2001 From: EX3 Date: Tue, 30 Apr 2024 22:39:00 +0900 Subject: [PATCH 3/5] Fixed to use alternative VC&CC if needed --- .../KoreanCVVCStandardPronunciationPhonemizer.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/OpenUtau.Plugin.Builtin/KoreanCVVCStandardPronunciationPhonemizer.cs b/OpenUtau.Plugin.Builtin/KoreanCVVCStandardPronunciationPhonemizer.cs index 5451915ec..f1440ce08 100644 --- a/OpenUtau.Plugin.Builtin/KoreanCVVCStandardPronunciationPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/KoreanCVVCStandardPronunciationPhonemizer.cs @@ -199,6 +199,15 @@ private Result ConvertForCVVC(Note[] notes, string[] prevLyric, string[] thisLyr } } + if (FindInOto(VC, note, true) == null) { + if (VC.EndsWith("w") || VC.EndsWith("y")) { + VC = VC.Substring(0, VC.Length - 1); + } + if (FindInOto(VC, note, true) == null) { + isItNeedsVC = false; + } + } + if (isItNeedsVV && FindInOto(VV, note, true) != null) { CV = VV; if (isItNeedsVSv) { // if use a wa, don't use a w wa @@ -246,7 +255,12 @@ private Result ConvertForCVVC(Note[] notes, string[] prevLyric, string[] thisLyr } CC = $"{LAST_CONSONANTS[thisLyric[2]][0]} {FIRST_CONSONANTS[nextLyric[0]]}{MIDDLE_VOWELS[nextLyric[1]][1]}"; - + if (FindInOto(CC, note, true) == null) { + if (CC.EndsWith("w") || CC.EndsWith("y")) { + CC = CC.Substring(0, CC.Length - 1); + } + } + if (FindInOto(CC, note, true) != null) { // batchim + CC if (isItNeedsFrontCV){ return GenerateResult(FindInOto(frontCV, note), FindInOto(batchim, note), FindInOto(CC, note), totalDuration, 120, 2, 3); From 6f44c6616fea3ae47d0a2194fdd028f145e9ce77 Mon Sep 17 00:00:00 2001 From: EX3 Date: Tue, 30 Apr 2024 22:44:36 +0900 Subject: [PATCH 4/5] fixed typo --- .../KoreanCVVCStandardPronunciationPhonemizer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenUtau.Plugin.Builtin/KoreanCVVCStandardPronunciationPhonemizer.cs b/OpenUtau.Plugin.Builtin/KoreanCVVCStandardPronunciationPhonemizer.cs index f1440ce08..c1f5e560a 100644 --- a/OpenUtau.Plugin.Builtin/KoreanCVVCStandardPronunciationPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/KoreanCVVCStandardPronunciationPhonemizer.cs @@ -189,7 +189,7 @@ private Result ConvertForCVVC(Note[] notes, string[] prevLyric, string[] thisLyr VV = $"{LAST_CONSONANTS["ㅇ"][0]} {thisMidVowelHead}{thisMidVowelTail}"; } isItNeedsVSv = thisLyric[2] == " " && nextLyric[0] == "ㅇ" && !PLAIN_VOWELS.Contains(nextLyric[1]) && FindInOto(VSv, note, true) != null; - isItNeedsVC = thisLyric[2] == " " && nextLyric[0] != "ㅇ" && nextLyric[0] != "null" && FindInOto(VC, note, true) != null; + isItNeedsVC = thisLyric[2] == " " && nextLyric[0] != "ㅇ" && nextLyric[0] != "null"; frontCV = $"- {CV}"; if (FindInOto(frontCV, note, true) == null) { From 75e2921bcbefc601cb64f181c1ead1244cccb85b Mon Sep 17 00:00:00 2001 From: EX3 Date: Tue, 30 Apr 2024 22:55:11 +0900 Subject: [PATCH 5/5] fixed typo --- OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs b/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs index 3871f31ea..a1552cdd8 100644 --- a/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs @@ -26,7 +26,7 @@ public abstract class BaseKoreanPhonemizer : Phonemizer { // this phonemizer will call ConvertPhonemes() when lyric is hanguel or additionalTest is true . (override to use) protected virtual bool additionalTest(string lyric) { - return true; + return false; } public override void SetSinger(USinger singer) => this.singer = singer; public static string? FindInOto(USinger singer, string phoneme, Note note, bool nullIfNotFound = false) {