diff --git a/OpenUtau.Core/PlaybackManager.cs b/OpenUtau.Core/PlaybackManager.cs index 863571c40..c25c8d427 100644 --- a/OpenUtau.Core/PlaybackManager.cs +++ b/OpenUtau.Core/PlaybackManager.cs @@ -12,7 +12,6 @@ using OpenUtau.Core.Ustx; using OpenUtau.Core.Util; using Serilog; -using System.Collections.Concurrent; namespace OpenUtau.Core { public class SineGenerator : ISampleProvider { @@ -265,6 +264,7 @@ private PlaybackManager() { double startMs; public int StartTick => DocManager.Inst.Project.timeAxis.MsPosToTickPos(startMs); + public int returnPosition = 0; // One cancellation source per render lane so starting playback, pre-rendering // or exporting no longer cancel each other in flight. CancellationTokenSource renderCancellation; @@ -435,6 +435,11 @@ public void StopPlayback() { } public void PausePlayback() { + if (Preferences.Default.LockStartTime != 0) { + StopPlayback(); + DocManager.Inst.ExecuteCmd(new SetPlayPosTickNotification(returnPosition, pause: true)); + return; + } AudioOutput.Pause(); StartingToPlay = false; PlayingMaster = false; @@ -468,14 +473,19 @@ private void Render(UProject project, int tick, int endTick, int trackNo) { RenderEngine engine = new RenderEngine(project, startTick: tick, endTick: endTick, trackNo: trackNo); var result = engine.RenderMixdown(DocManager.Inst.MainScheduler, ref renderCancellation, wait: false, applyMixFx: true, planner: MixPlanner); playbackMix = new PlaybackMix(result.Item1, metronomeEngine); + this.returnPosition = tick; + var startTick = tick; + if (Preferences.Default.PreRoll) { + startTick = Math.Max(0, tick - (480 * 4 * Preferences.Default.PreRollMeasure)); + } var playbackAdapter = new MasterAdapter(playbackMix); // Hold mode: wait for pending phrases (today's behaviour), except // in loop mode where a pending phrase must not stall the clock. // Failed phrases never hold in either mode. playbackAdapter.HoldWhenUnready = !LoopPlayback; - playbackAdapter.SetPosition((int)(project.timeAxis.TickPosToMsPos(tick) * 44100 / 1000) * 2); + playbackAdapter.SetPosition((int)(project.timeAxis.TickPosToMsPos(startTick) * 44100 / 1000) * 2); faders = result.Item2; - StartPlayback(project.timeAxis.TickPosToMsPos(tick), playbackAdapter); + StartPlayback(project.timeAxis.TickPosToMsPos(startTick), playbackAdapter); Task.Factory.StartNew(() => { DocManager.Inst.ExecuteCmd(new WaveformReadyNotification()); diff --git a/OpenUtau.Core/Util/Preferences.cs b/OpenUtau.Core/Util/Preferences.cs index e7d94c521..1d24ca879 100644 --- a/OpenUtau.Core/Util/Preferences.cs +++ b/OpenUtau.Core/Util/Preferences.cs @@ -215,6 +215,10 @@ public class SerializablePreferences { public int MetronomeVolume = 60; public int MetronomeHighFrequency = 2200; public int MetronomeLowFrequency = 1320; + public bool AutoMovePlayhead = false; + public bool PreRoll = false; + public int PreRollMeasure = 1; + /// On Pausing; 0 = Do nothing, 1 = Move cursor and scroll, 2 = Move cursor only public int LockStartTime = 0; public int PlaybackAutoScroll = 2; public bool ReverseLogOrder = true; diff --git a/OpenUtau/Controls/PianoRoll.axaml b/OpenUtau/Controls/PianoRoll.axaml index ff287a7eb..5af94b3ff 100644 --- a/OpenUtau/Controls/PianoRoll.axaml +++ b/OpenUtau/Controls/PianoRoll.axaml @@ -327,6 +327,11 @@ + + + + + diff --git a/OpenUtau/Controls/PianoRoll.axaml.cs b/OpenUtau/Controls/PianoRoll.axaml.cs index 7d3039a88..86ab2b907 100644 --- a/OpenUtau/Controls/PianoRoll.axaml.cs +++ b/OpenUtau/Controls/PianoRoll.axaml.cs @@ -315,6 +315,11 @@ void OnMenuDegreeStyle(object sender, RoutedEventArgs args) { MessageBus.Current.SendMessage(new PianorollRefreshEvent("Part")); } } + void OnMenuAutoMovePlayhead(object sender, RoutedEventArgs args) { + Preferences.Default.AutoMovePlayhead = !Preferences.Default.AutoMovePlayhead; + Preferences.Save(); + ViewModel.RaisePropertyChanged(nameof(ViewModel.AutoMovePlayhead)); + } void OnMenuLockStartTime(object sender, RoutedEventArgs args) { if (sender is MenuItem menu && int.TryParse(menu.Tag?.ToString(), out int tag)) { Preferences.Default.LockStartTime = tag; diff --git a/OpenUtau/OpenUtau.csproj b/OpenUtau/OpenUtau.csproj index 3dccfb966..77cbd2e63 100644 --- a/OpenUtau/OpenUtau.csproj +++ b/OpenUtau/OpenUtau.csproj @@ -119,6 +119,9 @@ TrackColorDialog.axaml + + ComboBoxDialog.axaml + diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index 430dbc7ae..544407d58 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -150,6 +150,7 @@ OpenUtau aims to be an open source editing environment for UTAU community, with Copy error to clipboard No resampler No resampler! Put your favourite resampler exe or dll in the Resamplers folder and choose it in Preferences! + Pre-roll Measures Previously, OpenUtau terminated abnormally. Click here to recover. Recovery project This tool will change the tempo of the project without changing the actual positions and durations (in seconds) of notes. @@ -368,6 +369,7 @@ Do you want to continue by splitting at the nearest position after current playh Singers... Install Wavtool/Resampler (.exe)... View + Move the playhead when selecting notes Back Failed to merge singer. @@ -870,6 +872,7 @@ General ◀ Scroll here to zoom horizontally ▶ Scroll here to zoom vertically ▶ Loop playback + Pre-roll Duplicate Track Duplicate Track Settings diff --git a/OpenUtau/ViewModels/MainWindowViewModel.cs b/OpenUtau/ViewModels/MainWindowViewModel.cs index 704a1f072..ce87988f1 100644 --- a/OpenUtau/ViewModels/MainWindowViewModel.cs +++ b/OpenUtau/ViewModels/MainWindowViewModel.cs @@ -90,6 +90,7 @@ public string AppVersion { public ReactiveCommand? DelTempoChangeCmd { get; set; } public ReactiveCommand? AddTimeSigChangeCmd { get; set; } public ReactiveCommand? DelTimeSigChangeCmd { get; set; } + [Reactive] public partial bool PreRoll { get; set; } = Preferences.Default.PreRoll; [Reactive] public partial bool CanUndo { get; set; } = false; [Reactive] public partial bool CanRedo { get; set; } = false; [Reactive] public partial string UndoText { get; set; } = ThemeManager.GetString("menu.edit.undo"); @@ -143,6 +144,11 @@ public MainWindowViewModel() { PianoRollMaxHeight = x ? double.PositiveInfinity : 0.01; PianoRollMinHeight = x ? ViewConstants.PianoRollMinHeight : 0.01; }); + this.WhenAnyValue(vm => vm.PreRoll) + .Subscribe(x => { + Preferences.Default.PreRoll = x; + Preferences.Save(); + }); } public void Undo() { diff --git a/OpenUtau/ViewModels/NotePropertiesViewModel.cs b/OpenUtau/ViewModels/NotePropertiesViewModel.cs index 0e9fd190f..54247c750 100644 --- a/OpenUtau/ViewModels/NotePropertiesViewModel.cs +++ b/OpenUtau/ViewModels/NotePropertiesViewModel.cs @@ -3,15 +3,15 @@ using System.Collections.ObjectModel; using System.Linq; using Avalonia.Media; +using OpenUtau.Api; using OpenUtau.Core; using OpenUtau.Core.Format; using OpenUtau.Core.Ustx; using OpenUtau.Core.Util; using ReactiveUI; +using ReactiveUI.Primitives; using ReactiveUI.SourceGenerators; using SharpCompress; -using OpenUtau.Api; -using ReactiveUI.Primitives; namespace OpenUtau.App.ViewModels { public partial class NotePropertiesViewModel : ViewModelBase, ICmdSubscriber { @@ -141,19 +141,22 @@ public NotePropertiesViewModel() { }); SubscribeExtensions.Subscribe(MessageBus.Current.Listen(), e => { - if (PanelControlPressed) { - PanelControlPressed = false; - DocManager.Inst.EndUndoGroup(); - } - NoteLoading = true; + if (PanelControlPressed) { + PanelControlPressed = false; + DocManager.Inst.EndUndoGroup(); + } - selectedNotes.Clear(); - selectedNotes.UnionWith(e.selectedNotes); - selectedNotes.UnionWith(e.tempSelectedNotes); - OnSelectNotes(); + NoteLoading = true; + selectedNotes.Clear(); + selectedNotes.UnionWith(e.selectedNotes); + selectedNotes.UnionWith(e.tempSelectedNotes); + OnSelectNotes(); + NoteLoading = false; - NoteLoading = false; - }); + if (Preferences.Default.AutoMovePlayhead && selectedNotes.Count > 0 && Part != null) { + DocManager.Inst.ExecuteCmd(new SetPlayPosTickNotification(Part.position + selectedNotes.First().position)); + } + }); DocManager.Inst.AddSubscriber(this); } diff --git a/OpenUtau/ViewModels/PianoRollViewModel.cs b/OpenUtau/ViewModels/PianoRollViewModel.cs index 11beac297..32540c849 100644 --- a/OpenUtau/ViewModels/PianoRollViewModel.cs +++ b/OpenUtau/ViewModels/PianoRollViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Avalonia.Input; @@ -61,6 +61,7 @@ public partial class PianoRollViewModel : ViewModelBase, ICmdSubscriber { public bool DegreeStyle0 { get => Preferences.Default.DegreeStyle == 0 ? true : false; } public bool DegreeStyle1 { get => Preferences.Default.DegreeStyle == 1 ? true : false; } public bool DegreeStyle2 { get => Preferences.Default.DegreeStyle == 2 ? true : false; } + public bool AutoMovePlayhead { get => Preferences.Default.AutoMovePlayhead; } public bool LockStartTime0 { get => Preferences.Default.LockStartTime == 0 ? true : false; } public bool LockStartTime1 { get => Preferences.Default.LockStartTime == 1 ? true : false; } public bool LockStartTime2 { get => Preferences.Default.LockStartTime == 2 ? true : false; } @@ -320,4 +321,4 @@ public void OnNext(UCommand cmd, bool isUndo) { #endregion } -} \ No newline at end of file +} diff --git a/OpenUtau/ViewModels/PlaybackViewModel.cs b/OpenUtau/ViewModels/PlaybackViewModel.cs index 164fd8928..99f270300 100644 --- a/OpenUtau/ViewModels/PlaybackViewModel.cs +++ b/OpenUtau/ViewModels/PlaybackViewModel.cs @@ -1,7 +1,6 @@ using System; using OpenUtau.Core; using OpenUtau.Core.Ustx; -using OpenUtau.Core.Util; using ReactiveUI; namespace OpenUtau.App.ViewModels { @@ -52,10 +51,6 @@ public void SeekEnd() { } public void PlayOrPause(int tick = -1, int endTick = -1, int trackNo = -1) { PlaybackManager.Inst.PlayOrPause(tick: tick, endTick: endTick, trackNo: trackNo); - var lockStartTime = Convert.ToBoolean(Preferences.Default.LockStartTime); - if (!PlaybackManager.Inst.OutputActive && !PlaybackManager.Inst.StartingToPlay && lockStartTime) { - DocManager.Inst.ExecuteCmd(new SeekPlayPosTickNotification(PlaybackManager.Inst.StartTick, true)); - } } public void Pause() { PlaybackManager.Inst.PausePlayback(); diff --git a/OpenUtau/Views/ComboBoxDialog.axaml b/OpenUtau/Views/ComboBoxDialog.axaml new file mode 100644 index 000000000..fa2768e92 --- /dev/null +++ b/OpenUtau/Views/ComboBoxDialog.axaml @@ -0,0 +1,14 @@ + + + + + + ToolTip.Tip="{DynamicResource tip.playback.loop}"> + + + - + diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index c32f6ba09..7e1c42474 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -889,6 +889,24 @@ private void LayoutSplit(double? x, double? y) { } } + void OnClickPreRoll(object sender, PointerPressedEventArgs arg) { + var point = arg.GetCurrentPoint(sender as Control); + if (point.Properties.IsRightButtonPressed) { + var dialog = new ComboBoxDialog(); + dialog.Initialize(ThemeManager.GetString("dialogs.preroll.caption"), + new[] { "1", "2", "3", "4" }, + Preferences.Default.PreRollMeasure - 1); + dialog.onFinish = index => { + if (0 <= index && index < 4) { + Preferences.Default.PreRollMeasure = index + 1; + Preferences.Save(); + } + }; + dialog.ShowDialog(this); + } + arg.Handled = true; + } + void OnKeyDown(object sender, KeyEventArgs args) { if (PianoRollContainer.IsKeyboardFocusWithin) { args.Handled = false;