diff --git a/OpenUtau.Core/Commands/TrackCommands.cs b/OpenUtau.Core/Commands/TrackCommands.cs index 7d2eb1718..031b42b82 100644 --- a/OpenUtau.Core/Commands/TrackCommands.cs +++ b/OpenUtau.Core/Commands/TrackCommands.cs @@ -78,10 +78,16 @@ public MoveTrackCommand(UProject project, UTrack track, bool up) { } public override string ToString() => "Move track"; public override void Execute() { + if (index < 0 || index + 1 >= project.tracks.Count) { + return; + } project.tracks.Reverse(index, 2); UpdateTrackNo(); } public override void Unexecute() { + if (index < 0 || index + 1 >= project.tracks.Count) { + return; + } project.tracks.Reverse(index, 2); UpdateTrackNo(); } @@ -113,6 +119,36 @@ public ChangeTrackColorCommand(UProject project, UTrack track, string colorName) public override void Unexecute() => track.TrackColor = oldName; } + public class TrackChangeSettingsCommand : TrackCommand { + readonly bool newMute; + readonly bool oldMute; + readonly double newVolume; + readonly double oldVolume; + readonly double newPan; + readonly double oldPan; + public TrackChangeSettingsCommand(UProject project, UTrack track, bool mute, double volume, double pan) { + this.project = project; + this.track = track; + newMute = mute; + newVolume = volume; + newPan = pan; + oldMute = track.Mute; + oldVolume = track.Volume; + oldPan = track.Pan; + } + public override string ToString() => "Change track settings"; + public override void Execute() { + track.Mute = newMute; + track.Volume = newVolume; + track.Pan = newPan; + } + public override void Unexecute() { + track.Mute = oldMute; + track.Volume = oldVolume; + track.Pan = oldPan; + } + } + public class TrackChangeSingerCommand : TrackCommand { readonly USinger newSinger, oldSinger; public TrackChangeSingerCommand(UProject project, UTrack track, USinger newSinger) { diff --git a/OpenUtau/Controls/ApplyToAllTracksButton.cs b/OpenUtau/Controls/ApplyToAllTracksButton.cs new file mode 100644 index 000000000..fd22f6d49 --- /dev/null +++ b/OpenUtau/Controls/ApplyToAllTracksButton.cs @@ -0,0 +1,32 @@ +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Input; +using Avalonia.Media; + +namespace OpenUtau.App.Controls { + + public class ApplyToAllTracksButton : Button { + public ApplyToAllTracksButton() { + Padding = new Avalonia.Thickness(0); + BorderThickness = new Avalonia.Thickness(0); + Background = Brushes.Transparent; + Focusable = false; + Content = new Path { + Stroke = ThemeManager.AccentBrush2, + StrokeThickness = 1.75, + Data = Geometry.Parse("M3,4 H11 M3,8 H11 M3,12 H11 M10,2 L13,4 L10,6 M10,6 L13,4 L10,2"), + }; + } + + protected override void OnPointerPressed(PointerPressedEventArgs e) { + base.OnPointerPressed(e); + e.Handled = true; + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) { + base.OnPointerReleased(e); + e.Handled = true; + } + } + +} diff --git a/OpenUtau/Controls/FavouriteToggleButton.cs b/OpenUtau/Controls/FavouriteToggleButton.cs index e1ffcc931..93e93374d 100644 --- a/OpenUtau/Controls/FavouriteToggleButton.cs +++ b/OpenUtau/Controls/FavouriteToggleButton.cs @@ -1,44 +1,54 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; +using Avalonia.Input; using Avalonia.Media; using OpenUtau.App; -public class FavouriteToggleButton : ToggleButton -{ - private readonly Path _iconPath; - - public FavouriteToggleButton() - { - //this.Height = 20; - //this.Width = 20; - // Create icon Path. - _iconPath = new Path - { - Fill = SolidColorBrush.Parse("#00000000"), - Stroke = ThemeManager.AccentBrush3, - StrokeThickness = 2, - Data = Geometry.Parse("M12,21.35L10.55,20.03C5.4,15.36,2,12.28,2,8.5C2,5.42,4.42,3,7.5,3C9.24,3,10.91,3.81,12,5.09C13.09,3.81,14.76,3,16.5,3C19.58,3,22,5.42,22,8.5C22,12.28,18.6,15.36,13.45,20.04L12,21.35Z"), - RenderTransform = new ScaleTransform { ScaleX = 0.6,ScaleY = 0.6} - }; - - this.Content = _iconPath; - - // Change icon on click. - this.PropertyChanged += (sender, e) => - { - if (e.Property == IsCheckedProperty) - { - UpdateIcon(IsChecked ?? false); +namespace OpenUtau.App.Controls { + + public class FavouriteToggleButton : ToggleButton { + private readonly Path _iconPath; + + public FavouriteToggleButton() { + Padding = new Avalonia.Thickness(0); + //this.Height = 20; + //this.Width = 20; + // Create icon Path. + _iconPath = new Path { + Fill = SolidColorBrush.Parse("#00000000"), + Stroke = ThemeManager.AccentBrush3, + StrokeThickness = 2, + Data = Geometry.Parse("M12,21.35L10.55,20.03C5.4,15.36,2,12.28,2,8.5C2,5.42,4.42,3,7.5,3C9.24,3,10.91,3.81,12,5.09C13.09,3.81,14.76,3,16.5,3C19.58,3,22,5.42,22,8.5C22,12.28,18.6,15.36,13.45,20.04L12,21.35Z"), + RenderTransform = new ScaleTransform { ScaleX = 0.5, ScaleY = 0.5 } + }; + + this.Content = _iconPath; + + // Change icon on click. + this.PropertyChanged += (sender, e) => { + if (e.Property == IsCheckedProperty) { + UpdateIcon(IsChecked ?? false); + } + }; + } + + private void UpdateIcon(bool isChecked) { + if (isChecked) { + _iconPath.Fill = ThemeManager.AccentBrush3; + } else { + _iconPath.Fill = SolidColorBrush.Parse("#00000000"); } - }; - } + } - private void UpdateIcon(bool isChecked) - { - if (isChecked) { - _iconPath.Fill = ThemeManager.AccentBrush3; - } else { - _iconPath.Fill = SolidColorBrush.Parse("#00000000"); + protected override void OnPointerPressed(PointerPressedEventArgs e) { + base.OnPointerPressed(e); + e.Handled = true; + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) { + base.OnPointerReleased(e); + e.Handled = true; } } + } diff --git a/OpenUtau/Controls/TrackHeader.axaml b/OpenUtau/Controls/TrackHeader.axaml index 24374fb0a..580b8be71 100644 --- a/OpenUtau/Controls/TrackHeader.axaml +++ b/OpenUtau/Controls/TrackHeader.axaml @@ -2,6 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:controls="clr-namespace:OpenUtau.App.Controls" xmlns:vm="using:OpenUtau.App.ViewModels" mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="104" x:Class="OpenUtau.App.Controls.TrackHeader" Width="300" Height="104" TrackNo="{Binding TrackNo}"> @@ -77,18 +78,42 @@ HorizontalOffset="-3" ItemsSource="{Binding PhonemizerMenuItems}"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenUtau/Controls/TrackHeaderCanvas.cs b/OpenUtau/Controls/TrackHeaderCanvas.cs index 0d72270a9..e3c2b420f 100644 --- a/OpenUtau/Controls/TrackHeaderCanvas.cs +++ b/OpenUtau/Controls/TrackHeaderCanvas.cs @@ -98,6 +98,7 @@ public TrackHeaderCanvas() { } } }); + MessageBus.Current.Listen() .Subscribe(e => { foreach (var (track, header) in trackHeaders) { diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index bfc779b45..a6a86633f 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -776,7 +776,7 @@ General Space: Play ◀ Scroll here to zoom horizontally ▶ Scroll here to zoom vertically ▶ - + Duplicate Track Duplicate Track Settings Favorites @@ -793,6 +793,7 @@ General Open singers location Remove Rename track + Rotate singers across selected tracks Select Renderer Select Singer (Singer default) @@ -800,6 +801,7 @@ General Solo additionally (which not removes solo from other tracks) Solo this only (which removes solo from other tracks) Unsolo all + Standardize Track Settings Change track color Track Settings diff --git a/OpenUtau/ViewModels/MenuItemViewModel.cs b/OpenUtau/ViewModels/MenuItemViewModel.cs index 701d3e05d..ca847037c 100644 --- a/OpenUtau/ViewModels/MenuItemViewModel.cs +++ b/OpenUtau/ViewModels/MenuItemViewModel.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Windows.Input; using Avalonia.Controls.Shapes; -using Avalonia.Data; using Avalonia.Input; using Avalonia.Threading; using OpenUtau.Core.Ustx; @@ -10,6 +9,7 @@ namespace OpenUtau.App.ViewModels { public class MenuItemViewModel { public string? Header { get; set; } public ICommand? Command { get; set; } + public ICommand? SecondaryCommand { get; set; } public object? CommandParameter { get; set; } public IList? Items { get; set; } public double Height { get; set; } = 24; @@ -17,6 +17,7 @@ public class MenuItemViewModel { public KeyGesture? InputGesture { get; set; } public bool IsEnabled { get; set; } = true; public object? Icon { get; set; } + public virtual object HeaderViewModel => this; public MenuItemViewModel() { } public MenuItemViewModel(bool isChecked) { @@ -44,19 +45,7 @@ public bool IsFavourite { } } } - private object? _icon; - public new object? Icon { - get { - if(_icon == null) { - if (CommandParameter is USinger) { - _icon = new FavouriteToggleButton() { - [!FavouriteToggleButton.IsCheckedProperty] = new Binding("IsFavourite") - }; - } - } - return _icon; - } - } + public string? Location { get { if (CommandParameter is USinger singer) { @@ -66,4 +55,7 @@ public string? Location { } } } + + public class PhonemizerMenuItemViewModel : MenuItemViewModel { + } } diff --git a/OpenUtau/ViewModels/TrackHeaderViewModel.cs b/OpenUtau/ViewModels/TrackHeaderViewModel.cs index 7bbde4843..7ecc15d8b 100644 --- a/OpenUtau/ViewModels/TrackHeaderViewModel.cs +++ b/OpenUtau/ViewModels/TrackHeaderViewModel.cs @@ -26,9 +26,11 @@ public class TrackHeaderViewModel : ViewModelBase, IActivatableViewModel { public string PhonemizerTag => track.Phonemizer.Tag; public Core.Render.IRenderer Renderer => track.RendererSettings.Renderer; public IReadOnlyList? SingerMenuItems { get; set; } - public ReactiveCommand SelectSingerCommand { get; } + public ReactiveCommand SelectSingerCommand { get; } + public ReactiveCommand AllSetSingerCommand { get; } public IReadOnlyList? PhonemizerMenuItems { get; set; } public ReactiveCommand SelectPhonemizerCommand { get; } + public ReactiveCommand AllSetPhonemizerCommand { get; } public IReadOnlyList? RenderersMenuItems { get; set; } public ReactiveCommand SelectRendererCommand { get; } [Reactive] public string TrackName { get; set; } = string.Empty; @@ -53,8 +55,10 @@ public class TrackHeaderViewModel : ViewModelBase, IActivatableViewModel { // Parameterless constructor for Avalonia preview only. public TrackHeaderViewModel() { - SelectSingerCommand = ReactiveCommand.Create(_ => { }); + SelectSingerCommand = ReactiveCommand.Create(_ => { }); + AllSetSingerCommand = ReactiveCommand.Create(_ => { }); SelectPhonemizerCommand = ReactiveCommand.Create(_ => { }); + AllSetPhonemizerCommand = ReactiveCommand.Create(_ => { }); SelectRendererCommand = ReactiveCommand.Create(_ => { }); Activator = new ViewModelActivator(); track = new UTrack(DocManager.Inst.Project); @@ -65,7 +69,7 @@ public TrackHeaderViewModel() { public TrackHeaderViewModel(UTrack track) { this.track = track; - SelectSingerCommand = ReactiveCommand.Create(singer => { + SelectSingerCommand = ReactiveCommand.Create(singer => { if (track.Singer != singer) { DocManager.Inst.StartUndoGroup("command.track.singer"); ApplySingerToTrack(track, singer); @@ -80,6 +84,28 @@ public TrackHeaderViewModel(UTrack track) { this.RaisePropertyChanged(nameof(Renderer)); RefreshAvatar(); }); + AllSetSingerCommand = ReactiveCommand.Create(singer => { + var targetTracks = DocManager.Inst.Project.tracks + .Where(projectTrack => projectTrack != null) + .Where(projectTrack => IsTrackSelected(projectTrack)) + .ToList(); + if (targetTracks.Count <= 1) { + targetTracks = DocManager.Inst.Project.tracks; + } + DocManager.Inst.StartUndoGroup("command.track.singer"); + foreach (var targetTrack in targetTracks) { + ApplySingerToTrack(targetTrack, singer); + } + DocManager.Inst.ExecuteCmd(new VoiceColorRemappingNotification(-1, true)); + DocManager.Inst.EndUndoGroup(); + UpdateRecentSingers(singer); + Preferences.Save(); + MessageBus.Current.SendMessage(new PianorollRefreshEvent("Part")); + MessageBus.Current.SendMessage(new TracksRefreshEvent()); + this.RaisePropertyChanged(nameof(Singer)); + this.RaisePropertyChanged(nameof(Renderer)); + RefreshAvatar(); + }); SelectPhonemizerCommand = ReactiveCommand.Create(factory => { if (track.Phonemizer.GetType() != factory.type) { DocManager.Inst.StartUndoGroup("command.track.setting"); @@ -102,6 +128,39 @@ public TrackHeaderViewModel(UTrack track) { this.RaisePropertyChanged(nameof(Phonemizer)); this.RaisePropertyChanged(nameof(PhonemizerTag)); }); + AllSetPhonemizerCommand = ReactiveCommand.Create(factory => { + if (factory == null) { + return; + } + var name = factory.type.FullName!; + Log.Information($"Loading Phonemizer: {factory}"); + var targetTracks = DocManager.Inst.Project.tracks + .Where(projectTrack => projectTrack != null) + .Where(projectTrack => IsTrackSelected(projectTrack)) + .ToList(); + if (targetTracks.Count <= 1) { + targetTracks = DocManager.Inst.Project.tracks; + } + DocManager.Inst.StartUndoGroup("command.track.setting"); + foreach (var targetTrack in targetTracks) { + var phonemizer = factory.Create(); + if (phonemizer != null) { + DocManager.Inst.ExecuteCmd(new TrackChangePhonemizerCommand(DocManager.Inst.Project, targetTrack, phonemizer)); + } + var targetSingerId = targetTrack.Singer?.Id; + if (!string.IsNullOrEmpty(targetSingerId) && targetTrack.Singer?.Found == true && phonemizer != null) { + Preferences.Default.SingerPhonemizers[targetSingerId] = name; + } + } + Preferences.Default.RecentPhonemizers.Remove(name); + Preferences.Default.RecentPhonemizers.Insert(0, name); + DocManager.Inst.EndUndoGroup(); + Preferences.Save(); + MessageBus.Current.SendMessage(new PianorollRefreshEvent("Part")); + MessageBus.Current.SendMessage(new TracksRefreshEvent()); + this.RaisePropertyChanged(nameof(Phonemizer)); + this.RaisePropertyChanged(nameof(PhonemizerTag)); + }); SelectRendererCommand = ReactiveCommand.Create(name => { var settings = new URenderSettings { renderer = name, @@ -228,6 +287,12 @@ public void JudgeMuted() { this.RaisePropertyChanged(nameof(Muted)); } + private static bool IsTrackSelected(UTrack projectTrack) { + var tracksViewModel = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime) + ?.MainWindow?.DataContext as MainWindowViewModel; + return tracksViewModel?.TracksViewModel.SelectedTracks.Contains(projectTrack) == true; + } + private void ApplySingerToTrack(UTrack targetTrack, USinger? singer) { if (singer is USinger selectedSinger) { Log.Information($"Loading Singer: {selectedSinger.Name}"); @@ -269,6 +334,7 @@ private SingerMenuItemViewModel CreateSingerMenuItem(USinger singer) { return new SingerMenuItemViewModel() { Header = singer.LocalizedName, Command = SelectSingerCommand, + SecondaryCommand = AllSetSingerCommand, CommandParameter = singer, }; } @@ -415,7 +481,7 @@ public void RefreshPhonemizers() { if (track != null && track.Singer != null && track.Singer.Found) { var factory = FindPhonemizerByName(track.Singer.DefaultPhonemizer); if (factory != null) { - items.Add(new MenuItemViewModel() { + items.Add(new PhonemizerMenuItemViewModel() { Header = ThemeManager.GetString("tracks.singerdefault") + factory.ToString(), Command = SelectPhonemizerCommand, CommandParameter = factory, @@ -427,9 +493,10 @@ public void RefreshPhonemizers() { .Select(name => FindPhonemizerByName(name)) .OfType() .OrderBy(factory => factory.tag) - .Select(factory => new MenuItemViewModel() { + .Select(factory => new PhonemizerMenuItemViewModel() { Header = factory.ToString(), Command = SelectPhonemizerCommand, + SecondaryCommand = AllSetPhonemizerCommand, CommandParameter = factory, })); //more phonemizers grouped by singing language @@ -439,9 +506,10 @@ public void RefreshPhonemizers() { .OrderBy(group => group.Key) .Select(group => new MenuItemViewModel() { Header = GetPhonemizerGroupHeader(group.Key), - Items = group.Select(factory => new MenuItemViewModel() { + Items = group.Select(factory => new PhonemizerMenuItemViewModel() { Header = factory.ToString(), Command = SelectPhonemizerCommand, + SecondaryCommand = AllSetPhonemizerCommand, CommandParameter = factory, }).ToArray(), }).ToArray() @@ -506,7 +574,16 @@ public void ManuallyRaise() { public void Remove() { DocManager.Inst.StartUndoGroup("command.track.delete"); - DocManager.Inst.ExecuteCmd(new RemoveTrackCommand(DocManager.Inst.Project, track)); + var targetTracks = DocManager.Inst.Project.tracks + .Where(projectTrack => projectTrack != null) + .Where(projectTrack => IsTrackSelected(projectTrack)) + .ToList(); + if (targetTracks.Count < 2) { + targetTracks = new List() { this.track }; + } + foreach (var track in targetTracks) { + DocManager.Inst.ExecuteCmd(new RemoveTrackCommand(DocManager.Inst.Project, track)); + } DocManager.Inst.EndUndoGroup(); } @@ -515,7 +592,21 @@ public void MoveUp() { return; } DocManager.Inst.StartUndoGroup("command.track.order"); - DocManager.Inst.ExecuteCmd(new MoveTrackCommand(DocManager.Inst.Project, track, true)); + var targetTracks = DocManager.Inst.Project.tracks + .Where(projectTrack => projectTrack != null) + .Where(projectTrack => IsTrackSelected(projectTrack)) + .ToList(); + if (targetTracks.Count < 2) { + targetTracks = new List() { this.track }; + } else { + targetTracks = targetTracks + .Where(targetTrack => targetTrack != DocManager.Inst.Project.tracks.First()) + .OrderBy(targetTrack => targetTrack.TrackNo) + .ToList(); + } + foreach (var track in targetTracks) { + DocManager.Inst.ExecuteCmd(new MoveTrackCommand(DocManager.Inst.Project, track, true)); + } DocManager.Inst.EndUndoGroup(); } @@ -524,7 +615,21 @@ public void MoveDown() { return; } DocManager.Inst.StartUndoGroup("command.track.order"); - DocManager.Inst.ExecuteCmd(new MoveTrackCommand(DocManager.Inst.Project, track, false)); + var targetTracks = DocManager.Inst.Project.tracks + .Where(projectTrack => projectTrack != null) + .Where(projectTrack => IsTrackSelected(projectTrack)) + .ToList(); + if (targetTracks.Count < 2) { + targetTracks = new List() { this.track }; + } else { + targetTracks = targetTracks + .Where(targetTrack => targetTrack != DocManager.Inst.Project.tracks.Last()) + .OrderByDescending(targetTrack => targetTrack.TrackNo) + .ToList(); + } + foreach (var track in targetTracks) { + DocManager.Inst.ExecuteCmd(new MoveTrackCommand(DocManager.Inst.Project, track, false)); + } DocManager.Inst.EndUndoGroup(); } @@ -535,8 +640,19 @@ public void Rename() { dialog.onFinish = name => { if (!string.IsNullOrWhiteSpace(name) && name != track.TrackName) { DocManager.Inst.StartUndoGroup("command.track.setting"); - this.TrackName = name; - DocManager.Inst.ExecuteCmd(new RenameTrackCommand(DocManager.Inst.Project, track, name)); + var targetTracks = DocManager.Inst.Project.tracks + .Where(projectTrack => projectTrack != null) + .Where(projectTrack => IsTrackSelected(projectTrack)) + .ToList(); + if (targetTracks.Count < 2) { + this.TrackName = name; + DocManager.Inst.ExecuteCmd(new RenameTrackCommand(DocManager.Inst.Project, track, name)); + } else { + for (int i = 0; i < targetTracks.Count; i++) { + string name_ = $"{name}_{i:000}"; + DocManager.Inst.ExecuteCmd(new RenameTrackCommand(DocManager.Inst.Project, targetTracks[i], name_)); + } + } DocManager.Inst.EndUndoGroup(); } }; @@ -546,66 +662,188 @@ public void Rename() { } public async void SelectTrackColor() { - var dialog = new TrackColorDialog(); - dialog.DataContext = new TrackColorViewModel(track); - - if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow != null) { - await dialog.ShowDialog(desktop.MainWindow); - TrackAccentColor = ThemeManager.GetTrackColor(track.TrackColor).AccentColor; - TrackColor = Preferences.Default.UseTrackColor - ? ThemeManager.GetTrackColor(track.TrackColor) - : ThemeManager.GetTrackColor("Blue"); - RefreshSelectionStyle(); + var targetTracks = DocManager.Inst.Project.tracks + .Where(projectTrack => projectTrack != null) + .Where(projectTrack => IsTrackSelected(projectTrack)) + .ToList(); + var dialogs = new List(); + if (targetTracks.Count < 2) { + var dialog = new TrackColorDialog(); + dialog.DataContext = new TrackColorViewModel(track); + dialogs.Add(dialog); + } else { + foreach (var track in targetTracks) { + var dialog = new TrackColorDialog(); + dialog.DataContext = new TrackColorViewModel(track); + dialogs.Add(dialog); + } + } + foreach (var dialog in dialogs) { + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow != null) { + await dialog.ShowDialog(desktop.MainWindow); + TrackAccentColor = ThemeManager.GetTrackColor(track.TrackColor).AccentColor; + TrackColor = Preferences.Default.UseTrackColor + ? ThemeManager.GetTrackColor(track.TrackColor) + : ThemeManager.GetTrackColor("Blue"); + RefreshSelectionStyle(); + } } } public void Duplicate() { + var targetTracks = DocManager.Inst.Project.tracks + .Where(projectTrack => projectTrack != null) + .Where(projectTrack => IsTrackSelected(projectTrack)) + .ToList(); + if (targetTracks.Count < 2) { + targetTracks = new List() { this.track }; + } else { + targetTracks = targetTracks + .OrderByDescending(targetTrack => targetTrack.TrackNo) + .ToList(); + } DocManager.Inst.StartUndoGroup("command.track.duplicate"); - var newTrack = new UTrack(track.TrackName + "_copy") { - TrackNo = track.TrackNo + 1, - Singer = track.Singer, - Phonemizer = track.Phonemizer, - RendererSettings = track.RendererSettings, - Mute = track.Mute, - Muted = track.Muted, - Solo = false, - Volume = track.Volume, - Pan = track.Pan, - TrackColor = track.TrackColor, - TrackExpressions = track.TrackExpressions.Select(exp => exp.Clone()).ToList() - }; - DocManager.Inst.ExecuteCmd(new AddTrackCommand(DocManager.Inst.Project, newTrack)); - var parts = DocManager.Inst.Project.parts - .Where(part => part.trackNo == track.TrackNo) - .Select(part => part.Clone()).ToList(); - foreach (var part in parts) { - part.trackNo = newTrack.TrackNo; - DocManager.Inst.ExecuteCmd(new AddPartCommand(DocManager.Inst.Project, part)); + foreach (var track in targetTracks) { + var sourceTrackNo = track.TrackNo; + var newTrack = new UTrack(track.TrackName + "_copy") { + TrackNo = sourceTrackNo + 1, + Singer = track.Singer, + Phonemizer = track.Phonemizer, + RendererSettings = track.RendererSettings, + Mute = track.Mute, + Muted = track.Muted, + Solo = false, + Volume = track.Volume, + Pan = track.Pan, + TrackColor = track.TrackColor, + TrackExpressions = track.TrackExpressions.Select(exp => exp.Clone()).ToList() + }; + DocManager.Inst.ExecuteCmd(new AddTrackCommand(DocManager.Inst.Project, newTrack)); + var parts = DocManager.Inst.Project.parts + .Where(part => part.trackNo == sourceTrackNo) + .Select(part => part.Clone()).ToList(); + foreach (var part in parts) { + part.trackNo = newTrack.TrackNo; + DocManager.Inst.ExecuteCmd(new AddPartCommand(DocManager.Inst.Project, part)); + } } DocManager.Inst.EndUndoGroup(); } public void DuplicateSettings() { + var targetTracks = DocManager.Inst.Project.tracks + .Where(projectTrack => projectTrack != null) + .Where(projectTrack => IsTrackSelected(projectTrack)) + .ToList(); + if (targetTracks.Count < 2) { + targetTracks = new List() { this.track }; + } else { + targetTracks = targetTracks + .OrderByDescending(targetTrack => targetTrack.TrackNo) + .ToList(); + } DocManager.Inst.StartUndoGroup("command.track.duplicate"); - DocManager.Inst.ExecuteCmd(new AddTrackCommand(DocManager.Inst.Project, new UTrack(track.TrackName + "_copy") { - TrackNo = track.TrackNo + 1, - Singer = track.Singer, - Phonemizer = track.Phonemizer, - RendererSettings = track.RendererSettings, - Mute = track.Mute, - Muted = track.Muted, - Solo = false, - Volume = track.Volume, - Pan = track.Pan, - TrackColor = track.TrackColor, - TrackExpressions = track.TrackExpressions.Select(exp => exp.Clone()).ToList() - })); + foreach (var track in targetTracks) { + DocManager.Inst.ExecuteCmd(new AddTrackCommand(DocManager.Inst.Project, new UTrack(track.TrackName + "_copy") { + TrackNo = track.TrackNo + 1, + Singer = track.Singer, + Phonemizer = track.Phonemizer, + RendererSettings = track.RendererSettings, + Mute = track.Mute, + Muted = track.Muted, + Solo = false, + Volume = track.Volume, + Pan = track.Pan, + TrackColor = track.TrackColor, + TrackExpressions = track.TrackExpressions.Select(exp => exp.Clone()).ToList() + })); + } + DocManager.Inst.EndUndoGroup(); + } + + public void StandardizeSettings() { + var targetTracks = DocManager.Inst.Project.tracks + .Where(projectTrack => projectTrack != null) + .Where(projectTrack => IsTrackSelected(projectTrack)) + .ToList(); + if (targetTracks.Count <= 1) { + targetTracks = DocManager.Inst.Project.tracks; + } + var phonemizerFactory = PhonemizerFactory.Get(track.Phonemizer.GetType()); + DocManager.Inst.StartUndoGroup("command.track.setting"); + foreach (var targetTrack in targetTracks) { + if (targetTrack == track) { + continue; + } + if (track.Singer != targetTrack.Singer) { + ApplySingerToTrack(targetTrack, track.Singer); + } + if (targetTrack.Phonemizer.GetType() != track.Phonemizer.GetType()) { + var phonemizer = phonemizerFactory?.Create(); + if (phonemizer != null) { + DocManager.Inst.ExecuteCmd(new TrackChangePhonemizerCommand(DocManager.Inst.Project, targetTrack, phonemizer)); + } + } + DocManager.Inst.ExecuteCmd(new TrackChangeRenderSettingCommand( + DocManager.Inst.Project, + targetTrack, + track.RendererSettings.Clone())); + DocManager.Inst.ExecuteCmd(new TrackChangeSettingsCommand( + DocManager.Inst.Project, + targetTrack, + track.Mute, + track.Volume, + track.Pan)); + DocManager.Inst.ExecuteCmd(new ChangeTrackColorCommand( + DocManager.Inst.Project, + targetTrack, + track.TrackColor)); + DocManager.Inst.ExecuteCmd(new ConfigureExpressionsCommand( + DocManager.Inst.Project, + DocManager.Inst.Project.expressions.Values.ToArray(), + targetTrack, + track.TrackExpressions.Select(exp => exp.Clone()).ToArray())); + } + DocManager.Inst.EndUndoGroup(); + MessageBus.Current.SendMessage(new TracksRefreshEvent()); + MessageBus.Current.SendMessage(new PianorollRefreshEvent("TrackColor")); + } + + public void RotateSelectedTrackSingers() { + var targetTracks = DocManager.Inst.Project.tracks + .Where(projectTrack => projectTrack != null) + .Where(projectTrack => IsTrackSelected(projectTrack)) + .OrderBy(targetTrack => targetTrack.TrackNo) + .ToList(); + if (targetTracks.Count <= 1) { + targetTracks = DocManager.Inst.Project.tracks; + } + var singers = targetTracks + .Select(targetTrack => targetTrack.Singer) + .ToList(); + DocManager.Inst.StartUndoGroup("command.track.singer"); + for (int i = 0; i < targetTracks.Count; i++) { + var singer = singers[(i + 1) % singers.Count]; + ApplySingerToTrack(targetTracks[i], singer); + } DocManager.Inst.EndUndoGroup(); + DocManager.Inst.ExecuteCmd(new VoiceColorRemappingNotification(-1, true)); + MessageBus.Current.SendMessage(new TracksRefreshEvent()); + MessageBus.Current.SendMessage(new PianorollRefreshEvent("Part")); } public void VoiceColorRemapping() { - if (track.Singer != null && track.Singer.Found && track.VoiceColorExp != null) { - DocManager.Inst.ExecuteCmd(new VoiceColorRemappingNotification(track.TrackNo, false)); + var targetTracks = DocManager.Inst.Project.tracks + .Where(projectTrack => projectTrack != null) + .Where(projectTrack => IsTrackSelected(projectTrack)) + .ToList(); + if (targetTracks.Count < 2) { + targetTracks = new List() { this.track }; + } + foreach (var track in targetTracks) { + if (track.Singer != null && track.Singer.Found && track.VoiceColorExp != null) { + DocManager.Inst.ExecuteCmd(new VoiceColorRemappingNotification(track.TrackNo, false)); + } } }