From 8433ab00a5f89f434ddc7111ed057dd35d2a13c7 Mon Sep 17 00:00:00 2001 From: Maiko Date: Thu, 18 Jun 2026 23:33:58 +0900 Subject: [PATCH 1/4] Fixed a crash in edge cases during envelope manipulation --- OpenUtau.Core/Commands/NoteCommands.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenUtau.Core/Commands/NoteCommands.cs b/OpenUtau.Core/Commands/NoteCommands.cs index 5872239a2..2f82d5156 100644 --- a/OpenUtau.Core/Commands/NoteCommands.cs +++ b/OpenUtau.Core/Commands/NoteCommands.cs @@ -542,7 +542,7 @@ public PhonemePreutterCommand(UVoicePart part, UNote note, int index, UPhoneme p } max = Math.Max(0, max); double min = -phoneme.autoPreutter; - newDelta = (float)Math.Clamp(delta, min, max); + newDelta = (float)Math.Max(Math.Min(delta, max), min); } public override void Execute() { var o = note.GetPhonemeOverride(index); @@ -574,7 +574,7 @@ public PhonemeOverlapCommand(UVoicePart part, UNote note, int index, UPhoneme ph double overlap = phoneme.preutter - phoneme.autoOverlap; double max = phoneme.envelope.data[3].X + overlap; double min = -phoneme.Prev?.DurationMs + 5 + overlap ?? 0; - newDelta = (float)Math.Clamp(delta, min, max); + newDelta = (float)Math.Max(Math.Min(delta, max), min); } public override void Execute() { var o = note.GetPhonemeOverride(index); @@ -605,7 +605,7 @@ public PhonemeAttackTimeCommand(UVoicePart part, UNote note, int index, UPhoneme double max = phoneme.autoPreutter - phoneme.GetFadeIn() + phoneme.envelope.data[3].X; double min = -phoneme.GetFadeIn() + 5; - newDelta = (float)Math.Clamp(delta, min, max); + newDelta = (float)Math.Max(Math.Min(delta, max), min); } public override void Execute() { var o = note.GetPhonemeOverride(index); @@ -637,7 +637,7 @@ public PhonemeReleaseTimeCommand(UVoicePart part, UNote note, int index, UPhonem var p3x = phoneme.envelope.data[4].X - phoneme.GetFadeOut(); double max = p3x - phoneme.envelope.data[2].X; double min = -phoneme.GetFadeOut() + 5; - newDelta = (float)Math.Clamp(delta, min, max); + newDelta = (float)Math.Max(Math.Min(delta, max), min); } public override void Execute() { var o = note.GetPhonemeOverride(index); From e8d5da36fea19cbf617386a8847aa31b0e7ec7aa Mon Sep 17 00:00:00 2001 From: Maiko Date: Thu, 18 Jun 2026 23:34:51 +0900 Subject: [PATCH 2/4] The phoneme timing reset menu includes attack and release times --- OpenUtau.Core/Commands/NoteCommands.cs | 8 ++++++-- OpenUtau/Strings/Strings.axaml | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/OpenUtau.Core/Commands/NoteCommands.cs b/OpenUtau.Core/Commands/NoteCommands.cs index 2f82d5156..fe671d952 100644 --- a/OpenUtau.Core/Commands/NoteCommands.cs +++ b/OpenUtau.Core/Commands/NoteCommands.cs @@ -652,7 +652,7 @@ public override void Unexecute() { public class ClearPhonemeTimingCommand : NoteCommand { readonly UNote note; - readonly Tuple[] oldValues; + readonly Tuple[] oldValues; public override ValidateOptions ValidateOptions => new ValidateOptions { SkipTiming = true, Part = Part, @@ -661,7 +661,7 @@ public class ClearPhonemeTimingCommand : NoteCommand { public ClearPhonemeTimingCommand(UVoicePart part, UNote note) : base(part, note) { this.note = note; oldValues = note.phonemeOverrides - .Select(o => Tuple.Create(o.index, o.offset, o.preutterDelta, o.overlapDelta)) + .Select(o => Tuple.Create(o.index, o.offset, o.preutterDelta, o.overlapDelta, o.attackTimeDelta, o.releaseTimeDelta)) .ToArray(); } @@ -670,6 +670,8 @@ public override void Execute() { o.offset = null; o.preutterDelta = null; o.overlapDelta = null; + o.attackTimeDelta = null; + o.releaseTimeDelta = null; } } public override void Unexecute() { @@ -678,6 +680,8 @@ public override void Unexecute() { o.offset = t.Item2; o.preutterDelta = t.Item3; o.overlapDelta = t.Item4; + o.attackTimeDelta = t.Item5; + o.releaseTimeDelta = t.Item6; } } public override string ToString() => "Clear phoneme timing"; diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index 0be61d251..3b1099a9e 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -482,7 +482,7 @@ Warning: this option removes custom presets. Reset notes to default Reset all parameters Reset all expressions - Reset phoneme timings + Reset phoneme timings and envelopes Reset pitch bends Reset vibratos Part From c944184e74be5f10c3bb27d90fcbbd0976d2403a Mon Sep 17 00:00:00 2001 From: Maiko Date: Fri, 19 Jun 2026 00:41:07 +0900 Subject: [PATCH 3/4] =?UTF-8?q?Fixed=20a=20bug=20in=20=E2=80=9CLengthen=20?= =?UTF-8?q?crossfades=E2=80=9D=20and=20improved=20the=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenUtau.Core/Editing/NoteBatchEdits.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/OpenUtau.Core/Editing/NoteBatchEdits.cs b/OpenUtau.Core/Editing/NoteBatchEdits.cs index d8b4248e1..3aa5a8204 100644 --- a/OpenUtau.Core/Editing/NoteBatchEdits.cs +++ b/OpenUtau.Core/Editing/NoteBatchEdits.cs @@ -313,10 +313,13 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do foreach (var note in notes) { foreach (UPhoneme phoneme in part.phonemes) { if (phoneme.Parent == note && phoneme.Prev != null && phoneme.PositionMs == phoneme.Prev.EndMs) { - docManager.ExecuteCmd(new PhonemePreutterCommand(part, note, phoneme.index, phoneme, (float)phoneme.maxOtoPreutter)); - var overlap = phoneme.preutter * ratio; - if (overlap > phoneme.autoOverlap) { - docManager.ExecuteCmd(new PhonemeOverlapCommand(part, note, phoneme.index, phoneme, (float)(overlap - phoneme.autoOverlap))); + var max = Math.Min(phoneme.maxOtoPreutter, phoneme.Prev.DurationMs - 5); + docManager.ExecuteCmd(new PhonemePreutterCommand(part, note, phoneme.index, phoneme, (float)(max - phoneme.autoPreutter))); + if (phoneme.autoOverlap > 0) { + var overlap = max * ratio; + if (overlap > phoneme.autoOverlap) { + docManager.ExecuteCmd(new PhonemeOverlapCommand(part, note, phoneme.index, phoneme, (float)(overlap - phoneme.autoOverlap))); + } } } } From c303e110c936437642c6b96b41289404c6af6f68 Mon Sep 17 00:00:00 2001 From: Maiko Date: Fri, 19 Jun 2026 01:04:00 +0900 Subject: [PATCH 4/4] Improved the hit-test logic for overlap points --- OpenUtau/Controls/PianoRoll.axaml.cs | 3 +-- OpenUtau/ViewModels/NotesViewModelHitTest.cs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenUtau/Controls/PianoRoll.axaml.cs b/OpenUtau/Controls/PianoRoll.axaml.cs index 1d5a660fe..60d2ad398 100644 --- a/OpenUtau/Controls/PianoRoll.axaml.cs +++ b/OpenUtau/Controls/PianoRoll.axaml.cs @@ -1258,8 +1258,7 @@ public void PhonemeCanvasPointerMoved(object sender, PointerEventArgs args) { return; } var hitInfo = ViewModel.NotesViewModel.HitTest.HitTestPhoneme(point.Position); - var adjacent = hitInfo.phoneme != null && hitInfo.phoneme.Next != null && hitInfo.phoneme.Next.adjacent; - if (hitInfo.hitPosition || hitInfo.hitPreutter || (hitInfo.hitOverlap && adjacent) || hitInfo.hitAttackTime || hitInfo.hitReleaseTime) { + if (hitInfo.hitPosition || hitInfo.hitPreutter || hitInfo.hitOverlap || hitInfo.hitAttackTime || hitInfo.hitReleaseTime) { Cursor = ViewConstants.cursorSizeWE; ViewModel.MouseoverPhoneme(null); return; diff --git a/OpenUtau/ViewModels/NotesViewModelHitTest.cs b/OpenUtau/ViewModels/NotesViewModelHitTest.cs index 4bf6c15f9..bf634641a 100644 --- a/OpenUtau/ViewModels/NotesViewModelHitTest.cs +++ b/OpenUtau/ViewModels/NotesViewModelHitTest.cs @@ -386,6 +386,7 @@ public PhonemeHitInfo HitTestPhoneme(Point mousePos) { return result; } // p4 Overlap + if (phoneme.Next == null || phoneme.Next.position != phoneme.End) continue; int p4Tick = timeAxis.MsPosToTickPos(phoneme.PositionMs + phoneme.envelope.data[4].X) - viewModel.Part.position; double p4x = viewModel.TickToneToPoint(p4Tick, 0).X; point = new Point(p4x, 60 - phoneme.envelope.data[4].Y * 0.24 - 1);