diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index de3583f3d..9e5418b4b 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -11,7 +11,7 @@ jobs: os: - runs-on: windows-latest arch: win-x64 - - runs-on: macos-latest + - runs-on: macos-13 arch: osx-x64 - runs-on: ubuntu-latest arch: linux-x64 @@ -19,6 +19,10 @@ jobs: steps: - uses: actions/checkout@v1 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + - name: restore run: dotnet restore OpenUtau -r ${{ matrix.os.arch }} diff --git a/OpenUtau.Core/Api/G2pPack.cs b/OpenUtau.Core/Api/G2pPack.cs index 1a2c92aa3..4c5b36305 100644 --- a/OpenUtau.Core/Api/G2pPack.cs +++ b/OpenUtau.Core/Api/G2pPack.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using Microsoft.ML.OnnxRuntime; @@ -82,7 +83,7 @@ public string[] UnpackHint(string hint, char separator = ' ') { return Dict.UnpackHint(hint, separator); } - protected string[] Predict(string grapheme) { + protected virtual string[] Predict(string grapheme) { Tensor src = EncodeWord(grapheme); if (src.Length == 0 || Session == null) { return new string[0]; diff --git a/OpenUtau.Core/Classic/ClassicRenderer.cs b/OpenUtau.Core/Classic/ClassicRenderer.cs index 45544d38c..eaae51d89 100644 --- a/OpenUtau.Core/Classic/ClassicRenderer.cs +++ b/OpenUtau.Core/Classic/ClassicRenderer.cs @@ -107,7 +107,7 @@ public Task RenderExternal(RenderPhrase phrase, Progress progress, result.samples = Wave.GetSamples(waveStream.ToSampleProvider().ToMono(1, 0)); } } catch (Exception e) { - Log.Error(e, "Failed to render."); + Log.Error(e, $"Failed to render: failed to open {wavPath}"); } } if (result.samples == null) { diff --git a/OpenUtau.Core/Commands/Notifications.cs b/OpenUtau.Core/Commands/Notifications.cs index 19ba82c2a..792be0254 100644 --- a/OpenUtau.Core/Commands/Notifications.cs +++ b/OpenUtau.Core/Commands/Notifications.cs @@ -103,10 +103,12 @@ public SelectExpressionNotification(string expKey, int index, bool updateShadow) public class SetPlayPosTickNotification : UNotification { public readonly int playPosTick; public readonly bool waitingRendering; + public readonly bool pause; public override bool Silent => true; - public SetPlayPosTickNotification(int tick, bool waitingRendering = false) { + public SetPlayPosTickNotification(int tick, bool waitingRendering = false, bool pause = false) { playPosTick = tick; this.waitingRendering = waitingRendering; + this.pause = pause; } public override string ToString() => $"Set play position to tick {playPosTick}"; } @@ -114,9 +116,11 @@ public SetPlayPosTickNotification(int tick, bool waitingRendering = false) { // Notification for playback manager to change play position public class SeekPlayPosTickNotification : UNotification { public int playPosTick; + public readonly bool pause; public override bool Silent => true; - public SeekPlayPosTickNotification(int tick) { + public SeekPlayPosTickNotification(int tick, bool pause = false) { playPosTick = tick; + this.pause = pause; } public override string ToString() => $"Seek play position to tick {playPosTick}"; } diff --git a/OpenUtau.Core/Commands/ProjectCommands.cs b/OpenUtau.Core/Commands/ProjectCommands.cs index f279c32c2..b2939d041 100644 --- a/OpenUtau.Core/Commands/ProjectCommands.cs +++ b/OpenUtau.Core/Commands/ProjectCommands.cs @@ -167,6 +167,7 @@ public ConfigureExpressionsCommand( public override string ToString() => "Configure expressions"; public override void Execute() { project.expressions = newDescriptors.ToDictionary(descriptor => descriptor.abbr); + Format.Ustx.AddDefaultExpressions(project); project.parts .Where(part => part is UVoicePart) .ToList() @@ -175,6 +176,7 @@ public override void Execute() { } public override void Unexecute() { project.expressions = oldDescriptors.ToDictionary(descriptor => descriptor.abbr); + Format.Ustx.AddDefaultExpressions(project); project.parts .Where(part => part is UVoicePart) .ToList() diff --git a/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs b/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs index 0bb3298f0..65c81fe06 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs @@ -151,7 +151,7 @@ float[] InvokeDiffsinger(RenderPhrase phrase, double depth, int steps, Cancellat } if (singer.dsConfig.mel_scale != "slaney" && singer.dsConfig.mel_scale != "htk") { throw new Exception( - $"Mel scale must be \"slaney\" or \"htk\", but got \"{vocoder.mel_scale}\" from acoustic model"); + $"Mel scale must be \"slaney\" or \"htk\", but got \"{singer.dsConfig.mel_scale}\" from acoustic model"); } //mel specification matching checks if(vocoder.sample_rate != singer.dsConfig.sample_rate) { diff --git a/OpenUtau.Core/DiffSinger/Phonemizers/DiffSingerKoreanG2PPhonemizer.cs b/OpenUtau.Core/DiffSinger/Phonemizers/DiffSingerKoreanG2PPhonemizer.cs new file mode 100644 index 000000000..c54212ac1 --- /dev/null +++ b/OpenUtau.Core/DiffSinger/Phonemizers/DiffSingerKoreanG2PPhonemizer.cs @@ -0,0 +1,21 @@ +using OpenUtau.Api; +using OpenUtau.Core.G2p; + +namespace OpenUtau.Core.DiffSinger +{ + [Phonemizer("DiffSinger Korean G2P Phonemizer", "DIFFS KO", language: "KO", author: "Cardroid6")] + public class DiffSingerKoreanG2PPhonemizer : DiffSingerG2pPhonemizer + { + protected override string GetDictionaryName() => "dsdict-ko.yaml"; + protected override IG2p LoadBaseG2p() => new KoreanG2p(); + protected override string[] GetBaseG2pVowels() => new string[] { + "a", "e", "eo", "eu", "i", "o", "u", "w", "y" + }; + + protected override string[] GetBaseG2pConsonants() => new string[] { + "K", "L", "M", "N", "NG", "P", "T", "b", "ch", "d", + "g", "h", "j", "jj", "k", "kk", "m", "n", "p", "pp", + "r", "s", "ss", "t", "tt" + }; + } +} diff --git a/OpenUtau.Core/DiffSinger/Phonemizers/DiffSingerKoreanPhonemizer.cs b/OpenUtau.Core/DiffSinger/Phonemizers/DiffSingerKoreanPhonemizer.cs index 8949ffdfd..e3c641418 100644 --- a/OpenUtau.Core/DiffSinger/Phonemizers/DiffSingerKoreanPhonemizer.cs +++ b/OpenUtau.Core/DiffSinger/Phonemizers/DiffSingerKoreanPhonemizer.cs @@ -1,380 +1,38 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.ML.OnnxRuntime; -using Microsoft.ML.OnnxRuntime.Tensors; using OpenUtau.Api; using OpenUtau.Core.Ustx; -using Serilog; - -namespace OpenUtau.Core.DiffSinger{ - [Phonemizer("DiffSinger Korean Phonemizer", "DIFFS KO", language: "KO", author: "EX3")] - public class DiffSingerKoreanPhonemizer : DiffSingerBasePhonemizer{ - USinger singer; - DsConfig dsConfig; - string rootPath; - float frameMs; - InferenceSession linguisticModel; - InferenceSession durationModel; - IG2p g2p; - List phonemes; - DiffSingerSpeakerEmbedManager speakerEmbedManager; +using System.Collections.Generic; +using System.Linq; - string defaultPause = "SP"; +namespace OpenUtau.Core.DiffSinger +{ + [Phonemizer("DiffSinger Korean Phonemizer", "DIFFS KO","EX3", language:"KO")] + public class DiffSingerKoreanPhonemizer : DiffSingerBasePhonemizer + { + protected override string GetDictionaryName()=>"dsdict-ko.yaml"; - public override void SetSinger(USinger singer) { - this.singer = singer; - if (File.Exists(Path.Join(singer.Location, "dsdur", "dsconfig.yaml"))) { - rootPath = Path.Combine(singer.Location, "dsdur"); - } else { - rootPath = singer.Location; - } - //Load Config - var configPath = Path.Join(rootPath, "dsconfig.yaml"); - try { - var configTxt = File.ReadAllText(configPath); - dsConfig = Yaml.DefaultDeserializer.Deserialize(configTxt); - } catch(Exception e) { - Log.Error(e, $"failed to load dsconfig from {configPath}"); - return; - } - this.frameMs = dsConfig.frameMs(); - //Load g2p - g2p = LoadG2p(rootPath); - //Load phonemes list - string phonemesPath = Path.Combine(rootPath, dsConfig.phonemes); - phonemes = File.ReadLines(phonemesPath,singer.TextFileEncoding).ToList(); - //Load models - var linguisticModelPath = Path.Join(rootPath, dsConfig.linguistic); - try { - linguisticModel = new InferenceSession(linguisticModelPath); - } catch (Exception e) { - Log.Error(e, $"failed to load linguistic model from {linguisticModelPath}"); + public override void SetUp(Note[][] groups, UProject project, UTrack track) { + if (groups.Length == 0) { return; } - var durationModelPath = Path.Join(rootPath, dsConfig.dur); - try { - durationModel = new InferenceSession(durationModelPath); - } catch (Exception e) { - Log.Error(e, $"failed to load duration model from {durationModelPath}"); - return; - } - } + // variate lyrics + KoreanPhonemizerUtil.RomanizeNotes(groups, false); - string[] GetSymbols(Note note) { - //priority: - //1. phonetic hint - //2. query from g2p dictionary - //3. treat lyric as phonetic hint, including single phoneme - //4. default pause - if (!string.IsNullOrEmpty(note.phoneticHint)) { - // Split space-separated symbols into an array. - return note.phoneticHint.Split() - .Where(s => g2p.IsValidSymbol(s)) // skip the invalid symbols. - .ToArray(); - } - // User has not provided hint, query g2p dictionary. - var g2presult = g2p.Query(note.lyric) - ?? g2p.Query(note.lyric.ToLowerInvariant()); - if(g2presult != null) { - return g2presult; - } - //not founded in g2p dictionary, treat lyric as phonetic hint - var lyricSplited = note.lyric.Split() - .Where(s => g2p.IsValidSymbol(s)) // skip the invalid symbols. - .ToArray(); - if (lyricSplited.Length > 0) { - return lyricSplited; - } - return new string[] { defaultPause }; - } - - // public List stretch(IList source, double ratio, double endPos, bool isVowelWithSemiPhoneme) { - // // 이중모음(y, w) 뒤의 모음일 경우, 이 함수를 호출해서 모음의 startPos를 자신의 8분의 1 길이만큼 추가한다. (타이밍을 뒤로 민다) - // //source:音素时长序列,单位ms - // //ratio:缩放比例 - // //endPos:目标终点时刻,单位ms - // //输出:缩放后的音素位置,单位ms - // if (isVowelWithSemiPhoneme){ - // double startPos = endPos - source.Sum() * ratio; - // startPos /= 2; - // var result = CumulativeSum(source.Select(x => x * ratio).Prepend(0), startPos).ToList(); - // result.RemoveAt(result.Count - 1); - // return result; - // } - // else{ - // return stretch(source, ratio, endPos); - // } - // } - string GetSpeakerAtIndex(Note note, int index){ - var attr = note.phonemeAttributes?.FirstOrDefault(attr => attr.index == index) ?? default; - var speaker = singer.Subbanks - .Where(subbank => subbank.Color == attr.voiceColor && subbank.toneSet.Contains(note.tone)) - .FirstOrDefault(); - if(speaker is null) { - return ""; - } - return speaker.Suffix; - } - - dsPhoneme[] GetDsPhonemes(Note note){ - return GetSymbols(note) - .Select((symbol, index) => new dsPhoneme(symbol, GetSpeakerAtIndex(note, index))) - .ToArray(); - } - - List ProcessWord(Note[] notes, bool isLastNote){ - var wordPhonemes = new List{ - new phonemesPerNote(-1, notes[0].tone) - }; - var dsPhonemes = GetDsPhonemes(notes[0]); - var isVowel = dsPhonemes.Select(s => isPlainVowel(s.Symbol)).ToArray(); - var symbols = dsPhonemes.Select(s => s.Symbol).ToArray(); - var isThisSemiVowel = dsPhonemes.Select(s => isSemiVowel(s.Symbol)).ToArray(); - - - var nonExtensionNotes = notes.Where(n=>!IsSyllableVowelExtensionNote(n)).ToArray(); - //distribute phonemes to notes - var noteIndex = 0; - for (int i = 0; i < dsPhonemes.Length; i++) { - if (isVowel[i] && noteIndex < nonExtensionNotes.Length && i == dsPhonemes.Length - 1) { - // 받침 없는 노트 - var note = nonExtensionNotes[noteIndex]; - wordPhonemes.Add(new phonemesPerNote(note.position, note.tone)); - noteIndex++; + //Split song into sentences (phrases) + var phrase = new List { groups[0] }; + for (int i = 1; i < groups.Length; ++i) { + //If the previous and current notes are connected, do not split the sentence + if (groups[i - 1][^1].position + groups[i - 1][^1].duration == groups[i][0].position) { + phrase.Add(groups[i]); + } else { + //If the previous and current notes are not connected, process the current sentence and start the next sentence + ProcessPart(phrase.ToArray()); + phrase.Clear(); + phrase.Add(groups[i]); } - else if (isVowel[i] && noteIndex < nonExtensionNotes.Length && i == dsPhonemes.Length - 2) { - // 받침 있는 노트 - var note = nonExtensionNotes[noteIndex]; - wordPhonemes.Add(new phonemesPerNote(note.position, note.tone)); - } - else if (isThisSemiVowel[i] && noteIndex < nonExtensionNotes.Length){ - // 반모음이 너무 짧으면 부자연스러우니 24분의 1만큼 늘려줌 - var note = nonExtensionNotes[noteIndex]; - wordPhonemes.Add(new phonemesPerNote(note.position - note.duration / 24, note.tone)); - } - - - wordPhonemes[^1].Phonemes.Add(dsPhonemes[i]); - } - return wordPhonemes; - } - - int makePos(int duration, int divider, int targetPos){ - return duration - Math.Min(duration / divider, targetPos); - } - - int framesBetweenTickPos(double tickPos1, double tickPos2) { - return (int)(timeAxis.TickPosToMsPos(tickPos2)/frameMs) - - (int)(timeAxis.TickPosToMsPos(tickPos1)/frameMs); - } - - - - protected override void ProcessPart(Note[][] phrase) { - - float padding = 1000f; //Padding time for consonants at the beginning of a sentence, ms - - float frameMs = dsConfig.frameMs(); - var startMs = timeAxis.TickPosToMsPos(phrase[0][0].position) - padding; - var lastNote = phrase[^1][^1]; - var endTick = lastNote.position+lastNote.duration; - //[(Tick position of note, [phonemes])] - //The first item of this list is for the consonants before the first note. - var phrasePhonemes = new List{ - new phonemesPerNote(-1,phrase[0][0].tone, new List{new dsPhoneme("SP", GetSpeakerAtIndex(phrase[0][0], 0))}) - }; - var notePhIndex = new List { 1 }; - String? next; - String? prev = null; - try{ - next = phrase[1][0].lyric; - } - catch{ - next = null; - } - int i = 0; - bool isLastNote = false; - foreach (var note in phrase) { - next = null; - if (i != phrase.Length - 1){ - next = phrase[i + 1][0].lyric; - } - - String? prevTemp = note[0].lyric; - - // Phoneme variation - if (KoreanPhonemizerUtil.IsHangeul(prevTemp)){ - // Debug.Print("prev: " + prev + "curr: " + character[0].lyric + "next: " + next); - note[0].lyric = KoreanPhonemizerUtil.Variate(prev, prevTemp, next); - // Debug.Print(character[0].lyric); - } - - prev = prevTemp; - - - if (i == phrase.Length - 1){ - isLastNote = true; - } - else{ - isLastNote = false; - } - - // Pass isLastNote to handle Last Consonant(Batchim)'s length. - var wordPhonemes = ProcessWord(note, isLastNote); - - phrasePhonemes[^1].Phonemes.AddRange(wordPhonemes[0].Phonemes); - phrasePhonemes.AddRange(wordPhonemes.Skip(1)); - notePhIndex.Add(notePhIndex[^1]+wordPhonemes.SelectMany(n=>n.Phonemes).Count()); - - i += 1; - } - - - - - phrasePhonemes.Add(new phonemesPerNote(endTick,lastNote.tone)); - phrasePhonemes[0].Position = timeAxis.MsPosToTickPos( - timeAxis.TickPosToMsPos(phrasePhonemes[1].Position)-padding - ); - //Linguistic Encoder - var tokens = phrasePhonemes - .SelectMany(n => n.Phonemes) - .Select(p => (Int64)phonemes.IndexOf(p.Symbol)) - .ToArray(); - var word_div = phrasePhonemes.Take(phrasePhonemes.Count-1) - .Select(n => (Int64)n.Phonemes.Count) - .ToArray(); - //Pairwise(phrasePhonemes) - var word_dur = phrasePhonemes - .Zip(phrasePhonemes.Skip(1), (a, b) => (long)framesBetweenTickPos(a.Position, b.Position)) - .ToArray(); - //Call Diffsinger Linguistic Encoder model - var linguisticInputs = new List(); - linguisticInputs.Add(NamedOnnxValue.CreateFromTensor("tokens", - new DenseTensor(tokens, new int[] { tokens.Length }, false) - .Reshape(new int[] { 1, tokens.Length }))); - linguisticInputs.Add(NamedOnnxValue.CreateFromTensor("word_div", - new DenseTensor(word_div, new int[] { word_div.Length }, false) - .Reshape(new int[] { 1, word_div.Length }))); - linguisticInputs.Add(NamedOnnxValue.CreateFromTensor("word_dur", - new DenseTensor(word_dur, new int[] { word_dur.Length }, false) - .Reshape(new int[] { 1, word_dur.Length }))); - var linguisticOutputs = linguisticModel.Run(linguisticInputs); - Tensor encoder_out = linguisticOutputs - .Where(o => o.Name == "encoder_out") - .First() - .AsTensor(); - Tensor x_masks = linguisticOutputs - .Where(o => o.Name == "x_masks") - .First() - .AsTensor(); - //Duration Predictor - var ph_midi = phrasePhonemes - .SelectMany(n=>Enumerable.Repeat((Int64)n.Tone, n.Phonemes.Count)) - .ToArray(); - //Call Diffsinger Duration Predictor model - var durationInputs = new List(); - durationInputs.Add(NamedOnnxValue.CreateFromTensor("encoder_out", encoder_out)); - durationInputs.Add(NamedOnnxValue.CreateFromTensor("x_masks", x_masks)); - durationInputs.Add(NamedOnnxValue.CreateFromTensor("ph_midi", - new DenseTensor(ph_midi, new int[] { ph_midi.Length }, false) - .Reshape(new int[] { 1, ph_midi.Length }))); - //Speaker - if(dsConfig.speakers != null){ - var speakerEmbedManager = getSpeakerEmbedManager(); - var speakersByPhone = phrasePhonemes - .SelectMany(n => n.Phonemes) - .Select(p => p.Speaker) - .ToArray(); - var spkEmbedTensor = speakerEmbedManager.PhraseSpeakerEmbedByPhone(speakersByPhone); - durationInputs.Add(NamedOnnxValue.CreateFromTensor("spk_embed", spkEmbedTensor)); - } - var durationOutputs = durationModel.Run(durationInputs); - List durationFrames = durationOutputs.First().AsTensor().Select(x=>(double)x).ToList(); - - //Alignment - //(the index of the phoneme to be aligned, the Ms position of the phoneme) - var phAlignPoints = new List>(); - phAlignPoints = CumulativeSum(phrasePhonemes.Select(n => n.Phonemes.Count).ToList(), 0) - .Zip(phrasePhonemes.Skip(1), // - (a, b) => new Tuple(a, timeAxis.TickPosToMsPos(b.Position))) - .ToList(); - var positions = new List(); - List alignGroup = durationFrames.GetRange(1, phAlignPoints[0].Item1 - 1); - - var phs = phrasePhonemes.SelectMany(n => n.Phonemes).ToList(); - //The starting consonant's duration keeps unchanged - positions.AddRange(stretch(alignGroup, frameMs, phAlignPoints[0].Item2)); - - - - int j = 0; - double prevRatio = 0; - //Stretch the duration of the rest phonemes - var prevAlignPoint = phAlignPoints[0]; - var zipped = phAlignPoints.Zip(phAlignPoints.Skip(1), (a, b) => Tuple.Create(a, b)); - foreach (var pair in zipped) { - var currAlignPoint = pair.Item1; - var nextAlignPoint = pair.Item2; - alignGroup = durationFrames.GetRange(currAlignPoint.Item1, nextAlignPoint.Item1 - currAlignPoint.Item1); - double ratio = (nextAlignPoint.Item2 - currAlignPoint.Item2) / alignGroup.Sum(); - - positions.AddRange(stretch(alignGroup, ratio, nextAlignPoint.Item2)); - - prevAlignPoint = phAlignPoints[j]; - prevRatio = ratio; - j += 1; - } - - //Convert the position sequence to tick and fill into the result list - int index = 1; - foreach (int groupIndex in Enumerable.Range(0, phrase.Length)) { - Note[] group = phrase[groupIndex]; - var noteResult = new List>(); - if (group[0].lyric.StartsWith("+")) { - continue; - } - double notePos = timeAxis.TickPosToMsPos(group[0].position);//start position of the note, ms - for (int phIndex = notePhIndex[groupIndex]; phIndex < notePhIndex[groupIndex + 1]; ++phIndex) { - if (!String.IsNullOrEmpty(phs[phIndex].Symbol)) { - noteResult.Add(Tuple.Create(phs[phIndex].Symbol, timeAxis.TicksBetweenMsPos( - notePos, positions[phIndex - 1]))); - } - } - partResult[group[0].position] = noteResult; - } - } - - private bool isPlainVowel(string symbol){ - if (isSemiVowel(symbol)){ - return false; - } - else if (isBatchim(symbol)){ - return false; - } - else{ - return g2p.IsVowel(symbol); - } - } - - private bool isSemiVowel(string symbol){ - if (symbol.Equals("w") || symbol.Equals("y")){ - return true; - } - else{ - return false; - } - } - - private bool isBatchim(string symbol){ - if (symbol.Equals("K") || symbol.Equals("N") || symbol.Equals("T") || symbol.Equals("L") || symbol.Equals("M") || symbol.Equals("P")|| symbol.Equals("NG")){ - return true; } - else{ - return false; + if (phrase.Count > 0) { + ProcessPart(phrase.ToArray()); } } } -} \ No newline at end of file +} diff --git a/OpenUtau.Core/Enunu/EnunuKoreanPhonemizer.cs b/OpenUtau.Core/Enunu/EnunuKoreanPhonemizer.cs index 1064ba437..e7acd5481 100644 --- a/OpenUtau.Core/Enunu/EnunuKoreanPhonemizer.cs +++ b/OpenUtau.Core/Enunu/EnunuKoreanPhonemizer.cs @@ -393,7 +393,7 @@ public enum BatchimType{ } Dictionary partResult = new Dictionary(); - + public override void SetUp(Note[][] notes, UProject project, UTrack track) { partResult.Clear(); if (notes.Length == 0 || singer == null || !singer.Found) { @@ -484,7 +484,7 @@ static Phoneme[] ParseLabel(string path) { } protected override EnunuNote[] NoteGroupsToEnunu(Note[][] notes) { - KoreanPhonemizerUtil.RomanizeNotes(notes, FirstConsonants, MiddleVowels, LastConsonants, semivowelSep); + KoreanPhonemizerUtil.RomanizeNotes(notes, true, FirstConsonants, MiddleVowels, LastConsonants, semivowelSep); var result = new List(); int position = 0; int index = 0; @@ -513,69 +513,6 @@ protected override EnunuNote[] NoteGroupsToEnunu(Note[][] notes) { return result.ToArray(); } - // public void AdjustPos(Phoneme[] phonemes, Note[] prevNote){ - // //TODO - // Phoneme? prevPhone = null; - // Phoneme? nextPhone = null; - // Phoneme currPhone; - - // int length = phonemes.Last().position; - // int prevLength; - // if (prevNote == null){ - // prevLength = length; - // } - // else{ - // prevLength = MsToTick(prevNote.Sum(n => n.duration)); - // } - - // for (int i=0; i < phonemes.Length; i++) { - // currPhone = phonemes[i]; - // if (i < phonemes.Length - 1){ - // nextPhone = phonemes[i+1]; - // } - // else{ - // nextPhone = null; - // } - - // if (i == 0){ - // // TODO 받침 + 자음 오면 받침길이 + 자음길이 / 2의 위치에 자음이 오도록 하기 - // if (isPlainVowel(phonemes[i].phoneme)) { - // phonemes[i].position = 0; - // } - // else if (nextPhone != null && ! isPlainVowel(((Phoneme)nextPhone).phoneme) && ! isSemivowel(((Phoneme)nextPhone).phoneme) && isPlainVowel(((Phoneme)nextPhone).phoneme) && isSemivowel(currPhone.phoneme)) { - // phonemes[i + 1].position = length / 10; - // } - // else if (nextPhone != null && isSemivowel(((Phoneme)nextPhone).phoneme)){ - // if (i + 2 < phonemes.Length){ - // phonemes[i + 2].position = length / 10; - // } - - // } - // } - // prevPhone = currPhone; - // } - // } - - // private bool isPlainVowel(string phoneme){ - // if (phoneme == koreanENUNUSetting.GetPlainVowelPhoneme("ㅏ") || phoneme == koreanENUNUSetting.GetPlainVowelPhoneme("ㅣ") || phoneme == koreanENUNUSetting.GetPlainVowelPhoneme("ㅜ") || phoneme == koreanENUNUSetting.GetPlainVowelPhoneme("ㅔ") || phoneme == koreanENUNUSetting.GetPlainVowelPhoneme("ㅗ") || phoneme == koreanENUNUSetting.GetPlainVowelPhoneme("ㅡ") || phoneme == koreanENUNUSetting.GetPlainVowelPhoneme("ㅓ")){ - // return true; - // } - // return false; - // } - - // private bool isBatchim(string phoneme){ - // if (phoneme == koreanENUNUSetting.GetFinalConsonantPhoneme("ㄱ") || phoneme == koreanENUNUSetting.GetFinalConsonantPhoneme("ㄴ") || phoneme == koreanENUNUSetting.GetFinalConsonantPhoneme("ㄷ") || phoneme == koreanENUNUSetting.GetFinalConsonantPhoneme("ㄹ") || phoneme == koreanENUNUSetting.GetFinalConsonantPhoneme("ㅁ") || phoneme == koreanENUNUSetting.GetFinalConsonantPhoneme("ㅂ") || phoneme == koreanENUNUSetting.GetFinalConsonantPhoneme("ㅇ")){ - // return true; - // } - // return false; - // } - - // private bool isSemivowel(string phoneme) { - // if (phoneme == koreanENUNUSetting.GetSemiVowelPhoneme("w") || phoneme == koreanENUNUSetting.GetSemiVowelPhoneme("y")){ - // return true; - // } - // return false; - // } public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevNeighbour, Note? nextNeighbour, Note[] prevs) { if (partResult.TryGetValue(notes, out var phonemes)) { var phonemes_ = phonemes.Select(p => { @@ -584,7 +521,6 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN return p; }).ToArray(); - //AdjustPos(phonemes_, prevs); return new Result { phonemes = phonemes_, }; diff --git a/OpenUtau.Core/Format/USTx.cs b/OpenUtau.Core/Format/USTx.cs index 566a84c2a..65b08d1c6 100644 --- a/OpenUtau.Core/Format/USTx.cs +++ b/OpenUtau.Core/Format/USTx.cs @@ -59,6 +59,32 @@ public static void AddDefaultExpressions(UProject project) { project.RegisterExpression(new UExpressionDescriptor("tone shift (curve)", SHFC, -1200, 1200, 0) { type = UExpressionType.Curve }); project.RegisterExpression(new UExpressionDescriptor("tension (curve)", TENC, -100, 100, 0) { type = UExpressionType.Curve }); project.RegisterExpression(new UExpressionDescriptor("voicing (curve)", VOIC, 0, 100, 100) { type = UExpressionType.Curve }); + + string message = string.Empty; + if (ValidateExpression(project, "g", GEN)) { + message += $"\ng flag -> gender"; + } + if (ValidateExpression(project, "B", BRE)) { + message += $"\nB flag -> {BRE}"; + } + if (ValidateExpression(project, "H", LPF)) { + message += $"\nH flag-> {LPF}"; + } + if (ValidateExpression(project, "P", NORM)) { + message += $"\nP flag-> normalize"; + } + if (message != string.Empty) { + var e = new MessageCustomizableException("Expressions have been merged due to duplicate flags", $":{message}", new Exception(), false); + DocManager.Inst.ExecuteCmd(new ErrorMessageNotification(e)); + } + } + private static bool ValidateExpression(UProject project, string flag, string abbr) { + if (project.expressions.Any(e => e.Value.flag == flag && e.Value.abbr != abbr)) { + var oldExp = project.expressions.First(e => e.Value.flag == flag && e.Value.abbr != abbr); + project.MargeExpression(oldExp.Value.abbr, abbr); + return true; + } + return false; } public static UProject Create() { diff --git a/OpenUtau.Core/G2p/Data/Resources.Designer.cs b/OpenUtau.Core/G2p/Data/Resources.Designer.cs index 51730f440..ed9d3451e 100644 --- a/OpenUtau.Core/G2p/Data/Resources.Designer.cs +++ b/OpenUtau.Core/G2p/Data/Resources.Designer.cs @@ -159,5 +159,15 @@ internal static byte[] g2p_ru { return ((byte[])(obj)); } } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] g2p_ko { + get { + object obj = ResourceManager.GetObject("g2p-ko", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/OpenUtau.Core/G2p/Data/Resources.resx b/OpenUtau.Core/G2p/Data/Resources.resx index 2ee20cd9e..fe1a0ffa5 100644 --- a/OpenUtau.Core/G2p/Data/Resources.resx +++ b/OpenUtau.Core/G2p/Data/Resources.resx @@ -136,6 +136,9 @@ g2p-jyutping.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + g2p-ko.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + g2p-man.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/OpenUtau.Core/G2p/Data/g2p-ko.zip b/OpenUtau.Core/G2p/Data/g2p-ko.zip new file mode 100644 index 000000000..87112a9e5 Binary files /dev/null and b/OpenUtau.Core/G2p/Data/g2p-ko.zip differ diff --git a/OpenUtau.Core/G2p/KoreanG2p.cs b/OpenUtau.Core/G2p/KoreanG2p.cs new file mode 100644 index 000000000..da3095760 --- /dev/null +++ b/OpenUtau.Core/G2p/KoreanG2p.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Microsoft.ML.OnnxRuntime; + +using OpenUtau.Api; + +namespace OpenUtau.Core.G2p { + public class KoreanG2p : G2pPack { + private static readonly string[] graphemes = new string[] { + "", "", "", "", "ㄱ", "ㄲ", "ㄳ", "ㄴ", "ㄵ", "ㄶ", "ㄷ", + "ㄸ", "ㄹ", "ㄺ", "ㄻ", "ㄼ", "ㄾ", "ㅀ", "ㅁ", "ㅂ", "ㅃ", + "ㅄ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", + "ㅎ", "ㅏ", "ㅐ", "ㅑ", "ㅒ", "ㅓ", "ㅔ", "ㅕ", "ㅖ", "ㅗ", + "ㅘ", "ㅙ", "ㅚ", "ㅛ", "ㅜ", "ㅝ", "ㅞ", "ㅟ", "ㅠ", "ㅡ", + "ㅢ", "ㅣ", + }; + + private static readonly string[] phonemes = new string[] { + "", "", "", "", "K", "L", "M", "N", "NG", "P", "T", + "a", "b", "ch", "d", "e", "eo", "eu", "g", "h", + "i", "j", "jj", "k", "kk", "m", "n", "o", "p", "pp", + "r", "s", "ss", "t", "tt", "u", "w", "y", + }; + + private static object lockObj = new object(); + private static Dictionary graphemeIndexes; + private static IG2p dict; + private static InferenceSession session; + private static Dictionary predCache = new Dictionary(); + + public KoreanG2p() { + lock (lockObj) { + if (graphemeIndexes == null) { + graphemeIndexes = graphemes + .Skip(4) + .Select((g, i) => Tuple.Create(g, i)) + .ToDictionary(t => t.Item1, t => t.Item2 + 4); + var tuple = LoadPack( + Data.Resources.g2p_ko, + s => s.ToLowerInvariant(), + s => RemoveTailDigits(s.ToLowerInvariant())); + dict = tuple.Item1; + session = tuple.Item2; + } + } + GraphemeIndexes = graphemeIndexes; + Phonemes = phonemes; + Dict = dict; + Session = session; + PredCache = predCache; + } + + protected override string[] Predict(string grapheme) { + var sb = new StringBuilder(); + foreach (var item in grapheme) { + if (TryDivideHangeul(item, out var jamo)) { + sb.Append(jamo); + } else { + sb.Append(item); + } + } + + return base.Predict(sb.ToString()); + } + + private static readonly string onset = "ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ"; + private static readonly string nucleus = "ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ"; + private static readonly string coda = " ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ"; + + private static readonly ushort UnicodeHangeulBase = 0xAC00; + private static readonly ushort UnicodeHangeulLast = 0xD79F; + + public bool TryDivideHangeul(char c, out string result) { + ushort check = Convert.ToUInt16(c); + + if (check > UnicodeHangeulLast || check < UnicodeHangeulBase) { + result = ""; + return false; + } + + int Code = check - UnicodeHangeulBase; + + int codaCode = Code % 28; + Code = (Code - codaCode) / 28; + + int nucleusCode = Code % 21; + Code = (Code - nucleusCode) / 21; + + int onsetCode = Code; + + result = $"{onset[onsetCode]}{nucleus[nucleusCode]}{coda[codaCode]}"; + return true; + } + } +} diff --git a/OpenUtau.Core/KoreanPhonemizerUtil.cs b/OpenUtau.Core/KoreanPhonemizerUtil.cs index 632d195cd..5f91af913 100644 --- a/OpenUtau.Core/KoreanPhonemizerUtil.cs +++ b/OpenUtau.Core/KoreanPhonemizerUtil.cs @@ -8,6 +8,7 @@ using OpenUtau.Classic; using Serilog; using static OpenUtau.Api.Phonemizer; +using OpenUtau.Api; namespace OpenUtau.Core { /// @@ -177,20 +178,20 @@ public static Hashtable Separate(string character) { } /// - /// merges separated hangeul into complete hangeul. (Example: {[0]: "ㄱ", [1]: "ㅏ", [2]: " "} => "가"}) + /// merges separated hangeul into complete hangeul. (Example: {[offset + 0]: "ㄱ", [offset + 1]: "ㅏ", [offset + 2]: " "} => "가"}) /// 자모로 쪼개진 한글을 합쳐진 한글로 반환합니다. /// /// separated Hangeul. /// Returns complete Hangeul Character. - public static string Merge(Hashtable separatedHangeul){ + public static string Merge(Hashtable separatedHangeul, int offset = 0){ int firstConsonantIndex; // (ex) 2 int middleVowelIndex; // (ex) 2 int lastConsonantIndex; // (ex) 21 - char firstConsonant = ((string)separatedHangeul[0])[0]; // (ex) "ㄴ" - char middleVowel = ((string)separatedHangeul[1])[0]; // (ex) "ㅑ" - char lastConsonant = ((string)separatedHangeul[2])[0]; // (ex) "ㅇ" + char firstConsonant = ((string)separatedHangeul[offset + 0])[0]; // (ex) "ㄴ" + char middleVowel = ((string)separatedHangeul[offset + 1])[0]; // (ex) "ㅑ" + char lastConsonant = ((string)separatedHangeul[offset + 2])[0]; // (ex) "ㅇ" if (firstConsonant == ' ') {firstConsonant = 'ㅇ';} @@ -919,10 +920,7 @@ public static String Variate(String? prevNeighbour, String note, String? nextNei result.Add(7, thisNoteSeparated[4]); result.Add(8, thisNoteSeparated[5]); - return Merge(new Hashtable{ - [0] = (string)result[3], - [1] = (string)result[4], - [2] = (string)result[5]}); + return Merge(result, 3); } } else if ((lyrics[0] != null) && (lyrics[2] == null)) { @@ -941,10 +939,7 @@ public static String Variate(String? prevNeighbour, String note, String? nextNei result.Add(7, "null"); result.Add(8, "null"); - return Merge(new Hashtable{ - [0] = (string)result[3], - [1] = (string)result[4], - [2] = (string)result[5]}); + return Merge(result, 3); } else if (whereYeonEum == 0) { // 앞 노트에서 단어가 끝났다고 가정 @@ -959,10 +954,7 @@ public static String Variate(String? prevNeighbour, String note, String? nextNei result.Add(7, "null"); result.Add(8, "null"); - return Merge(new Hashtable{ - [0] = (string)result[3], - [1] = (string)result[4], - [2] = (string)result[5]}); + return Merge(result, 3); } else { Hashtable result = Variate(lyrics[0], lyrics[1], 0); // 첫 글자 @@ -976,10 +968,7 @@ public static String Variate(String? prevNeighbour, String note, String? nextNei result.Add(7, "null"); result.Add(8, "null"); - return Merge(new Hashtable{ - [0] = (string)result[3], - [1] = (string)result[4], - [2] = (string)result[5]}); + return Merge(result, 3); } } else if ((lyrics[0] != null) && (lyrics[2] != null)) { @@ -998,10 +987,7 @@ public static String Variate(String? prevNeighbour, String note, String? nextNei result.Add(7, thisNoteSeparated[4]); result.Add(8, thisNoteSeparated[5]); - return Merge(new Hashtable{ - [0] = (string)result[3], - [1] = (string)result[4], - [2] = (string)result[5]}); + return Merge(result, 3); } else if (whereYeonEum == 0) { // 앞 노트에서 단어가 끝났다고 가정 / 릎. [위] 놓 @@ -1016,10 +1002,7 @@ public static String Variate(String? prevNeighbour, String note, String? nextNei result.Add(7, thisNoteSeparated[4]); result.Add(8, thisNoteSeparated[5]); - return Merge(new Hashtable{ - [0] = (string)result[3], - [1] = (string)result[4], - [2] = (string)result[5]}); + return Merge(result, 3); } else { Hashtable result = Variate(lyrics[0], lyrics[1], 0); @@ -1033,10 +1016,7 @@ public static String Variate(String? prevNeighbour, String note, String? nextNei result.Add(7, thisNoteSeparated[4]); result.Add(8, thisNoteSeparated[5]); - return Merge(new Hashtable{ - [0] = (string)result[3], - [1] = (string)result[4], - [2] = (string)result[5]}); + return Merge(result, 3); } } else { @@ -1060,11 +1040,7 @@ public static String Variate(String? prevNeighbour, String note, String? nextNei result.Add(7, "null"); result.Add(8, "null"); - return Merge(new Hashtable{ - [0] = (string)result[3], - [1] = (string)result[4], - [2] = (string)result[5] - }); + return Merge(result, 3); } } @@ -1081,10 +1057,27 @@ public static Note[] ChangeLyric(Note[] group, string lyric) { }; return group; } - public static void RomanizeNotes(Note[][] groups, Dictionary firstConsonants, Dictionary vowels, Dictionary lastConsonants, string semivowelSeparator=" ") { - // for ENUNU Phonemizer - + + public static void ModifyLyrics(Hashtable lyricSeparated,string lyric, Dictionary firstConsonants, Dictionary vowels, Dictionary lastConsonants, string semivowelSeparator){ + lyric += firstConsonants[(string)lyricSeparated[3]][0]; + if (vowels[(string)lyricSeparated[4]][1] != "") { + // this vowel contains semivowel + lyric += semivowelSeparator + vowels[(string)lyricSeparated[4]][1] + vowels[(string)lyricSeparated[4]][2]; + } + else{ + lyric += " " + vowels[(string)lyricSeparated[4]][2]; + } + + lyric += lastConsonants[(string)lyricSeparated[5]][0]; + } + + public static void RomanizeNotes(Note[][] groups, bool _modifyLyrics = false, Dictionary firstConsonants = null, Dictionary vowels = null, Dictionary lastConsonants = null, string semivowelSeparator = " ") { + // for ENUNU & DIFFS Phonemizer + int noteIdx = 0; + string lyric; + bool modifyLyrics = (!_modifyLyrics || firstConsonants == null || vowels == null || lastConsonants == null) ? false : true; + Note[] currentNote; Note[]? prevNote = null; Note[]? nextNote; @@ -1092,10 +1085,13 @@ public static void RomanizeNotes(Note[][] groups, Dictionary f Note? prevNote_; Note? nextNote_; - List ResultLyrics = new List(); + foreach (Note[] group in groups){ currentNote = groups[noteIdx]; + string originalLyric; // uses this when no variation needed + originalLyric = currentNote[0].lyric; + if (groups.Length > noteIdx + 1 && IsHangeul(groups[noteIdx + 1][0].lyric)) { nextNote = groups[noteIdx + 1]; } @@ -1120,7 +1116,7 @@ public static void RomanizeNotes(Note[][] groups, Dictionary f } else{nextNote_ = null;} - string lyric = ""; + lyric = originalLyric; if (! IsHangeul(currentNote[0].lyric)){ ResultLyrics.Add(currentNote[0].lyric); @@ -1129,27 +1125,27 @@ public static void RomanizeNotes(Note[][] groups, Dictionary f continue; } - Hashtable lyricSeparated = Variate(prevNote_, currentNote[0], nextNote_); - lyric += firstConsonants[(string)lyricSeparated[3]][0]; - if (vowels[(string)lyricSeparated[4]][1] != "") { - // this vowel contains semivowel - lyric += semivowelSeparator + vowels[(string)lyricSeparated[4]][1] + vowels[(string)lyricSeparated[4]][2]; - } - else{ - lyric += " " + vowels[(string)lyricSeparated[4]][2]; - } - - lyric += lastConsonants[(string)lyricSeparated[5]][0]; + + Hashtable lyricSeparated = Variate(prevNote_, currentNote[0], nextNote_); - ResultLyrics.Add(lyric.Trim()); + if (modifyLyrics) { + ModifyLyrics(lyricSeparated, lyric, firstConsonants, vowels, lastConsonants, semivowelSeparator); + } + else { + lyric = Merge(lyricSeparated, 3); + } + + ResultLyrics.Add(lyric.Trim()); - prevNote = currentNote; + prevNote = currentNote; - noteIdx++; + noteIdx++; + } Enumerable.Zip(groups, ResultLyrics.ToArray(), ChangeLyric).Last(); } + /// /// abstract class for Ini Management /// To use, child phonemizer should implement this class(BaseIniManager) with its own setting values! diff --git a/OpenUtau.Core/PlaybackManager.cs b/OpenUtau.Core/PlaybackManager.cs index 3f26a713c..a0b369094 100644 --- a/OpenUtau.Core/PlaybackManager.cs +++ b/OpenUtau.Core/PlaybackManager.cs @@ -222,9 +222,10 @@ void SchedulePreRender() { public void OnNext(UCommand cmd, bool isUndo) { if (cmd is SeekPlayPosTickNotification) { + var _cmd = cmd as SeekPlayPosTickNotification; StopPlayback(); - int tick = ((SeekPlayPosTickNotification)cmd).playPosTick; - DocManager.Inst.ExecuteCmd(new SetPlayPosTickNotification(tick)); + int tick = _cmd!.playPosTick; + DocManager.Inst.ExecuteCmd(new SetPlayPosTickNotification(tick, false, _cmd.pause)); } else if (cmd is VolumeChangeNotification) { var _cmd = cmd as VolumeChangeNotification; if (faders != null && faders.Count > _cmd.TrackNo) { @@ -232,7 +233,7 @@ public void OnNext(UCommand cmd, bool isUndo) { } } else if (cmd is PanChangeNotification) { var _cmd = cmd as PanChangeNotification; - if (faders != null && faders.Count > _cmd.TrackNo) { + if (faders != null && faders.Count > _cmd!.TrackNo) { faders[_cmd.TrackNo].Pan = (float)_cmd.Pan; } } else if (cmd is LoadProjectNotification) { diff --git a/OpenUtau.Core/Render/RenderPhrase.cs b/OpenUtau.Core/Render/RenderPhrase.cs index f2be55af8..0c48aee62 100644 --- a/OpenUtau.Core/Render/RenderPhrase.cs +++ b/OpenUtau.Core/Render/RenderPhrase.cs @@ -88,7 +88,7 @@ internal RenderPhone(UProject project, UTrack track, UVoicePart part, UNote note tone = note.tone; tempos = project.timeAxis.TemposBetweenTicks(part.position + phoneme.position - leading, part.position + phoneme.End); UTempo[] noteTempos = project.timeAxis.TemposBetweenTicks(part.position + phoneme.position, part.position + phoneme.End); - tempo = noteTempos[0].bpm; + tempo = noteTempos.Length > 0 ? noteTempos[0].bpm : project.tempos[0].bpm; double actualTickDuration = 0; for (int i = 0; i < noteTempos.Length; i++) { @@ -107,7 +107,7 @@ internal RenderPhone(UProject project, UTrack track, UVoicePart part, UNote note resampler = track.RendererSettings.resampler; int eng = (int)phoneme.GetExpression(project, track, Format.Ustx.ENG).Item1; - if (project.expressions.TryGetValue(Format.Ustx.ENG, out var descriptor) + if (track.TryGetExpDescriptor(project, Format.Ustx.ENG, out var descriptor) && eng >= 0 && eng < descriptor.options.Length && !string.IsNullOrEmpty(descriptor.options[eng])) { resampler = descriptor.options[eng]; @@ -316,7 +316,7 @@ internal RenderPhrase(UProject project, UTrack track, UVoicePart part, IEnumerab } var frq = phoneme.oto.Frq; UTempo[] noteTempos = project.timeAxis.TemposBetweenTicks(part.position + phoneme.position, part.position + phoneme.End); - var tempo = noteTempos[0].bpm; // compromise 妥協! + var tempo = noteTempos.Length > 0 ? noteTempos[0].bpm : project.tempos[0].bpm; // compromise 妥協! var frqIntervalTick = MusicMath.TempoMsToTick(tempo, (double)1 * 1000 / 44100 * frq.hopSize); double consonantStretch = Math.Pow(2f, 1.0f - phoneme.GetExpression(project, track, Format.Ustx.VEL).Item1 / 100f); diff --git a/OpenUtau.Core/SingerManager.cs b/OpenUtau.Core/SingerManager.cs index 708161375..a25455585 100644 --- a/OpenUtau.Core/SingerManager.cs +++ b/OpenUtau.Core/SingerManager.cs @@ -45,13 +45,18 @@ public void SearchAllSingers() { stopWatch.Stop(); Log.Information($"Search all singers: {stopWatch.Elapsed}"); } catch (Exception e) { - Log.Error(e, "Failed to search singers."); + if (InitializationTask.Status == TaskStatus.Running) { + Log.Error(e, "Failed to search singers."); + } else { + var customEx = new MessageCustomizableException("Failed to search singers.", "", e); + DocManager.Inst.ExecuteCmd(new ErrorMessageNotification(customEx)); + } Singers = new Dictionary(); } } public USinger GetSinger(string name) { - Log.Information(name); + Log.Information($"Attach singer to track: {name}"); name = name.Replace("%VOICE%", ""); if (Singers.ContainsKey(name)) { return Singers[name]; diff --git a/OpenUtau.Core/Ustx/UExpression.cs b/OpenUtau.Core/Ustx/UExpression.cs index d46bcf90b..8ccf660a1 100644 --- a/OpenUtau.Core/Ustx/UExpression.cs +++ b/OpenUtau.Core/Ustx/UExpression.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Linq; using YamlDotNet.Serialization; namespace OpenUtau.Core.Ustx { @@ -9,7 +10,7 @@ public enum UExpressionType : int { Curve = 2, } - public class UExpressionDescriptor { + public class UExpressionDescriptor : IEquatable { public string name; public string abbr; public UExpressionType type; @@ -66,6 +67,18 @@ public UExpressionDescriptor Clone() { } public override string ToString() => $"{abbr.ToUpper()}: {name}"; + + public bool Equals(UExpressionDescriptor other) { + return this.name == other.name && + this.abbr == other.abbr && + this.type == other.type && + this.min == other.min && + this.max == other.max && + this.defaultValue == other.defaultValue && + this.isFlag == other.isFlag && + this.flag == other.flag && + ((this.options == null && other.options == null) || this.options.SequenceEqual(other.options)); + } } public class UExpression { diff --git a/OpenUtau.Core/Ustx/UNote.cs b/OpenUtau.Core/Ustx/UNote.cs index 02ad70aa1..cf8604c04 100644 --- a/OpenUtau.Core/Ustx/UNote.cs +++ b/OpenUtau.Core/Ustx/UNote.cs @@ -63,10 +63,11 @@ public override string ToString() { public void AfterLoad(UProject project, UTrack track, UVoicePart part) { foreach (var exp in phonemeExpressions) { - if (project.expressions.TryGetValue(exp.abbr, out var descriptor)) { + if (track.TryGetExpDescriptor(project, exp.abbr, out var descriptor)) { exp.descriptor = descriptor; } } + phonemeExpressions = phonemeExpressions.Where(exp => exp.descriptor != null).ToList(); } public void BeforeSave(UProject project, UTrack track, UVoicePart part) { diff --git a/OpenUtau.Core/Ustx/UProject.cs b/OpenUtau.Core/Ustx/UProject.cs index 974fb00c7..8597b8e19 100644 --- a/OpenUtau.Core/Ustx/UProject.cs +++ b/OpenUtau.Core/Ustx/UProject.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using OpenUtau.Core.Util; +using SharpCompress; using YamlDotNet.Serialization; namespace OpenUtau.Core.Ustx { @@ -84,6 +85,32 @@ public void RegisterExpression(UExpressionDescriptor descriptor) { } } + public void MargeExpression(string oldAbbr, string newAbbr) { + if (parts != null && parts.Count > 0) { + parts.Where(p => p is UVoicePart) + .OfType() + .ForEach(p => p.notes.ForEach(n => ConvertNoteExp(n, tracks[p.trackNo]))); + } else if (voiceParts != null &&voiceParts.Count > 0) { + voiceParts.ForEach(p => p.notes.ForEach(n => ConvertNoteExp(n, tracks[p.trackNo]))); + } + expressions.Remove(oldAbbr); + + void ConvertNoteExp(UNote note, UTrack track) { + if (note.phonemeExpressions.Any(e => e.abbr == oldAbbr)) { + note.phonemeExpressions.ForEach(oldExp => { + if (!note.phonemeExpressions.Any(newExp => newExp.abbr == newAbbr && newExp.index == oldExp.index)) { + oldExp.abbr = newAbbr; + if (track.TryGetExpDescriptor(this, newAbbr, out var descriptor)) { + oldExp.descriptor = descriptor; + } + } else { + note.phonemeExpressions.Remove(oldExp); + } + }); + } + } + } + public UNote CreateNote() { UNote note = UNote.Create(); int start = NotePresets.Default.DefaultPortamento.PortamentoStart; diff --git a/OpenUtau.Core/Ustx/UTrack.cs b/OpenUtau.Core/Ustx/UTrack.cs index 6f3f2d4bb..d3b02b0a4 100644 --- a/OpenUtau.Core/Ustx/UTrack.cs +++ b/OpenUtau.Core/Ustx/UTrack.cs @@ -144,22 +144,23 @@ public bool TryGetExpression(UProject project, string abbr, out UExpression expr var trackExp = TrackExpressions.FirstOrDefault(e => e.descriptor.abbr == abbr); if (trackExp != null) { expression = trackExp.Clone(); - expression.descriptor = descriptor; } else { expression = new UExpression(descriptor) { value = descriptor.defaultValue }; } return true; } - public void SetTrackExpression(string abbr, float? value) { - if (!TryGetExpDescriptor(DocManager.Inst.Project, abbr, out var descriptor)) { + // May be used in the future + public void SetTrackExpression(UExpressionDescriptor descriptor, float? value) { + if (!TryGetExpDescriptor(DocManager.Inst.Project, descriptor.abbr, out var pDescriptor)) { + TrackExpressions.RemoveAll(exp => exp.descriptor?.abbr == descriptor.abbr); return; } - if (value == null || descriptor.defaultValue == value) { - TrackExpressions.RemoveAll(exp => exp.descriptor?.abbr == abbr); + if (value == null || (descriptor.Equals(pDescriptor) && pDescriptor.defaultValue == value)) { + TrackExpressions.RemoveAll(exp => exp.descriptor?.abbr == descriptor.abbr); } else { - var trackExp = TrackExpressions.FirstOrDefault(e => e.descriptor.abbr == abbr); + var trackExp = TrackExpressions.FirstOrDefault(e => e.descriptor.abbr == descriptor.abbr); if (trackExp != null) { trackExp.descriptor = descriptor; trackExp.value = (float)value; diff --git a/OpenUtau.Core/Util/LyricsHelper.cs b/OpenUtau.Core/Util/LyricsHelper.cs index 66ba39aee..3abc39c9f 100644 --- a/OpenUtau.Core/Util/LyricsHelper.cs +++ b/OpenUtau.Core/Util/LyricsHelper.cs @@ -43,6 +43,7 @@ public Type GetPreferred() { typeof(PortugueseG2pLyricsHelper), typeof(RussianG2pLyricsHelper), typeof(SpanishG2pLyricsHelper), + typeof(KoreanG2pLyricsHelper), }; } @@ -112,5 +113,9 @@ public RussianG2pLyricsHelper() : base(new RussianG2p()) { } public class SpanishG2pLyricsHelper : G2pLyricsHelper { public SpanishG2pLyricsHelper() : base(new SpanishG2p()) { } } + + public class KoreanG2pLyricsHelper : G2pLyricsHelper { + public KoreanG2pLyricsHelper() : base(new KoreanG2p()) { } + } } diff --git a/OpenUtau.Core/Util/ProcessRunner.cs b/OpenUtau.Core/Util/ProcessRunner.cs index 80ac17669..bf1db9a43 100644 --- a/OpenUtau.Core/Util/ProcessRunner.cs +++ b/OpenUtau.Core/Util/ProcessRunner.cs @@ -23,13 +23,13 @@ public static void Run(string file, string args, ILogger logger, string workDir if (DebugSwitch) { proc.OutputDataReceived += (o, e) => { if (!string.IsNullOrEmpty(e.Data)) { - logger.Information($" >>> [thread-{threadId}] {e.Data}"); + logger.Information($"ProcessRunner >>> [thread-{threadId}] {e.Data}"); } }; } proc.ErrorDataReceived += (o, e) => { if (!string.IsNullOrEmpty(e.Data)) { - logger.Error($" >>> [thread-{threadId}] {e.Data}"); + logger.Error($"ProcessRunner >>> [thread-{threadId}] {e.Data}"); } }; proc.Start(); @@ -43,12 +43,12 @@ public static void Run(string file, string args, ILogger logger, string workDir if (proc.WaitForExit(timeoutMs)) { return; } - logger.Warning($"[thread-{threadId}] Timeout, killing..."); + logger.Warning($"ProcessRunner >>> [thread-{threadId}] Timeout, killing..."); try { proc.Kill(); - logger.Warning($"[thread-{threadId}] Killed."); + logger.Warning($"ProcessRunner >>> [thread-{threadId}] Killed."); } catch (Exception e) { - logger.Error(e, $"[thread-{threadId}] Failed to kill"); + logger.Error(e, $"ProcessRunner >>> [thread-{threadId}] Failed to kill"); } } } diff --git a/OpenUtau.Plugin.Builtin/ArpasingPlusPhonemizer.cs b/OpenUtau.Plugin.Builtin/ArpasingPlusPhonemizer.cs index 558008d6f..4e21f13b2 100644 --- a/OpenUtau.Plugin.Builtin/ArpasingPlusPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/ArpasingPlusPhonemizer.cs @@ -424,7 +424,7 @@ protected override List ProcessSyllable(Syllable syllable) { var ccv = $"{string.Join("", cc)} {v}"; var ccv1 = string.Join("", cc.Skip(i)) + " " + v; /// CCV - if (syllable.CurrentWordCc.Length >= 2 && !ccvException.Contains(cc[i] + cc[i + 1])) { + if (syllable.CurrentWordCc.Length >= 2 && !ccvException.Contains(cc[i])) { if (HasOto(ccv, syllable.vowelTone) || HasOto(ValidateAlias(ccv), syllable.vowelTone)) { basePhoneme = ccv; lastC = i; @@ -446,7 +446,7 @@ protected override List ProcessSyllable(Syllable syllable) { var vc = $"{prevV} {cc[0]}"; // Boolean Triggers bool CCV = false; - if (syllable.CurrentWordCc.Length >= 2 && !ccvException.Contains(cc[1])) { + if (syllable.CurrentWordCc.Length >= 2 && !ccvException.Contains(cc[0])) { if (HasOto($"{string.Join("", cc)} {v}", syllable.vowelTone) || HasOto(ValidateAlias($"{string.Join("", cc)} {v}"), syllable.vowelTone)) { CCV = true; } diff --git a/OpenUtau.Plugin.Builtin/EnXSampaPhonemizer.cs b/OpenUtau.Plugin.Builtin/EnXSampaPhonemizer.cs index 2d1aa7e3d..62183b029 100644 --- a/OpenUtau.Plugin.Builtin/EnXSampaPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/EnXSampaPhonemizer.cs @@ -53,6 +53,15 @@ public class EnXSampaPhonemizer : SyllableBasedPhonemizer { private bool isSimpleDelta = false; + // For banks with slightly fewer vowels + private readonly Dictionary CanadianRaising = "VI=aI;VU=aU".Split(';') + .Select(entry => entry.Split('=')) + .Where(parts => parts.Length == 2) + .Where(parts => parts[0] != parts[1]) + .ToDictionary(parts => parts[0], parts => parts[1]); + + private bool isMissingCanadianRaising = false; + // For banks with only minimal vowels private readonly Dictionary miniDelta = "I=i;U=u".Split(';') .Select(entry => entry.Split('=')) @@ -214,6 +223,10 @@ protected override List ProcessSyllable(Syllable syllable) { isVocaSampa = true; } + if (!HasOto($"- VI", syllable.tone) || HasOto($"VI", syllable.tone) || (!HasOto($"- VU", syllable.tone) && !HasOto($"VU", syllable.tone))) { + isMissingCanadianRaising = true; + } + if (!HasOto($"- V", syllable.vowelTone) && !HasOto($"V", syllable.vowelTone) || (!HasOto($"- bV", syllable.vowelTone) && !HasOto($"bV", syllable.vowelTone))) { isSimpleDelta = true; } diff --git a/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs b/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs index 04d219f16..4e5dce126 100644 --- a/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs @@ -31,7 +31,6 @@ public class EnglishVCCVPhonemizer : SyllableBasedPhonemizer { {"i ng","1 ng"}, {"ing","1ng"}, {"0 r","0r-"}, - {"9 r","0r-"}, {"9r","0r"}, {"9r-","0r-"}, {"er-","Ar-" }, @@ -46,6 +45,9 @@ public class EnglishVCCVPhonemizer : SyllableBasedPhonemizer { {"@ng","Ang"}, {"ang","9ng"}, {"a ng","9ng-"}, + //{"a l","9l-"}, + {"al","9l"}, + {"al-","9l-"}, //{"O l","0l"}, {"0 l","0l-"}, {"Ol","0l"}, @@ -60,8 +62,10 @@ public class EnglishVCCVPhonemizer : SyllableBasedPhonemizer { {"o","w"}, {"O","w"}, {"8","w"}, + {"W","w"}, {"A","y"}, {"I","y"}, + {"Y","y"}, {"E","y"}, {"Q","y"}, {"i","y"}, @@ -96,7 +100,7 @@ public class EnglishVCCVPhonemizer : SyllableBasedPhonemizer { //spl, shr, skr, spr, str, thr, skw, thw, sky, spy private readonly string[] ccNoParsing = { "sk", "sm", "sn", "sp", "st", "hhy" }; private readonly string[] stopCs = { "b", "d", "g", "k", "p", "t" }; - private readonly string[] ucvCs = { "r", "l", "w", "y" }; + private readonly string[] ucvCs = { "r", "l", "w", "y", "f"}; @@ -164,7 +168,7 @@ protected override List ProcessSyllable(Syllable syllable) { vc = $"{prevV}{vvExceptions[prevV]}"; } phonemes.Add(vc); - basePhoneme = $"_{vvExceptions[prevV]}{v}"; + basePhoneme = $"{vvExceptions[prevV]}{v}"; } if (!HasOto(basePhoneme, syllable.vowelTone)) { basePhoneme = $"{v}"; @@ -239,26 +243,29 @@ protected override List ProcessSyllable(Syllable syllable) { if (syllable.IsVCVWithOneConsonant) { basePhoneme = $"{cc.Last()}{v}"; var vc = $"{prevV} {cc.Last()}"; + if (vc == $"i ng") { + vc = $"1 ng"; + } if (!HasOto(basePhoneme, syllable.vowelTone)) { if ($"{cc.Last()}" == "ng") basePhoneme = $"_{v}"; - } + } + - if (lastCPrevWord == 1 && CurrentWordCc.Length == 0) if (($"{PreviousWordCc.Last()}" == "r") || ($"{PreviousWordCc.Last()}" == "l") || ($"{PreviousWordCc.Last()}" == "ng")) { if (HasOto($"{prevV}{PreviousWordCc.Last()}-", syllable.vowelTone) && HasOto($"{PreviousWordCc.Last()} {v}", syllable.vowelTone)) { - basePhoneme = $"{PreviousWordCc.Last()} {v}"; - vc = $"{prevV}{PreviousWordCc.Last()}-"; - } else - vc = $"{prevV}{PreviousWordCc.Last()}-"; + basePhoneme = $"{PreviousWordCc.Last()} {v}"; + vc = $"{prevV}{PreviousWordCc.Last()}-"; + } else + vc = $"{prevV}{PreviousWordCc.Last()}-"; } if (!HasOto(vc, syllable.vowelTone)) { if ($"{cc.Last()}" == "ng") vc = $"{prevV}ng"; - } + } vc = CheckVCExceptions(vc); phonemes.Add(vc); @@ -295,7 +302,7 @@ protected override List ProcessSyllable(Syllable syllable) { if ($"{ccNoParse}" == "hhy") { vc = $"{prevV} hh"; } - + phonemes.Add(vc); } @@ -318,18 +325,18 @@ protected override List ProcessSyllable(Syllable syllable) { if (phonemes.Count == 0) { // opera [9 p] + [pr] + [_ru] - parsingCC = $"{cc[0]}{cc[1]}"; - if (HasOto(parsingCC, syllable.vowelTone) && lastCPrevWord != 1 && ucvCs.Contains($"{cc[1]}")) { - parsingVCC = $"{prevV} {cc[0]}"; + parsingCC = $"{cc[0]}{cc[1]}"; + if (HasOto(parsingCC, syllable.vowelTone) && lastCPrevWord != 1 && ucvCs.Contains($"{cc[1]}")) { + parsingVCC = $"{prevV} {cc[0]}"; - basePhoneme = $"_{cc.Last()}{v}"; - if (lastCPrevWord == cc.Length) { - parsingVCC = $"{prevV}{cc[0]}-"; - if (stopCs.Contains($"{cc.Last()}")) { - basePhoneme = $"-{v}"; + basePhoneme = $"_{cc.Last()}{v}"; + if (lastCPrevWord == cc.Length) { + parsingVCC = $"{prevV}{cc[0]}-"; + if (stopCs.Contains($"{cc.Last()}")) { + basePhoneme = $"-{v}"; - } } + } // sp fix if ($"{cc[0]}" == "s" && $"{cc[1]}" == "p") { parsingVCC = $"{prevV} sp"; @@ -339,210 +346,213 @@ protected override List ProcessSyllable(Syllable syllable) { } else { // bonehead [On-] + [n h] + [he] parsingCC = $"{cc[0]} {cc[1]}"; - if (!HasOto(parsingCC, syllable.vowelTone)){ + if (!HasOto(parsingCC, syllable.vowelTone)) { if (ccFallback.ContainsKey(cc[1])) - parsingCC = $"{cc[0]} {ccFallback[cc[1]]}"; - } - if (HasOto(parsingCC, syllable.vowelTone)){ - //if (HasOto(parsingCC, syllable.vowelTone) && lastCPrevWord !=2) { - if (!HasOto(parsingVCC, syllable.vowelTone)) { - parsingVCC = $"{prevV} {cc[0]}"; - parsingVCC = CheckVCExceptions(parsingVCC); + parsingCC = $"{cc[0]} {ccFallback[cc[1]]}"; } + if (HasOto(parsingCC, syllable.vowelTone)) { + //if (HasOto(parsingCC, syllable.vowelTone) && lastCPrevWord !=2) { + if (!HasOto(parsingVCC, syllable.vowelTone)) { + parsingVCC = CheckVCExceptions(parsingVCC); + } + if (!HasOto(parsingVCC, syllable.vowelTone)) { + parsingVCC = $"{prevV} {cc[0]}"; + } - // sp fix - if ($"{cc[0]}" == "s" && $"{cc[1]}" == "p") { - parsingVCC = $"{prevV} sp"; + // sp fix + if ($"{cc[0]}" == "s" && $"{cc[1]}" == "p") { + parsingVCC = $"{prevV} sp"; + } + phonemes.Add(parsingVCC); + phonemes.Add(parsingCC); + } else { + // backpack [@k] + [p@] + + // sp fix + if ($"{cc[0]}" == "s" && $"{cc[1]}" == "p") { + parsingVCC = $"{prevV} sp"; + } else + parsingVCC = $"{prevV}{cc[0]}"; + phonemes.Add(parsingVCC); } - phonemes.Add(parsingVCC); - phonemes.Add(parsingCC); - } else { - // backpack [@k] + [p@] - - // sp fix - if ($"{cc[0]}" == "s" && $"{cc[1]}" == "p") { - parsingVCC = $"{prevV} sp"; - } else - parsingVCC = $"{prevV}{cc[0]}"; - phonemes.Add(parsingVCC); } } } - } - // LOGIC FOR MORE THAN 2 CONSONANTS - if (cc.Length > 2 && phonemes.Count == 0) { - // also [VC CC] exceptions - var vccExceptions = $"{prevV}{cc[0]}{cc[1]} {cc[2]}"; - var startingC = 2; - // 1nks exception - bool ing = false; - if (exIng || ex1ng || ex1nk) { - vccExceptions = $"1ng {cc[1]}"; - ing = true; - startingC = 1; - if(lastCPrevWord == 2) { - vccExceptions = $"1ng{cc[1]}"; - } - if ($"{cc[1]}" == "k" && lastCPrevWord >= 2) { - vccExceptions = $"1nk"; - startingC = 2; - if ($"{cc[2]}" == "s" && lastCPrevWord == 3) { - vccExceptions = $"1nks"; - startingC = 3; + // LOGIC FOR MORE THAN 2 CONSONANTS + if (cc.Length > 2 && phonemes.Count == 0) { + // also [VC CC] exceptions + var vccExceptions = $"{prevV}{cc[0]}{cc[1]} {cc[2]}"; + var startingC = 2; + // 1nks exception + bool ing = false; + if (exIng || ex1ng || ex1nk) { + vccExceptions = $"1ng {cc[1]}"; + ing = true; + startingC = 1; + if (lastCPrevWord == 2) { + vccExceptions = $"1ng{cc[1]}"; + } + if ($"{cc[1]}" == "k" && lastCPrevWord >= 2) { + vccExceptions = $"1nk"; + startingC = 2; + if ($"{cc[2]}" == "s" && lastCPrevWord == 3) { + vccExceptions = $"1nks"; + startingC = 3; + } } } - } - var ccNoParse = $"{cc[cc.Length - 3]}{cc[cc.Length - 2]}{cc[cc.Length - 1]}"; - bool dontParse = false; - var lastCforLoop = cc.Length - 1; - - // str exceptions - if (cccExceptions.Contains($"{ccNoParse}") && cc.Length - 3 >= lastCPrevWord) { - var vc = $"{prevV}{cc[0]}-"; - if (cc.Length == 3) { - var vccE = vcccExceptions[ccNoParse]; - vc = $"{prevV} {vccE}"; - } - if (cc.Length == 4) { - vc = $"{prevV}{cc[0]}"; - } + var ccNoParse = $"{cc[cc.Length - 3]}{cc[cc.Length - 2]}{cc[cc.Length - 1]}"; + bool dontParse = false; + var lastCforLoop = cc.Length - 1; - if (vc == "ing") - vc = "1ng"; + // str exceptions + if (cccExceptions.Contains($"{ccNoParse}") && cc.Length - 3 >= lastCPrevWord) { + var vc = $"{prevV}{cc[0]}-"; + if (cc.Length == 3) { + var vccE = vcccExceptions[ccNoParse]; + vc = $"{prevV} {vccE}"; + } + if (cc.Length == 4) { + vc = $"{prevV}{cc[0]}"; + } - phonemes.Add(vc); - startingC = 0; - lastCforLoop -= 2; - } else { - ccNoParse = $"{cc[cc.Length - 2]}{cc[cc.Length - 1]}"; - var ccSP = $"{cc[0]}{cc[1]}"; - - // sk, sm, sn, sp & st exceptions - if (cc.Length - lastCPrevWord > 1) { - for (int i = 0; i < ccNoParsing.Length; i++) { - if (ccNoParsing.Contains(ccNoParse)) { - dontParse = true; - break; + if (vc == "ing") + vc = "1ng"; + + phonemes.Add(vc); + startingC = 0; + lastCforLoop -= 2; + } else { + ccNoParse = $"{cc[cc.Length - 2]}{cc[cc.Length - 1]}"; + var ccSP = $"{cc[0]}{cc[1]}"; + + // sk, sm, sn, sp & st exceptions + if (cc.Length - lastCPrevWord > 1) { + for (int i = 0; i < ccNoParsing.Length; i++) { + if (ccNoParsing.Contains(ccNoParse)) { + dontParse = true; + break; + } } } - } - if (dontParse) { - - basePhoneme = $"{cc[cc.Length - 2]}{cc[cc.Length - 1]}{v}"; - vccExceptions = $"1ng {cc[1]}{cc[2]}"; + if (dontParse) { - if (ing && HasOto(vccExceptions, syllable.vowelTone)) { + basePhoneme = $"{cc[cc.Length - 2]}{cc[cc.Length - 1]}{v}"; vccExceptions = $"1ng {cc[1]}{cc[2]}"; - phonemes.Add(vccExceptions); - startingC = 2; - } else { - - vccExceptions = $"{prevV}{cc[0]}-"; - if (vccExceptions == "ing-") { - vccExceptions = "1ng-"; - } - phonemes.Add(vccExceptions); - if (HasOto($"{cc[0]} {cc[1]}{cc[2]}", syllable.vowelTone)) { - phonemes.Add($"{cc[0]} {cc[1]}{cc[2]}"); + if (ing && HasOto(vccExceptions, syllable.vowelTone)) { + vccExceptions = $"1ng {cc[1]}{cc[2]}"; + phonemes.Add(vccExceptions); startingC = 2; - } else { basePhoneme = $"-{cc[cc.Length - 2]}{cc[cc.Length - 1]}{v}"; - startingC = 0; + } else { + + vccExceptions = $"{prevV}{cc[0]}-"; + + if (vccExceptions == "ing-") { + vccExceptions = "1ng-"; + } + phonemes.Add(vccExceptions); + if (HasOto($"{cc[0]} {cc[1]}{cc[2]}", syllable.vowelTone)) { + phonemes.Add($"{cc[0]} {cc[1]}{cc[2]}"); + startingC = 2; + } else { + basePhoneme = $"-{cc[cc.Length - 2]}{cc[cc.Length - 1]}{v}"; + startingC = 0; + } } } - } - if (phonemes.Count == 0) { + if (phonemes.Count == 0) { - if (HasOto(vccExceptions, syllable.vowelTone)) { - phonemes.Add(vccExceptions); - } else { startingC = 0; } + if (HasOto(vccExceptions, syllable.vowelTone)) { + phonemes.Add(vccExceptions); + } else { startingC = 0; } - if (phonemes.Count == 0) { - parsingVCC = $"{prevV}{cc[0]}-"; - if (!HasOto(parsingVCC, syllable.vowelTone)) { - parsingVCC = CheckVCExceptions($"{prevV}{cc[0]}") + "-"; + if (phonemes.Count == 0) { + parsingVCC = $"{prevV}{cc[0]}-"; if (!HasOto(parsingVCC, syllable.vowelTone)) { - parsingVCC = $"{prevV} {cc[0]}"; + parsingVCC = CheckVCExceptions($"{prevV}{cc[0]}") + "-"; + if (!HasOto(parsingVCC, syllable.vowelTone)) { + parsingVCC = $"{prevV} {cc[0]}"; + } + } + if (lastCPrevWord == 1 && stopCs.Contains($"{cc[0]}")) { + parsingVCC = $"{prevV}{cc[0]}"; } - } - if (lastCPrevWord == 1 && stopCs.Contains($"{cc[0]}")) { - parsingVCC = $"{prevV}{cc[0]}"; - } - if (ccSP == "sp") { - parsingVCC = $"{prevV} sp"; - } + if (ccSP == "sp") { + parsingVCC = $"{prevV} sp"; + } - phonemes.Add(parsingVCC); + phonemes.Add(parsingVCC); + } } } - } - for (int i = startingC; i < lastCforLoop; i++) { - parsingCC = $"{cc[i]}{cc[i + 1]}-"; + for (int i = startingC; i < lastCforLoop; i++) { + parsingCC = $"{cc[i]}{cc[i + 1]}-"; - if (dontParse && i == cc.Length - 3) { - parsingCC = $"{cc[i]} {cc[i + 1]}{cc[i + 2]}"; - } + if (dontParse && i == cc.Length - 3) { + parsingCC = $"{cc[i]} {cc[i + 1]}{cc[i + 2]}"; + } - if (i == lastCPrevWord-1) { - parsingCC = $"{cc[i]} {cc[i + 1]}"; - } + if (i == lastCPrevWord - 1) { + parsingCC = $"{cc[i]} {cc[i + 1]}"; + } - if (i == lastCPrevWord - 2) { - parsingCC = $"{cc[i]}{cc[i + 1]}"; - if(!HasOto(parsingCC,syllable.vowelTone)) { - parsingCC = $"{cc[i]}{cc[i + 1]}-"; + if (i == lastCPrevWord - 2) { + parsingCC = $"{cc[i]}{cc[i + 1]}"; if (!HasOto(parsingCC, syllable.vowelTone)) { - parsingCC = $"{cc[i]} {cc[i + 1]}-"; + parsingCC = $"{cc[i]}{cc[i + 1]}-"; + if (!HasOto(parsingCC, syllable.vowelTone)) { + parsingCC = $"{cc[i]} {cc[i + 1]}-"; + } } } - } - if(!HasOto(parsingCC,syllable.vowelTone) && i != lastCPrevWord-1) { + if (!HasOto(parsingCC, syllable.vowelTone) && i != lastCPrevWord - 1) { - parsingCC = $"{cc[i]}{cc[i + 1]}"; - } + parsingCC = $"{cc[i]}{cc[i + 1]}"; + } - //if (i + 1 != lastCforLoop - 1) { - // parsingCC = $"{cc[i]}{cc[i + 1]}"; - if (dontParse && i == cc.Length - 2) { - parsingCC = ""; - } - //} + //if (i + 1 != lastCforLoop - 1) { + // parsingCC = $"{cc[i]}{cc[i + 1]}"; + if (dontParse && i == cc.Length - 2) { + parsingCC = ""; + } + //} - //ng to nk exception - if ($"{cc[i]}" == "ng" && $"{cc[i + 1]}" == "th" && i + 1 != lastCPrevWord) { - parsingCC = $"nkth"; + //ng to nk exception + if ($"{cc[i]}" == "ng" && $"{cc[i + 1]}" == "th" && i + 1 != lastCPrevWord) { + parsingCC = $"nkth"; + } + + if (parsingCC != "" && HasOto(parsingCC, syllable.vowelTone)) { + phonemes.Add(parsingCC); + } } - if (parsingCC != "" && HasOto(parsingCC, syllable.vowelTone)) { - phonemes.Add(parsingCC); + if (cc.Length - lastCPrevWord - 1 > 0 && !dontParse) { + basePhoneme = $"_{cc.Last()}{v}"; } - } - if (cc.Length - lastCPrevWord - 1 > 0 && !dontParse) { - basePhoneme = $"_{cc.Last()}{v}"; + //if (ccNoParse == "str") { + if (cccExceptions.Contains($"{ccNoParse}")) { + phonemes.Add(ccNoParse); + } } - //if (ccNoParse == "str") { - if (cccExceptions.Contains($"{ccNoParse}")) { - phonemes.Add(ccNoParse); - } } - } - } - if (!HasOto(basePhoneme, syllable.vowelTone)) { basePhoneme = $"{cc.Last()}{v}"; } - phonemes.Add(basePhoneme); - return phonemes; - } + if (!HasOto(basePhoneme, syllable.vowelTone)) { basePhoneme = $"{cc.Last()}{v}"; } + phonemes.Add(basePhoneme); + return phonemes; + } protected override List ProcessEnding(Ending ending) { string[] cc = ending.cc; diff --git a/OpenUtau/OpenUtau.csproj b/OpenUtau/OpenUtau.csproj index 210ec7452..4b6b5234b 100644 --- a/OpenUtau/OpenUtau.csproj +++ b/OpenUtau/OpenUtau.csproj @@ -33,15 +33,15 @@ - + - - - - - + + + + + diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index 23cad72be..ddd5e6e0a 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -69,12 +69,14 @@ Abbreviations must be unique Default value must be between min and max Flags must be unique + The following expressions have been merged due to duplicate flags Min must be smaller than max Name must be set Failed to export Failed to import audio Failed to import files Failed to import midi + Failed to install singer Failed to load Failed to load prefs. Initialize it. Failed to open @@ -83,6 +85,7 @@ Failed to run editing macro Failed to save Failed to save singer config file + Failed to search singers Abbreviation Apply @@ -386,7 +389,8 @@ Warning: this option removes custom presets. Playback Device On Pausing Do nothing - Move cursor back to where you started playing + Move cursor and view position back to where you started playing + Move only cursor back to where you started playing Test Rendering Default renderer (for classic voicebanks) diff --git a/OpenUtau/Strings/Strings.ja-JP.axaml b/OpenUtau/Strings/Strings.ja-JP.axaml index 3a50ce81c..a1f265833 100644 --- a/OpenUtau/Strings/Strings.ja-JP.axaml +++ b/OpenUtau/Strings/Strings.ja-JP.axaml @@ -69,6 +69,7 @@ パラメータの略称が重複しています デフォルト値は最小値以上、最大値以下にしてください フラグが重複しています + フラグが重複しているため、以下の表情は統合されました 最小値は最大値より小さくしてください 表情名を入力してください エクスポートに失敗しました @@ -83,6 +84,7 @@ 一括処理に失敗しました 保存に失敗しました シンガー設定の保存に失敗しました + シンガーの取得に失敗しました パラメータの略称 適用 @@ -386,7 +388,8 @@ 再生デバイス 一時停止した時のカーソル 何もしない - 再生を開始した場所へカーソルを戻す + 再生を開始した場所へカーソルと表示位置を戻す + 再生を開始した場所へカーソルだけを戻す 再生テスト レンダリング Classic UTAU音源のデフォルトレンダラー diff --git a/OpenUtau/ViewModels/NotePropertiesViewModel.cs b/OpenUtau/ViewModels/NotePropertiesViewModel.cs index 52fa19106..0adbbb159 100644 --- a/OpenUtau/ViewModels/NotePropertiesViewModel.cs +++ b/OpenUtau/ViewModels/NotePropertiesViewModel.cs @@ -162,12 +162,12 @@ public void LoadPart(UPart? part) { Expressions.Clear(); if (part != null && part is UVoicePart) { this.Part = part as UVoicePart; + var track = DocManager.Inst.Project.tracks[part.trackNo]; foreach (KeyValuePair pair in DocManager.Inst.Project.expressions) { - if (pair.Value.type != UExpressionType.Curve) { - var viewModel = new NotePropertyExpViewModel(pair.Value, this); - if (pair.Value.abbr == Ustx.CLR) { - var track = DocManager.Inst.Project.tracks[part.trackNo]; + if (track.TryGetExpDescriptor(DocManager.Inst.Project, pair.Key, out var descriptor) && descriptor.type != UExpressionType.Curve) { + var viewModel = new NotePropertyExpViewModel(descriptor, this); + if (descriptor.abbr == Ustx.CLR) { if (track.VoiceColorExp != null && track.VoiceColorExp.options.Length > 0) { viewModel.Options.Clear(); track.VoiceColorExp.options.ForEach(opt => viewModel.Options.Add(opt)); diff --git a/OpenUtau/ViewModels/NotesViewModel.cs b/OpenUtau/ViewModels/NotesViewModel.cs index 314ff61a4..2113c018a 100644 --- a/OpenUtau/ViewModels/NotesViewModel.cs +++ b/OpenUtau/ViewModels/NotesViewModel.cs @@ -206,13 +206,8 @@ public NotesViewModel() { KnifeTool = false; SelectToolCommand = ReactiveCommand.Create(index => { CursorTool = index == "1"; - if (Preferences.Default.PenPlusDefault) { - PenPlusTool = index == "2"; - PenTool = index == "2+"; - } else { - PenTool = index == "2"; - PenPlusTool = index == "2+"; - } + PenTool = index == "2"; + PenPlusTool = index == "2+"; EraserTool = index == "3"; DrawPitchTool = index == "4"; KnifeTool = index == "5"; @@ -971,7 +966,7 @@ bool IsExpSupported(string expKey) { if (track.RendererSettings.Renderer == null) { return true; } - if (Project.expressions.TryGetValue(expKey, out var descriptor)) { + if (track.TryGetExpDescriptor(Project, expKey, out var descriptor)) { return track.RendererSettings.Renderer.SupportsExpression(descriptor); } if (expKey == track.VoiceColorExp.abbr) { @@ -997,7 +992,9 @@ public void OnNext(UCommand cmd, bool isUndo) { PrimaryKeyNotSupported = !IsExpSupported(PrimaryKey); } else if (cmd is SetPlayPosTickNotification setPlayPosTick) { SetPlayPos(setPlayPosTick.playPosTick, setPlayPosTick.waitingRendering); - MaybeAutoScroll(PlayPosX); + if (!setPlayPosTick.pause || Preferences.Default.LockStartTime == 1) { + MaybeAutoScroll(PlayPosX); + } } else if (cmd is FocusNoteNotification focusNote) { if (focusNote.part == Part) { FocusNote(focusNote.note); diff --git a/OpenUtau/ViewModels/PhoneticAssistantViewModel.cs b/OpenUtau/ViewModels/PhoneticAssistantViewModel.cs index 5b6bdf2c4..10a8e2a2c 100644 --- a/OpenUtau/ViewModels/PhoneticAssistantViewModel.cs +++ b/OpenUtau/ViewModels/PhoneticAssistantViewModel.cs @@ -33,6 +33,7 @@ public G2pOption(Type klass) { new G2pOption(typeof(PortugueseG2p)), new G2pOption(typeof(RussianG2p)), new G2pOption(typeof(SpanishG2p)), + new G2pOption(typeof(KoreanG2p)), }; private Api.G2pPack? g2p; diff --git a/OpenUtau/ViewModels/PianoRollViewModel.cs b/OpenUtau/ViewModels/PianoRollViewModel.cs index fb98aee9f..4b7607a86 100644 --- a/OpenUtau/ViewModels/PianoRollViewModel.cs +++ b/OpenUtau/ViewModels/PianoRollViewModel.cs @@ -58,7 +58,9 @@ public class PianoRollViewModel : ViewModelBase, ICmdSubscriber { public bool DegreeStyle0 { get => Preferences.Default.DegreeStyle == 0 ? true : false; } public bool DegreeStyle1 { get => Preferences.Default.DegreeStyle == 1 ? true : false; } public bool DegreeStyle2 { get => Preferences.Default.DegreeStyle == 2 ? true : false; } - public bool LockStartTime { get => Preferences.Default.LockStartTime == 1 ? true : false; } + public bool LockStartTime0 { get => Preferences.Default.LockStartTime == 0 ? true : false; } + public bool LockStartTime1 { get => Preferences.Default.LockStartTime == 1 ? true : false; } + public bool LockStartTime2 { get => Preferences.Default.LockStartTime == 2 ? true : false; } public bool PlaybackAutoScroll0 { get => Preferences.Default.PlaybackAutoScroll == 0 ? true : false; } public bool PlaybackAutoScroll1 { get => Preferences.Default.PlaybackAutoScroll == 1 ? true : false; } public bool PlaybackAutoScroll2 { get => Preferences.Default.PlaybackAutoScroll == 2 ? true : false; } diff --git a/OpenUtau/ViewModels/PlaybackViewModel.cs b/OpenUtau/ViewModels/PlaybackViewModel.cs index fac8753b1..04e5c6d2e 100644 --- a/OpenUtau/ViewModels/PlaybackViewModel.cs +++ b/OpenUtau/ViewModels/PlaybackViewModel.cs @@ -33,7 +33,7 @@ public void PlayOrPause(int tick = -1, int endTick = -1, int trackNo = -1) { PlaybackManager.Inst.PlayOrPause(tick: tick, endTick: endTick, trackNo: trackNo); var lockStartTime = Convert.ToBoolean(Preferences.Default.LockStartTime); if (!PlaybackManager.Inst.Playing && !PlaybackManager.Inst.StartingToPlay && lockStartTime) { - DocManager.Inst.ExecuteCmd(new SeekPlayPosTickNotification(PlaybackManager.Inst.StartTick)); + DocManager.Inst.ExecuteCmd(new SeekPlayPosTickNotification(PlaybackManager.Inst.StartTick, true)); } } public void Pause() { diff --git a/OpenUtau/ViewModels/TracksViewModel.cs b/OpenUtau/ViewModels/TracksViewModel.cs index eb8cd7fa8..811f8d1c8 100644 --- a/OpenUtau/ViewModels/TracksViewModel.cs +++ b/OpenUtau/ViewModels/TracksViewModel.cs @@ -428,7 +428,9 @@ public void OnNext(UCommand cmd, bool isUndo) { MessageBus.Current.SendMessage(new TracksRefreshEvent()); } else if (cmd is SetPlayPosTickNotification setPlayPosTick) { SetPlayPos(setPlayPosTick.playPosTick, setPlayPosTick.waitingRendering); - MaybeAutoScroll(); + if (!setPlayPosTick.pause || Preferences.Default.LockStartTime == 1) { + MaybeAutoScroll(); + } } Notify(); } diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index de00d0b0a..9f6848ae1 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -557,7 +557,7 @@ public void OpenSingersWindow() { return; } - DocManager.Inst.ExecuteCmd(new LoadingNotification(typeof(MainWindow), true, "singers window")); + MessageBox.ShowLoading(this); var dialog = lifetime.Windows.FirstOrDefault(w => w is SingersDialog); try { if (dialog == null) { @@ -581,7 +581,7 @@ public void OpenSingersWindow() { } catch (Exception e) { DocManager.Inst.ExecuteCmd(new ErrorMessageNotification(e)); } finally { - DocManager.Inst.ExecuteCmd(new LoadingNotification(typeof(MainWindow), false, "singers window")); + MessageBox.CloseLoading(); } if (dialog != null) { dialog.Activate(); @@ -852,14 +852,19 @@ async void OnDrop(object? sender, DragEventArgs args) { _ = await MessageBox.ShowError(this, new MessageCustomizableException("Failed to import midi", "", e)); } } else if (ext == ".zip" || ext == ".rar" || ext == ".uar") { - var setup = new SingerSetupDialog() { - DataContext = new SingerSetupViewModel() { - ArchiveFilePath = file, - }, - }; - _ = setup.ShowDialog(this); - if (setup.Position.Y < 0) { - setup.Position = setup.Position.WithY(0); + try{ + var setup = new SingerSetupDialog() { + DataContext = new SingerSetupViewModel() { + ArchiveFilePath = file, + }, + }; + _ = setup.ShowDialog(this); + if (setup.Position.Y < 0) { + setup.Position = setup.Position.WithY(0); + } + } catch (Exception e) { + Log.Error(e, $"Failed to install singer {file}"); + _ = await MessageBox.ShowError(this, new MessageCustomizableException("Failed to install singer", "", e)); } } else if (ext == Core.Vogen.VogenSingerInstaller.FileExt) { Core.Vogen.VogenSingerInstaller.Install(file); @@ -1078,12 +1083,12 @@ public void PartsCanvasDoubleTapped(object sender, TappedEventArgs args) { var control = canvas.InputHitTest(args.GetPosition(canvas)); if (control is PartControl partControl && partControl.part is UVoicePart) { if (pianoRollWindow == null) { - DocManager.Inst.ExecuteCmd(new LoadingNotification(typeof(MainWindow), true, "pianoroll window")); + MessageBox.ShowLoading(this); pianoRollWindow = new PianoRollWindow() { MainWindow = this, }; pianoRollWindow.ViewModel.PlaybackViewModel = viewModel.PlaybackViewModel; - DocManager.Inst.ExecuteCmd(new LoadingNotification(typeof(MainWindow), false, "pianoroll window")); + MessageBox.CloseLoading(); } // Workaround for new window losing focus. openPianoRollWindow = true; diff --git a/OpenUtau/Views/PianoRollWindow.axaml b/OpenUtau/Views/PianoRollWindow.axaml index 11a4246a5..13d6b31e5 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml +++ b/OpenUtau/Views/PianoRollWindow.axaml @@ -268,12 +268,17 @@ - + - + + + + + + diff --git a/OpenUtau/Views/PianoRollWindow.axaml.cs b/OpenUtau/Views/PianoRollWindow.axaml.cs index 2f5e1ae1e..7992dfe45 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml.cs +++ b/OpenUtau/Views/PianoRollWindow.axaml.cs @@ -143,7 +143,9 @@ void OnMenuLockStartTime(object sender, RoutedEventArgs args) { if (sender is MenuItem menu && int.TryParse(menu.Tag?.ToString(), out int tag)) { Preferences.Default.LockStartTime = tag; Preferences.Save(); - ViewModel.RaisePropertyChanged(nameof(ViewModel.LockStartTime)); + ViewModel.RaisePropertyChanged(nameof(ViewModel.LockStartTime0)); + ViewModel.RaisePropertyChanged(nameof(ViewModel.LockStartTime1)); + ViewModel.RaisePropertyChanged(nameof(ViewModel.LockStartTime2)); } } void OnMenuPlaybackAutoScroll(object sender, RoutedEventArgs args) { @@ -997,6 +999,9 @@ bool OnKeyExtendedHandler(KeyEventArgs args) { return true; } + string mainPenIdx = Preferences.Default.PenPlusDefault ? "2+" : "2"; + string altPenIdx = Preferences.Default.PenPlusDefault ? "2" : "2+"; + switch (args.Key) { #region document keys case Key.Space: @@ -1060,7 +1065,7 @@ bool OnKeyExtendedHandler(KeyEventArgs args) { break; case Key.D2: if (isNone) { - notesVm.SelectToolCommand?.Execute("2").Subscribe(); + notesVm.SelectToolCommand?.Execute(mainPenIdx).Subscribe(); return true; } if (isAlt) { @@ -1068,7 +1073,7 @@ bool OnKeyExtendedHandler(KeyEventArgs args) { return true; } if (isCtrl) { - notesVm.SelectToolCommand?.Execute("2+").Subscribe(); + notesVm.SelectToolCommand?.Execute(altPenIdx).Subscribe(); return true; } break; diff --git a/OpenUtau/Views/PreferencesDialog.axaml b/OpenUtau/Views/PreferencesDialog.axaml index 45171aa1b..7144c742b 100644 --- a/OpenUtau/Views/PreferencesDialog.axaml +++ b/OpenUtau/Views/PreferencesDialog.axaml @@ -52,6 +52,7 @@ + diff --git a/OpenUtau/Views/PreferencesDialog.axaml.cs b/OpenUtau/Views/PreferencesDialog.axaml.cs index 54760d656..4a8f18aa3 100644 --- a/OpenUtau/Views/PreferencesDialog.axaml.cs +++ b/OpenUtau/Views/PreferencesDialog.axaml.cs @@ -1,5 +1,5 @@ -using System; -using System.IO; +using System.IO; +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Platform.Storage; @@ -26,16 +26,13 @@ async void SelectAddlSingersPath(object sender, RoutedEventArgs e) { } } - void ReloadSingers(object sender, RoutedEventArgs e) { - DocManager.Inst.ExecuteCmd(new LoadingNotification(typeof(PreferencesDialog), true, "singer")); - try { + async void ReloadSingers(object sender, RoutedEventArgs e) { + MessageBox.ShowLoading(this); + await Task.Run(() => { SingerManager.Inst.SearchAllSingers(); - DocManager.Inst.ExecuteCmd(new SingersRefreshedNotification()); - } catch (Exception ex) { - DocManager.Inst.ExecuteCmd(new ErrorMessageNotification(ex)); - } finally { - DocManager.Inst.ExecuteCmd(new LoadingNotification(typeof(PreferencesDialog), false, "singer")); - } + }); + DocManager.Inst.ExecuteCmd(new SingersRefreshedNotification()); + MessageBox.CloseLoading(); } void ResetVLabelerPath(object sender, RoutedEventArgs e) {