Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Koromaji #1306

Closed
wants to merge 9 commits into from
191 changes: 188 additions & 3 deletions OpenUtau.Core/KoreanPhonemizerUtil.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
Expand All @@ -9,6 +9,7 @@
using Serilog;
using static OpenUtau.Api.Phonemizer;
using OpenUtau.Api;
using System.Text;

namespace OpenUtau.Core {
/// <summary>
Expand Down Expand Up @@ -90,6 +91,76 @@ public static class KoreanPhonemizerUtil {
["ㅁ"] = 2
};

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

/// <summary>
/// A dictionary of middle vowels composed of {Romanization: Hangul}.
/// <br/><br/>{로마자:한글} 로 구성된 중성 딕셔너리 입니다.
/// <br/>로마자의 길이 순으로 정렬 되어 있습니다.
/// </summary>
public static readonly Dictionary<String, String> ROMAJI_KOREAN_MIDDLE_VOWELS_DICT = new Dictionary<String, String>() {
{"yeo", "ㅕ"},
{"weo", "ㅝ"},
{"eui", "ㅢ"},
{"ui", "ㅢ"},
{"wa", "ㅘ"},
{"wi", "ㅟ"},
{"we", "ㅙ"},
{"ya", "ㅑ"},
{"yu", "ㅠ"},
{"ye", "ㅖ"},
{"yo", "ㅛ"},
{"eu", "ㅡ"},
{"eo", "ㅓ"},
{"a", "ㅏ"},
{"i", "ㅣ"},
{"u", "ㅜ"},
{"e", "ㅔ"},
{"o", "ㅗ"},
};

// <summary>
/// A dictionary of last consonants composed of {Romanization: Hangul}.
/// <br/><br/>{로마자:한글} 로 구성된 종성 딕셔너리 입니다.
/// </summary>
public static readonly Dictionary<String, String> ROMAJI_KOREAN_LAST_CONSONANTS_DICT = new Dictionary<String, String>() {
{"k", "ㄱ"},
{"n", "ㄴ"},
{"t", "ㄷ"},
{"l", "ㄹ"},
{"m", "ㅁ"},
{"p", "ㅂ"},
{"ng", "ㅇ"},
{"", " " }
};

/// <summary>
/// Confirms if input string is hangeul.
Expand Down Expand Up @@ -124,6 +195,77 @@ public static bool IsHangeul(string? character) {

return isHangeul;
}

/// <summary>
/// It checks if the input string is valid Korean Romanization.
/// <br/> 입력된 문자열이 유효한 표기의 한국어 로마자인지 확인합니다.
/// </summary>
/// <param name="lyric">
/// <br/>(Example: 'rin')
/// </param>
/// <returns> Bool
/// </returns>
public static bool IsKoreanRomaji(string lyric) {
if (!KoreanPhonemizerUtil.IsHangeul(lyric) && KoreanPhonemizerUtil.TryParseKoreanRomaji(lyric) != null) {
return true;
}
return false;
}

/// <summary>
/// It checks if the input string is valid Korean Romanization, converts it to Korean if valid, and returns null if not.
/// <br/> 입력된 문자열이 유효한 표기의 한국어 로마자인지 확인하고, 유효할 경우 한국어로 변환합니다. 아닐 경우 null을 반환합니다.
/// </summary>
/// <param name="romaji">
/// <br/>(Example: 'rin')
/// </param>
/// <returns> String or null
/// (ex) 린
/// </returns>
public static string? TryParseKoreanRomaji(string? romaji) {

if (string.IsNullOrEmpty(romaji)) {
return null;
}
List<string> allRomajiHangeul = new List<string>();
List<string> allRomajiRomaji = new List<string>();
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 (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;
}

}

/// <summary>
/// Separates complete hangeul string's first character in three parts - firstConsonant(초성), middleVowel(중성), lastConsonant(종성).
/// <br/>입력된 문자열의 0번째 글자를 초성, 중성, 종성으로 분리합니다.
Expand Down Expand Up @@ -177,6 +319,49 @@ public static Hashtable Separate(string character) {
return separatedHangeul;
}

/// <summary>
/// It separates the input Korean Romanized string into initial consonant, medial vowel, and final consonant.
/// <br/> If the Romanized string contains incorrect notation, it returns a list of length 3 filled with empty strings.
/// <br/> 입력된 한국어 로마자의 문자열을 로마자 표기 초성, 중성, 종성으로 분리합니다.
/// <br/> 올바르지 않은 표기법의 로마자가 들어오면 빈 문자열이 담긴 Length 3의 리스트를 반환합니다.
/// </summary>
/// <param name="character">
/// <br/>(Example: 'nyang')
/// </param>
/// <returns>{firstConsonant(초성), middleVowel(중성), lastConsonant(종성)}
/// (ex) {"n", "ya", "ng"}
/// </returns>
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[] {"", "", ""};
}

/// <summary>
/// merges separated hangeul into complete hangeul. (Example: {[offset + 0]: "ㄱ", [offset + 1]: "ㅏ", [offset + 2]: " "} => "가"})
/// <para>자모로 쪼개진 한글을 합쳐진 한글로 반환합니다.</para>
Expand All @@ -202,7 +387,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;
}

Expand Down Expand Up @@ -1426,4 +1611,4 @@ public FinalConsonantData(string grapheme, string phoneme) {
}
}

}
}
Loading
Loading