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/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/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/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/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/NotePropertyExpression.axaml b/OpenUtau/Controls/NotePropertyExpression.axaml index 122c4a65a..5f2ee47c9 100644 --- a/OpenUtau/Controls/NotePropertyExpression.axaml +++ b/OpenUtau/Controls/NotePropertyExpression.axaml @@ -5,7 +5,7 @@ xmlns:vm="using:OpenUtau.App.ViewModels" x:Class="OpenUtau.App.Controls.NotePropertyExpression"> -