diff --git a/OpenUtau.Core/Api/Phonemizer.cs b/OpenUtau.Core/Api/Phonemizer.cs index 5149c735c..91d9fc780 100644 --- a/OpenUtau.Core/Api/Phonemizer.cs +++ b/OpenUtau.Core/Api/Phonemizer.cs @@ -120,6 +120,8 @@ public struct Phoneme { /// public PhonemeAttributes attributes; + public int? index; + public override string ToString() => $"\"{phoneme}\" pos:{position}"; } diff --git a/OpenUtau.Core/Classic/ClassicSinger.cs b/OpenUtau.Core/Classic/ClassicSinger.cs index 17265b256..f4c356211 100644 --- a/OpenUtau.Core/Classic/ClassicSinger.cs +++ b/OpenUtau.Core/Classic/ClassicSinger.cs @@ -77,7 +77,7 @@ public override void Reload() { void Load() { if (Avatar != null && File.Exists(Avatar)) { try { - using (var stream = new FileStream(Avatar, FileMode.Open)) { + using (var stream = new FileStream(Avatar, FileMode.Open, FileAccess.Read)) { using (var memoryStream = new MemoryStream()) { stream.CopyTo(memoryStream); avatarData = memoryStream.ToArray(); diff --git a/OpenUtau.Core/Classic/VoicebankPublisher.cs b/OpenUtau.Core/Classic/VoicebankPublisher.cs new file mode 100644 index 000000000..f91528cc6 --- /dev/null +++ b/OpenUtau.Core/Classic/VoicebankPublisher.cs @@ -0,0 +1,83 @@ +using Ignore; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; + +using OpenUtau.Core.Ustx; + +namespace OpenUtau.Classic { + public class VoicebankPublisher { + private readonly Action progress; + private readonly Ignore.Ignore? ignore; + + public VoicebankPublisher(Action progress, string? gitIgnore) { + this.progress = progress; + if(gitIgnore != null) { + ignore = new Ignore.Ignore(); + ignore.Add(gitIgnore.Split("\n")); + } + } + + private static void ModifyConfig(USinger singer, Action modify) { + var yamlFile = Path.Combine(singer.Location, "character.yaml"); + VoicebankConfig? config = null; + if (File.Exists(yamlFile)) { + using (var stream = File.OpenRead(yamlFile)) { + config = VoicebankConfig.Load(stream); + } + } + if (config == null) { + config = new VoicebankConfig(); + } + modify(config); + using (var stream = File.Open(yamlFile, FileMode.Create)) { + config.Save(stream); + } + } + + private bool IsIgnored(string relativePath){ + return ignore?.IsIgnored(relativePath.Replace('\\', '/')) ?? false; + } + + private List GetFilesToPack(string singerPath) + { + List fileList = Directory.EnumerateFiles(singerPath, "*.*", SearchOption.AllDirectories).ToList(); + List packList = fileList.FindAll(x => !IsIgnored(System.IO.Path.GetRelativePath(singerPath, x))); + return packList; + } + + /// + ///Compress a voicebank into an optimized zip archive for distribution. + ///This function only supports voicebanks that follow the classic packaging model, + ///including utau, enunu and diffsinger. + ///Vogen voicebanks aren't supported. + /// + public void Publish(USinger singer, string outputFile){ + var location = singer.Location; + if(!Directory.Exists(location)){ + return; + } + progress.Invoke(0, $"Publishing {singer.Name}"); + //Write singer type into character.yaml + try { + ModifyConfig(singer, config => config.SingerType = singer.SingerType.ToString().ToLower()); + } catch (Exception e) { } + var packList = GetFilesToPack(location); + int index = 0; + int fileCount = packList.Count(); + using(ZipArchive archive = new ZipArchive(File.Create(outputFile), ZipArchiveMode.Create)) + { + foreach (var absFilePath in packList) + { + index++; + progress.Invoke(100.0 * index / fileCount, $"Compressing {absFilePath}"); + string reFilePath = Path.GetRelativePath(location, absFilePath); + archive.CreateEntryFromFile(absFilePath, reFilePath); + } + } + progress.Invoke(0, $"Published {singer.Name} to {outputFile}"); + } + } +} diff --git a/OpenUtau.Core/Commands/ExpCommands.cs b/OpenUtau.Core/Commands/ExpCommands.cs index af90b4876..13eaaafc8 100644 --- a/OpenUtau.Core/Commands/ExpCommands.cs +++ b/OpenUtau.Core/Commands/ExpCommands.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; - using OpenUtau.Core.Ustx; using OpenUtau.Core.Util; @@ -24,15 +23,15 @@ public ExpCommand(UVoicePart part) { public class SetNoteExpressionCommand : ExpCommand { public readonly UProject project; public readonly UTrack track; - public readonly float[] newValue; - public readonly float[] oldValue; - public SetNoteExpressionCommand(UProject project, UTrack track, UVoicePart part, UNote note, string abbr, float[] values) : base(part) { + public readonly float?[] newValue; + public readonly float?[] oldValue; + public SetNoteExpressionCommand(UProject project, UTrack track, UVoicePart part, UNote note, string abbr, float?[] values) : base(part) { this.project = project; this.track = track; this.Note = note; Key = abbr; newValue = values; - oldValue = note.GetExpression(project, track, abbr).Select(t => t.Item1).ToArray(); + oldValue = note.GetExpressionNoteHas(project, track, abbr); } public override string ToString() => $"Set note expression {Key}"; public override void Execute() => Note.SetExpression(project, track, Key, newValue); @@ -47,21 +46,26 @@ public class SetPhonemeExpressionCommand : ExpCommand { public readonly UProject project; public readonly UTrack track; public readonly UPhoneme phoneme; - public readonly float newValue; - public readonly float oldValue; + public readonly float? newValue; + public readonly float? oldValue; public override ValidateOptions ValidateOptions => new ValidateOptions { SkipTiming = true, Part = Part, SkipPhonemizer = !needsPhonemizer.Contains(Key), }; - public SetPhonemeExpressionCommand(UProject project, UTrack track, UVoicePart part, UPhoneme phoneme, string abbr, float value) : base(part) { + public SetPhonemeExpressionCommand(UProject project, UTrack track, UVoicePart part, UPhoneme phoneme, string abbr, float? value) : base(part) { this.project = project; this.track = track; this.phoneme = phoneme; Key = abbr; newValue = value; - oldValue = phoneme.GetExpression(project, track, abbr).Item1; + var oldExp = phoneme.GetExpression(project, track, abbr); + if (oldExp.Item2) { + oldValue = oldExp.Item1; + } else { + oldValue = null; + } } public override string ToString() => $"Set phoneme expression {Key}"; public override void Execute() { diff --git a/OpenUtau.Core/DiffSinger/DiffSingerBasePhonemizer.cs b/OpenUtau.Core/DiffSinger/DiffSingerBasePhonemizer.cs index 0cbe10f62..61af0c478 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerBasePhonemizer.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerBasePhonemizer.cs @@ -88,7 +88,7 @@ string[] GetSymbols(Note note) { //1. phonetic hint //2. query from g2p dictionary //3. treat lyric as phonetic hint, including single phoneme - //4. default pause + //4. empty if (!string.IsNullOrEmpty(note.phoneticHint)) { // Split space-separated symbols into an array. return note.phoneticHint.Split() @@ -108,7 +108,7 @@ string[] GetSymbols(Note note) { if (lyricSplited.Length > 0) { return lyricSplited; } - return new string[] { defaultPause }; + return new string[] { }; } string GetSpeakerAtIndex(Note note, int index){ @@ -122,21 +122,20 @@ string GetSpeakerAtIndex(Note note, int index){ return speaker.Suffix; } - dsPhoneme[] GetDsPhonemes(Note note){ - return GetSymbols(note) - .Select((symbol, index) => new dsPhoneme(symbol, GetSpeakerAtIndex(note, index))) - .ToArray(); - } - protected bool IsSyllableVowelExtensionNote(Note note) { return note.lyric.StartsWith("+~") || note.lyric.StartsWith("+*"); } - List ProcessWord(Note[] notes){ + /// + /// distribute phonemes to each note inside the group + /// + List ProcessWord(Note[] notes, string[] symbols){ var wordPhonemes = new List{ new phonemesPerNote(-1, notes[0].tone) }; - var dsPhonemes = GetDsPhonemes(notes[0]); + var dsPhonemes = symbols + .Select((symbol, index) => new dsPhoneme(symbol, GetSpeakerAtIndex(notes[0], index))) + .ToArray(); var isVowel = dsPhonemes.Select(s => g2p.IsVowel(s.Symbol)).ToArray(); var isGlide = dsPhonemes.Select(s => g2p.IsGlide(s.Symbol)).ToArray(); var nonExtensionNotes = notes.Where(n=>!IsSyllableVowelExtensionNote(n)).ToArray(); @@ -217,8 +216,17 @@ protected override void ProcessPart(Note[][] phrase) { new phonemesPerNote(-1,phrase[0][0].tone, new List{new dsPhoneme("SP", GetSpeakerAtIndex(phrase[0][0], 0))}) }; var notePhIndex = new List { 1 }; - foreach (var word in phrase) { - var wordPhonemes = ProcessWord(word); + var wordFound = new bool[phrase.Length]; + foreach (int wordIndex in Enumerable.Range(0, phrase.Length)) { + Note[] word = phrase[wordIndex]; + var symbols = GetSymbols(word[0]); + if (symbols == null || symbols.Length == 0) { + symbols = new string[] { defaultPause }; + wordFound[wordIndex] = false; + } else { + wordFound[wordIndex] = true; + } + var wordPhonemes = ProcessWord(word, symbols); phrasePhonemes[^1].Phonemes.AddRange(wordPhonemes[0].Phonemes); phrasePhonemes.AddRange(wordPhonemes.Skip(1)); notePhIndex.Add(notePhIndex[^1]+wordPhonemes.SelectMany(n=>n.Phonemes).Count()); @@ -310,20 +318,24 @@ protected override void ProcessPart(Note[][] phrase) { //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]; + foreach (int wordIndex in Enumerable.Range(0, phrase.Length)) { + Note[] word = phrase[wordIndex]; var noteResult = new List>(); - if (group[0].lyric.StartsWith("+")) { + if (!wordFound[wordIndex]){ + //partResult[word[0].position] = noteResult; + continue; + } + if (word[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) { + double notePos = timeAxis.TickPosToMsPos(word[0].position);//start position of the note, ms + for (int phIndex = notePhIndex[wordIndex]; phIndex < notePhIndex[wordIndex + 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; + partResult[word[0].position] = noteResult; } } } diff --git a/OpenUtau.Core/DiffSinger/DiffSingerConfig.cs b/OpenUtau.Core/DiffSinger/DiffSingerConfig.cs index f75ce88dd..4843b1c5a 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerConfig.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerConfig.cs @@ -23,7 +23,9 @@ public class DsConfig { public bool useKeyShiftEmbed = false; public bool useSpeedEmbed = false; public bool useEnergyEmbed = false; - public bool useBreathinessEmbed= false; + public bool useBreathinessEmbed = false; + public bool useVoicingEmbed = false; + public bool useTensionEmbed = false; public AugmentationArgs augmentationArgs; public bool useShallowDiffusion = false; public int maxDepth = -1; @@ -34,6 +36,10 @@ public class DsConfig { public int hop_size = 512; public int sample_rate = 44100; public bool predict_dur = true; + public bool predict_energy = true; + public bool predict_breathiness = true; + public bool predict_voicing = false; + public bool predict_tension = false; public bool use_expr = false; public bool use_note_rest = false; public float frameMs(){ diff --git a/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs b/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs index fa8f60f01..dd71f66ee 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs @@ -29,6 +29,8 @@ public class DiffSingerRenderer : IRenderer { Format.Ustx.GENC, Format.Ustx.CLR, Format.Ustx.BREC, + Format.Ustx.VOIC, + Format.Ustx.TENC, VELC, ENE, PEXP, @@ -228,9 +230,12 @@ float[] InvokeDiffsinger(RenderPhrase phrase, int depth, int speedup, Cancellati acousticInputs.Add(NamedOnnxValue.CreateFromTensor("velocity", velocityTensor)); } - //Variance: Energy and Breathiness - - if(singer.dsConfig.useBreathinessEmbed || singer.dsConfig.useEnergyEmbed){ + //Variance: Energy, Breathiness, Voicing and Tension + if( + singer.dsConfig.useBreathinessEmbed + || singer.dsConfig.useEnergyEmbed + || singer.dsConfig.useVoicingEmbed + || singer.dsConfig.useTensionEmbed) { var variancePredictor = singer.getVariancePredictor(); VarianceResult varianceResult; lock(variancePredictor){ @@ -266,6 +271,26 @@ float[] InvokeDiffsinger(RenderPhrase phrase, int depth, int speedup, Cancellati new DenseTensor(breathiness, new int[] { breathiness.Length }) .Reshape(new int[] { 1, breathiness.Length }))); } + if(singer.dsConfig.useVoicingEmbed){ + var userVoicing = DiffSingerUtils.SampleCurve(phrase, phrase.voicing, + 0, frameMs, totalFrames, headFrames, tailFrames, + x => x); + var predictedVoicing = DiffSingerUtils.ResampleCurve(varianceResult.voicing, totalFrames); + var voicing = predictedVoicing.Zip(userVoicing, (x,y)=>(float)Math.Min(x + (y-100)*12/100, 0)).ToArray(); + acousticInputs.Add(NamedOnnxValue.CreateFromTensor("voicing", + new DenseTensor(voicing, new int[] { voicing.Length }) + .Reshape(new int[] { 1, voicing.Length }))); + } + if(singer.dsConfig.useTensionEmbed){ + var userTension = DiffSingerUtils.SampleCurve(phrase, phrase.tension, + 0, frameMs, totalFrames, headFrames, tailFrames, + x => x); + var predictedTension = DiffSingerUtils.ResampleCurve(varianceResult.tension, totalFrames); + var tension = predictedTension.Zip(userTension, (x,y)=>(float)(x + y * 5 / 100)).ToArray(); + acousticInputs.Add(NamedOnnxValue.CreateFromTensor("tension", + new DenseTensor(tension, new int[] { tension.Length }) + .Reshape(new int[] { 1, tension.Length }))); + } } Tensor mel; lock(acousticModel){ diff --git a/OpenUtau.Core/DiffSinger/DiffSingerSinger.cs b/OpenUtau.Core/DiffSinger/DiffSingerSinger.cs index b3353dd9e..db02db87d 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerSinger.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerSinger.cs @@ -55,7 +55,7 @@ public DiffSingerSinger(Voicebank voicebank) { //Load Avatar if (Avatar != null && File.Exists(Avatar)) { try { - using (var stream = new FileStream(Avatar, FileMode.Open)) { + using (var stream = new FileStream(Avatar, FileMode.Open, FileAccess.Read)) { using (var memoryStream = new MemoryStream()) { stream.CopyTo(memoryStream); avatarData = memoryStream.ToArray(); diff --git a/OpenUtau.Core/DiffSinger/DiffSingerVariance.cs b/OpenUtau.Core/DiffSinger/DiffSingerVariance.cs index 95f32e619..017d04ea2 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerVariance.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerVariance.cs @@ -14,8 +14,10 @@ namespace OpenUtau.Core.DiffSinger{ public struct VarianceResult{ - public float[] energy; - public float[] breathiness; + public float[]? energy; + public float[]? breathiness; + public float[]? voicing; + public float[]? tension; } public class DsVariance : IDisposable{ string rootPath; @@ -127,9 +129,6 @@ public VarianceResult Process(RenderPhrase phrase){ var pitch = DiffSingerUtils.SampleCurve(phrase, phrase.pitches, 0, frameMs, totalFrames, headFrames, tailFrames, x => x * 0.01) .Select(f => (float)f).ToArray(); - var energy = Enumerable.Repeat(0f, totalFrames).ToArray(); - var breathiness = Enumerable.Repeat(0f, totalFrames).ToArray(); - var retake = Enumerable.Repeat(true, totalFrames*2).ToArray(); var speedup = Preferences.Default.DiffsingerSpeedup; var varianceInputs = new List(); @@ -140,15 +139,41 @@ public VarianceResult Process(RenderPhrase phrase){ varianceInputs.Add(NamedOnnxValue.CreateFromTensor("pitch", new DenseTensor(pitch, new int[] { pitch.Length }, false) .Reshape(new int[] { 1, totalFrames }))); - varianceInputs.Add(NamedOnnxValue.CreateFromTensor("energy", - new DenseTensor(energy, new int[] { energy.Length }, false) - .Reshape(new int[] { 1, totalFrames }))); - varianceInputs.Add(NamedOnnxValue.CreateFromTensor("breathiness", - new DenseTensor(breathiness, new int[] { breathiness.Length }, false) - .Reshape(new int[] { 1, totalFrames }))); + if (dsConfig.predict_energy) { + var energy = Enumerable.Repeat(0f, totalFrames).ToArray(); + varianceInputs.Add(NamedOnnxValue.CreateFromTensor("energy", + new DenseTensor(energy, new int[] { energy.Length }, false) + .Reshape(new int[] { 1, totalFrames }))); + } + if (dsConfig.predict_breathiness) { + var breathiness = Enumerable.Repeat(0f, totalFrames).ToArray(); + varianceInputs.Add(NamedOnnxValue.CreateFromTensor("breathiness", + new DenseTensor(breathiness, new int[] { breathiness.Length }, false) + .Reshape(new int[] { 1, totalFrames }))); + } + if (dsConfig.predict_voicing) { + var voicing = Enumerable.Repeat(0f, totalFrames).ToArray(); + varianceInputs.Add(NamedOnnxValue.CreateFromTensor("voicing", + new DenseTensor(voicing, new int[] { voicing.Length }, false) + .Reshape(new int[] { 1, totalFrames }))); + } + if (dsConfig.predict_tension) { + var tension = Enumerable.Repeat(0f, totalFrames).ToArray(); + varianceInputs.Add(NamedOnnxValue.CreateFromTensor("tension", + new DenseTensor(tension, new int[] { tension.Length }, false) + .Reshape(new int[] { 1, totalFrames }))); + } + + var numVariances = new[] { + dsConfig.predict_energy, + dsConfig.predict_breathiness, + dsConfig.predict_voicing, + dsConfig.predict_tension, + }.Sum(Convert.ToInt32); + var retake = Enumerable.Repeat(true, totalFrames * numVariances).ToArray(); varianceInputs.Add(NamedOnnxValue.CreateFromTensor("retake", new DenseTensor(retake, new int[] { retake.Length }, false) - .Reshape(new int[] { 1, totalFrames, 2 }))); + .Reshape(new int[] { 1, totalFrames, numVariances }))); varianceInputs.Add(NamedOnnxValue.CreateFromTensor("speedup", new DenseTensor(new long[] { speedup }, new int[] { 1 },false))); //Speaker @@ -159,17 +184,35 @@ public VarianceResult Process(RenderPhrase phrase){ } Onnx.VerifyInputNames(varianceModel, varianceInputs); var varianceOutputs = varianceModel.Run(varianceInputs); - Tensor energy_pred = varianceOutputs - .Where(o => o.Name == "energy_pred") - .First() - .AsTensor(); - Tensor breathiness_pred = varianceOutputs - .Where(o => o.Name == "breathiness_pred") - .First() - .AsTensor(); + Tensor? energy_pred = dsConfig.predict_energy + ? varianceOutputs + .Where(o => o.Name == "energy_pred") + .First() + .AsTensor() + : null; + Tensor? breathiness_pred = dsConfig.predict_breathiness + ? varianceOutputs + .Where(o => o.Name == "breathiness_pred") + .First() + .AsTensor() + : null; + Tensor? voicing_pred = dsConfig.predict_voicing + ? varianceOutputs + .Where(o => o.Name == "voicing_pred") + .First() + .AsTensor() + : null; + Tensor? tension_pred = dsConfig.predict_tension + ? varianceOutputs + .Where(o => o.Name == "tension_pred") + .First() + .AsTensor() + : null; return new VarianceResult{ - energy = energy_pred.ToArray(), - breathiness = breathiness_pred.ToArray() + energy = energy_pred?.ToArray(), + breathiness = breathiness_pred?.ToArray(), + voicing = voicing_pred?.ToArray(), + tension = tension_pred?.ToArray(), }; } diff --git a/OpenUtau.Core/Editing/LyricBatchEdits.cs b/OpenUtau.Core/Editing/LyricBatchEdits.cs index 8c64606c5..0d7676c32 100644 --- a/OpenUtau.Core/Editing/LyricBatchEdits.cs +++ b/OpenUtau.Core/Editing/LyricBatchEdits.cs @@ -138,7 +138,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do docManager.ExecuteCmd(new ChangeNoteLyricCommand(part, note, lyric)); int index = colors.FirstOrDefault(c => c.Value == suffix).Key; - docManager.ExecuteCmd(new SetNoteExpressionCommand(project, track, part, note, Format.Ustx.CLR, new float[] { index })); + docManager.ExecuteCmd(new SetNoteExpressionCommand(project, track, part, note, Format.Ustx.CLR, new float?[] { index })); break; } } diff --git a/OpenUtau.Core/Editing/NoteBatchEdits.cs b/OpenUtau.Core/Editing/NoteBatchEdits.cs index 298722edc..338347ac4 100644 --- a/OpenUtau.Core/Editing/NoteBatchEdits.cs +++ b/OpenUtau.Core/Editing/NoteBatchEdits.cs @@ -23,7 +23,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (note.lyric != lyric && (note.Next == null || note.Next.position > note.End + 120)) { var addNote = project.CreateNote(note.tone, note.End, 120); foreach(var exp in note.phonemeExpressions.OrderBy(exp => exp.index)) { - addNote.SetExpression(project, project.tracks[part.trackNo], exp.abbr, new float[] { exp.value }); + addNote.SetExpression(project, project.tracks[part.trackNo], exp.abbr, new float?[] { exp.value }); } toAdd.Add(addNote); } @@ -99,7 +99,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do } var addNote = project.CreateNote(note.tone, note.position - duration, duration); foreach (var exp in note.phonemeExpressions.Where(exp => exp.index == 0)) { - addNote.SetExpression(project, project.tracks[part.trackNo], exp.abbr, new float[] { exp.value }); + addNote.SetExpression(project, project.tracks[part.trackNo], exp.abbr, new float?[] { exp.value }); } toAdd.Add(addNote); } diff --git a/OpenUtau.Core/Enunu/EnunuSinger.cs b/OpenUtau.Core/Enunu/EnunuSinger.cs index 1a4172fa5..6b7a17670 100644 --- a/OpenUtau.Core/Enunu/EnunuSinger.cs +++ b/OpenUtau.Core/Enunu/EnunuSinger.cs @@ -151,7 +151,7 @@ void Load() { if (Avatar != null && File.Exists(Avatar)) { try { - using (var stream = new FileStream(Avatar, FileMode.Open)) { + using (var stream = new FileStream(Avatar, FileMode.Open, FileAccess.Read)) { using (var memoryStream = new MemoryStream()) { stream.CopyTo(memoryStream); avatarData = memoryStream.ToArray(); diff --git a/OpenUtau.Core/Format/OpusOggWaveReader.cs b/OpenUtau.Core/Format/OpusOggWaveReader.cs index 0aed6e25e..1760f3729 100644 --- a/OpenUtau.Core/Format/OpusOggWaveReader.cs +++ b/OpenUtau.Core/Format/OpusOggWaveReader.cs @@ -14,7 +14,7 @@ public class OpusOggWaveReader : WaveStream { byte[] wavData; public OpusOggWaveReader(string oggFile) { - using (FileStream fileStream = new FileStream(oggFile, FileMode.Open)) { + using (FileStream fileStream = new FileStream(oggFile, FileMode.Open, FileAccess.Read)) { oggStream = new MemoryStream(); fileStream.CopyTo(oggStream); } diff --git a/OpenUtau.Core/OpenUtau.Core.csproj b/OpenUtau.Core/OpenUtau.Core.csproj index 5360cdbd8..5a78d01b1 100644 --- a/OpenUtau.Core/OpenUtau.Core.csproj +++ b/OpenUtau.Core/OpenUtau.Core.csproj @@ -10,6 +10,7 @@ + diff --git a/OpenUtau.Core/Ustx/UNote.cs b/OpenUtau.Core/Ustx/UNote.cs index 77395a85d..aa4548c4c 100644 --- a/OpenUtau.Core/Ustx/UNote.cs +++ b/OpenUtau.Core/Ustx/UNote.cs @@ -35,6 +35,7 @@ public class UNote : IComparable { [YamlIgnore] public int RightBound => position + duration; [YamlIgnore] public bool Error { get; set; } = false; [YamlIgnore] public bool OverlapError { get; set; } = false; + [YamlIgnore] public List phonemizerExpressions = new List(); public static UNote Create() { var note = new UNote(); @@ -165,38 +166,60 @@ public List> GetExpression(UProject project, UTrack track, st if (phonemeExp != null) { list.Add(Tuple.Create(phonemeExp.value, true)); } else { - list.Add(Tuple.Create(trackExp.value, false)); + var phonemizerExp = phonemizerExpressions.FirstOrDefault(exp => exp.descriptor?.abbr == abbr && exp.index == i); + if (phonemizerExp != null) { + list.Add(Tuple.Create(phonemizerExp.value, false)); + } else { + list.Add(Tuple.Create(trackExp.value, false)); + } } } return list; } - public void SetExpression(UProject project, UTrack track, string abbr, float[] values) { + /// + /// Returns value if phoneme has expression, null otherwise. + /// + public float?[] GetExpressionNoteHas(UProject project, UTrack track, string abbr) { + var list = new List(); + int indexes = (phonemeExpressions.Max(exp => exp.index) ?? 0) + 1; + for (int i = 0; i < indexes; i++) { + var phonemeExp = phonemeExpressions.FirstOrDefault(exp => exp.descriptor?.abbr == abbr && exp.index == i); + if (phonemeExp != null) { + list.Add(phonemeExp.value); + } else { + list.Add(null); + } + } + return list.ToArray(); + } + + public void SetExpression(UProject project, UTrack track, string abbr, float?[] values) { if (!track.TryGetExpression(project, abbr, out UExpression trackExp)) { return; } int indexes = (phonemeExpressions.Max(exp => exp.index) ?? 0) + 1; for (int i = 0; i < indexes; i++) { - float value; + float? value; if (values.Length > i) { value = values[i]; } else { value = values.Last(); } - if (trackExp.value == value) { + if (value == null) { phonemeExpressions.RemoveAll(exp => exp.descriptor?.abbr == abbr && exp.index == i); continue; } var phonemeExp = phonemeExpressions.FirstOrDefault(exp => exp.descriptor?.abbr == abbr && exp.index == i); if (phonemeExp != null) { phonemeExp.descriptor = trackExp.descriptor; - phonemeExp.value = value; + phonemeExp.value = (float)value; } else { phonemeExpressions.Add(new UExpression(trackExp.descriptor) { index = i, - value = value, + value = (float)value, }); } } diff --git a/OpenUtau.Core/Ustx/UPart.cs b/OpenUtau.Core/Ustx/UPart.cs index 9e2963e59..808aaf2d0 100644 --- a/OpenUtau.Core/Ustx/UPart.cs +++ b/OpenUtau.Core/Ustx/UPart.cs @@ -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]), }); } diff --git a/OpenUtau.Core/Ustx/UPhoneme.cs b/OpenUtau.Core/Ustx/UPhoneme.cs index 5ae0dc8aa..4ed49ef76 100644 --- a/OpenUtau.Core/Ustx/UPhoneme.cs +++ b/OpenUtau.Core/Ustx/UPhoneme.cs @@ -165,20 +165,22 @@ void ValidateEnvelope(UProject project, UTrack track, UNote note) { envelope.data[4] = p4; } - /** - - If the phoneme does not have the corresponding expression, return the track's expression and false - - */ + /// + /// If the phoneme does not have the corresponding expression, return the track's expression and false + /// public Tuple GetExpression(UProject project, UTrack track, string abbr) { track.TryGetExpression(project, abbr, out UExpression trackExp); var note = Parent.Extends ?? Parent; - var phonemeExp = note.phonemeExpressions.FirstOrDefault( - exp => exp.descriptor?.abbr == abbr && exp.index == index); + var phonemeExp = note.phonemeExpressions.FirstOrDefault(exp => exp.descriptor?.abbr == abbr && exp.index == index); if (phonemeExp != null) { return Tuple.Create(phonemeExp.value, true); } else { - return Tuple.Create(trackExp.value, false); + var phonemizerExp = note.phonemizerExpressions.FirstOrDefault(exp => exp.descriptor?.abbr == abbr && exp.index == index); + if (phonemizerExp != null) { + return Tuple.Create(phonemizerExp.value, false); + } else { + return Tuple.Create(trackExp.value, false); + } } } @@ -187,7 +189,7 @@ public void SetExpression(UProject project, UTrack track, string abbr, float? va return; } var note = Parent.Extends ?? Parent; - if (value == null || trackExp.value == value) { + if (value == null) { note.phonemeExpressions.RemoveAll(exp => exp.descriptor?.abbr == abbr && exp.index == index); } else { var phonemeExp = note.phonemeExpressions.FirstOrDefault(exp => exp.descriptor?.abbr == abbr && exp.index == index); diff --git a/OpenUtau.Core/Util/Preferences.cs b/OpenUtau.Core/Util/Preferences.cs index 1b6224d36..0fabcc1fb 100644 --- a/OpenUtau.Core/Util/Preferences.cs +++ b/OpenUtau.Core/Util/Preferences.cs @@ -132,6 +132,7 @@ public class SerializablePreferences { public bool ShowPrefs = true; public bool ShowTips = true; public int Theme; + public bool PenPlusDefault = false; public int DegreeStyle; public bool UseTrackColor = false; public bool ClearCacheOnQuit = false; @@ -185,6 +186,9 @@ public class SerializablePreferences { public string PhoneticAssistant = string.Empty; public string RecentOpenSingerDirectory = string.Empty; public string RecentOpenProjectDirectory = string.Empty; + + public bool VoicebankPublishUseIgnore = true; + public string VoicebankPublishIgnores = "#Adobe Audition\n*.pkf\n\n#UTAU Engines\n*.ctspec\n*.d4c\n*.dio\n*.frc\n*.frt\n*.frq\n*.harvest\n*.lessaudio\n*.llsm\n*.mrq\n*.pitchtier\n*.pkf\n*.platinum\n*.pmk\n*.star\n*.uspec\n*.vs4ufrq\n\n#UTAU related tools\n$read\n*.setParam-Scache\n*.lbp\n*.lbp.caches/*\n\n#OpenUtau\nerrors.txt"; } } } diff --git a/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs b/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs index e7a673848..3ad330f0d 100644 --- a/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs @@ -13,7 +13,7 @@ namespace OpenUtau.Plugin.Builtin { // This is a temporary solution until Cz's comes out with their own. // Feel free to use the Lyric Parser plugin for more accurate pronunciations & support of ConVel. - // Thanks to cubialpha, Cz, Halo/BagelHero and nago for their help. + // Thanks to cubialpha, Cz, Halo/BagelHero, nago, and AnAndroNerd for their help. public class EnglishVCCVPhonemizer : SyllableBasedPhonemizer { private readonly string[] vowels = "a,@,u,0,8,I,e,3,A,i,E,O,Q,6,o,1ng,9,&,x,1".Split(","); @@ -28,33 +28,36 @@ public class EnglishVCCVPhonemizer : SyllableBasedPhonemizer { private readonly Dictionary vcExceptions = new Dictionary() { - {"i ng","1ng"}, + {"i ng","1 ng"}, {"ing","1ng"}, - {"0 r","0r"}, - {"9 r","0r"}, + {"0 r","0r-"}, + {"9 r","0r-"}, {"9r","0r"}, + {"9r-","0r-"}, + {"er-","Ar-" }, //{"e r","Ar"}, {"er","Ar"}, //{"@ m","&m"}, {"@m","&m"}, {"@n","&n"}, - {"1 ng","1ng"}, - {"@ ng","Ang"}, + {"@m-","&m-"}, + {"@n-","&n-"}, + {"@ ng","Ang-"}, {"@ng","Ang"}, {"ang","9ng"}, - {"a ng","9ng"}, + {"a ng","9ng-"}, //{"O l","0l"}, - {"0 l","0l"}, + {"0 l","0l-"}, {"Ol","0l"}, //{"6 l","6l"}, //{"i r","Er"}, {"ir","Er"}, + {"ir-","Er-"}, }; private readonly Dictionary vvExceptions = new Dictionary() { {"o","w"}, - {"0","w"}, {"O","w"}, {"8","w"}, {"A","y"}, @@ -65,6 +68,18 @@ public class EnglishVCCVPhonemizer : SyllableBasedPhonemizer { {"3","r"}, }; + private readonly Dictionary ccFallback = + new Dictionary() { + {"z","s"}, + {"g","k"}, + {"zh","sh"}, + {"j","ch"}, + {"b","p"}, + {"v","f"}, + {"d","t"}, + {"dh","th"}, + }; + private readonly string[] ccExceptions = { "th", "ch", "dh", "zh", "sh", "ng" }; private readonly string[] cccExceptions = { "spr", "spl", "skr", "str", "skw", "sky", "spy", "skt" }; @@ -81,6 +96,8 @@ 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" }; + protected override string[] GetVowels() => vowels; @@ -117,10 +134,13 @@ protected override IG2p LoadBaseDictionary() { protected override List ProcessSyllable(Syllable syllable) { string prevV = syllable.prevV; string[] cc = syllable.cc; + string[] PreviousWordCc = syllable.PreviousWordCc; + string[] CurrentWordCc = syllable.CurrentWordCc; string v = syllable.v; var lastC = cc.Length - 1; var lastCPrevWord = syllable.prevWordConsonantsCount; + string basePhoneme = null; var phonemes = new List(); // --------------------------- STARTING V ------------------------------- // @@ -144,7 +164,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}"; @@ -215,24 +235,32 @@ protected override List ProcessSyllable(Syllable syllable) { var parsingVCC = $"{prevV}{cc[0]}-"; var parsingCC = ""; - // if only one Consonant [V C] + [CV] + // if only one Consonant [V C] + [CV], [VC-][CV], or [VC][_V] if certain rules are met if (syllable.IsVCVWithOneConsonant) { basePhoneme = $"{cc.Last()}{v}"; + var vc = $"{prevV} {cc.Last()}"; + if (!HasOto(basePhoneme, syllable.vowelTone)) { - if ($"{cc.Last()}" == "ng") { + if ($"{cc.Last()}" == "ng") + basePhoneme = $"_{v}"; + } - basePhoneme = $"g{v}"; - } else 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()}-"; + } - var vc = $"{prevV} {cc.Last()}"; + if (!HasOto(vc, syllable.vowelTone)) { + if ($"{cc.Last()}" == "ng") + vc = $"{prevV}ng"; + } vc = CheckVCExceptions(vc); - if ($"{cc.Last()}" == "ng") { - - vc += "-"; - } - phonemes.Add(vc); } else if (syllable.IsVCVWithMoreThanOneConsonant) { @@ -267,7 +295,7 @@ protected override List ProcessSyllable(Syllable syllable) { if ($"{ccNoParse}" == "hhy") { vc = $"{prevV} hh"; } - + phonemes.Add(vc); } @@ -289,24 +317,9 @@ protected override List ProcessSyllable(Syllable syllable) { } if (phonemes.Count == 0) { - // bonehead [On-] + [n h] + [he] - parsingCC = $"{cc[0]} {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); - } - // sp fix - if ($"{cc[0]}" == "s" && $"{cc[1]}" == "p") { - parsingVCC = $"{prevV} sp"; - } - phonemes.Add(parsingVCC); - phonemes.Add(parsingCC); - } else { - // opera [9 p] + [pr] + [_ru] + // opera [9 p] + [pr] + [_ru] parsingCC = $"{cc[0]}{cc[1]}"; - if (HasOto(parsingCC, syllable.vowelTone) && lastCPrevWord != 1 && !stopCs.Contains($"{cc[1]}")) { + if (HasOto(parsingCC, syllable.vowelTone) && lastCPrevWord != 1 && ucvCs.Contains($"{cc[1]}")) { parsingVCC = $"{prevV} {cc[0]}"; basePhoneme = $"_{cc.Last()}{v}"; @@ -317,6 +330,25 @@ protected override List ProcessSyllable(Syllable syllable) { } } + // sp fix + if ($"{cc[0]}" == "s" && $"{cc[1]}" == "p") { + parsingVCC = $"{prevV} sp"; + } + phonemes.Add(parsingVCC); + phonemes.Add(parsingCC); + } else { + // bonehead [On-] + [n h] + [he] + parsingCC = $"{cc[0]} {cc[1]}"; + 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); + } // sp fix if ($"{cc[0]}" == "s" && $"{cc[1]}" == "p") { @@ -505,8 +537,6 @@ protected override List ProcessSyllable(Syllable syllable) { } } - - } if (!HasOto(basePhoneme, syllable.vowelTone)) { basePhoneme = $"{cc.Last()}{v}"; } @@ -626,7 +656,7 @@ protected override List ProcessEnding(Ending ending) { } } - + } } @@ -648,11 +678,9 @@ protected override string ValidateAlias(string alias) { //} foreach (var consonant in new[] { "6r" }) { alias = alias.Replace(consonant, "3"); - } + } return alias; } - - } } diff --git a/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs b/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs index 47bd632df..e67f9dbe6 100644 --- a/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs @@ -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(); + bool preCFlag = false; + var note = notes[0]; var currentLyric = note.lyric.Normalize(); // Normalize(): measures for Unicode if (!string.IsNullOrEmpty(note.phoneticHint)) { @@ -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; @@ -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 { 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 { $"-{vcvpad}{phoneme.Consonant}" }, note, 2, out var coto) + && checkOtoUntilHit(new List { 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(); @@ -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{ $"{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 { $"{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; @@ -255,7 +287,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN var tests = new List{ 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() }; } } @@ -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() }; } } } @@ -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 input, Note note, out UOto oto) { - oto = default; - var attr = note.phonemeAttributes?.FirstOrDefault(attr => attr.index == 0) ?? default; - - var otos = new List(); - 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 input, Note note, out UOto oto) { + private bool checkOtoUntilHit(List 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(); foreach (string test in input) { @@ -353,7 +354,8 @@ private bool checkOtoUntilHitVc(List 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; diff --git a/OpenUtau/Controls/ExpressionCanvas.cs b/OpenUtau/Controls/ExpressionCanvas.cs index 108312b7a..e15d7dab9 100644 --- a/OpenUtau/Controls/ExpressionCanvas.cs +++ b/OpenUtau/Controls/ExpressionCanvas.cs @@ -172,7 +172,9 @@ public override void Render(DrawingContext context) { double y = optionHeight * (descriptor.options.Length - 1 - i + 0.5); using (var state = context.PushTransform(Matrix.CreateTranslation(x1 + 4.5, y))) { if ((int)value == i) { - context.DrawGeometry(brush, null, pointGeometry); + if (overriden) { + context.DrawGeometry(brush, null, pointGeometry); + } context.DrawGeometry(null, hPen, circleGeometry); } else { context.DrawGeometry(null, ThemeManager.NeutralAccentPenSemi, circleGeometry); diff --git a/OpenUtau/Controls/NotePropertiesControl.axaml b/OpenUtau/Controls/NotePropertiesControl.axaml index d2f3dba01..14d0aff2b 100644 --- a/OpenUtau/Controls/NotePropertiesControl.axaml +++ b/OpenUtau/Controls/NotePropertiesControl.axaml @@ -51,14 +51,14 @@