From 0e89ae9611ca2783503f84ad69acf6caa7275981 Mon Sep 17 00:00:00 2001 From: sabihoshi Date: Sat, 29 May 2021 21:56:33 +0800 Subject: [PATCH] Add feature for outputting MIDI to a Piano Sheet --- GenshinLyreMidiPlayer.Data/Midi/MidiFile.cs | 5 + GenshinLyreMidiPlayer.WPF/App.xaml | 4 + .../GenshinLyreMidiPlayer.WPF.csproj | 2 +- .../ViewModels/MainWindowViewModel.cs | 9 +- .../ViewModels/PianoSheetViewModel.cs | 98 +++++++++++++++++++ .../Views/MainWindowView.xaml | 3 +- .../Views/PianoSheetView.xaml | 67 +++++++++++++ .../Views/SettingsPageView.xaml | 6 +- 8 files changed, 185 insertions(+), 9 deletions(-) create mode 100644 GenshinLyreMidiPlayer.WPF/ViewModels/PianoSheetViewModel.cs create mode 100644 GenshinLyreMidiPlayer.WPF/Views/PianoSheetView.xaml diff --git a/GenshinLyreMidiPlayer.Data/Midi/MidiFile.cs b/GenshinLyreMidiPlayer.Data/Midi/MidiFile.cs index ded7d92..06eedc4 100644 --- a/GenshinLyreMidiPlayer.Data/Midi/MidiFile.cs +++ b/GenshinLyreMidiPlayer.Data/Midi/MidiFile.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using Melanchall.DryWetMidi.Core; using Melanchall.DryWetMidi.Interaction; +using Melanchall.DryWetMidi.Tools; using Stylet; using static System.IO.Path; @@ -33,6 +35,9 @@ public int Position public TimeSpan Duration => Midi.GetDuration(); + public IEnumerable Split(uint bars, uint beats, uint ticks) => + Midi.SplitByGrid(new SteppedGrid(new BarBeatTicksTimeSpan(bars, beats, ticks))); + public void InitializeMidi() { Midi = Melanchall.DryWetMidi.Core.MidiFile.Read(Path, _settings); } } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/App.xaml b/GenshinLyreMidiPlayer.WPF/App.xaml index f8be83d..69f518f 100644 --- a/GenshinLyreMidiPlayer.WPF/App.xaml +++ b/GenshinLyreMidiPlayer.WPF/App.xaml @@ -42,6 +42,10 @@ + \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj b/GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj index c241a9d..0a6415d 100644 --- a/GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj +++ b/GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj @@ -6,7 +6,7 @@ true GenshinLyreMidiPlayer.WPF.App app.manifest - 2.0.4 + 2.1.0 item_windsong_lyre.ico enable https://github.com/sabihoshi/GenshinLyreMidiPlayer diff --git a/GenshinLyreMidiPlayer.WPF/ViewModels/MainWindowViewModel.cs b/GenshinLyreMidiPlayer.WPF/ViewModels/MainWindowViewModel.cs index f7bfbde..23bef16 100644 --- a/GenshinLyreMidiPlayer.WPF/ViewModels/MainWindowViewModel.cs +++ b/GenshinLyreMidiPlayer.WPF/ViewModels/MainWindowViewModel.cs @@ -18,15 +18,18 @@ public MainWindowViewModel(IContainer ioc, IEventAggregator events) { _ioc = ioc; - SettingsView = ioc.Get(); - PlaylistView = ioc.Get(); - PlayerView = new(ioc, SettingsView, PlaylistView); + SettingsView = ioc.Get(); + PlaylistView = ioc.Get(); + PlayerView = new(ioc, SettingsView, PlaylistView); + PianoSheetView = new(SettingsView, PlaylistView); } public bool ShowUpdate => SettingsView.NeedsUpdate && ActiveItem != SettingsView; public LyrePlayerViewModel PlayerView { get; } + public PianoSheetViewModel PianoSheetView { get; } + public PlaylistViewModel PlaylistView { get; } public SettingsPageViewModel SettingsView { get; } diff --git a/GenshinLyreMidiPlayer.WPF/ViewModels/PianoSheetViewModel.cs b/GenshinLyreMidiPlayer.WPF/ViewModels/PianoSheetViewModel.cs new file mode 100644 index 0000000..3221e57 --- /dev/null +++ b/GenshinLyreMidiPlayer.WPF/ViewModels/PianoSheetViewModel.cs @@ -0,0 +1,98 @@ +using System; +using System.Linq; +using System.Text; +using GenshinLyreMidiPlayer.WPF.Core; +using Melanchall.DryWetMidi.Interaction; +using PropertyChanged; +using Stylet; + +namespace GenshinLyreMidiPlayer.WPF.ViewModels +{ + public class PianoSheetViewModel : Screen + { + private uint _bars = 1; + private uint _beats; + private uint _shorten = 1; + + public PianoSheetViewModel(SettingsPageViewModel settingsPage, + PlaylistViewModel playlistView) + { + SettingsPage = settingsPage; + PlaylistView = playlistView; + } + + [OnChangedMethod(nameof(Update))] public char Delimeter { get; set; } = '.'; + + public PlaylistViewModel PlaylistView { get; } + + public SettingsPageViewModel SettingsPage { get; } + + public string Result { get; private set; } + + [OnChangedMethod(nameof(Update))] + public uint Bars + { + get => _bars; + set => SetAndNotify(ref _bars, Math.Max(value, 0)); + } + + [OnChangedMethod(nameof(Update))] + public uint Beats + { + get => _beats; + set => SetAndNotify(ref _beats, Math.Max(value, 0)); + } + + [OnChangedMethod(nameof(Update))] + public uint Shorten + { + get => _shorten; + set => SetAndNotify(ref _shorten, Math.Max(value, 1)); + } + + protected override void OnActivate() { Update(); } + + public void Update() + { + if (PlaylistView.OpenedFile is null) + return; + + if (Bars == 0 && Beats == 0) + return; + + var layout = SettingsPage.SelectedLayout.Key; + + // Ticks is too small so it is not included + var split = PlaylistView.OpenedFile.Split(Bars, Beats, 0); + + var sb = new StringBuilder(); + foreach (var bar in split) + { + var notes = bar.GetNotes(); + if (notes.Count == 0) + continue; + + var last = 0; + + foreach (var note in notes) + { + var id = LyrePlayer.TransposeNote(note.NoteNumber); + if (layout.TryGetKey(id, out var key)) + { + var difference = note.Time - last; + var dotCount = difference / Shorten; + + sb.Append(new string(Delimeter, (int) dotCount)); + sb.Append(key.ToString().Last()); + + last = (int) note.Time; + } + } + + sb.AppendLine(); + } + + Result = sb.ToString(); + } + } +} \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/Views/MainWindowView.xaml b/GenshinLyreMidiPlayer.WPF/Views/MainWindowView.xaml index cde07b2..3d6e54b 100644 --- a/GenshinLyreMidiPlayer.WPF/Views/MainWindowView.xaml +++ b/GenshinLyreMidiPlayer.WPF/Views/MainWindowView.xaml @@ -27,7 +27,8 @@ - + + diff --git a/GenshinLyreMidiPlayer.WPF/Views/PianoSheetView.xaml b/GenshinLyreMidiPlayer.WPF/Views/PianoSheetView.xaml new file mode 100644 index 0000000..baf87b1 --- /dev/null +++ b/GenshinLyreMidiPlayer.WPF/Views/PianoSheetView.xaml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + Current song: + + + + + + + + + + + + + + + + + + + + + + + + + + +