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!;