Skip to content

Commit

Permalink
Add feature for outputting MIDI to a Piano Sheet
Browse files Browse the repository at this point in the history
  • Loading branch information
sabihoshi committed May 29, 2021
1 parent 9368ed0 commit 0e89ae9
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 9 deletions.
5 changes: 5 additions & 0 deletions GenshinLyreMidiPlayer.Data/Midi/MidiFile.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -33,6 +35,9 @@ public int Position

public TimeSpan Duration => Midi.GetDuration<MetricTimeSpan>();

public IEnumerable<Melanchall.DryWetMidi.Core.MidiFile> 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); }
}
}
4 changes: 4 additions & 0 deletions GenshinLyreMidiPlayer.WPF/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
</Style.Resources>
</Style>

<Style TargetType="ui:NumberBox">
<Setter Property="Minimum" Value="0" />
<Setter Property="SpinButtonPlacementMode" Value="Compact" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
2 changes: 1 addition & 1 deletion GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<UseWPF>true</UseWPF>
<StartupObject>GenshinLyreMidiPlayer.WPF.App</StartupObject>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Version>2.0.4</Version>
<Version>2.1.0</Version>
<ApplicationIcon>item_windsong_lyre.ico</ApplicationIcon>
<Nullable>enable</Nullable>
<RepositoryUrl>https://github.com/sabihoshi/GenshinLyreMidiPlayer</RepositoryUrl>
Expand Down
9 changes: 6 additions & 3 deletions GenshinLyreMidiPlayer.WPF/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ public MainWindowViewModel(IContainer ioc, IEventAggregator events)
{
_ioc = ioc;

SettingsView = ioc.Get<SettingsPageViewModel>();
PlaylistView = ioc.Get<PlaylistViewModel>();
PlayerView = new(ioc, SettingsView, PlaylistView);
SettingsView = ioc.Get<SettingsPageViewModel>();
PlaylistView = ioc.Get<PlaylistViewModel>();
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; }
Expand Down
98 changes: 98 additions & 0 deletions GenshinLyreMidiPlayer.WPF/ViewModels/PianoSheetViewModel.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
3 changes: 2 additions & 1 deletion GenshinLyreMidiPlayer.WPF/Views/MainWindowView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
<ui:NavigationView.MenuItems>
<ui:NavigationViewItemHeader Content="Player" />
<ui:NavigationViewItem Content="Windsong Lyre Player" Icon="60495" Tag="{Binding PlayerView}" />
<ui:NavigationViewItem Content="Playlist" Icon="57666" Tag="{Binding PlaylistView}" />
<ui:NavigationViewItem Content="Playlist" Icon="59708" Tag="{Binding PlaylistView}" />
<ui:NavigationViewItem Content="Piano Sheet" Icon="57666" Tag="{Binding PianoSheetView}" />
</ui:NavigationView.MenuItems>

<modernWpf:AnimatedContentControl Margin="10" s:View.Model="{Binding ActiveItem}" />
Expand Down
67 changes: 67 additions & 0 deletions GenshinLyreMidiPlayer.WPF/Views/PianoSheetView.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<UserControl
x:Class="GenshinLyreMidiPlayer.WPF.Views.PianoSheetView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d"

xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:s="https://github.com/canton7/Stylet"

xmlns:viewModels="clr-namespace:GenshinLyreMidiPlayer.WPF.ViewModels"
xmlns:core="clr-namespace:GenshinLyreMidiPlayer.WPF.Core"
xmlns:properties="clr-namespace:GenshinLyreMidiPlayer.Data.Properties;assembly=GenshinLyreMidiPlayer.Data"

d:DataContext="{d:DesignInstance Type=viewModels:PianoSheetViewModel}">

<UserControl.Resources>
<properties:Settings x:Key="Settings" />
</UserControl.Resources>

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<ui:SimpleStackPanel Grid.Row="0">
<TextBlock VerticalAlignment="Center">
Current song: <Run Text="{Binding PlaylistView.OpenedFile.Title, Mode=OneWay, FallbackValue=Nothing}" />
</TextBlock>

<ui:SimpleStackPanel Orientation="Horizontal">
<GroupBox Header="Delimeter">
<TextBox Text="{Binding Delimeter, UpdateSourceTrigger=PropertyChanged}" />
</GroupBox>

<GroupBox Header="Layout">
<ComboBox
ItemsSource="{x:Static core:Keyboard.LayoutNames}"
SelectedItem="{Binding SettingsPage.SelectedLayout}"
SelectedIndex="{Binding Default.SelectedLayout, Source={StaticResource Settings}}"
DisplayMemberPath="Value" />
</GroupBox>
</ui:SimpleStackPanel>

<ui:SimpleStackPanel Orientation="Horizontal">
<GroupBox Header="Split Size">
<ui:SimpleStackPanel Orientation="Horizontal">
<ui:NumberBox Header="Bars" Value="{Binding Bars}" />
<ui:NumberBox Header="Beats" Value="{Binding Beats}" />
<ui:NumberBox Header="Shorten every" ToolTip="This will divide the delimiter by this number"
Minimum="1" Value="{Binding Shorten}" />
</ui:SimpleStackPanel>
</GroupBox>
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>

<TextBox Grid.Row="1" Text="{Binding Result, Mode=OneWay}" IsReadOnly="True" Margin="0,10" />

<ui:SimpleStackPanel Grid.Row="2" Orientation="Horizontal">
<Button Content="Copy" />
<Button Content="Refresh" Command="{s:Action Update}" />
</ui:SimpleStackPanel>
</Grid>
</UserControl>
6 changes: 2 additions & 4 deletions GenshinLyreMidiPlayer.WPF/Views/SettingsPageView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@
IsOn="{Binding Default.TransposeNotes, Source={StaticResource Settings}}" />

<ui:NumberBox
Header="{Binding Key}" SpinButtonPlacementMode="Compact"
Minimum="{Binding MinOffset}" Maximum="{Binding MaxOffset}"
Value="{Binding KeyOffset}" />
Header="{Binding Key}" Value="{Binding KeyOffset}"
Minimum="{Binding MinOffset}" Maximum="{Binding MaxOffset}" />
</ui:SimpleStackPanel>

<ui:SimpleStackPanel Orientation="Horizontal">
Expand All @@ -49,7 +48,6 @@

<ui:NumberBox
Header="Tolerance (ms)" IsEnabled="{Binding MergeNotes}"
Minimum="0" SpinButtonPlacementMode="Compact"
Value="{Binding MergeMilliseconds}" />
</ui:SimpleStackPanel>

Expand Down

0 comments on commit 0e89ae9

Please sign in to comment.