From d7f6c85d193a53d6302bba743d6dda70f78a7468 Mon Sep 17 00:00:00 2001 From: sabihoshi Date: Tue, 11 May 2021 18:21:41 +0800 Subject: [PATCH] Show error when note cannot be played Adds an option to let the user pick to between moving the semitone higher, lower, or ignoring the note when it cannot be played even after considering transpose settings. --- GenshinLyreMidiPlayer.WPF/Core/LyrePlayer.cs | 38 +++++++-- .../GenshinLyreMidiPlayer.WPF.csproj | 2 +- .../ModernWPF/Errors/ErrorContentDialog.cs | 6 +- .../ViewModels/LyrePlayerViewModel.cs | 84 +++++++++++++------ .../ViewModels/SettingsPageViewModel.cs | 2 + 5 files changed, 95 insertions(+), 37 deletions(-) diff --git a/GenshinLyreMidiPlayer.WPF/Core/LyrePlayer.cs b/GenshinLyreMidiPlayer.WPF/Core/LyrePlayer.cs index b4972d1..91bd98b 100644 --- a/GenshinLyreMidiPlayer.WPF/Core/LyrePlayer.cs +++ b/GenshinLyreMidiPlayer.WPF/Core/LyrePlayer.cs @@ -9,6 +9,13 @@ namespace GenshinLyreMidiPlayer.WPF.Core { public static class LyrePlayer { + public enum Tranpose + { + Ignore, + Up, + Down + } + private static readonly IInputSimulator Input = new InputSimulator(); private static readonly List LyreNotes = new() @@ -38,7 +45,8 @@ public static class LyrePlayer 83 // B5 }; - public static int TransposeNote(int noteId) + public static int TransposeNote(int noteId, + Tranpose direction = Tranpose.Ignore) { while (true) { @@ -50,7 +58,14 @@ public static int TransposeNote(int noteId) else if (noteId > LyreNotes.Last()) noteId -= 12; else - noteId++; + { + return direction switch + { + Tranpose.Ignore => noteId, + Tranpose.Up => ++noteId, + Tranpose.Down => --noteId + }; + } } } @@ -72,13 +87,22 @@ public static void NoteUp(int noteId, Layout selectedLayout) public static void InteractNote(int noteId, Layout selectedLayout, Func action) { - var layout = GetLayout(selectedLayout); + if (selectedLayout.TryGetKey(noteId, out var key)) + action.Invoke(key); + } + + public static bool TryGetKey(this Layout layout, int noteId, out VirtualKeyCode key) + { + var keys = GetLayout(layout); + return TryGetKey(keys, noteId, out key); + } + + public static bool TryGetKey(this IEnumerable keys, int noteId, out VirtualKeyCode key) + { var keyIndex = LyreNotes.IndexOf(noteId); - if (keyIndex < 0 || keyIndex > layout.Count) - return; + key = keys.ElementAtOrDefault(keyIndex); - var key = layout[keyIndex]; - action.Invoke(key); + return keyIndex != -1; } } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj b/GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj index d617d24..759ae4b 100644 --- a/GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj +++ b/GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj @@ -6,7 +6,7 @@ true GenshinLyreMidiPlayer.WPF.App app.manifest - 1.9.3 + 1.9.4 item_windsong_lyre.ico enable https://github.com/sabihoshi/GenshinLyreMidiPlayer diff --git a/GenshinLyreMidiPlayer.WPF/ModernWPF/Errors/ErrorContentDialog.cs b/GenshinLyreMidiPlayer.WPF/ModernWPF/Errors/ErrorContentDialog.cs index aae97ed..8a6cf0f 100644 --- a/GenshinLyreMidiPlayer.WPF/ModernWPF/Errors/ErrorContentDialog.cs +++ b/GenshinLyreMidiPlayer.WPF/ModernWPF/Errors/ErrorContentDialog.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Humanizer; @@ -8,7 +8,7 @@ namespace GenshinLyreMidiPlayer.WPF.ModernWPF.Errors { public class ErrorContentDialog : ContentDialog { - public ErrorContentDialog(Exception e, IReadOnlyCollection? options = null) + public ErrorContentDialog(Exception e, IReadOnlyCollection? options = null, string? closeText = null) { Title = e.Message; Content = e; @@ -16,7 +16,7 @@ public ErrorContentDialog(Exception e, IReadOnlyCollection? options = null PrimaryButtonText = options?.ElementAtOrDefault(0)?.Humanize(); SecondaryButtonText = options?.ElementAtOrDefault(1)?.Humanize(); - CloseButtonText = "Abort"; + CloseButtonText = closeText ?? "Abort"; } } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/ViewModels/LyrePlayerViewModel.cs b/GenshinLyreMidiPlayer.WPF/ViewModels/LyrePlayerViewModel.cs index d1b9a42..096849c 100644 --- a/GenshinLyreMidiPlayer.WPF/ViewModels/LyrePlayerViewModel.cs +++ b/GenshinLyreMidiPlayer.WPF/ViewModels/LyrePlayerViewModel.cs @@ -7,13 +7,16 @@ using GenshinLyreMidiPlayer.Data.Notification; using GenshinLyreMidiPlayer.Data.Properties; using GenshinLyreMidiPlayer.WPF.Core; +using GenshinLyreMidiPlayer.WPF.ModernWPF.Errors; using Melanchall.DryWetMidi.Core; using Melanchall.DryWetMidi.Devices; using Melanchall.DryWetMidi.Interaction; using Melanchall.DryWetMidi.Tools; +using ModernWpf.Controls; using Stylet; using StyletIoC; using static Windows.Media.MediaPlaybackAutoRepeatMode; +using static GenshinLyreMidiPlayer.WPF.Core.LyrePlayer.Tranpose; using MidiFile = GenshinLyreMidiPlayer.Data.Midi.MidiFile; namespace GenshinLyreMidiPlayer.WPF.ViewModels @@ -60,8 +63,8 @@ public LyrePlayerViewModel(IContainer ioc, _player.CommandManager.NextReceived += (_, _) => Next(); _player.CommandManager.PreviousReceived += (_, _) => Previous(); - _player.CommandManager.PlayReceived += (_, _) => PlayPause(); - _player.CommandManager.PauseReceived += (_, _) => PlayPause(); + _player.CommandManager.PlayReceived += async (_, _) => await PlayPause(); + _player.CommandManager.PauseReceived += async (_, _) => await PlayPause(); } } @@ -153,27 +156,27 @@ public MidiInput? SelectedMidiInput public TimeSpan MaximumTime => Playlist.OpenedFile?.Duration ?? TimeSpan.Zero; - public void Handle(MergeNotesNotification message) + public async void Handle(MergeNotesNotification message) { if (!message.Merge) InitializeTracks(); - InitializePlayback(); + await InitializePlayback(); } - public void Handle(MidiFile file) + public async void Handle(MidiFile file) { CloseFile(); Playlist.OpenedFile = file; Playlist.History.Push(file); InitializeTracks(); - InitializePlayback(); + await InitializePlayback(); } - public void Handle(MidiTrack track) { InitializePlayback(); } + public async void Handle(MidiTrack track) { await InitializePlayback(); } - public void Handle(SettingsPageViewModel message) { InitializePlayback(); } + public async void Handle(SettingsPageViewModel message) { await InitializePlayback(); } public void UpdateButtons() { @@ -242,7 +245,7 @@ public void CloseFile() Playlist.OpenedFile = null; } - private void InitializePlayback() + private async Task InitializePlayback() { Playback?.Stop(); Playback?.Dispose(); @@ -265,6 +268,29 @@ private void InitializePlayback() }); } + // Check for notes that cannot be played even after transposing. + var outOfRange = midi.GetNotes().Where(note => + !_settings.SelectedLayout.Key.TryGetKey(ApplyNoteSettings(note.NoteNumber), out _)); + + if (outOfRange.Any()) + { + var options = new Enum[] { Up, Down }; + var exceptionDialog = new ErrorContentDialog( + new IndexOutOfRangeException( + "Some notes cannot be played by the Lyre because it is missing Sharps & Flats. " + + "This can be solved by snapping to the nearest semitone."), + options, "Ignore"); + + var result = await exceptionDialog.ShowAsync(); + + _settings.Transpose = result switch + { + ContentDialogResult.None => Ignore, + ContentDialogResult.Primary => Up, + ContentDialogResult.Secondary => Down + }; + } + Playback = midi.GetPlayback(); Playback.Speed = _settings.SelectedSpeed.Speed; @@ -292,6 +318,16 @@ private void InitializePlayback() UpdateButtons(); } + private int ApplyNoteSettings(int noteId) + { + noteId -= Settings.KeyOffset; + + if (Settings.TransposeNotes) + noteId = LyrePlayer.TransposeNote(noteId, _settings.Transpose); + + return noteId; + } + public void Previous() { if (CurrentTime > TimeSpan.FromSeconds(3)) @@ -303,7 +339,7 @@ public void Previous() Playlist.Previous(); } - public void Next() + public async void Next() { var next = Playlist.Next(); if (next is null) @@ -315,12 +351,13 @@ public void Next() Handle(next); if (Playback is not null) - PlayPause(); + await PlayPause(); } - public void PlayPause() + public async Task PlayPause() { - if (Playback is null) InitializePlayback(); + if (Playback is null) + await InitializePlayback(); if (Playback!.IsRunning) Playback.Stop(); @@ -336,17 +373,14 @@ public void PlayPause() Playback.Start(); else { - Task.Run(async () => + WindowHelper.EnsureGameOnTop(); + await Task.Delay(100); + + if (WindowHelper.IsGameFocused()) { - WindowHelper.EnsureGameOnTop(); - await Task.Delay(100); - - if (WindowHelper.IsGameFocused()) - { - Playback.PlaybackStart = Playback.GetCurrentTime(TimeSpanType.Midi); - Playback.Start(); - } - }); + Playback.PlaybackStart = Playback.GetCurrentTime(TimeSpanType.Midi); + Playback.Start(); + } } } } @@ -393,9 +427,7 @@ private void PlayNote(NoteEvent noteEvent) } var layout = _settings.SelectedLayout.Key; - var note = noteEvent.NoteNumber - Settings.KeyOffset; - if (Settings.TransposeNotes) - note = LyrePlayer.TransposeNote(note); + var note = ApplyNoteSettings(noteEvent.NoteNumber); switch (noteEvent.EventType) { diff --git a/GenshinLyreMidiPlayer.WPF/ViewModels/SettingsPageViewModel.cs b/GenshinLyreMidiPlayer.WPF/ViewModels/SettingsPageViewModel.cs index fa371ce..33b6ce0 100644 --- a/GenshinLyreMidiPlayer.WPF/ViewModels/SettingsPageViewModel.cs +++ b/GenshinLyreMidiPlayer.WPF/ViewModels/SettingsPageViewModel.cs @@ -139,6 +139,8 @@ public int KeyOffset public string UpdateString { get; set; } = string.Empty; + public LyrePlayer.Tranpose Transpose { get; set; } = LyrePlayer.Tranpose.Ignore; + public uint MergeMilliseconds { get; set; } = Settings.MergeMilliseconds; public static Version ProgramVersion => Assembly.GetExecutingAssembly().GetName().Version!;