From 7f6784734004d5098938ca9ff366f67c3aed607e Mon Sep 17 00:00:00 2001 From: 2xxbin <2xxbin1208@gmail.com> Date: Thu, 17 Oct 2024 21:31:09 +0900 Subject: [PATCH 1/9] Declare an onset, nucleus, and coda dictionary with Romanized keys and Korean values. --- OpenUtau.Core/KoreanPhonemizerUtil.cs | 68 +++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/OpenUtau.Core/KoreanPhonemizerUtil.cs b/OpenUtau.Core/KoreanPhonemizerUtil.cs index 432d16694..c9a7ee918 100644 --- a/OpenUtau.Core/KoreanPhonemizerUtil.cs +++ b/OpenUtau.Core/KoreanPhonemizerUtil.cs @@ -90,6 +90,74 @@ public static class KoreanPhonemizerUtil { ["ㅁ"] = 2 }; + /// + /// A dictionary of first consonants composed of {Romanization: Hangul}. + ///

{로마자:한글} 로 구성된 초성 딕셔너리 입니다. + ///
+ public static readonly Dictionary ROMAJI_KOREAN_FIRST_CONSONANTS_DICT = new Dictionary() { + {"g", "ㄱ"}, + {"n", "ㄴ"}, + {"d", "ㄷ"}, + {"r", "ㄹ"}, + {"l", "ㄹ"}, + {"m", "ㅁ"}, + {"b", "ㅂ"}, + {"s", "ㅅ"}, + {"j", "ㅈ"}, + {"ch", "ㅊ"}, + {"k", "ㅋ"}, + {"t", "ㅌ"}, + {"p", "ㅍ"}, + {"h", "ㅎ"}, + {"gg", "ㄲ"}, + {"kk", "ㄲ"}, + {"dd", "ㄸ"}, + {"tt", "ㄸ"}, + {"bb", "ㅃ"}, + {"pp", "ㅃ"}, + {"ss", "ㅆ"}, + {"jj", "ㅉ"} + }; + + /// + /// A dictionary of middle vowels composed of {Romanization: Hangul}. + ///

{로마자:한글} 로 구성된 중성 딕셔너리 입니다. + ///
+ public static readonly Dictionary ROMAJI_KOREAN_MIDDLE_VOWELS_DICT = new Dictionary() { + {"yeo", "ㅕ"}, + {"weo", "ㅝ"}, + {"eui", "ㅢ"}, + {"ui", "ㅢ"}, + {"wa", "ㅘ"}, + {"wi", "ㅟ"}, + {"we", "ㅙ"}, + {"ya", "ㅑ"}, + {"yu", "ㅠ"}, + {"ye", "ㅖ"}, + {"yo", "ㅛ"}, + {"eu", "ㅡ"}, + {"eo", "ㅓ"}, + {"a", "ㅏ"}, + {"i", "ㅣ"}, + {"u", "ㅜ"}, + {"e", "ㅔ"}, + {"o", "ㅗ"}, + }; + + // + /// A dictionary of last consonants composed of {Romanization: Hangul}. + ///

{로마자:한글} 로 구성된 종성 딕셔너리 입니다. + ///
+ public static readonly Dictionary ROMAJI_KOREAN_LAST_CONSONANTS_DICT = new Dictionary() { + {"k", "ㄱ"}, + {"n", "ㄴ"}, + {"t", "ㅅ"}, + {"l", "ㄹ"}, + {"m", "ㅁ"}, + {"p", "ㅍ"}, + {"ng", "ㅇ"}, + {"", ""}, + }; /// /// Confirms if input string is hangeul. From c0f3b01c441c85320554d154000237cbe0dcdce8 Mon Sep 17 00:00:00 2001 From: 2xxbin <2xxbin1208@gmail.com> Date: Thu, 17 Oct 2024 21:31:54 +0900 Subject: [PATCH 2/9] fix typo --- OpenUtau.Core/KoreanPhonemizerUtil.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/OpenUtau.Core/KoreanPhonemizerUtil.cs b/OpenUtau.Core/KoreanPhonemizerUtil.cs index c9a7ee918..318d86a14 100644 --- a/OpenUtau.Core/KoreanPhonemizerUtil.cs +++ b/OpenUtau.Core/KoreanPhonemizerUtil.cs @@ -151,12 +151,11 @@ public static class KoreanPhonemizerUtil { public static readonly Dictionary ROMAJI_KOREAN_LAST_CONSONANTS_DICT = new Dictionary() { {"k", "ㄱ"}, {"n", "ㄴ"}, - {"t", "ㅅ"}, + {"t", "ㄷ"}, {"l", "ㄹ"}, {"m", "ㅁ"}, - {"p", "ㅍ"}, + {"p", "ㅂ"}, {"ng", "ㅇ"}, - {"", ""}, }; /// From a97a2dd71eda5d2f65326026e3b8dd3ee86bd381 Mon Sep 17 00:00:00 2001 From: 2xxbin <2xxbin1208@gmail.com> Date: Thu, 17 Oct 2024 22:58:58 +0900 Subject: [PATCH 3/9] Add SeparateRomaji and IsKoreanRomaji --- OpenUtau.Core/KoreanPhonemizerUtil.cs | 77 +++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/OpenUtau.Core/KoreanPhonemizerUtil.cs b/OpenUtau.Core/KoreanPhonemizerUtil.cs index 318d86a14..7f8d82dc1 100644 --- a/OpenUtau.Core/KoreanPhonemizerUtil.cs +++ b/OpenUtau.Core/KoreanPhonemizerUtil.cs @@ -122,6 +122,7 @@ public static class KoreanPhonemizerUtil { /// /// A dictionary of middle vowels composed of {Romanization: Hangul}. ///

{로마자:한글} 로 구성된 중성 딕셔너리 입니다. + ///
로마자의 길이 순으로 정렬 되어 있습니다. ///
public static readonly Dictionary ROMAJI_KOREAN_MIDDLE_VOWELS_DICT = new Dictionary() { {"yeo", "ㅕ"}, @@ -191,6 +192,40 @@ public static bool IsHangeul(string? character) { return isHangeul; } + /// + /// + ///
입력된 문자열이 유효한 표기의 한국어 로마자인지 확인합니다. + ///
중성 -> 초성 -> 종성 순으로 로마자 표기와 동일하다면, 유효한 한국어 로마자로 판단합니다. + ///
문자열 중 로마자가 아닌 글자가 들어갈 경우, False를 반환합니다. + ///
+ /// + ///
(Example: 'rin') + /// + /// True / False + /// (ex) True + /// + public static bool IsKoreanRomaji(string? romaji) { + if (string.IsNullOrEmpty(romaji)) { return false; } + + // 모든 글자가 로마자인지 판별 + foreach (char c in romaji) { + if (!char.IsLetter(char.ToLower(c))) { + return false; + } + } + + if (ROMAJI_KOREAN_MIDDLE_VOWELS_DICT.ContainsKey(romaji)) { + var separatedRomaji = SeparateRomaji(romaji); + + if (ROMAJI_KOREAN_FIRST_CONSONANTS_DICT.ContainsKey(separatedRomaji[0]) && + (separatedRomaji[2] == "" || ROMAJI_KOREAN_LAST_CONSONANTS_DICT.ContainsKey(separatedRomaji[2]))) { + return true; + } + } + + return false; + } + /// /// Separates complete hangeul string's first character in three parts - firstConsonant(초성), middleVowel(중성), lastConsonant(종성). ///
입력된 문자열의 0번째 글자를 초성, 중성, 종성으로 분리합니다. @@ -244,6 +279,48 @@ public static Hashtable Separate(string character) { return separatedHangeul; } + /// + /// + ///
입력된 한국어 로마자의 문자열을 로마자 표기 초성, 중성, 종성으로 분리합니다. + ///
올바르지 않은 표기법의 로마자가 들어오면 빈 문자열이 담긴 Length 3의 리스트를 반환합니다. + ///
+ /// + ///
(Example: 'nyang') + /// + /// {firstConsonant(초성), middleVowel(중성), lastConsonant(종성)} + /// (ex) {"n", "ya", "ng"} + /// + public static string[] SeparateRomaji(string character) { + try { + string[] separatedCharacter = new string[0]; + foreach (var vowel in ROMAJI_KOREAN_MIDDLE_VOWELS_DICT.Keys) { + if (character.Contains(vowel)) { + // 예시를 기준으로 변수 part는 {"n", "ng"} + var part = character.Split(vowel); + if (!(part[1] == "")) { // 글자에 초성, 중성, 종성이 전부 있는 경우 + separatedCharacter = new string[] { part[0], vowel, part[1] }; + } else if (part == new string[] {"", ""}) { // 글자에 중성만 존재하는 경우 + separatedCharacter = new string[] { "", vowel, "" }; + } else if (part.Length == 2) { // 글자에 초성, 중성만 존재 하는 경우 + separatedCharacter = new string[] { part[0], vowel, "" }; + } + break; + } + + if (separatedCharacter.Length == 0) { // 무엇도 해당하지 않을경우 빈 문자열 3개만 담음 + separatedCharacter = new string[] { "", "", ""}; + } + + return separatedCharacter; + } + } catch (Exception e) { + Log.Error(e, "SeparateRomaji Method Error!"); + return new string[] {"", "", ""}; + } + + return new string[] {"", "", ""}; + } + /// /// merges separated hangeul into complete hangeul. (Example: {[offset + 0]: "ㄱ", [offset + 1]: "ㅏ", [offset + 2]: " "} => "가"}) /// 자모로 쪼개진 한글을 합쳐진 한글로 반환합니다. From 76540546854f3e83d3391ccc60e6f05a9216d034 Mon Sep 17 00:00:00 2001 From: EX3 Date: Fri, 18 Oct 2024 00:53:32 +0900 Subject: [PATCH 4/9] Implemented Korean Romaji Parsing TODO fix InvalidOperationException at BaseKoreanPhonemizer's line 301~ --- OpenUtau.Core/KoreanPhonemizerUtil.cs | 67 +++++++++++++------ .../BaseKoreanPhonemizer.cs | 42 +++++++++++- 2 files changed, 85 insertions(+), 24 deletions(-) diff --git a/OpenUtau.Core/KoreanPhonemizerUtil.cs b/OpenUtau.Core/KoreanPhonemizerUtil.cs index 7f8d82dc1..4e52ce119 100644 --- a/OpenUtau.Core/KoreanPhonemizerUtil.cs +++ b/OpenUtau.Core/KoreanPhonemizerUtil.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -9,6 +9,7 @@ using Serilog; using static OpenUtau.Api.Phonemizer; using OpenUtau.Api; +using System.Text; namespace OpenUtau.Core { /// @@ -116,7 +117,8 @@ public static class KoreanPhonemizerUtil { {"bb", "ㅃ"}, {"pp", "ㅃ"}, {"ss", "ㅆ"}, - {"jj", "ㅉ"} + {"jj", "ㅉ"}, + {"", "ㅇ" } }; /// @@ -157,6 +159,7 @@ public static class KoreanPhonemizerUtil { {"m", "ㅁ"}, {"p", "ㅂ"}, {"ng", "ㅇ"}, + {"", " " } }; /// @@ -194,9 +197,7 @@ public static bool IsHangeul(string? character) { } /// /// - ///
입력된 문자열이 유효한 표기의 한국어 로마자인지 확인합니다. - ///
중성 -> 초성 -> 종성 순으로 로마자 표기와 동일하다면, 유효한 한국어 로마자로 판단합니다. - ///
문자열 중 로마자가 아닌 글자가 들어갈 경우, False를 반환합니다. + ///
입력된 문자열이 유효한 표기의 한국어 로마자인지 확인하고, 유효할 경우 한국어로 변환합니다. 아닐 경우 null을 반환합니다. ///
/// ///
(Example: 'rin') @@ -204,26 +205,48 @@ public static bool IsHangeul(string? character) { /// True / False /// (ex) True /// - public static bool IsKoreanRomaji(string? romaji) { - if (string.IsNullOrEmpty(romaji)) { return false; } + public static string? TryParseKoreanRomaji(string? romaji) { - // 모든 글자가 로마자인지 판별 - foreach (char c in romaji) { - if (!char.IsLetter(char.ToLower(c))) { - return false; + if (string.IsNullOrEmpty(romaji)) { + return null; + } + List allRomajiHangeul = new List(); + List allRomajiRomaji = new List(); + StringBuilder sb = new StringBuilder(); + foreach (var first in ROMAJI_KOREAN_FIRST_CONSONANTS_DICT.Keys) { + foreach (var middle in ROMAJI_KOREAN_MIDDLE_VOWELS_DICT.Keys) { + foreach (var last in ROMAJI_KOREAN_LAST_CONSONANTS_DICT.Keys) { + sb.Clear(); + sb.Append(first); + sb.Append(middle); + sb.Append(last); + allRomajiRomaji.Add(sb.ToString()); + sb.Clear(); + sb.Append(ROMAJI_KOREAN_FIRST_CONSONANTS_DICT[first]); + sb.Append("\t"); + sb.Append(ROMAJI_KOREAN_MIDDLE_VOWELS_DICT[middle]); + sb.Append("\t"); + sb.Append(ROMAJI_KOREAN_LAST_CONSONANTS_DICT[last]); + allRomajiHangeul.Add(sb.ToString()); + + } } } - - if (ROMAJI_KOREAN_MIDDLE_VOWELS_DICT.ContainsKey(romaji)) { - var separatedRomaji = SeparateRomaji(romaji); - - if (ROMAJI_KOREAN_FIRST_CONSONANTS_DICT.ContainsKey(separatedRomaji[0]) && - (separatedRomaji[2] == "" || ROMAJI_KOREAN_LAST_CONSONANTS_DICT.ContainsKey(separatedRomaji[2]))) { - return true; - } + + if (allRomajiRomaji.Contains(romaji)) { + string hangeul = allRomajiHangeul[allRomajiRomaji.IndexOf(romaji)]; + Hashtable separated = new Hashtable() { + [0] = hangeul.Split("\t")[0].ToString(), + [1] = hangeul.Split("\t")[1].ToString(), + [2] = hangeul.Split("\t")[2].ToString() + }; + string result = Merge(separated); + Log.Debug("Korean Romaji Parsed: " + romaji + " -> " + result); + return result; + } else { + return null; } - return false; } /// @@ -346,7 +369,7 @@ public static string Merge(Hashtable separatedHangeul, int offset = 0){ int mergedCode = HANGEUL_UNICODE_START + (firstConsonantIndex * 21 + middleVowelIndex) * 28 + lastConsonantIndex; string result = Convert.ToChar(mergedCode).ToString(); - Debug.Print("Hangeul merged: " + $"{firstConsonant} + {middleVowel} + {lastConsonant} = " + result); + //Debug.Print("Hangeul merged: " + $"{firstConsonant} + {middleVowel} + {lastConsonant} = " + result); return result; } @@ -1570,4 +1593,4 @@ public FinalConsonantData(string grapheme, string phoneme) { } } -} \ No newline at end of file +} diff --git a/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs b/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs index a1552cdd8..ceb0875b2 100644 --- a/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using OpenUtau.Api; @@ -298,6 +298,44 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN phonemes = phonemes }; } + else if (!KoreanPhonemizerUtil.IsHangeul(lyric) && KoreanPhonemizerUtil.TryParseKoreanRomaji(lyric) != null) { // TODO + notes[0] = new Note() { + lyric = KoreanPhonemizerUtil.TryParseKoreanRomaji(lyric), + phoneticHint = note.phoneticHint, + position = note.position, + duration = note.duration, + tone = note.tone, + phonemeAttributes = note.phonemeAttributes + }; + Note? prevNoteNew = prevNeighbour; + Note? nextNoteNew = nextNeighbour; + if (KoreanPhonemizerUtil.IsHangeul(prevNote.Value.lyric) != null) { + + if (prevNote != null) { + prevNoteNew = new Note() { + lyric = KoreanPhonemizerUtil.TryParseKoreanRomaji(prevNote.Value.lyric), + phoneticHint = prevNote.Value.phoneticHint, + position = prevNote.Value.position, + duration = prevNote.Value.duration, + tone = prevNote.Value.tone, + phonemeAttributes = prevNote.Value.phonemeAttributes + }; + } + if (nextNote != null) { + nextNoteNew = new Note() { + lyric = KoreanPhonemizerUtil.TryParseKoreanRomaji(nextNote.Value.lyric), + phoneticHint = nextNote.Value.phoneticHint, + position = nextNote.Value.position, + duration = nextNote.Value.duration, + tone = nextNote.Value.tone, + phonemeAttributes = nextNote.Value.phonemeAttributes + }; + + } + + } + return ConvertPhonemes(notes, prev, next, prevNoteNew, nextNoteNew, prevNeighbours); + } else if (KoreanPhonemizerUtil.IsHangeul(lyric) || !KoreanPhonemizerUtil.IsHangeul(lyric) && additionalTest(lyric)) { return ConvertPhonemes(notes, prev, next, prevNeighbour, nextNeighbour, prevNeighbours); } @@ -313,4 +351,4 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN /// public abstract class BaseIniManager : KoreanPhonemizerUtil.BaseIniManager{} } -} \ No newline at end of file +} From 2a4cfae1396ef218ce063b142c92774e2244f144 Mon Sep 17 00:00:00 2001 From: 2xxbin <2xxbin1208@gmail.com> Date: Fri, 18 Oct 2024 01:56:30 +0900 Subject: [PATCH 5/9] Separate the logic into functional units. --- OpenUtau.Core/KoreanPhonemizerUtil.cs | 21 +++- .../BaseKoreanPhonemizer.cs | 106 ++++++++++++------ 2 files changed, 88 insertions(+), 39 deletions(-) diff --git a/OpenUtau.Core/KoreanPhonemizerUtil.cs b/OpenUtau.Core/KoreanPhonemizerUtil.cs index 4e52ce119..4760a0de3 100644 --- a/OpenUtau.Core/KoreanPhonemizerUtil.cs +++ b/OpenUtau.Core/KoreanPhonemizerUtil.cs @@ -195,6 +195,23 @@ public static bool IsHangeul(string? character) { return isHangeul; } + + /// + /// + ///
입력된 문자열이 유효한 표기의 한국어 로마자인지 확인합니다. + ///
+ /// + ///
(Example: 'rin') + /// + /// Bool + /// + public static bool IsKoreanRomaji(string lyric) { + if (!KoreanPhonemizerUtil.IsHangeul(lyric) && KoreanPhonemizerUtil.TryParseKoreanRomaji(lyric) != null) { + return true; + } + return false; + } + /// /// ///
입력된 문자열이 유효한 표기의 한국어 로마자인지 확인하고, 유효할 경우 한국어로 변환합니다. 아닐 경우 null을 반환합니다. @@ -202,8 +219,8 @@ public static bool IsHangeul(string? character) { /// ///
(Example: 'rin') /// - /// True / False - /// (ex) True + /// String or null + /// (ex) 린 /// public static string? TryParseKoreanRomaji(string? romaji) { diff --git a/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs b/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs index ceb0875b2..817e3cca8 100644 --- a/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs @@ -168,6 +168,70 @@ public Result GenerateResult(String firstPhoneme, String secondPhoneme, String t }; } + public class ProcessResult { + public Note[] KoreanLryicNotes { get; set; } + public Note? KoreanLryicPrevNote { get; set; } + public Note? KoreanLryicNextNote { get; set; } + } + + // + /// + /// 로마자 가사들을 전부 한글로 바꿔줍니다. + /// + /// + /// + /// /// - /// /// ProcessResult? public static ProcessResult? ConvertRomajiNoteToHangeul(Note[] notes, Note? prevNote, Note? nextNote) { var note = notes[0]; @@ -232,44 +232,16 @@ public class ProcessResult { return null; } - /// - /// Returns Result with three input Phonemes. + // /// + /// 발음 힌트를 분석하여 Result를 반환합니다. /// - /// - /// - /// + /// + /// /// - /// - /// - /// - /// Result - public Result GenerateResult(String firstPhoneme, String secondPhoneme, String thirdPhoneme, int totalDuration, int secondTotalDurationDivider=3, int thirdTotalDurationDivider=8){ - return new Result() { - phonemes = new Phoneme[] { - new Phoneme { phoneme = firstPhoneme}, - new Phoneme { phoneme = secondPhoneme, - position = totalDuration - totalDuration / secondTotalDurationDivider}, - new Phoneme { phoneme = thirdPhoneme, - position = totalDuration - totalDuration / thirdTotalDurationDivider}, - }// -음소 있이 이어줌 - }; - } - /// - /// It AUTOMATICALLY generates phonemes based on phoneme hints (each phonemes should be separated by ",". (Example: [a, a i, ya])) - /// But it can't generate phonemes automatically, so should implement ConvertPhonemes() Method in child class. - /// Also it can't generate Endsounds automatically, so should implement GenerateEndSound() Method in child class. - /// - public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevNeighbour, Note? nextNeighbour, Note[] prevNeighbours) { - Note note = notes[0]; - string lyric = note.lyric; - string phoneticHint = note.phoneticHint; - - Note? prevNote = prevNeighbour; // null or Note - Note thisNote = note; - Note? nextNote = nextNeighbour; // null or Note - - int totalDuration = notes.Sum(n => n.duration); + /// Result? + public Result? RenderPhoneticHint(USinger singer, Note note, int totalDuration) { + var phoneticHint = note.phoneticHint; if (phoneticHint != null) { // if there are phonetic hint @@ -361,7 +333,52 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN return new Result() { phonemes = phonemes }; - } + } + return null; + } + + /// + /// Returns Result with three input Phonemes. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// Result + public Result GenerateResult(String firstPhoneme, String secondPhoneme, String thirdPhoneme, int totalDuration, int secondTotalDurationDivider=3, int thirdTotalDurationDivider=8){ + return new Result() { + phonemes = new Phoneme[] { + new Phoneme { phoneme = firstPhoneme}, + new Phoneme { phoneme = secondPhoneme, + position = totalDuration - totalDuration / secondTotalDurationDivider}, + new Phoneme { phoneme = thirdPhoneme, + position = totalDuration - totalDuration / thirdTotalDurationDivider}, + }// -음소 있이 이어줌 + }; + } + /// + /// It AUTOMATICALLY generates phonemes based on phoneme hints (each phonemes should be separated by ",". (Example: [a, a i, ya])) + /// But it can't generate phonemes automatically, so should implement ConvertPhonemes() Method in child class. + /// Also it can't generate Endsounds automatically, so should implement GenerateEndSound() Method in child class. + /// + public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevNeighbour, Note? nextNeighbour, Note[] prevNeighbours) { + Note note = notes[0]; + string lyric = note.lyric; + + Note? prevNote = prevNeighbour; // null or Note + Note thisNote = note; + Note? nextNote = nextNeighbour; // null or Note + + int totalDuration = notes.Sum(n => n.duration); + + var phoneticHint = RenderPhoneticHint(singer, note, totalDuration); + if (phoneticHint != null) { + return (Result) phoneticHint; + } var romaji2Korean = ConvertRomajiNoteToHangeul(notes, prevNeighbour, nextNeighbour); if (romaji2Korean != null) { From e09c4984efb4465a31d725b270fcab67089b3425 Mon Sep 17 00:00:00 2001 From: 2xxbin <2xxbin1208@gmail.com> Date: Fri, 18 Oct 2024 02:29:48 +0900 Subject: [PATCH 7/9] Add phonetic hint and Romanized input code. --- .../KoreanCVCCVPhonemizer.cs | 18 ++++++++++++++++++ OpenUtau.Plugin.Builtin/KoreanCVCPhonemizer.cs | 12 +++++++----- OpenUtau.Plugin.Builtin/KoreanVCVPhonemizer.cs | 15 +++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/OpenUtau.Plugin.Builtin/KoreanCVCCVPhonemizer.cs b/OpenUtau.Plugin.Builtin/KoreanCVCCVPhonemizer.cs index 35263e0a6..1c5b43e68 100644 --- a/OpenUtau.Plugin.Builtin/KoreanCVCCVPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/KoreanCVCCVPhonemizer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Melanchall.DryWetMidi.MusicTheory; using OpenUtau.Api; using OpenUtau.Core; using OpenUtau.Core.Ustx; @@ -294,12 +295,29 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN int totalDuration = notes.Sum(n => n.duration); int vcLength = 120; + + var phoneticHint = RenderPhoneticHint(singer, notes[0], totalDuration); + if (phoneticHint != null) { + return (Result) phoneticHint; + } + + var romaji2Korean = ConvertRomajiNoteToHangeul(notes, prevNeighbour, nextNeighbour); + if (romaji2Korean != null) { + notes = romaji2Korean.KoreanLryicNotes; + prevNeighbour = romaji2Korean.KoreanLryicPrevNote; + nextNeighbour = romaji2Korean.KoreanLryicNextNote; + + prevLyric = prevNeighbour?.lyric; + nextLyric = nextNeighbour?.lyric; + } + List phonemesArr = new List(); var currentLyric = notes[0].lyric; currentKoreanLyrics = SeparateHangul(currentLyric != null ? currentLyric[0] : '\0'); + if (currentLyric[0] >= '가' && currentLyric[0] <= '힣') { } else { diff --git a/OpenUtau.Plugin.Builtin/KoreanCVCPhonemizer.cs b/OpenUtau.Plugin.Builtin/KoreanCVCPhonemizer.cs index 2ac3ba5e9..6c1fa2afd 100644 --- a/OpenUtau.Plugin.Builtin/KoreanCVCPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/KoreanCVCPhonemizer.cs @@ -169,6 +169,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN string currentLyric = note.lyric; // 현재 가사 var attr0 = note.phonemeAttributes?.FirstOrDefault(attr => attr.index == 0) ?? default; var attr1 = note.phonemeAttributes?.FirstOrDefault(attr => attr.index == 1) ?? default; + int totalDuration = notes.Sum(n => n.duration); // 가사의 초성, 중성, 종성 분리 // P(re)Lconsonant, PLvowel, PLfinal / C(urrent)Lconsonant, CLvowel, CLfinal / N(ext)Lconsonant, NLvowel, NLfinal @@ -213,6 +214,11 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN int uCL, uPL, uNL; lCL = 0; lPL = 0; lNL = 0; + var phoneticHint = RenderPhoneticHint(singer, notes[0], totalDuration); + if (phoneticHint != null) { + return (Result) phoneticHint; + } + // 현재 노트 첫번째 글자 확인 firstCL = currentLyric[0]; @@ -583,7 +589,6 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN // 만약 받침이 있다면 if (FC != "") { - int totalDuration = notes.Sum(n => n.duration); int fcLength = totalDuration / 3; if ((TCLfinal == "k") || (TCLfinal == "p") || (TCLfinal == "t")) { fcLength = totalDuration / 2; } @@ -610,7 +615,6 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN // 뒤에 노트가 있다면 if (nextExist) { if ((nextNeighbour?.lyric)[0] == 'ㄹ') { VC = TCLplainvowel + "l"; } } if ((VC != "") && (TNLconsonant != "")) { - int totalDuration = notes.Sum(n => n.duration); int vcLength = 60; if ((TNLconsonant == "r") || (TNLconsonant == "h")) { vcLength = 30; } else if (TNLconsonant == "s") { vcLength = totalDuration/3; } @@ -634,7 +638,6 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN }; } } else if (VC != "" && TNLconsonant == "") { - int totalDuration = notes.Sum(n => n.duration); int vcLength = 60; var nextAttr = nextNeighbour.Value.phonemeAttributes?.FirstOrDefault(attr => attr.index == 0) ?? default; var nextUnicode = ToUnicodeElements(nextNeighbour?.lyric); @@ -886,8 +889,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN }, }; } - - int totalDuration = notes.Sum(n => n.duration); + int vcLength = 60; var nextAttr = nextNeighbour.Value.phonemeAttributes?.FirstOrDefault(attr => attr.index == 0) ?? default; if (singer.TryGetMappedOto(nextLyric, nextNeighbour.Value.tone + nextAttr.toneShift, nextAttr.voiceColor, out var oto)) { diff --git a/OpenUtau.Plugin.Builtin/KoreanVCVPhonemizer.cs b/OpenUtau.Plugin.Builtin/KoreanVCVPhonemizer.cs index ef45888ad..c3a800f3e 100644 --- a/OpenUtau.Plugin.Builtin/KoreanVCVPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/KoreanVCVPhonemizer.cs @@ -131,6 +131,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN string color = string.Empty; int shift = 0; int? alt; + int totalDuration = notes.Sum(n => n.duration); string color1 = string.Empty; int shift1 = 0; @@ -150,6 +151,20 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN string currPhoneme; string[] prevIMF; + var phoneticHint = RenderPhoneticHint(singer, notes[0], totalDuration); + if (phoneticHint != null) { + return (Result) phoneticHint; + } + + var romaji2Korean = ConvertRomajiNoteToHangeul(notes, prevNeighbour, nextNeighbour); + if (romaji2Korean != null) { + notes = romaji2Korean.KoreanLryicNotes; + prevNeighbour = romaji2Korean.KoreanLryicPrevNote; + nextNeighbour = romaji2Korean.KoreanLryicNextNote; + + note = notes[0]; + } + // Check if lyric is R, - or an end breath and return appropriate Result; otherwise, move to next steps if (note.lyric == "R" || note.lyric == "R2" || note.lyric == "-" || note.lyric == "H" || note.lyric == "B" || note.lyric == "bre") { From 538d91cac73b25f60fe62bc733d79ff7acaee50a Mon Sep 17 00:00:00 2001 From: 2xxbin <2xxbin1208@gmail.com> Date: Fri, 18 Oct 2024 22:19:01 +0900 Subject: [PATCH 8/9] Fixed BaseKoreanPhonemizer to use voice color --- OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs b/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs index b07d35516..1fd84b16d 100644 --- a/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs @@ -34,7 +34,8 @@ protected virtual bool additionalTest(string lyric) { // nullIfNotFound가 true이면 음소가 찾아지지 않을 때 음소가 아닌 null을 리턴한다. // nullIfNotFound가 false면 음소가 찾아지지 않을 때 그대로 음소를 반환 string phonemeToReturn; - string color = string.Empty; + var attr = note.phonemeAttributes?.FirstOrDefault(attr => attr.index == 0) ?? default; + string color = attr.voiceColor ?? string.Empty; int toneShift = 0; int? alt = null; if (phoneme.Equals("")) {return phoneme;} From 2a1b2b9b1396170b768fba83f53bb8807a86e915 Mon Sep 17 00:00:00 2001 From: 2xxbin <2xxbin1208@gmail.com> Date: Fri, 18 Oct 2024 22:24:22 +0900 Subject: [PATCH 9/9] Translate the Korean comments into English. --- OpenUtau.Core/KoreanPhonemizerUtil.cs | 7 ++++--- OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/OpenUtau.Core/KoreanPhonemizerUtil.cs b/OpenUtau.Core/KoreanPhonemizerUtil.cs index 4760a0de3..42535faa9 100644 --- a/OpenUtau.Core/KoreanPhonemizerUtil.cs +++ b/OpenUtau.Core/KoreanPhonemizerUtil.cs @@ -197,7 +197,7 @@ public static bool IsHangeul(string? character) { } /// - /// + /// It checks if the input string is valid Korean Romanization. ///
입력된 문자열이 유효한 표기의 한국어 로마자인지 확인합니다. ///
/// @@ -213,7 +213,7 @@ public static bool IsKoreanRomaji(string lyric) { } /// - /// + /// It checks if the input string is valid Korean Romanization, converts it to Korean if valid, and returns null if not. ///
입력된 문자열이 유효한 표기의 한국어 로마자인지 확인하고, 유효할 경우 한국어로 변환합니다. 아닐 경우 null을 반환합니다. ///
/// @@ -320,7 +320,8 @@ public static Hashtable Separate(string character) { } /// - /// + /// It separates the input Korean Romanized string into initial consonant, medial vowel, and final consonant. + ///
If the Romanized string contains incorrect notation, it returns a list of length 3 filled with empty strings. ///
입력된 한국어 로마자의 문자열을 로마자 표기 초성, 중성, 종성으로 분리합니다. ///
올바르지 않은 표기법의 로마자가 들어오면 빈 문자열이 담긴 Length 3의 리스트를 반환합니다. ///
diff --git a/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs b/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs index 1fd84b16d..9a5217a5c 100644 --- a/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/BaseKoreanPhonemizer.cs @@ -176,7 +176,7 @@ public class ProcessResult { } // - /// + /// The Romanized lyrics will be completely converted to Hangul. /// 로마자 가사들을 전부 한글로 바꿔줍니다. /// /// @@ -234,7 +234,7 @@ public class ProcessResult { } // - /// + /// It analyzes the phonetic hint and returns the result. /// 발음 힌트를 분석하여 Result를 반환합니다. /// ///