diff --git a/OpenUtau.Core/Util/Preferences.cs b/OpenUtau.Core/Util/Preferences.cs
index f14fe185d..121d00cc5 100644
--- a/OpenUtau.Core/Util/Preferences.cs
+++ b/OpenUtau.Core/Util/Preferences.cs
@@ -216,8 +216,6 @@ public class SerializablePreferences {
public bool LockUnselectedNotesPitch = true;
public bool LockUnselectedNotesVibrato = true;
public bool LockUnselectedNotesExpressions = true;
- public bool LyricLivePreview = true;
- public bool LyricApplySelectionOnly = true;
public bool VoicebankPublishUseIgnore = true;
public string VoicebankPublishIgnores = @"#Adobe Audition
*.pkf
diff --git a/OpenUtau/Controls/LyricsPanel.axaml b/OpenUtau/Controls/LyricsPanel.axaml
new file mode 100644
index 000000000..01542f038
--- /dev/null
+++ b/OpenUtau/Controls/LyricsPanel.axaml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OpenUtau/Controls/LyricsPanel.axaml.cs b/OpenUtau/Controls/LyricsPanel.axaml.cs
new file mode 100644
index 000000000..7d615de46
--- /dev/null
+++ b/OpenUtau/Controls/LyricsPanel.axaml.cs
@@ -0,0 +1,70 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using OpenUtau.App.ViewModels;
+using OpenUtau.Core;
+
+namespace OpenUtau.App.Controls {
+ public partial class LyricsPanel : UserControl {
+ private LyricsViewModel? viewModel;
+
+ public LyricsPanel() {
+ InitializeComponent();
+ IsVisible = false;
+
+ LyricsBox.AddHandler(GotFocusEvent, TextBoxGotFocus, RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
+ LyricsBox.AddHandler(KeyDownEvent, TextBoxKeyDown, RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
+ LyricsBox.AddHandler(LostFocusEvent, TextBoxLostFocus, RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
+ }
+
+ public void Show(LyricsViewModel viewModel) {
+ DataContext = this.viewModel = viewModel;
+ IsVisible = true;
+ }
+
+ private void TextBoxGotFocus(object? sender, RoutedEventArgs e) {
+ if (viewModel == null) return;
+ DocManager.Inst.StartUndoGroup("command.note.lyric");
+ viewModel.IsFocused = true;
+ }
+
+ private void TextBoxKeyDown(object? sender, KeyEventArgs e) {
+ if (viewModel == null || !LyricsBox.IsFocused) return;
+ switch (e.Key) {
+ case Key.Enter:
+ //If Shift+Enter, insert line break (default textbox behavior).
+ if (e.KeyModifiers == KeyModifiers.Shift) {
+ return;
+ }
+ this.Focus();
+ e.Handled = true;
+ break;
+ case Key.Escape:
+ this.Focus();
+ Close();
+ e.Handled = true;
+ break;
+ default:
+ if (e.Key == Key.Z && e.KeyModifiers != KeyModifiers.None || e.Key == Key.Y && e.KeyModifiers != KeyModifiers.None) { // Todo: Supports shortcut remapping
+ // Finish lyrics editing and use the original shortcut
+ this.Focus();
+ }
+ break;
+ }
+ }
+
+ private void TextBoxLostFocus(object? sender, RoutedEventArgs e) {
+ if (viewModel == null) return;
+ viewModel.IsFocused = false;
+ DocManager.Inst.EndUndoGroup();
+ }
+
+ public void OnClose(object sender, RoutedEventArgs args) {
+ Close();
+ }
+ private void Close() {
+ IsVisible = false;
+ viewModel = null;
+ }
+ }
+}
diff --git a/OpenUtau/Controls/PianoRoll.axaml b/OpenUtau/Controls/PianoRoll.axaml
index fdc205387..204176167 100644
--- a/OpenUtau/Controls/PianoRoll.axaml
+++ b/OpenUtau/Controls/PianoRoll.axaml
@@ -551,11 +551,15 @@
+ Margin="12,12,12,70">
+
+
+
diff --git a/OpenUtau/Controls/PianoRoll.axaml.cs b/OpenUtau/Controls/PianoRoll.axaml.cs
index 1d5a660fe..38d80bff6 100644
--- a/OpenUtau/Controls/PianoRoll.axaml.cs
+++ b/OpenUtau/Controls/PianoRoll.axaml.cs
@@ -413,9 +413,22 @@ void SearchNote() {
if (ViewModel.NotesViewModel.Part == null || ViewModel.NotesViewModel.Part.notes.Count == 0) {
return;
}
+ LyricsPanel.IsVisible = false;
SearchBar.Show(ViewModel.NotesViewModel);
}
+ void OnMenuEditLyrics(object? sender, RoutedEventArgs e) {
+ EditLyrics();
+ }
+
+ void EditLyrics() {
+ if (ViewModel.NotesViewModel.Part == null) {
+ return;
+ }
+ SearchBar.IsVisible = false;
+ LyricsPanel.Show(ViewModel.NotesViewModel.LyricsViewModel);
+ }
+
void ReplaceLyrics() {
if (ViewModel.NotesViewModel.Part == null) {
return;
@@ -440,32 +453,6 @@ void ReplaceLyrics() {
dialog.ShowDialog(RootWindow);
}
- void OnMenuEditLyrics(object? sender, RoutedEventArgs e) {
- EditLyrics();
- }
-
- void EditLyrics() {
- if (ViewModel.NotesViewModel.Part == null) {
- return;
- }
- if (ViewModel.NotesViewModel.Part.notes.Count < 1) {
- _ = MessageBox.Show(
- RootWindow,
- ThemeManager.GetString("lyrics.nonote"),
- ThemeManager.GetString("lyrics.caption"),
- MessageBox.MessageBoxButtons.Ok);
- return;
- }
-
- var vm = new LyricsViewModel();
- var (notes, selection) = ViewModel.NotesViewModel.PrepareInsertLyrics();
- vm.Start(ViewModel.NotesViewModel.Part, notes, selection);
- var dialog = new LyricsDialog() {
- DataContext = vm,
- };
- dialog.ShowDialog(RootWindow);
- }
-
void OnMenuNoteDefaults(object sender, RoutedEventArgs args) {
EditNoteDefaults();
}
diff --git a/OpenUtau/OpenUtau.csproj b/OpenUtau/OpenUtau.csproj
index 85bf69c8b..4ecdac41c 100644
--- a/OpenUtau/OpenUtau.csproj
+++ b/OpenUtau/OpenUtau.csproj
@@ -99,6 +99,9 @@
+
+ LyricsPanel.axaml
+
NotePropertyExpression.axaml
diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml
index 0be61d251..d901975b6 100644
--- a/OpenUtau/Strings/Strings.axaml
+++ b/OpenUtau/Strings/Strings.axaml
@@ -242,15 +242,8 @@ Do you want to continue by splitting at the nearest position after current playh
Chinese
Cantonese
- Apply
- Apply only to selected notes
- Cancel
- Edit Lyrics
- Live preview
- There is no note!
- Reset
- Select some notes first!
Separators
+ Skip symbols such as "R" or "-"
After :
Before :
diff --git a/OpenUtau/ViewModels/LyricsViewModel.cs b/OpenUtau/ViewModels/LyricsViewModel.cs
index abeecb92d..a9265aa68 100644
--- a/OpenUtau/ViewModels/LyricsViewModel.cs
+++ b/OpenUtau/ViewModels/LyricsViewModel.cs
@@ -1,100 +1,95 @@
using System;
using System.Linq;
+using System.Reactive.Linq;
using OpenUtau.Core;
using OpenUtau.Core.Ustx;
using OpenUtau.Core.Util;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
+using SharpCompress;
namespace OpenUtau.App.ViewModels {
- class LyricsViewModel : ViewModelBase {
- [Reactive] public string? Text { get; set; } = string.Empty;
+ public class LyricsViewModel : ViewModelBase {
+ [Reactive] public string Text { get; set; } = string.Empty;
[Reactive] public int CurrentCount { get; set; }
[Reactive] public int TotalCount { get; set; }
- [Reactive] public bool LivePreview { get; set; } = Preferences.Default.LyricLivePreview;
- [Reactive] public bool ApplySelection { get; set; } = Preferences.Default.LyricApplySelectionOnly;
+ [Reactive] public bool SkipSymbols { get; set; } = true;
- private UVoicePart? part;
- private UNote[]? notes;
- private UNote[]? selection;
- private string[]? startLyrics;
+ public bool IsFocused { get; set; } = false;
- public LyricsViewModel() {
- this.WhenAnyValue(x => x.LivePreview,
- x => x.Text)
- .Subscribe(t => {
- Preview(t.Item1);
- });
- this.WhenAnyValue(x => x.ApplySelection)
+ private NotesViewModel notesViewModel;
+ private UNote[] notes = [];
+ private UNote[] selection = [];
+
+ public LyricsViewModel(NotesViewModel notesVm) {
+ notesViewModel = notesVm;
+
+ this.WhenAnyValue(x => x.SkipSymbols)
.Subscribe(a => {
- UpdateTotalCount();
- Preview(LivePreview);
+ FilterNotes();
});
- }
-
- public void Start(UVoicePart part, UNote[] notes, UNote[] selection) {
- this.part = part;
- this.notes = notes;
- this.selection = selection;
- if (selection.Length < 1) {
- ApplySelection = false;
- }
- UpdateTotalCount();
- CurrentCount = TotalCount;
- Text = SplitLyrics.Join(startLyrics!);
- DocManager.Inst.StartUndoGroup("command.note.lyric");
+ MessageBus.Current.Listen()
+ .Subscribe(e => {
+ if (e.tempSelectedNotes.Length > 0) {
+ selection = [];
+ } else {
+ selection = e.selectedNotes.ToArray();
+ }
+ FilterNotes();
+ });
+ selection = notesViewModel.Selection.ToArray();
+ FilterNotes();
}
- private void Preview(bool update) {
- var notes = ApplySelection ? selection : this.notes;
- if (startLyrics == null || notes == null || part == null) {
- return;
+ private void FilterNotes() {
+ if (IsFocused) {
+ DocManager.Inst.EndUndoGroup();
+ IsFocused = false;
}
- DocManager.Inst.RollBackUndoGroup();
- var lyrics = SplitLyrics.Split(Text);
- CurrentCount = lyrics.Count;
- if (update) {
- for (int i = 0; i < lyrics.Count && i < notes.Length; ++i) {
- if (notes[i].lyric != lyrics[i]) {
- DocManager.Inst.ExecuteCmd(new ChangeNoteLyricCommand(part, notes[i], lyrics[i]));
- }
+ if (notesViewModel == null || notesViewModel.Part == null) {
+ notes = [];
+ Text = string.Empty;
+ TotalCount = 0;
+ CurrentCount = 0;
+ } else if (selection.Length == 0) {
+ notes = [];
+ CurrentCount = SplitLyrics.Split(Text).Count;
+ if (TotalCount == 0) return;
+ Text = string.Empty;
+ TotalCount = 0;
+ CurrentCount = 0;
+ } else {
+ if (SkipSymbols) {
+ notes = selection.Where(n => n.lyric != "R" && n.lyric != "-" && n.lyric != "+~").ToArray();
+ } else {
+ notes = selection.ToArray();
}
+ Text = SplitLyrics.Join(notes.Select(n => n.lyric));
+ TotalCount = notes.Length;
+ CurrentCount = SplitLyrics.Split(Text).Count;
}
}
- private void UpdateTotalCount() {
- if (ApplySelection) {
- TotalCount = selection?.Length ?? 0;
- startLyrics = selection?.Select(n => n.lyric).ToArray();
- } else {
- TotalCount = notes?.Length ?? 0;
- startLyrics = notes?.Select(n => n.lyric).ToArray();
- }
- }
+ public void ApplyLyrics() {
+ var lyrics = SplitLyrics.Split(Text);
+ CurrentCount = lyrics.Count;
- public void Reset() {
- if (startLyrics == null) {
+ if (notesViewModel == null || notesViewModel.Part == null || !IsFocused || notes.Length == 0 || lyrics.Count == 0) {
return;
}
- DocManager.Inst.RollBackUndoGroup();
- Text = SplitLyrics.Join(startLyrics);
- }
- public void Cancel() {
- DocManager.Inst.RollBackUndoGroup();
- DocManager.Inst.EndUndoGroup();
- Preferences.Default.LyricLivePreview = LivePreview;
- Preferences.Default.LyricApplySelectionOnly = ApplySelection;
- Preferences.Save();
+ int count = Math.Min(lyrics.Count, notes.Length);
+ DocManager.Inst.ExecuteCmd(new ChangeNoteLyricCommand(notesViewModel.Part, notes.Take(count).ToArray(), lyrics.Take(count).ToArray()));
}
- public void Finish() {
- Preview(true);
- DocManager.Inst.EndUndoGroup();
- Preferences.Default.LyricLivePreview = LivePreview;
- Preferences.Default.LyricApplySelectionOnly = ApplySelection;
- Preferences.Save();
+ public string? GetFirstLyric() {
+ var split = SplitLyrics.Split(Text);
+ if (string.IsNullOrWhiteSpace(split.FirstOrDefault())) return null;
+ var lyric = split[0];
+ split.RemoveAt(0);
+ Text = SplitLyrics.Join(split);
+ return lyric;
}
}
}
diff --git a/OpenUtau/ViewModels/NotesViewModel.cs b/OpenUtau/ViewModels/NotesViewModel.cs
index 7e80353ca..320656483 100644
--- a/OpenUtau/ViewModels/NotesViewModel.cs
+++ b/OpenUtau/ViewModels/NotesViewModel.cs
@@ -101,6 +101,7 @@ public class NotesViewModel : ViewModelBase, ICmdSubscriber {
private readonly ObservableAsPropertyHelper smallChangeY;
public readonly NoteSelectionViewModel Selection = new NoteSelectionViewModel();
+ public readonly LyricsViewModel LyricsViewModel;
internal NotesViewModelHitTest HitTest;
private int _lastNoteLength = 480;
@@ -110,6 +111,8 @@ public class NotesViewModel : ViewModelBase, ICmdSubscriber {
private int userKey => Project.key;
public NotesViewModel() {
+ LyricsViewModel = new LyricsViewModel(this);
+
SnapDivs = new List();
SetSnapUnitCommand = ReactiveCommand.Create(div => {
userSnapDiv = div;
@@ -209,6 +212,10 @@ public NotesViewModel() {
CommandParameter = index,
}));
});
+ this.WhenAnyValue(x => x.LyricsViewModel.Text)
+ .Subscribe(text => {
+ LyricsViewModel.ApplyLyrics();
+ });
ShowTips = Preferences.Default.ShowTips;
IsSnapOn = true;
diff --git a/OpenUtau/Views/LyricsDialog.axaml b/OpenUtau/Views/LyricsDialog.axaml
deleted file mode 100644
index c84346fbb..000000000
--- a/OpenUtau/Views/LyricsDialog.axaml
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/OpenUtau/Views/LyricsDialog.axaml.cs b/OpenUtau/Views/LyricsDialog.axaml.cs
deleted file mode 100644
index 13e256329..000000000
--- a/OpenUtau/Views/LyricsDialog.axaml.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using System;
-using Avalonia.Controls;
-using Avalonia.Input;
-using Avalonia.Interactivity;
-using OpenUtau.App.ViewModels;
-
-namespace OpenUtau.App.Views {
- public partial class LyricsDialog : Window {
- public LyricsDialog() {
- InitializeComponent();
- DIALOG_Box.AddHandler(KeyDownEvent, TextBoxKeyDown, RoutingStrategies.Tunnel);
- }
-
- void OnOpened(object? sender, EventArgs e) {
- DIALOG_Box.Focus();
- }
-
- void OnCancel(object? sender, RoutedEventArgs e) {
- (DataContext as LyricsViewModel)!.Cancel();
- Close();
- }
-
- void OnFinish(object? sender, RoutedEventArgs e) {
- (DataContext as LyricsViewModel)!.Finish();
- Close();
- }
-
- private void TextBoxKeyDown(object? sender, KeyEventArgs e) {
- switch (e.Key) {
- case Key.Enter:
- //If Shift+Enter, insert line break (default textbox behavior).
- if (e.KeyModifiers == KeyModifiers.Shift) {
- return;
- }
- OnFinish(sender, e);
- e.Handled = true;
- break;
- case Key.Escape:
- OnCancel(sender, e);
- e.Handled = true;
- break;
- default:
- break;
- }
- }
- }
-}
diff --git a/OpenUtau/Views/NoteEditStates.cs b/OpenUtau/Views/NoteEditStates.cs
index 2b0c6c20a..a189bbad2 100644
--- a/OpenUtau/Views/NoteEditStates.cs
+++ b/OpenUtau/Views/NoteEditStates.cs
@@ -233,13 +233,19 @@ public NoteDrawEditState(
public override void Begin(IPointer pointer, Point point) {
base.Begin(pointer, point);
note = vm.NotesViewModel.MaybeAddNote(point, false);
- if (note != null && playTone) {
- if (PlaybackManager.Inst.PlayingMaster) {
- // Stop playback if playing project
- PlaybackManager.Inst.StopPlayback();
+ if (note != null) {
+ var flowin = vm.NotesViewModel.LyricsViewModel.GetFirstLyric();
+ if (!string.IsNullOrEmpty(flowin)) {
+ note.lyric = flowin;
+ }
+ if (playTone) {
+ if (PlaybackManager.Inst.PlayingMaster) {
+ // Stop playback if playing project
+ PlaybackManager.Inst.StopPlayback();
+ }
+ activeTone = note.tone;
+ PlaybackManager.Inst.PlayTone(MusicMath.ToneToFreq(note.tone));
}
- activeTone = note.tone;
- PlaybackManager.Inst.PlayTone(MusicMath.ToneToFreq(note.tone));
}
if (note != null) {
var prev = vm.NotesViewModel.Part!.notes.FirstOrDefault(n => n.position < note.position && note.position < n.End);