Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9c07e90
Instance-based dictionary
Cadlaxa Oct 4, 2025
fcde2b5
Merge branch 'master' of https://github.com/stakira/OpenUtau into SBP…
Cadlaxa Nov 8, 2025
fd90a8a
Merge branch 'master' of https://github.com/stakira/OpenUtau into SBP…
Cadlaxa Mar 19, 2026
ff17204
SBP Update
Cadlaxa Mar 19, 2026
ed9e368
implement GetTransitionBasicLengthMsByOto per child phonemizer
Cadlaxa Mar 19, 2026
38cd838
Fix DeVCCV test file to reflect correct pitch suffix
Cadlaxa Mar 19, 2026
962e785
Fix phoneme overrides method
Cadlaxa Mar 21, 2026
ae5d8a2
fix EN C+V ending timings
Cadlaxa Mar 29, 2026
27b10cc
Utilize ValidateAlias on AliasFormat
Cadlaxa Mar 29, 2026
9ea15af
Fix override timings
Cadlaxa Mar 31, 2026
5301558
Fix empty array bug
Cadlaxa Apr 1, 2026
25e4d30
LANG2JA phonemizers: fix timings for VCV banks
Cadlaxa Apr 9, 2026
aed17c1
Merge branch 'master' of https://github.com/stakira/OpenUtau into SBP…
Cadlaxa May 2, 2026
1e7fb1e
Fix conflicts with SBP
Cadlaxa May 2, 2026
b80b181
Merge branch 'master' of https://github.com/stakira/OpenUtau into SBP…
Cadlaxa May 2, 2026
63a79ea
Merge branch 'master' of https://github.com/stakira/OpenUtau into SBP…
Cadlaxa May 2, 2026
d9de4d8
Fixes in EN VCCV and ES VCCV
Cadlaxa May 3, 2026
92e35e7
Fix GetSymbols not updated from the pr conflict
Cadlaxa May 3, 2026
fce1b36
Implement CustomParameters
Cadlaxa May 10, 2026
50ebbfd
Don't include AssignAllAffixes in the CustomParameters
Cadlaxa May 10, 2026
68bd993
small fixes
Cadlaxa May 10, 2026
ae0bbee
fix double ending consonant for EN C+V
Cadlaxa May 15, 2026
7430073
Merge branch 'master' of https://github.com/stakira/OpenUtau into SBP…
Cadlaxa May 21, 2026
3935493
Proper isGlide phoneme struct
Cadlaxa May 21, 2026
b85f965
Add "null" boundary + phoneme group fixes
Cadlaxa May 29, 2026
e827c95
Account for the negative overlap
Cadlaxa May 30, 2026
cf49163
fix glides in en-xsampa
Cadlaxa May 30, 2026
032ae64
Concatenate phoneme symbols with replacements
Cadlaxa Jun 1, 2026
7f2e23d
replace + with & and added parenthesis for phoneme groupings
Cadlaxa Jun 1, 2026
b27c98d
Fix yaml timings
Cadlaxa Jun 3, 2026
4168646
Fix EN C+P replacement group leftover
Cadlaxa Jun 9, 2026
0dadb5a
Native diphthong split
Cadlaxa Jun 10, 2026
2694fe2
Global dictionary
Cadlaxa Jun 10, 2026
2238d9c
Update C+V setSinger global dictionary
Cadlaxa Jun 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 82 additions & 186 deletions OpenUtau.Plugin.Builtin/ArpasingPlusPhonemizer.cs

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions OpenUtau.Plugin.Builtin/ENtoJAPhonemizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -460,5 +460,39 @@ private string ToHiragana(string romaji) {
hiragana = hiragana.Replace("ゔ", "ヴ");
return hiragana;
}

// Endings has 50 ticks gap
protected override bool NoGap => true;

protected override double GetTransitionBasicLengthMs(string alias, int tone, PhonemeAttributes attr) {
double otoLength = GetTransitionBasicLengthMsByOto(alias, tone, attr);

var parts = alias.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
bool isVcv = false;

if (parts.Length == 2) {
var startingVowels = new[] { "a", "i", "u", "e", "o", "n", "N", "-" };
var endingVowels = vowels;

// First part must be a vowel (or a rest)
if (startingVowels.Contains(parts[0])) {
string cv = parts[1];

// Second part must end in a vowel (Romaji CV) OR be Japanese (Hiragana/Katakana)
bool isRomajiVcv = endingVowels.Contains(cv.Last().ToString());
bool isJapaneseVcv = cv.Any(c => c > 0xFF);

if (isRomajiVcv || isJapaneseVcv) {
isVcv = true;
}
}
}

if (isVcv) {
return GetTransitionBasicLengthMsByConstant() * 1.0;
}

return otoLength;
}
}
}
34 changes: 34 additions & 0 deletions OpenUtau.Plugin.Builtin/EStoJAPhonemizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -669,5 +669,39 @@ private string ToHiragana(string romaji) {
hiragana = hiragana.Replace("ゔ", "ヴ");
return hiragana;
}

// Endings has 50 ticks gap
protected override bool NoGap => true;

protected override double GetTransitionBasicLengthMs(string alias, int tone, PhonemeAttributes attr) {
double otoLength = GetTransitionBasicLengthMsByOto(alias, tone, attr);

var parts = alias.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
bool isVcv = false;

if (parts.Length == 2) {
var startingVowels = new[] { "a", "i", "u", "e", "o", "n", "N", "-" };
var endingVowels = vowels;

// First part must be a vowel (or a rest)
if (startingVowels.Contains(parts[0])) {
string cv = parts[1];

// Second part must end in a vowel (Romaji CV) OR be Japanese (Hiragana/Katakana)
bool isRomajiVcv = endingVowels.Contains(cv.Last().ToString());
bool isJapaneseVcv = cv.Any(c => c > 0xFF);

if (isRomajiVcv || isJapaneseVcv) {
isVcv = true;
}
}
}

if (isVcv) {
return GetTransitionBasicLengthMsByConstant() * 1.0;
}

return otoLength;
}
}
}
226 changes: 63 additions & 163 deletions OpenUtau.Plugin.Builtin/EnXSampaPhonemizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,38 +173,21 @@ public EnXSampaPhonemizer() {
{"@u","u"},
{"3", "r"}
};
protected override IG2p LoadBaseDictionary() {
var g2ps = new List<IG2p>();

// Load dictionary from plugin folder.
string path = Path.Combine(PluginDir, YamlFileName);
if (!File.Exists(path)) {
Directory.CreateDirectory(PluginDir);
File.WriteAllBytes(path, YamlTemplate);
}
g2ps.Add(G2pDictionary.NewBuilder().Load(File.ReadAllText(path)).Build());

// Load dictionary from singer folder.
if (singer != null && singer.Found && singer.Loaded) {
string file = Path.Combine(singer.Location, YamlFileName);
if (File.Exists(file)) {
try {
g2ps.Add(G2pDictionary.NewBuilder().Load(File.ReadAllText(file)).Build());
} catch (Exception e) {
Log.Error(e, $"Failed to load {file}");
}
}
}
g2ps.Add(new ArpabetG2p());
return new G2pFallbacks(g2ps.ToArray());
protected override IG2p[] GetBaseG2ps() {
return new IG2p[] { new ArpabetG2p() };
}

protected override string[] GetSymbols(Note note) {
string[] original = base.GetSymbols(note);
if (original == null) {
return null;
}
List<string> finalProcessedPhonemes = new List<string>();

for (int i = 0; i < original.Length; i++) {
if (dictionaryReplacements.TryGetValue(original[i], out string replaced)) {
original[i] = replaced;
}
}

// Splits diphthongs and affricates if not present in the bank
string[] diphthongs = new[] { "aI", "eI", "OI", "aU", "oU", "VI", "VU", "@U", "ai", "ei", "Oi", "au", "ou", "Ou", "@u", };
Expand All @@ -227,10 +210,6 @@ private string ReplacePhoneme(string phoneme, int tone) {
if (HasOto(phoneme, tone) || HasOto(ValidateAlias(phoneme), tone)) {
return phoneme;
}
// Otherwise, try to apply the dictionary replacement.
if (dictionaryReplacements.TryGetValue(phoneme, out var replaced)) {
return replaced;
}
return phoneme;
}

Expand Down Expand Up @@ -307,38 +286,49 @@ protected override List<string> ProcessSyllable(Syllable syllable) {
}
} else if (syllable.IsVV) {
if (!CanMakeAliasExtension(syllable)) {
var vv = $"{prevV} {v}";
basePhoneme = vv;
if (!HasOto(vv, syllable.vowelTone) && !HasOto(ValidateAlias(vv), syllable.vowelTone) && (vvExceptions.ContainsKey(prevV) && prevV != v || Delta5vvExceptions.ContainsKey(prevV) && prevV != v)) {
// VV splits to [V C][CV] or [V][V]
var delta5vc = $"{Delta5vvExceptions[prevV]}";
bool CV = false;
if ((!HasOto(delta5vc, syllable.vowelTone) && !HasOto(ValidateAlias(delta5vc), syllable.vowelTone))) {
delta5vc = $"{prevV} {vvExceptions[prevV]}";
CV = true;
if (HasOto($"{prevV} {v}", syllable.vowelTone) || HasOto(ValidateAlias($"{prevV} {v}"), syllable.vowelTone)) {
basePhoneme = $"{prevV} {v}";
} else if (HasOto($"{prevV}{v}", syllable.vowelTone) || HasOto(ValidateAlias($"{prevV}{v}"), syllable.vowelTone)) {
basePhoneme = $"{prevV}{v}";
}

// Diphthong Fallbacks
else if (diphthongSplits.ContainsKey(prevV) || diphthongTails.ContainsKey(prevV)) {
string cv = "";
if (diphthongSplits.ContainsKey(prevV)) {
var splitOverride = diphthongSplits[prevV];
var vc = splitOverride[0].Replace("{v}", v);
cv = splitOverride[1].Replace("{v}", v);
TryAddPhoneme(phonemes, syllable.tone, vc, ValidateAlias(vc));
}
else { // Default YAML diphthong logic
var tail = diphthongTails[prevV];
var vcSpace = $"{prevV} {tail}";
var vcNoSpace = $"{prevV}{tail}";
cv = $"{tail}{v}";
TryAddPhoneme(phonemes, syllable.tone, vcSpace, ValidateAlias(vcSpace), vcNoSpace, ValidateAlias(vcNoSpace));
}
phonemes.Add(delta5vc);
// if delta5 vc is not available, turn v to cv
var cv = $"{vvExceptions[prevV]}{v}";
basePhoneme = v;
if (CV && (HasOto(cv, syllable.vowelTone) || HasOto(ValidateAlias(cv), syllable.vowelTone))) {

if (HasOto(cv, syllable.vowelTone) || HasOto(ValidateAlias(cv), syllable.vowelTone)) {
basePhoneme = cv;
} else if (!HasOto(cv, syllable.vowelTone) || !HasOto(ValidateAlias(cv), syllable.vowelTone)) {
basePhoneme = $"{diphthongTails[prevV]} {v}";
} else if (HasOto(v, syllable.vowelTone) || HasOto(ValidateAlias(v), syllable.vowelTone)) {
basePhoneme = v;
} else {
basePhoneme = ValidateAlias($"- {v}");
phonemes.Add($"{prevV} -");
}
} else {
// VV to V
if (HasOto($"{prevV} {v}", syllable.vowelTone) || HasOto(ValidateAlias($"{prevV} {v}"), syllable.vowelTone)) {
basePhoneme = $"{prevV} {v}";
} else if (HasOto($"{prevV}{v}", syllable.vowelTone) || HasOto(ValidateAlias($"{prevV}{v}"), syllable.vowelTone)) {
basePhoneme = $"{prevV}{v}";
} else if (HasOto(v, syllable.vowelTone) || HasOto(ValidateAlias(v), syllable.vowelTone)) {
if (HasOto(v, syllable.vowelTone) || HasOto(ValidateAlias(v), syllable.vowelTone)) {
basePhoneme = v;
} else {
basePhoneme = ValidateAlias($"- {v}");
phonemes.Add($"{prevV} -");
}
}
// EXTEND AS [V]
} else if (HasOto($"{v}", syllable.vowelTone) && HasOto(ValidateAlias($"{v}"), syllable.vowelTone)) {
basePhoneme = v;
} else {
// PREVIOUS ALIAS WILL EXTEND as [V V]
}
else {
basePhoneme = null;
}
} else if (syllable.IsStartingCVWithOneConsonant) {
Expand Down Expand Up @@ -492,6 +482,11 @@ protected override List<string> ProcessSyllable(Syllable syllable) {
if (CurrentWordCc.Length >= 2 && !PreviousWordCc.Contains(cc1)) {
cc1 = $"{string.Join("", cc.Skip(i))}";
}
if (CurrentWordCc.Length >= 2) {
if (liquid.Contains(cc[i + 1]) || semivowel.Contains(cc[i + 1])) {
glides(cc1);
}
}
if (!HasOto(cc1, syllable.tone)) {
cc1 = ValidateAlias(cc1);
}
Expand Down Expand Up @@ -525,6 +520,11 @@ protected override List<string> ProcessSyllable(Syllable syllable) {
if (!HasOto(cc2, syllable.tone)) {
cc2 = ValidateAlias(cc2);
}
if (CurrentWordCc.Length >= 2) {
if (liquid.Contains(cc[i + 1]) || semivowel.Contains(cc[i + 1])) {
glides(cc1);
}
}
// Use [C2C3] when current word has 2 consonants or more and [C2C3C4...] does not exist
if (!HasOto(cc2, syllable.tone) && CurrentWordCc.Length >= 2 && CurrentWordCc.Contains(cc2)) {
cc2 = $"{cc[i + 1]}{cc[i + 2]}";
Expand Down Expand Up @@ -833,123 +833,23 @@ protected override string ValidateAlias(string alias) {
return alias;
}

bool PhonemeIsPresent(string alias, string phoneme) {
if (string.IsNullOrEmpty(alias) || string.IsNullOrEmpty(phoneme))
return false;

// Exact token match
if (alias == phoneme)
return true;

return alias.EndsWith(phoneme);
}

private bool PhonemeHasEndingSuffix(string alias, string phoneme) {
var escapedPhoneme = Regex.Escape(phoneme);
if (Regex.IsMatch(alias, $@"\b{escapedPhoneme}\b\s*-") ||
Regex.IsMatch(alias, $@"\b{escapedPhoneme}\b-")) {
return true;
}
if (Regex.IsMatch(alias, $@"\b{escapedPhoneme}\b R")) {
return true;
}
return false;
}

protected override double GetTransitionBasicLengthMs(string alias = "") {
//I wish these were automated instead :')
double transitionMultiplier = 1.0; // Default multiplier

var fricative_def = 2.3;
var aspirate_def = 1.3;
var semivowel_def = 1.2;
var liquid_def = 1.5;
var nasal_def = 1.5;
var stop_def = 1.8;
var tap_def = 0.5;
var affricate_def = 1.5;
// Endings has 50 ticks gap
protected override bool NoGap => true;

var allConsonants = fricative.Concat(aspirate)
.Concat(semivowel)
.Concat(liquid)
.Concat(nasal)
.Concat(stop)
.Concat(tap)
.Concat(affricate)
.Distinct(); // Ensure no duplicates

foreach (var c in allConsonants) {
if (PhonemeHasEndingSuffix(alias, c)) {
return base.GetTransitionBasicLengthMs() * 0.5;
}
}

foreach (var v in vowels) {
if (alias.EndsWith("-")) {
return base.GetTransitionBasicLengthMs() * 0.5;
}
}

// consonant timings
protected override double GetTransitionBasicLengthMs(string alias, int tone, PhonemeAttributes attr) {
double otoLength = GetTransitionBasicLengthMsByOto(alias, tone, attr);

var sortedOverrides = PhonemeOverrides.OrderByDescending(kv => kv.Key.Length);
foreach (var kvp in sortedOverrides) {
var overridePhoneme = kvp.Key;
var overrideValue = kvp.Value;
if (PhonemeIsPresent(alias, overridePhoneme)) {
return base.GetTransitionBasicLengthMs() * overrideValue;
}
}

foreach (var c in fricative) {
if (PhonemeIsPresent(alias, c)) {
return base.GetTransitionBasicLengthMs() * fricative_def;
}
}

foreach (var c in aspirate) {
if (PhonemeIsPresent(alias, c)) {
return base.GetTransitionBasicLengthMs() * aspirate_def;
}
}

foreach (var c in semivowel) {
if (PhonemeIsPresent(alias, c)) {
return base.GetTransitionBasicLengthMs() * semivowel_def;
}
}

foreach (var c in liquid) {
if (PhonemeIsPresent(alias, c)) {
return base.GetTransitionBasicLengthMs() * liquid_def;
}
}

foreach (var c in nasal) {
if (PhonemeIsPresent(alias, c)) {
return base.GetTransitionBasicLengthMs() * nasal_def;
}
}

foreach (var c in stop) {
if (PhonemeIsPresent(alias, c)) {
return base.GetTransitionBasicLengthMs() * stop_def;
}
}

foreach (var c in tap) {
if (PhonemeIsPresent(alias, c)) {
return base.GetTransitionBasicLengthMs() * tap_def;
}
}
var symbol = kvp.Key;
var value = kvp.Value;

foreach (var c in affricate) {
if (PhonemeIsPresent(alias, c)) {
return base.GetTransitionBasicLengthMs() * affricate_def;
if (Regex.IsMatch(alias, $@"(?<![a-zA-Z]){Regex.Escape(symbol)}(?![a-zA-Z])")) {
return GetTransitionBasicLengthMsByConstant() * value;
}
}

return base.GetTransitionBasicLengthMs() * transitionMultiplier;
return otoLength;
}
}
}
Loading
Loading