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를 반환합니다.
///
///