From 2fbe8cc0e7db6ee8166ce2cfbf6f7193e4ab113f Mon Sep 17 00:00:00 2001 From: oxygen-dioxide <54425948+oxygen-dioxide@users.noreply.github.com> Date: Fri, 20 Oct 2023 13:17:18 +0800 Subject: [PATCH 01/31] publish voicebank (unfinished) --- OpenUtau.Core/Classic/VoicebankPublisher.cs | 94 +++++++++++++++++++++ OpenUtau.Core/OpenUtau.Core.csproj | 1 + 2 files changed, 95 insertions(+) create mode 100644 OpenUtau.Core/Classic/VoicebankPublisher.cs diff --git a/OpenUtau.Core/Classic/VoicebankPublisher.cs b/OpenUtau.Core/Classic/VoicebankPublisher.cs new file mode 100644 index 000000000..9f90fbf68 --- /dev/null +++ b/OpenUtau.Core/Classic/VoicebankPublisher.cs @@ -0,0 +1,94 @@ +using Ignore; +using System; +using System.Collections.Generic; +using System.IO; +using SharpCompress.Archives.Zip; +using SharpCompress.Common; +using SharpCompress.Writers; +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) ?? 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; + } + + public void Publish(USinger singer, string outputPath){ + /// + ///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. + /// + 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(); + var options = new WriterOptions(compressionType: CompressionType.Deflate); + options.ArchiveEncoding = new ArchiveEncoding{ + Forced = System.Text.Encoding.UTF8 + }; + using(var archive = ZipArchive.Create()) + { + foreach (var absFilePath in packList) + { + index++; + progress.Invoke(100.0 * index / fileCount, $"Compressing {absFilePath}"); + string reFilePath = Path.GetRelativePath(location, absFilePath); + using(var inputStream = File.OpenRead(absFilePath)){ + archive.AddEntry(reFilePath, inputStream); + } + } + using(var outputStream = File.OpenWrite(outputPath)){ + archive.SaveTo(outputStream, options); + } + } + progress.Invoke(0, $"Published {singer.Name} to {outputPath}"); + } + } +} diff --git a/OpenUtau.Core/OpenUtau.Core.csproj b/OpenUtau.Core/OpenUtau.Core.csproj index 306b5e2e0..df1953711 100644 --- a/OpenUtau.Core/OpenUtau.Core.csproj +++ b/OpenUtau.Core/OpenUtau.Core.csproj @@ -10,6 +10,7 @@ + From 45b5748b5b63497f9dd4486b17ad07c3367cbb4f Mon Sep 17 00:00:00 2001 From: yqzhishen Date: Sun, 22 Oct 2023 14:49:10 +0800 Subject: [PATCH 02/31] Support tension as TENC (experimental) --- OpenUtau.Core/DiffSinger/DiffSingerConfig.cs | 6 +- .../DiffSinger/DiffSingerRenderer.cs | 16 +++- .../DiffSinger/DiffSingerVariance.cs | 74 +++++++++++++------ 3 files changed, 69 insertions(+), 27 deletions(-) diff --git a/OpenUtau.Core/DiffSinger/DiffSingerConfig.cs b/OpenUtau.Core/DiffSinger/DiffSingerConfig.cs index f75ce88dd..2be651944 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerConfig.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerConfig.cs @@ -23,7 +23,8 @@ 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 useTensionEmbed = false; public AugmentationArgs augmentationArgs; public bool useShallowDiffusion = false; public int maxDepth = -1; @@ -34,6 +35,9 @@ 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_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 792c2aafb..12ab6b88e 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs @@ -29,6 +29,7 @@ public class DiffSingerRenderer : IRenderer { Format.Ustx.GENC, Format.Ustx.CLR, Format.Ustx.BREC, + Format.Ustx.TENC, VELC, ENE, PEXP, @@ -218,7 +219,7 @@ float[] InvokeDiffsinger(RenderPhrase phrase, int depth, int speedup) { } //Variance: Energy and Breathiness - if(singer.dsConfig.useBreathinessEmbed || singer.dsConfig.useEnergyEmbed){ + if(singer.dsConfig.useBreathinessEmbed || singer.dsConfig.useEnergyEmbed || singer.dsConfig.useTensionEmbed){ var varianceResult = singer.getVariancePredictor().Process(phrase); //TODO: let user edit variance curves if(singer.dsConfig.useEnergyEmbed){ @@ -231,7 +232,7 @@ float[] InvokeDiffsinger(RenderPhrase phrase, int depth, int speedup) { } else{ userEnergy = Enumerable.Repeat(0d, totalFrames); } - var energy = varianceResult.energy.Zip(userEnergy, (x,y)=>(float)Math.Min(x + y*12/100, 0)).ToArray(); + var energy = varianceResult.energy!.Zip(userEnergy, (x,y)=>(float)Math.Min(x + y*12/100, 0)).ToArray(); acousticInputs.Add(NamedOnnxValue.CreateFromTensor("energy", new DenseTensor(energy, new int[] { energy.Length }) .Reshape(new int[] { 1, energy.Length }))); @@ -240,11 +241,20 @@ float[] InvokeDiffsinger(RenderPhrase phrase, int depth, int speedup) { var userBreathiness = DiffSingerUtils.SampleCurve(phrase, phrase.breathiness, 0, frameMs, totalFrames, headFrames, tailFrames, x => x); - var breathiness = varianceResult.breathiness.Zip(userBreathiness, (x,y)=>(float)Math.Min(x + y*12/100, 0)).ToArray(); + var breathiness = varianceResult.breathiness!.Zip(userBreathiness, (x,y)=>(float)Math.Min(x + y*12/100, 0)).ToArray(); acousticInputs.Add(NamedOnnxValue.CreateFromTensor("breathiness", new DenseTensor(breathiness, new int[] { breathiness.Length }) .Reshape(new int[] { 1, breathiness.Length }))); } + if(singer.dsConfig.useTensionEmbed){ + var userTension = DiffSingerUtils.SampleCurve(phrase, phrase.tension, + 0, frameMs, totalFrames, headFrames, tailFrames, + x => x); + var tension = varianceResult.tension!.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; diff --git a/OpenUtau.Core/DiffSinger/DiffSingerVariance.cs b/OpenUtau.Core/DiffSinger/DiffSingerVariance.cs index 853736f7f..cfaeefc33 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerVariance.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerVariance.cs @@ -14,8 +14,9 @@ namespace OpenUtau.Core.DiffSinger{ public struct VarianceResult{ - public float[] energy; - public float[] breathiness; + public float[]? energy; + public float[]? breathiness; + public float[]? tension; } public class DsVariance{ string rootPath; @@ -115,7 +116,7 @@ public VarianceResult Process(RenderPhrase phrase){ new DenseTensor(ph_dur.Select(x=>(Int64)x).ToArray(), new int[] { ph_dur.Length }, false) .Reshape(new int[] { 1, ph_dur.Length }))); } - + var linguisticOutputs = linguisticModel.Run(linguisticInputs); Tensor encoder_out = linguisticOutputs .Where(o => o.Name == "encoder_out") @@ -126,9 +127,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(); @@ -139,15 +137,34 @@ 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_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_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 @@ -157,17 +174,28 @@ public VarianceResult Process(RenderPhrase phrase){ varianceInputs.Add(NamedOnnxValue.CreateFromTensor("spk_embed", spkEmbedTensor)); } 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? 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(), + tension = tension_pred?.ToArray(), }; } } From de3edf8d1f241756619b999eca6d3b937824bd2f Mon Sep 17 00:00:00 2001 From: yqzhishen Date: Sun, 22 Oct 2023 00:24:59 +0800 Subject: [PATCH 03/31] Fix invalid tensor in memory when errors occur during *.emb loading --- OpenUtau.Core/DiffSinger/DiffSingerSpeakerEmbedManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/OpenUtau.Core/DiffSinger/DiffSingerSpeakerEmbedManager.cs b/OpenUtau.Core/DiffSinger/DiffSingerSpeakerEmbedManager.cs index 551d6c826..979d49dd7 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerSpeakerEmbedManager.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerSpeakerEmbedManager.cs @@ -37,10 +37,11 @@ public NDArray getSpeakerEmbeds() { if(dsConfig.speakers == null) { return null; } else { - speakerEmbeds = np.zeros(dsConfig.hiddenSize, dsConfig.speakers.Count); + var embeds = np.zeros(dsConfig.hiddenSize, dsConfig.speakers.Count); foreach(var spkId in Enumerable.Range(0, dsConfig.speakers.Count)) { - speakerEmbeds[":", spkId] = loadSpeakerEmbed(dsConfig.speakers[spkId]); + embeds[":", spkId] = loadSpeakerEmbed(dsConfig.speakers[spkId]); } + speakerEmbeds = embeds; } } return speakerEmbeds; From f4de0ed63847919c05e7ed993f311a7391a6e38a Mon Sep 17 00:00:00 2001 From: yqzhishen Date: Sun, 22 Oct 2023 01:26:21 +0800 Subject: [PATCH 04/31] Fix wrong logic to get rest note groups --- OpenUtau.Core/DiffSinger/DiffSingerPitch.cs | 22 +++++++-------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/OpenUtau.Core/DiffSinger/DiffSingerPitch.cs b/OpenUtau.Core/DiffSinger/DiffSingerPitch.cs index 7cbec606d..22177a696 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerPitch.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerPitch.cs @@ -162,21 +162,13 @@ public RenderPitchResult Process(RenderPhrase phrase){ .Prepend((float)phrase.notes[0].tone) .ToArray(); //get the index of groups of consecutive rest notes - int restGroupStart = 0; - var restGroups = new List>{}; - foreach(int noteIndex in Enumerable.Range(1,note_rest.Count - 1)) { - if(!note_rest[noteIndex-1] && note_rest[noteIndex]) { - //start a new rest group - restGroupStart = noteIndex; - } - if(note_rest[noteIndex-1] && !note_rest[noteIndex]) { - //end the current rest group - restGroups.Add(new Tuple(restGroupStart,noteIndex)); - } - } - if(!note_rest[^1]) { - //end the last rest group - restGroups.Add(new Tuple(restGroupStart,note_rest.Count)); + var restGroups = new List>(); + for (var i = 0; i < note_rest.Count; ++i) { + if (!note_rest[i]) continue; + var j = i + 1; + for (; j < note_rest.Count && note_rest[j]; ++j) { } + restGroups.Add(new Tuple(i, j)); + i = j; } //Set tone for each rest group foreach(var restGroup in restGroups){ From 156282db7d31f353f4adb97ed5750ccc7343b0cb Mon Sep 17 00:00:00 2001 From: AnAndroNerd <87346264+AnAndroNerd@users.noreply.github.com> Date: Thu, 1 Feb 2024 19:37:19 +0000 Subject: [PATCH 05/31] Changes Behavor of vvExceptions and of when CC last is ng next to a vowel --- OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs b/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs index e7a673848..04b061fcf 100644 --- a/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs @@ -54,7 +54,6 @@ public class EnglishVCCVPhonemizer : SyllableBasedPhonemizer { private readonly Dictionary vvExceptions = new Dictionary() { {"o","w"}, - {"0","w"}, {"O","w"}, {"8","w"}, {"A","y"}, @@ -144,7 +143,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}"; @@ -221,17 +220,13 @@ protected override List ProcessSyllable(Syllable syllable) { if (!HasOto(basePhoneme, syllable.vowelTone)) { if ($"{cc.Last()}" == "ng") { - basePhoneme = $"g{v}"; - } else basePhoneme = $"_{v}"; + basePhoneme = $"_{v}"; + } else basePhoneme = $"g{v}"; } var vc = $"{prevV} {cc.Last()}"; vc = CheckVCExceptions(vc); - if ($"{cc.Last()}" == "ng") { - - vc += "-"; - } phonemes.Add(vc); From 36e08a36b9ba9b9bba0c25f34c3a5fd5f9fb2701 Mon Sep 17 00:00:00 2001 From: AnAndroNerd <87346264+AnAndroNerd@users.noreply.github.com> Date: Fri, 2 Feb 2024 17:26:02 +0000 Subject: [PATCH 06/31] Bug fixes, new VCV behavior, changed vcExceptions --- .../EnglishVCCVPhonemizer.cs | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs b/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs index 04b061fcf..a3566152f 100644 --- a/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs @@ -28,23 +28,24 @@ public class EnglishVCCVPhonemizer : SyllableBasedPhonemizer { private readonly Dictionary vcExceptions = new Dictionary() { - {"i ng","1ng"}, + {"i ng","1ng-"}, {"ing","1ng"}, - {"0 r","0r"}, - {"9 r","0r"}, + {"0 r","0r-"}, + {"9 r","0r-"}, {"9r","0r"}, + {"er-","Ar-" }, //{"e r","Ar"}, {"er","Ar"}, //{"@ m","&m"}, {"@m","&m"}, {"@n","&n"}, - {"1 ng","1ng"}, - {"@ ng","Ang"}, + {"1 ng","1ng-"}, + {"@ 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"}, @@ -116,6 +117,7 @@ protected override IG2p LoadBaseDictionary() { protected override List ProcessSyllable(Syllable syllable) { string prevV = syllable.prevV; string[] cc = syllable.cc; + string[] PreviousWordCc = syllable.PreviousWordCc; string v = syllable.v; var lastC = cc.Length - 1; var lastCPrevWord = syllable.prevWordConsonantsCount; @@ -217,6 +219,8 @@ protected override List ProcessSyllable(Syllable syllable) { // if only one Consonant [V C] + [CV] if (syllable.IsVCVWithOneConsonant) { basePhoneme = $"{cc.Last()}{v}"; + + if (!HasOto(basePhoneme, syllable.vowelTone)) { if ($"{cc.Last()}" == "ng") { @@ -226,6 +230,18 @@ protected override List ProcessSyllable(Syllable syllable) { var vc = $"{prevV} {cc.Last()}"; + if ($"{PreviousWordCc.Last()}" == "r") { + vc = $"{prevV}r-"; + } + + if ($"{PreviousWordCc.Last()}" == "l") { + vc = $"{prevV}l-"; + } + + if (!HasOto(vc, syllable.vowelTone)) ; + if ($"{cc.Last()}" == "ng") { + vc = $"{prevV}ng"; + } vc = CheckVCExceptions(vc); phonemes.Add(vc); From 44a7d2f9c0b4e61ee514d8c758df43287039aa2d Mon Sep 17 00:00:00 2001 From: AnAndroNerd <87346264+AnAndroNerd@users.noreply.github.com> Date: Fri, 2 Feb 2024 17:45:46 +0000 Subject: [PATCH 07/31] Semi fixed an ARPABET conflict --- OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs b/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs index a3566152f..3090780b5 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(","); @@ -33,6 +33,7 @@ public class EnglishVCCVPhonemizer : SyllableBasedPhonemizer { {"0 r","0r-"}, {"9 r","0r-"}, {"9r","0r"}, + {"9r-","0r-"}, {"er-","Ar-" }, //{"e r","Ar"}, {"er","Ar"}, From ced943459cc8ef3ff21d3dc2a347b698e16757b9 Mon Sep 17 00:00:00 2001 From: Maiko Date: Tue, 6 Feb 2024 23:07:44 +0900 Subject: [PATCH 08/31] Support "- C" in JA VCV&CVVC Phonemizer --- OpenUtau.Core/Api/Phonemizer.cs | 2 + OpenUtau.Core/Ustx/UPart.cs | 2 +- .../JapanesePresampPhonemizer.cs | 95 ++++++++----------- 3 files changed, 44 insertions(+), 55 deletions(-) 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/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.Plugin.Builtin/JapanesePresampPhonemizer.cs b/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs index 47bd632df..19887d7c4 100644 --- a/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs @@ -50,6 +50,8 @@ 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(); + var note = notes[0]; var currentLyric = note.lyric.Normalize(); // Normalize(): measures for Unicode if (!string.IsNullOrEmpty(note.phoneticHint)) { @@ -191,12 +193,27 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN } } } + result.Add(new Phoneme() { phoneme = currentLyric, index = 1 }); + + // Insert "- C" + if (string.IsNullOrEmpty(note.phoneticHint) && !currentLyric.Contains(vcvpad) && presamp.PhonemeList.TryGetValue(currentLyric, out PresampPhoneme phoneme)) { + if (phoneme.HasConsonant) { + if (checkOtoUntilHit(new List { $"-{vcvpad}{phoneme.Consonant}" }, note, 0, out var coto) + && checkOtoUntilHit(new List { currentLyric }, note, out var oto)) { + result.Insert(0, new Phoneme() { + phoneme = coto.Alias, + position = - MsToTick(oto.Preutter), + index = 0 + }); + } + } + } // 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 +227,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, 2, 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 +272,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 +283,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, 2, out var oto)) { vcPhoneme = oto.Alias; } else { - return MakeSimpleResult(currentLyric); + return new Result { phonemes = result.ToArray() }; } } } @@ -286,57 +303,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 = 2 + }); } } - // 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, 1, 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 +339,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; From bd84e16c3e890879765602365dd4f3797cf507e7 Mon Sep 17 00:00:00 2001 From: Maiko Date: Tue, 6 Feb 2024 23:32:58 +0900 Subject: [PATCH 09/31] change main phoneme's index to 0 --- OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs b/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs index 19887d7c4..e57d4cea8 100644 --- a/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs @@ -198,7 +198,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN // Insert "- C" if (string.IsNullOrEmpty(note.phoneticHint) && !currentLyric.Contains(vcvpad) && presamp.PhonemeList.TryGetValue(currentLyric, out PresampPhoneme phoneme)) { if (phoneme.HasConsonant) { - if (checkOtoUntilHit(new List { $"-{vcvpad}{phoneme.Consonant}" }, note, 0, out var coto) + if (checkOtoUntilHit(new List { $"-{vcvpad}{phoneme.Consonant}" }, note, 2, out var coto) && checkOtoUntilHit(new List { currentLyric }, note, out var oto)) { result.Insert(0, new Phoneme() { phoneme = coto.Alias, @@ -250,7 +250,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN } // next is CV (VC is needed) tests = new List { $"{vowel}{vcpad}・" }; - if (checkOtoUntilHit(tests, note, 2, out oto1)) { + if (checkOtoUntilHit(tests, note, 1, out oto1)) { vcPhoneme = oto1.Alias; } else { return new Result { phonemes = result.ToArray() }; @@ -283,7 +283,7 @@ 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 (checkOtoUntilHit(vcPhonemes, note, 2, out var oto)) { + if (checkOtoUntilHit(vcPhonemes, note, 1, out var oto)) { vcPhoneme = oto.Alias; } else { return new Result { phonemes = result.ToArray() }; @@ -317,7 +317,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN // 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) { - return checkOtoUntilHit(input, note, 1, out oto); + return checkOtoUntilHit(input, note, 0, out oto); } private bool checkOtoUntilHit(List input, Note note, int index, out UOto oto) { From 72a3926368e1d67fc6ac57b740d0f8d0711c0a8b Mon Sep 17 00:00:00 2001 From: AnAndroNerd <87346264+AnAndroNerd@users.noreply.github.com> Date: Tue, 6 Feb 2024 14:55:28 +0000 Subject: [PATCH 10/31] Adds support for banks with [V ng] --- OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs b/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs index 3090780b5..6c21c3c64 100644 --- a/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/EnglishVCCVPhonemizer.cs @@ -217,7 +217,7 @@ 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}"; @@ -243,7 +243,13 @@ protected override List ProcessSyllable(Syllable syllable) { if ($"{cc.Last()}" == "ng") { vc = $"{prevV}ng"; } + vc = CheckVCExceptions(vc); + + if (HasOto($"{prevV} ng", syllable.vowelTone)) ; + if ($"{cc.Last()}" == "ng") { + vc = $"{prevV} ng"; + } phonemes.Add(vc); From d3a575764222c7a6e2007df00e1c46906f094d58 Mon Sep 17 00:00:00 2001 From: Maiko Date: Wed, 7 Feb 2024 02:07:14 +0900 Subject: [PATCH 11/31] fixed small bug --- .../JapanesePresampPhonemizer.cs | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs b/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs index e57d4cea8..7740bbd8a 100644 --- a/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs @@ -51,6 +51,7 @@ 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 @@ -80,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; @@ -187,25 +189,28 @@ 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 = 1 }); + result.Add(new Phoneme() { phoneme = currentLyric, index = 0 }); // Insert "- C" - if (string.IsNullOrEmpty(note.phoneticHint) && !currentLyric.Contains(vcvpad) && presamp.PhonemeList.TryGetValue(currentLyric, out PresampPhoneme phoneme)) { - if (phoneme.HasConsonant) { - if (checkOtoUntilHit(new List { $"-{vcvpad}{phoneme.Consonant}" }, note, 2, out var coto) - && checkOtoUntilHit(new List { currentLyric }, note, out var oto)) { - result.Insert(0, new Phoneme() { - phoneme = coto.Alias, - position = - MsToTick(oto.Preutter), - index = 0 - }); - } + if (string.IsNullOrEmpty(note.phoneticHint) + && preCFlag + && !currentLyric.Contains(vcvpad) + && presamp.PhonemeList.TryGetValue(currentLyric, 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)) { + result.Insert(0, new Phoneme() { + phoneme = coto.Alias, + position = - MsToTick(oto.Preutter), + index = 2 + }); } } @@ -307,7 +312,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN result.Add(new Phoneme() { phoneme = vcPhoneme, position = totalDuration - vcLength, - index = 2 + index = 1 }); } } From 2b673e7c6c6d5e54d26f561b32a25f2190367c0f Mon Sep 17 00:00:00 2001 From: oxygen-dioxide <54425948+oxygen-dioxide@users.noreply.github.com> Date: Wed, 7 Feb 2024 09:30:22 +0800 Subject: [PATCH 12/31] DiffSinger phonemizers: Return error if lyric isn't found in the dictionary --- .../DiffSinger/DiffSingerBasePhonemizer.cs | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) 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; } } } From c1e13bcef0ad69f50c85e97c17a17c1bfda1ebfb Mon Sep 17 00:00:00 2001 From: Astel123457 <41224955+Astel123457@users.noreply.github.com> Date: Fri, 9 Feb 2024 16:00:57 -0500 Subject: [PATCH 13/31] Add toggle for defaulting to pen plus too This PR adds a toggle for the pen plus tool to be the default tool when OpenUtau is first loaded, and when pressing 2 instead of CTRL+2 --- OpenUtau.Core/Util/Preferences.cs | 1 + OpenUtau/Strings/Strings.axaml | 1 + OpenUtau/ViewModels/NotesViewModel.cs | 18 ++++++++++++++---- OpenUtau/ViewModels/PreferencesViewModel.cs | 7 +++++++ OpenUtau/Views/PreferencesDialog.axaml | 4 ++++ 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/OpenUtau.Core/Util/Preferences.cs b/OpenUtau.Core/Util/Preferences.cs index 1b6224d36..b17580b1e 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; diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index 1baed278b..febc98f6a 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -345,6 +345,7 @@ Warning: this option removes custom presets. Load all depth folders Reset Select + Set Pen Plus Tool as Default Playback Auto-Scroll Auto-Scroll Mode diff --git a/OpenUtau/ViewModels/NotesViewModel.cs b/OpenUtau/ViewModels/NotesViewModel.cs index c7b189188..ad09f50ed 100644 --- a/OpenUtau/ViewModels/NotesViewModel.cs +++ b/OpenUtau/ViewModels/NotesViewModel.cs @@ -194,15 +194,25 @@ public NotesViewModel() { }); CursorTool = false; - PenTool = true; - PenPlusTool = false; + if (Preferences.Default.PenPlusDefault) { + PenPlusTool = true; + PenTool = false; + } else { + PenTool = true; + PenPlusTool = false; + } EraserTool = false; DrawPitchTool = false; KnifeTool = false; SelectToolCommand = ReactiveCommand.Create(index => { CursorTool = index == "1"; - PenTool = index == "2"; - PenPlusTool = index == "2+"; + if (Preferences.Default.PenPlusDefault) { + PenPlusTool = index == "2"; + PenTool = index == "2+"; + } else { + PenTool = index == "2"; + PenPlusTool = index == "2+"; + } EraserTool = index == "3"; DrawPitchTool = index == "4"; KnifeTool = index == "5"; diff --git a/OpenUtau/ViewModels/PreferencesViewModel.cs b/OpenUtau/ViewModels/PreferencesViewModel.cs index b7cba6b3a..67f1a41c2 100644 --- a/OpenUtau/ViewModels/PreferencesViewModel.cs +++ b/OpenUtau/ViewModels/PreferencesViewModel.cs @@ -44,6 +44,7 @@ public AudioOutputDevice? AudioOutputDevice { [Reactive] public int DiffsingerSpeedup { get; set; } [Reactive] public bool HighThreads { get; set; } [Reactive] public int Theme { get; set; } + [Reactive] public bool PenPlusDefault { get; set; } [Reactive] public int DegreeStyle { get; set; } [Reactive] public bool UseTrackColor { get; set; } [Reactive] public bool ShowPortrait { get; set; } @@ -141,6 +142,7 @@ public PreferencesViewModel() { DiffSingerDepth = Preferences.Default.DiffSingerDepth; DiffsingerSpeedup = Preferences.Default.DiffsingerSpeedup; Theme = Preferences.Default.Theme; + PenPlusDefault = Preferences.Default.PenPlusDefault; DegreeStyle = Preferences.Default.DegreeStyle; UseTrackColor = Preferences.Default.UseTrackColor; ShowPortrait = Preferences.Default.ShowPortrait; @@ -203,6 +205,11 @@ public PreferencesViewModel() { Preferences.Default.PreRender = preRender; Preferences.Save(); }); + this.WhenAnyValue(vm => vm.PenPlusDefault) + .Subscribe(penPlusDefault => { + Preferences.Default.PenPlusDefault = penPlusDefault; + Preferences.Save(); + }); this.WhenAnyValue(vm => vm.Language) .Subscribe(lang => { Preferences.Default.Language = lang?.Name ?? string.Empty; diff --git a/OpenUtau/Views/PreferencesDialog.axaml b/OpenUtau/Views/PreferencesDialog.axaml index bd9ccc1e4..b5b4f81eb 100644 --- a/OpenUtau/Views/PreferencesDialog.axaml +++ b/OpenUtau/Views/PreferencesDialog.axaml @@ -228,6 +228,10 @@ + + + + From 9538d2a4df34e51cfc5f6d0e27309ec989220a69 Mon Sep 17 00:00:00 2001 From: Maiko Date: Sat, 10 Feb 2024 22:23:22 +0900 Subject: [PATCH 14/31] - C support completed --- OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs b/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs index 7740bbd8a..875dd5a80 100644 --- a/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs @@ -202,7 +202,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN if (string.IsNullOrEmpty(note.phoneticHint) && preCFlag && !currentLyric.Contains(vcvpad) - && presamp.PhonemeList.TryGetValue(currentLyric, out PresampPhoneme phoneme) + && 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)) { From a64a961729e45ca6b14af7ed6e8d985bc8bcd70c Mon Sep 17 00:00:00 2001 From: Maiko Date: Sat, 10 Feb 2024 23:05:42 +0900 Subject: [PATCH 15/31] fix not supported VEL --- OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs b/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs index 875dd5a80..e67f9dbe6 100644 --- a/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/JapanesePresampPhonemizer.cs @@ -206,9 +206,19 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN && 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 = - MsToTick(oto.Preutter), + position = Convert.ToInt32(- cLength), index = 2 }); } From 31736280f3840205584b8380eba7dc91db9774b1 Mon Sep 17 00:00:00 2001 From: liuycsd Date: Fri, 16 Feb 2024 00:53:12 +0000 Subject: [PATCH 16/31] Fix keyboard bindings of PianoRollWindow to match the comments --- OpenUtau/Views/PianoRollWindow.axaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenUtau/Views/PianoRollWindow.axaml.cs b/OpenUtau/Views/PianoRollWindow.axaml.cs index 645238951..5dec3e092 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml.cs +++ b/OpenUtau/Views/PianoRollWindow.axaml.cs @@ -1321,7 +1321,7 @@ bool OnKeyExtendedHandler(KeyEventArgs args) { } // to view end if (isShift) { - playVm.MovePlayPos(notesVm.Part.position + (int)(notesVm.TickOffset)); + playVm.MovePlayPos(notesVm.Part.position + (int)(notesVm.TickOffset + notesVm.Bounds.Width / notesVm.TickWidth)); return true; } break; @@ -1349,7 +1349,7 @@ bool OnKeyExtendedHandler(KeyEventArgs args) { } // select none if (isCtrl) { - notesVm.SelectAllNotes(); + notesVm.DeselectNotes(); return true; } break; From 1458949c08c56db3eea4e991b3364fa16b98777a Mon Sep 17 00:00:00 2001 From: liuycsd Date: Fri, 16 Feb 2024 00:57:26 +0000 Subject: [PATCH 17/31] Make MainWindow and PianoRollWindow focusable to fix focusing OnMenuClosed --- OpenUtau/Views/MainWindow.axaml | 1 + OpenUtau/Views/PianoRollWindow.axaml | 1 + 2 files changed, 2 insertions(+) diff --git a/OpenUtau/Views/MainWindow.axaml b/OpenUtau/Views/MainWindow.axaml index 2181498e0..5b94bfd29 100644 --- a/OpenUtau/Views/MainWindow.axaml +++ b/OpenUtau/Views/MainWindow.axaml @@ -9,6 +9,7 @@ Icon="/Assets/open-utau.ico" Title="{Binding Title}" MinWidth="300" MinHeight="200" KeyDown="OnKeyDown" PointerPressed="OnPointerPressed" Closing="WindowClosing" + Focusable="True" TransparencyLevelHint="None" ExtendClientAreaToDecorationsHint="{Binding ExtendToFrame}"> diff --git a/OpenUtau/Views/PianoRollWindow.axaml b/OpenUtau/Views/PianoRollWindow.axaml index 1aa18efec..7f9484a8d 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml +++ b/OpenUtau/Views/PianoRollWindow.axaml @@ -9,6 +9,7 @@ x:Class="OpenUtau.App.Views.PianoRollWindow" Icon="/Assets/open-utau.ico" Title="{Binding NotesViewModel.WindowTitle}" MinWidth="300" MinHeight="200" KeyDown="OnKeyDown" Closing="WindowClosing" + Focusable="True" TransparencyLevelHint="None" ExtendClientAreaToDecorationsHint="{Binding ExtendToFrame}" Deactivated="WindowDeactivated"> From 67f22fd563db4286f265a4cc6eb52f826833074c Mon Sep 17 00:00:00 2001 From: liuycsd Date: Sat, 17 Feb 2024 03:11:57 +0000 Subject: [PATCH 18/31] Clear solo flag on copied tracks --- OpenUtau/ViewModels/TrackHeaderViewModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenUtau/ViewModels/TrackHeaderViewModel.cs b/OpenUtau/ViewModels/TrackHeaderViewModel.cs index 38667f91a..4e6a3cbdf 100644 --- a/OpenUtau/ViewModels/TrackHeaderViewModel.cs +++ b/OpenUtau/ViewModels/TrackHeaderViewModel.cs @@ -430,7 +430,7 @@ public void Duplicate() { RendererSettings = track.RendererSettings, Mute = track.Mute, Muted = track.Muted, - Solo = track.Solo, + Solo = false, Volume = track.Volume, Pan = track.Pan, TrackColor = track.TrackColor @@ -456,7 +456,7 @@ public void DuplicateSettings() { RendererSettings = track.RendererSettings, Mute = track.Mute, Muted = track.Muted, - Solo = track.Solo, + Solo = false, Volume = track.Volume, Pan = track.Pan, TrackColor = track.TrackColor From 65805110f72191bc67e60fef35e2bd78a4231ecf Mon Sep 17 00:00:00 2001 From: liuycsd Date: Sat, 17 Feb 2024 16:26:40 +0000 Subject: [PATCH 19/31] Open singer icon read-only --- OpenUtau.Core/Classic/ClassicSinger.cs | 2 +- OpenUtau.Core/DiffSinger/DiffSingerSinger.cs | 2 +- OpenUtau.Core/Enunu/EnunuSinger.cs | 2 +- OpenUtau.Core/Format/OpusOggWaveReader.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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/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/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); } From b0931987dc38096ea22a34cf0775bf62d5c99290 Mon Sep 17 00:00:00 2001 From: yqzhishen Date: Sun, 18 Feb 2024 21:11:00 +0800 Subject: [PATCH 20/31] Adapt recent updates --- OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs b/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs index 4b84fea45..cbe93daff 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs @@ -229,8 +229,7 @@ float[] InvokeDiffsinger(RenderPhrase phrase, int depth, int speedup, Cancellati acousticInputs.Add(NamedOnnxValue.CreateFromTensor("velocity", velocityTensor)); } - //Variance: Energy and Breathiness - + //Variance: Energy, Breathiness and Tension if(singer.dsConfig.useBreathinessEmbed || singer.dsConfig.useEnergyEmbed || singer.dsConfig.useTensionEmbed){ var variancePredictor = singer.getVariancePredictor(); VarianceResult varianceResult; @@ -271,7 +270,8 @@ float[] InvokeDiffsinger(RenderPhrase phrase, int depth, int speedup, Cancellati var userTension = DiffSingerUtils.SampleCurve(phrase, phrase.tension, 0, frameMs, totalFrames, headFrames, tailFrames, x => x); - var tension = varianceResult.tension!.Zip(userTension, (x,y)=>(float)(x + y * 5 / 100)).ToArray(); + 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 }))); From ca68af3d6e9d63100215e2967fb6d5ee4afb1622 Mon Sep 17 00:00:00 2001 From: yqzhishen Date: Mon, 19 Feb 2024 02:31:36 +0800 Subject: [PATCH 21/31] Support voicing as VOIC --- OpenUtau.Core/DiffSinger/DiffSingerConfig.cs | 2 ++ .../DiffSinger/DiffSingerRenderer.cs | 19 +++++++++++++++++-- .../DiffSinger/DiffSingerVariance.cs | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/OpenUtau.Core/DiffSinger/DiffSingerConfig.cs b/OpenUtau.Core/DiffSinger/DiffSingerConfig.cs index 2be651944..4843b1c5a 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerConfig.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerConfig.cs @@ -24,6 +24,7 @@ public class DsConfig { public bool useSpeedEmbed = false; public bool useEnergyEmbed = false; public bool useBreathinessEmbed = false; + public bool useVoicingEmbed = false; public bool useTensionEmbed = false; public AugmentationArgs augmentationArgs; public bool useShallowDiffusion = false; @@ -37,6 +38,7 @@ public class DsConfig { 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; diff --git a/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs b/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs index cbe93daff..dd71f66ee 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs @@ -29,6 +29,7 @@ public class DiffSingerRenderer : IRenderer { Format.Ustx.GENC, Format.Ustx.CLR, Format.Ustx.BREC, + Format.Ustx.VOIC, Format.Ustx.TENC, VELC, ENE, @@ -229,8 +230,12 @@ float[] InvokeDiffsinger(RenderPhrase phrase, int depth, int speedup, Cancellati acousticInputs.Add(NamedOnnxValue.CreateFromTensor("velocity", velocityTensor)); } - //Variance: Energy, Breathiness and Tension - if(singer.dsConfig.useBreathinessEmbed || singer.dsConfig.useEnergyEmbed || singer.dsConfig.useTensionEmbed){ + //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,16 @@ 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, diff --git a/OpenUtau.Core/DiffSinger/DiffSingerVariance.cs b/OpenUtau.Core/DiffSinger/DiffSingerVariance.cs index b789443f9..017d04ea2 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerVariance.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerVariance.cs @@ -16,6 +16,7 @@ namespace OpenUtau.Core.DiffSinger{ public struct VarianceResult{ public float[]? energy; public float[]? breathiness; + public float[]? voicing; public float[]? tension; } public class DsVariance : IDisposable{ @@ -150,6 +151,12 @@ public VarianceResult Process(RenderPhrase phrase){ 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", @@ -160,6 +167,7 @@ public VarianceResult Process(RenderPhrase phrase){ 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(); @@ -188,6 +196,12 @@ public VarianceResult Process(RenderPhrase phrase){ .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") @@ -197,6 +211,7 @@ public VarianceResult Process(RenderPhrase phrase){ return new VarianceResult{ energy = energy_pred?.ToArray(), breathiness = breathiness_pred?.ToArray(), + voicing = voicing_pred?.ToArray(), tension = tension_pred?.ToArray(), }; } From 60903db9be132e30f45aea55c87b197239fa9d7a Mon Sep 17 00:00:00 2001 From: Maiko Date: Fri, 23 Feb 2024 23:37:11 +0900 Subject: [PATCH 22/31] Fix NoteProperiesPanel bug --- OpenUtau/Controls/NotePropertiesControl.axaml | 20 +++++++++---------- .../Controls/NotePropertiesControl.axaml.cs | 7 +++++-- .../Controls/NotePropertyExpression.axaml | 2 +- .../Controls/NotePropertyExpression.axaml.cs | 5 ++++- 4 files changed, 20 insertions(+), 14 deletions(-) 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 @@