diff --git a/OpenUtau.Core/Commands/ExpCommands.cs b/OpenUtau.Core/Commands/ExpCommands.cs index 55bf85e1b..df10c5e6e 100644 --- a/OpenUtau.Core/Commands/ExpCommands.cs +++ b/OpenUtau.Core/Commands/ExpCommands.cs @@ -21,24 +21,23 @@ public ExpCommand(UVoicePart part) { } } - /* public class SetNoteExpressionCommand : ExpCommand { public readonly UProject project; - public readonly UPhoneme phoneme; - public readonly float newValue; - public readonly float oldValue; - public SetNoteExpressionCommand(UProject project, UNote note, string abbr, float value) { + 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) { this.project = project; + this.track = track; this.Note = note; Key = abbr; - newValue = value; - oldValue = phoneme.GetExpression(project, abbr).Item1; + newValue = values; + oldValue = note.GetExpression(project, track, abbr).Select(t => t.Item1).ToArray(); } public override string ToString() => $"Set note expression {Key}"; - public override void Execute() => Note.SetExpression(project, Key, newValue); - public override void Unexecute() => Note.SetExpression(project, Key, oldValue); + public override void Execute() => Note.SetExpression(project, track, Key, newValue); + public override void Unexecute() => Note.SetExpression(project, track, Key, oldValue); } - */ public class SetPhonemeExpressionCommand : ExpCommand { static readonly HashSet needsPhonemizer = new HashSet { diff --git a/OpenUtau.Core/Editing/LyricBatchEdits.cs b/OpenUtau.Core/Editing/LyricBatchEdits.cs index 9ba72f54a..3f2468452 100644 --- a/OpenUtau.Core/Editing/LyricBatchEdits.cs +++ b/OpenUtau.Core/Editing/LyricBatchEdits.cs @@ -138,11 +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; - foreach (UPhoneme phoneme in part.phonemes) { - if (phoneme.Parent == note) { - docManager.ExecuteCmd(new SetPhonemeExpressionCommand(project, track, part, phoneme, Format.Ustx.CLR, 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 d38972a14..c80b0df7c 100644 --- a/OpenUtau.Core/Editing/NoteBatchEdits.cs +++ b/OpenUtau.Core/Editing/NoteBatchEdits.cs @@ -21,7 +21,11 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); foreach (var note in notes) { if (note.lyric != lyric && (note.Next == null || note.Next.position > note.End + 120)) { - toAdd.Add(project.CreateNote(note.tone, 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 }); + } + toAdd.Add(addNote); } } if (toAdd.Count == 0) { diff --git a/OpenUtau.Core/Ustx/UNote.cs b/OpenUtau.Core/Ustx/UNote.cs index d64d246a0..155368500 100644 --- a/OpenUtau.Core/Ustx/UNote.cs +++ b/OpenUtau.Core/Ustx/UNote.cs @@ -155,34 +155,53 @@ public UPhonemeOverride GetPhonemeOverride(int index) { return result; } - /* - public float GetExpression(UProject project, string abbr) { - var descriptor = project.expressions[abbr]; - Trace.Assert(descriptor.isNoteExpression); - var note = Extends ?? this; - var expression = note.noteExpressions.FirstOrDefault(exp => exp.descriptor == descriptor); - return expression == null ? expression.value : descriptor.defaultValue; + public List> GetExpression(UProject project, UTrack track, string abbr) { + track.TryGetExpression(project, abbr, out var descriptor); + var list = new List>(); + int indexes = (phonemeExpressions.Max(exp => exp.index) ?? 0) + 1; + + for (int i = 0; i < indexes; i++) { + var expression = phonemeExpressions.FirstOrDefault(exp => exp.descriptor?.abbr == descriptor.abbr && exp.index == i); + if (expression != null) { + list.Add(Tuple.Create(expression.value, true)); + } else { + list.Add(Tuple.Create(descriptor.defaultValue, false)); + } + } + return list; } - public void SetExpression(UProject project, string abbr, float value) { - var descriptor = project.expressions[abbr]; - Trace.Assert(descriptor.isNoteExpression); - var note = Extends ?? this; - if (value == descriptor.defaultValue) { - note.noteExpressions.RemoveAll(exp => exp.descriptor == descriptor); + public void SetExpression(UProject project, UTrack track, string abbr, float[] values) { + if (!track.TryGetExpression(project, abbr, out var descriptor)) { return; } - var expression = note.noteExpressions.FirstOrDefault(exp => exp.descriptor == descriptor); - if (expression != null) { - expression.value = value; - } else { - note.noteExpressions.Add(new UExpression(descriptor) { - descriptor = descriptor, - value = value, - }); + int indexes = (phonemeExpressions.Max(exp => exp.index) ?? 0) + 1; + + for (int i = 0; i < indexes; i++) { + float value; + if (values.Length > i) { + value = values[i]; + } else { + value = values.Last(); + } + + if (descriptor.defaultValue == value) { + phonemeExpressions.RemoveAll(exp => exp.descriptor?.abbr == descriptor.abbr && exp.index == i); + continue; + } + var expression = phonemeExpressions.FirstOrDefault(exp => exp.descriptor?.abbr == descriptor.abbr && exp.index == i); + if (expression != null) { + expression.descriptor = descriptor; + expression.value = value; + } else { + phonemeExpressions.Add(new UExpression(descriptor) { + descriptor = descriptor, + index = i, + value = value, + }); + } } } - */ public UNote Clone() { return new UNote() { diff --git a/OpenUtau/OpenUtau.csproj b/OpenUtau/OpenUtau.csproj index 330342be0..423c0c02b 100644 --- a/OpenUtau/OpenUtau.csproj +++ b/OpenUtau/OpenUtau.csproj @@ -79,6 +79,9 @@ True Resources.resx + + PasteParamDialog.axaml + TrackSettingsDialog.axaml diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index 254096ddf..4cc50c6cf 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -4,6 +4,7 @@ Copy note Delete note + Select and paste parameters Delete part Rename part Reselect audio file diff --git a/OpenUtau/Strings/Strings.ja-JP.axaml b/OpenUtau/Strings/Strings.ja-JP.axaml index 4a3701881..9f0c42f72 100644 --- a/OpenUtau/Strings/Strings.ja-JP.axaml +++ b/OpenUtau/Strings/Strings.ja-JP.axaml @@ -4,6 +4,7 @@ 音符をコピー 音符を削除 + パラメータを選択して貼り付け パートを削除 パートの名前を変更 オーディオファイルを変更 diff --git a/OpenUtau/ViewModels/NotesViewModel.cs b/OpenUtau/ViewModels/NotesViewModel.cs index 0cb3e33cd..7f1c1bd55 100644 --- a/OpenUtau/ViewModels/NotesViewModel.cs +++ b/OpenUtau/ViewModels/NotesViewModel.cs @@ -11,6 +11,7 @@ using Avalonia.Media.Imaging; using DynamicData; using DynamicData.Binding; +using OpenUtau.App.Views; using OpenUtau.Core; using OpenUtau.Core.Ustx; using OpenUtau.Core.Util; @@ -760,6 +761,63 @@ public void PasteNotes() { } } + public async void PasteSelectedParams(PianoRollWindow window) { + if (Part != null && DocManager.Inst.NotesClipboard != null && DocManager.Inst.NotesClipboard.Count > 0) { + var selectedNotes = Selection.ToList(); + if(selectedNotes.Count == 0) { + return; + } + + var dialog = new PasteParamDialog(); + var vm = new PasteParamViewModel(); + dialog.DataContext = vm; + await dialog.ShowDialog(window); + + if (dialog.Apply) { + DocManager.Inst.StartUndoGroup(); + + int c = 0; + var track = Project.tracks[Part.trackNo]; + foreach (var note in selectedNotes) { + var copyNote = DocManager.Inst.NotesClipboard[c]; + + for (int i = 0; i < vm.Params.Count; i++) { + switch (i) { + case 0: + if (vm.Params[i].IsSelected) { + DocManager.Inst.ExecuteCmd(new SetPitchPointsCommand(Part, note, copyNote.pitch)); + } + break; + case 1: + if (vm.Params[i].IsSelected) { + DocManager.Inst.ExecuteCmd(new VibratoLengthCommand(Part, note, copyNote.vibrato.length)); + DocManager.Inst.ExecuteCmd(new VibratoDepthCommand(Part, note, copyNote.vibrato.depth)); + DocManager.Inst.ExecuteCmd(new VibratoPeriodCommand(Part, note, copyNote.vibrato.period)); + DocManager.Inst.ExecuteCmd(new VibratoFadeInCommand(Part, note, copyNote.vibrato.@in)); + DocManager.Inst.ExecuteCmd(new VibratoFadeOutCommand(Part, note, copyNote.vibrato.@out)); + DocManager.Inst.ExecuteCmd(new VibratoShiftCommand(Part, note, copyNote.vibrato.shift)); + // DocManager.Inst.ExecuteCmd(new VibratoDriftCommand(Part, note, copyNote.vibrato.drift)); + } + break; + default: + if (vm.Params[i].IsSelected) { + float[] values = copyNote.GetExpression(Project, track, vm.Params[i].Abbr).Select(t => t.Item1).ToArray(); + DocManager.Inst.ExecuteCmd(new SetNoteExpressionCommand(Project, track, Part, note, vm.Params[i].Abbr, values)); + } + break; + } + } + + c++; + if (c >= DocManager.Inst.NotesClipboard.Count) { + c = 0; + } + } + DocManager.Inst.EndUndoGroup(); + } + } + } + public void ToggleVibrato(UNote note) { if (Part == null) { return; diff --git a/OpenUtau/ViewModels/PasteParamViewModel.cs b/OpenUtau/ViewModels/PasteParamViewModel.cs new file mode 100644 index 000000000..41a105c17 --- /dev/null +++ b/OpenUtau/ViewModels/PasteParamViewModel.cs @@ -0,0 +1,33 @@ +using System.Collections.ObjectModel; +using OpenUtau.Core; +using ReactiveUI.Fody.Helpers; + +namespace OpenUtau.App.ViewModels { + public class PasteParamViewModel { + + public PasteParamViewModel() { + Params.Add(new PasteParameter("pitch points", "")); + Params.Add(new PasteParameter("vibrato", "")); + foreach(var exp in DocManager.Inst.Project.expressions) { + if(exp.Value.type != Core.Ustx.UExpressionType.Curve) { + Params.Add(new PasteParameter(exp.Value.name, exp.Key)); + } + } + Params[0].IsSelected = true; + } + + public ObservableCollection Params { get; } = new ObservableCollection(); + } + + public class PasteParameter { + public PasteParameter(string name, string abbr) { + Name = name; + Abbr = abbr; + } + public string Name { get; set; } + public string Abbr { get; set; } + [Reactive] public bool IsSelected { get; set; } = false; + + public override string ToString() { return Name; } + } +} diff --git a/OpenUtau/Views/PasteParamDialog.axaml b/OpenUtau/Views/PasteParamDialog.axaml new file mode 100644 index 000000000..b08392a7e --- /dev/null +++ b/OpenUtau/Views/PasteParamDialog.axaml @@ -0,0 +1,23 @@ + + + + + + + + +