Skip to content

Commit

Permalink
Merge pull request #1034 from maiko3tattun/0206_SupportStartCinJAPhon…
Browse files Browse the repository at this point in the history
…emizer

Support "- C" in JA VCV&CVVC Phonemizer
  • Loading branch information
stakira authored Feb 25, 2024
2 parents d4b3cc2 + a64a961 commit d2271dd
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 55 deletions.
2 changes: 2 additions & 0 deletions OpenUtau.Core/Api/Phonemizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ public struct Phoneme {
/// </summary>
public PhonemeAttributes attributes;

public int? index;

public override string ToString() => $"\"{phoneme}\" pos:{position}";
}

Expand Down
2 changes: 1 addition & 1 deletion OpenUtau.Core/Ustx/UPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public override void Validate(ValidateOptions options, UProject project, UTrack
phonemes.Add(new UPhoneme() {
rawPosition = resp.phonemes[i][j].position - position,
rawPhoneme = resp.phonemes[i][j].phoneme,
index = j,
index = resp.phonemes[i][j].index ?? j,
Parent = notes.ElementAtOrDefault(resp.noteIndexes[i]),
});
}
Expand Down
110 changes: 56 additions & 54 deletions OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public override void SetSinger(USinger singer) {


public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevNeighbour, Note? nextNeighbour, Note[] prevNeighbours) {
var result = new List<Phoneme>();
bool preCFlag = false;

var note = notes[0];
var currentLyric = note.lyric.Normalize(); // Normalize(): measures for Unicode
if (!string.IsNullOrEmpty(note.phoneticHint)) {
Expand Down Expand Up @@ -78,6 +81,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN
currentLyric = oto.Alias;
}
} else if (prevNeighbour == null) { // beginning of phrase
preCFlag = true;
if (currentLyric.Contains("・")) {
if (checkOtoUntilHit(glottalCVtests, note, out var oto1)) {
currentLyric = oto1.Alias;
Expand Down Expand Up @@ -185,18 +189,46 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN
}
} else {
// try "- CV" (prev is R, breath, etc.)
preCFlag = true;
var tests = new List<string> { initial, currentLyric };
if (checkOtoUntilHit(tests, note, out var oto)) {
currentLyric = oto.Alias;
}
}
}
result.Add(new Phoneme() { phoneme = currentLyric, index = 0 });

// Insert "- C"
if (string.IsNullOrEmpty(note.phoneticHint)
&& preCFlag
&& !currentLyric.Contains(vcvpad)
&& presamp.PhonemeList.TryGetValue(currentAlias, out PresampPhoneme phoneme)
&& phoneme.HasConsonant) {
if (checkOtoUntilHit(new List<string> { $"-{vcvpad}{phoneme.Consonant}" }, note, 2, out var coto)
&& checkOtoUntilHit(new List<string> { currentLyric }, note, out var oto)) {

var attr = note.phonemeAttributes?.FirstOrDefault(attr => attr.index == 0) ?? default;
var cLength = Math.Max(30, MsToTick(oto.Preutter) * (attr.consonantStretchRatio ?? 1));

if (prevNeighbour != null) {
cLength = Math.Min(prevNeighbour.Value.duration / 2, cLength);
} else if(prev != null) {
cLength = Math.Min(note.position - prev.Value.position - prev.Value.duration, cLength);
}

result.Insert(0, new Phoneme() {
phoneme = coto.Alias,
position = Convert.ToInt32(- cLength),
index = 2
});
}
}

// Insert 2nd phoneme (when next doesn't have hint)
if (nextNeighbour != null && string.IsNullOrEmpty(nextNeighbour.Value.phoneticHint)) {
int totalDuration = notes.Sum(n => n.duration);
if (TickToMs(totalDuration) < 100 && presamp.MustVC == false) {
return MakeSimpleResult(currentLyric);
return new Result { phonemes = result.ToArray() };
}

var nextLyric = nextNeighbour.Value.lyric.Normalize();
Expand All @@ -210,40 +242,40 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN

// Without current vowel, VC cannot be created
if (!presamp.PhonemeList.TryGetValue(currentAlias, out PresampPhoneme currentPhoneme) || !currentPhoneme.HasVowel) {
return MakeSimpleResult(currentLyric);
return new Result { phonemes = result.ToArray() };
}
var vowel = currentPhoneme.Vowel;

if (Regex.IsMatch(nextLyric, "[aiueonN]" + vcvpad) || Regex.IsMatch(nextLyric, "[aiueonN]" + vcpad)) {
// next is VCV or VC (VC is not needed)
return MakeSimpleResult(currentLyric);
return new Result { phonemes = result.ToArray() };
} else {
if (nextLyric.Contains("・")) { // Glottal stop
if (nextLyric == "・") { // next is VC (VC is not needed)
return MakeSimpleResult(currentLyric);
return new Result { phonemes = result.ToArray() };
} else {
vowelUpper = Regex.Match(nextLyric, "[あいうえおんン]").Value;
if (vowelUpper == null) {
return MakeSimpleResult(currentLyric);
return new Result { phonemes = result.ToArray() };
}
// next is VCV (VC is not needed)
var tests = new List<string>{ $"{vowel}{vcvpad}{vowelUpper}・", $"{vowel}{vcvpad}{vowelUpper}" };
if (checkOtoUntilHit(tests, (Note)nextNeighbour, out var oto1) && oto1.Alias.Contains(vcvpad)) {
return MakeSimpleResult(currentLyric);
return new Result { phonemes = result.ToArray() };
}
// next is CV (VC is needed)
tests = new List<string> { $"{vowel}{vcpad}・" };
if (checkOtoUntilHitVc(tests, note, out oto1)) {
if (checkOtoUntilHit(tests, note, 1, out oto1)) {
vcPhoneme = oto1.Alias;
} else {
return MakeSimpleResult(currentLyric);
return new Result { phonemes = result.ToArray() };
}
}
} else {

// Without next consonant, VC cannot be created
if (!presamp.PhonemeList.TryGetValue(nextAlias, out PresampPhoneme nextPhoneme) || !nextPhoneme.HasConsonant) {
return MakeSimpleResult(currentLyric);
return new Result { phonemes = result.ToArray() };
}
var consonant = nextPhoneme.Consonant;

Expand All @@ -255,7 +287,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN
var tests = new List<string>{ nextVCV, nextVC, nextAlias };
if (checkOtoUntilHit(tests, nextNeighbour.Value, out var oto1)
&& (Regex.IsMatch(oto1.Alias, "[aiueonN]" + vcvpad) || Regex.IsMatch(oto1.Alias, "[aiueonN]" + vcpad))) {
return MakeSimpleResult(currentLyric);
return new Result { phonemes = result.ToArray() };
}
}

Expand All @@ -266,10 +298,10 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN
if (substituteLookup.TryGetValue(consonant ?? string.Empty, out var con)) {
vcPhonemes.Add($"{vowel}{vcpad}{con}");
}
if (checkOtoUntilHitVc(vcPhonemes, note, out var oto)) {
if (checkOtoUntilHit(vcPhonemes, note, 1, out var oto)) {
vcPhoneme = oto.Alias;
} else {
return MakeSimpleResult(currentLyric);
return new Result { phonemes = result.ToArray() };
}
}
}
Expand All @@ -286,57 +318,26 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN
}
// Minimam is 30 tick, maximum is half of note
vcLength = Convert.ToInt32(Math.Min(totalDuration / 2, Math.Max(30, vcLength * (nextAttr.consonantStretchRatio ?? 1))));

return new Result {
phonemes = new Phoneme[] {
new Phoneme() {
phoneme = currentLyric
},
new Phoneme() {
phoneme = vcPhoneme,
position = totalDuration - vcLength
}
},
};

result.Add(new Phoneme() {
phoneme = vcPhoneme,
position = totalDuration - vcLength,
index = 1
});
}
}

// No next neighbor
return MakeSimpleResult(currentLyric);
return new Result { phonemes = result.ToArray() };
}

// make it quicker to check multiple oto occurrences at once rather than spamming if else if
private bool checkOtoUntilHit(List<string> input, Note note, out UOto oto) {
oto = default;
var attr = note.phonemeAttributes?.FirstOrDefault(attr => attr.index == 0) ?? default;

var otos = new List<UOto>();
foreach (string test in input) {
if (singer.TryGetMappedOto(test + attr.alternate, note.tone + attr.toneShift, attr.voiceColor, out var otoAlt)) {
otos.Add(otoAlt);
} else if (singer.TryGetMappedOto(test, note.tone + attr.toneShift, attr.voiceColor, out var otoCandidacy)) {
otos.Add(otoCandidacy);
}
}

string color = attr.voiceColor ?? "";
if (otos.Count > 0) {
if (otos.Any(oto => (oto.Color ?? string.Empty) == color)) {
oto = otos.Find(oto => (oto.Color ?? string.Empty) == color);
return true;
} else {
oto = otos.First();
return true;
}
}
return false;
return checkOtoUntilHit(input, note, 0, out oto);
}

// checking VCs
// when VC does not exist, it will not be inserted
private bool checkOtoUntilHitVc(List<string> input, Note note, out UOto oto) {
private bool checkOtoUntilHit(List<string> input, Note note, int index, out UOto oto) {
oto = default;
var attr = note.phonemeAttributes?.FirstOrDefault(attr => attr.index == 1) ?? default;
var attr = note.phonemeAttributes?.FirstOrDefault(attr => attr.index == index) ?? default;

var otos = new List<UOto>();
foreach (string test in input) {
Expand All @@ -353,7 +354,8 @@ private bool checkOtoUntilHitVc(List<string> input, Note note, out UOto oto) {
oto = otos.Find(oto => (oto.Color ?? string.Empty) == color);
return true;
} else {
return false;
oto = otos.First();
return true;
}
}
return false;
Expand Down

0 comments on commit d2271dd

Please sign in to comment.