Replaces old midi2piano with a new version

This commit is contained in:
CitadelStationBot
2017-06-26 22:06:15 -05:00
parent bd0f0c794b
commit d99ccf36a2
18 changed files with 4878 additions and 706 deletions

View File

@@ -1,30 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual C# Express 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "midi2piano", "midi2piano\midi2piano.csproj", "{68C84B61-F710-491C-BEE8-5E362C167897}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|Mixed Platforms = Release|Mixed Platforms
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{68C84B61-F710-491C-BEE8-5E362C167897}.Debug|Any CPU.ActiveCfg = Debug|x86
{68C84B61-F710-491C-BEE8-5E362C167897}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{68C84B61-F710-491C-BEE8-5E362C167897}.Debug|Mixed Platforms.Build.0 = Debug|x86
{68C84B61-F710-491C-BEE8-5E362C167897}.Debug|x86.ActiveCfg = Debug|x86
{68C84B61-F710-491C-BEE8-5E362C167897}.Debug|x86.Build.0 = Debug|x86
{68C84B61-F710-491C-BEE8-5E362C167897}.Release|Any CPU.ActiveCfg = Release|x86
{68C84B61-F710-491C-BEE8-5E362C167897}.Release|Mixed Platforms.ActiveCfg = Release|x86
{68C84B61-F710-491C-BEE8-5E362C167897}.Release|Mixed Platforms.Build.0 = Release|x86
{68C84B61-F710-491C-BEE8-5E362C167897}.Release|x86.ActiveCfg = Release|x86
{68C84B61-F710-491C-BEE8-5E362C167897}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -1,135 +0,0 @@
namespace midi2piano
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.OutputTxt = new System.Windows.Forms.TextBox();
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.importMIDIToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.copyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.importDlg = new System.Windows.Forms.OpenFileDialog();
this.halpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.menuStrip1.SuspendLayout();
this.SuspendLayout();
//
// OutputTxt
//
this.OutputTxt.Dock = System.Windows.Forms.DockStyle.Fill;
this.OutputTxt.Location = new System.Drawing.Point(0, 24);
this.OutputTxt.Multiline = true;
this.OutputTxt.Name = "OutputTxt";
this.OutputTxt.ReadOnly = true;
this.OutputTxt.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.OutputTxt.Size = new System.Drawing.Size(284, 240);
this.OutputTxt.TabIndex = 0;
//
// menuStrip1
//
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.fileToolStripMenuItem,
this.copyToolStripMenuItem,
this.halpToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(284, 24);
this.menuStrip1.TabIndex = 1;
this.menuStrip1.Text = "menuStrip1";
//
// fileToolStripMenuItem
//
this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.importMIDIToolStripMenuItem,
this.exitToolStripMenuItem});
this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
this.fileToolStripMenuItem.Text = "&File";
//
// importMIDIToolStripMenuItem
//
this.importMIDIToolStripMenuItem.Name = "importMIDIToolStripMenuItem";
this.importMIDIToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.I)));
this.importMIDIToolStripMenuItem.Size = new System.Drawing.Size(184, 22);
this.importMIDIToolStripMenuItem.Text = "&Import MIDI...";
this.importMIDIToolStripMenuItem.Click += new System.EventHandler(this.importMIDIToolStripMenuItem_Click);
//
// exitToolStripMenuItem
//
this.exitToolStripMenuItem.Name = "exitToolStripMenuItem";
this.exitToolStripMenuItem.Size = new System.Drawing.Size(184, 22);
this.exitToolStripMenuItem.Text = "E&xit";
this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click);
//
// copyToolStripMenuItem
//
this.copyToolStripMenuItem.Name = "copyToolStripMenuItem";
this.copyToolStripMenuItem.Size = new System.Drawing.Size(47, 20);
this.copyToolStripMenuItem.Text = "&Copy";
this.copyToolStripMenuItem.Click += new System.EventHandler(this.copyToolStripMenuItem_Click);
//
// importDlg
//
this.importDlg.Filter = "MIDI File|*.midi;*.mid";
//
// halpToolStripMenuItem
//
this.halpToolStripMenuItem.Name = "halpToolStripMenuItem";
this.halpToolStripMenuItem.Size = new System.Drawing.Size(44, 20);
this.halpToolStripMenuItem.Text = "&Halp";
this.halpToolStripMenuItem.Click += new System.EventHandler(this.halpToolStripMenuItem_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 264);
this.Controls.Add(this.OutputTxt);
this.Controls.Add(this.menuStrip1);
this.MainMenuStrip = this.menuStrip1;
this.Name = "Form1";
this.Text = "MIDI2Piano";
this.menuStrip1.ResumeLayout(false);
this.menuStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox OutputTxt;
private System.Windows.Forms.MenuStrip menuStrip1;
private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem importMIDIToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem;
private System.Windows.Forms.OpenFileDialog importDlg;
private System.Windows.Forms.ToolStripMenuItem copyToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem halpToolStripMenuItem;
}
}

View File

@@ -1,298 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Sanford.Multimedia;
using Sanford.Multimedia.Midi;
namespace midi2piano
{
public partial class Form1 : Form
{
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new Form1());
}
public Form1()
{
InitializeComponent();
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
Close();
}
struct PNote
{
public float Length;
public string Note;
public PNote(float length, string note)
{
Length = length;
Note = note;
}
public static readonly PNote Default = new PNote(0, "");
}
private void importMIDIToolStripMenuItem_Click(object sender, EventArgs e)
{
if (importDlg.ShowDialog(this)
== System.Windows.Forms.DialogResult.Cancel)
return;
List<PNote> notes = new List<PNote>();
PNote curNote = PNote.Default;
float tempo = 1;
float timeSig = 4f;
// first, we pull midi data
Sequence s = new Sequence(importDlg.FileName);
// quickly see if there's a piano track first
// and get the tempo as well
int piano = -1;
for (int it = 0; it < s.Count; it++)
{
Track t = s[it];
foreach (MidiEvent me in t.Iterator())
{
switch (me.MidiMessage.MessageType)
{
case MessageType.Channel:
{
ChannelMessage m = (ChannelMessage)me.MidiMessage;
if (m.Command == ChannelCommand.ProgramChange)
if ((GeneralMidiInstrument)m.Data1 == GeneralMidiInstrument.AcousticGrandPiano)
{
piano = it;
}
}
break;
case MessageType.Meta:
{
MetaMessage m = (MetaMessage)me.MidiMessage;
if (m.MetaType == MetaType.Tempo)
tempo = (new TempoChangeBuilder(m)).Tempo;
else if (m.MetaType == MetaType.TimeSignature)
timeSig = new TimeSignatureBuilder(m).Denominator;
}
break;
}
if (piano >= 0)
break;
}
if (piano >= 0)
break;
}
// didn't find one, so just try 0th track anyway
if (piano == -1)
piano = 0;
// now, pull all notes (and tempo)
// and make sure it's a channel that has content
for (int it = piano; it < s.Count; it++)
{
Track t = s[it];
int delta = 0;
foreach (MidiEvent me in t.Iterator())
{
delta += me.DeltaTicks;
switch (me.MidiMessage.MessageType)
{
case MessageType.Channel:
{
ChannelMessage m = (ChannelMessage)me.MidiMessage;
switch (m.Command)
{
case ChannelCommand.NoteOn:
if (curNote.Note != "")
{
curNote.Length = delta / 1000F;
delta = 0;
notes.Add(curNote);
}
curNote.Note = note2Piano(m.Data1);
break;
}
}
break;
case MessageType.Meta:
{
MetaMessage m = (MetaMessage)me.MidiMessage;
if (m.MetaType == MetaType.Tempo)
tempo = (new TempoChangeBuilder(m)).Tempo;
}
break;
}
}
// make sure we get last note
if (curNote.Note != "")
{
curNote.Length = delta / 1000F;
notes.Add(curNote);
}
// we found a track with content!
if (notes.Count > 0)
break;
}
// compress redundant accidentals/octaves
char[] notemods = new char[7];
int[] noteocts = new int[7];
for (int i = 0; i < 7; i++)
{
notemods[i] = 'n';
noteocts[i] = 3;
}
for (int i = 0; i < notes.Count; i++)
{
string noteStr = notes[i].Note;
int cur_note = noteStr[0] - 0x41;
char mod = noteStr[1];
int oct = int.Parse(noteStr.Substring(2));
noteStr = noteStr.Substring(0, 1);
if (mod != notemods[cur_note])
{
noteStr += new string(mod, 1);
notemods[cur_note] = mod;
}
if (oct != noteocts[cur_note])
{
noteStr += oct.ToString();
noteocts[cur_note] = oct;
}
notes[i] = new PNote(notes[i].Length, noteStr);
}
// now, we find what the "beat" length should be,
// by counting numbers of times for each length, and finding statistical mode
Dictionary<float, int> scores = new Dictionary<float, int>();
foreach (PNote n in notes)
{
if (n.Length != 0)
if (scores.Keys.Contains(n.Length))
scores[n.Length]++;
else
scores.Add(n.Length, 1);
}
float winner = 1;
int score = 0;
foreach (KeyValuePair<float, int> kv in scores)
{
if (kv.Value > score)
{
winner = kv.Key;
score = kv.Value;
}
}
// realign all of them to match beat length
for (int i = 0; i < notes.Count; i++)
{
notes[i] = new PNote(notes[i].Length / winner, notes[i].Note);
}
// compress chords down
for (int i = 0; i < notes.Count; i++)
{
if (notes[i].Length == 0 && i < notes.Count - 1)
{
notes[i + 1] = new PNote(notes[i + 1].Length, notes[i].Note + "-" + notes[i + 1].Note);
notes.RemoveAt(i);
i--;
}
}
// add in time
for (int i = 0; i < notes.Count; i++)
{
float len = notes[i].Length;
notes[i] = new PNote(len, notes[i].Note + (len != 1 ? "/" + (1 / len).ToString("0.##") : ""));
}
// what is the bpm, anyway?
int rpm = (int)(28800000 / tempo / winner); // 60 * 1,000,000 * .48 the .48 is because note lengths for some reason midi makes the beat note be .48 long
// now, output!
string line = "";
string output = "";
int lineCount = 1;
foreach (PNote n in notes)
{
if (line.Length + n.Note.Length + 1 > 51)
{
output += line.Substring(0, line.Length - 1) + "\r\n";
line = "";
if (lineCount == 50)
break;
lineCount++;
}
line += n.Note + ",";
}
if (line.Length > 0)
output += line.Substring(0, line.Length - 1);
OutputTxt.Text = "BPM: " + rpm.ToString() + "\r\n" + output;
OutputTxt.SelectAll();
}
public enum NoteNames
{
C = 0,
D = 2,
E = 4,
F = 5,
G = 7,
A = 9,
B = 11
}
string note2Piano(int n)
{
string name, arg, octave;
name = Enum.GetName(typeof(NoteNames), (NoteNames)(n % 12));
if (name == null)
{
name = Enum.GetName(typeof(NoteNames), (NoteNames)((n + 1) % 12));
arg = "b";
}
else
{
arg = "n";
}
octave = (n / 12 - 1).ToString();
return name + arg + octave;
}
private void copyToolStripMenuItem_Click(object sender, EventArgs e)
{
OutputTxt.SelectAll();
OutputTxt.Copy();
}
private void halpToolStripMenuItem_Click(object sender, EventArgs e)
{
MessageBox.Show(this,
"This program prefers MIDIs that have a single track, otherwise it picks the first piano track it finds, else the first track. Songs with odd tempos may have their BPM's calculated wrong.",
"Halp", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}

View File

@@ -1,126 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="importDlg.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>132, 17</value>
</metadata>
</root>

View File

@@ -1,34 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("midi2piano")]
[assembly: AssemblyProduct("midi2piano")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyCopyright("Copyright © 2011")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type. Only Windows
// assemblies support COM.
[assembly: ComVisible(false)]
// On Windows, the following GUID is for the ID of the typelib if this
// project is exposed to COM. On other platforms, it unique identifies the
// title storage container when deploying this assembly to the device.
[assembly: Guid("9752c562-edc1-40da-8fa1-619df747e0f3")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.0.0")]

View File

@@ -1,83 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>midi2piano</RootNamespace>
<AssemblyName>midi2piano</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\x86\Debug</OutputPath>
<DefineConstants>DEBUG;TRACE;WINDOWS</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<NoStdLib>true</NoStdLib>
<UseVSHostingProcess>false</UseVSHostingProcess>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\x86\Release</OutputPath>
<DefineConstants>TRACE;WINDOWS</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<NoStdLib>true</NoStdLib>
<UseVSHostingProcess>false</UseVSHostingProcess>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib">
<Private>False</Private>
</Reference>
<Reference Include="Sanford.Multimedia.Midi, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>.\Sanford.Multimedia.Midi.dll</HintPath>
</Reference>
<Reference Include="System">
<Private>False</Private>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml">
<Private>False</Private>
</Reference>
<Reference Include="System.Core">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
<Private>False</Private>
</Reference>
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
<Private>False</Private>
</Reference>
<Reference Include="System.Net">
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,31 @@
This is a remake of 2013 midi2piano tool. Previous version should be considered obsolete.
Requirements:
Python 3
Simply run midi2piano.py and choose midi file you want to convert.
The "sheet music" will be copied to the clipboard.
There are some constants defined at the top of midi2piano.py.
Change their value if needed.
LINE_LENGTH_LIM - max length of line allowed in the sheet music
LINES_LIMIT - max amount of lines allowed in the sheet music. Extra lines will be cropped.
OVERALL_IMPORT_LIM - max amount of characters allowed in the sheet music.
You can also transpose music if you need to
OCTAVE_TRANSPOSE - amount of octaves you melody will be shifted by
FLOAT_PRECISION - read comment
Additional notes:
1. Unlike previous midi2piano, this tool optimizes sheet music to fit more in less lines.
2. If two notes are less than 50 ms apart, they are chorded. BYOND works in 1/10th of a second so 50 ms is time quanta.
4. MIDI event set_tempo is NOT supported. If your MIDI file uses set_tempo to change BPM significantly, consider using some other midi file.
This tool is considered final.
Made by EditorRUS/Delta Epsilon from Animus Station, ss13.ru
Contact me in Discord if you find any major issues: DeltaEpsilon#7787

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
from midi.midi import *

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,307 @@
"""
This module allows user to convert MIDI melodies to SS13 sheet music ready
for copy-and-paste
"""
from functools import reduce
import midi as mi
import easygui as egui
import pyperclip as pclip
LINE_LENGTH_LIM = 50
LINES_LIMIT = 200
OVERALL_IMPORT_LIM = 12000
END_OF_LINE_CHAR = """
""" # BYOND can't parse \n and I am forced to define my own NEWLINE char
OCTAVE_TRANSPOSE = 0 # Change here to transpose melodies by octaves
FLOAT_PRECISION = 2 # Change here to allow more or less numbers after dot in floats
OCTAVE_KEYS = 12
HIGHEST_OCTAVE = 8
"""
class Meta():
version = 1.0
integer = 1
anti_integer = -1
maximum = 1000
epsilon = 0.51
delta_epsilon = -0.1
integral = []
tensor = [[],[],[]]
o_complexity = epsilon**2
random_variance = 0.01
"""
# UTILITY FUNCTIONS
def condition(event):
"""
This function check if given MIDI event is meaningful
"""
if event[0] == 'track_name' and event[2] == 'Drums': # Percussion
return False
if event[0] == 'note': # Only thing that matters
return True
return False
def notenum2string(num, accidentals, octaves):
"""
This function converts given notenum to SS13 note according to previous
runs expressed using _accidentals_ and _octaves_
"""
names = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
convert_table = {1:0, 3:1, 6:2, 8:3, 10:4}
inclusion_table = {0:0, 2:1, 5:2, 7:3, 9:4}
num += OCTAVE_KEYS * OCTAVE_TRANSPOSE
octave = int(num / OCTAVE_KEYS)
if octave < 1 or octave > HIGHEST_OCTAVE:
return ["", accidentals, octaves]
accidentals = accidentals.copy()
octaves = octaves.copy()
output_octaves = list(octaves)
name_indx = num % OCTAVE_KEYS
accidental = (len(names[name_indx]) == 2)
output_octaves[name_indx] = octave
add_n = False
if accidental:
accidentals[convert_table[name_indx]] = True
else:
if name_indx in inclusion_table:
add_n = accidentals[inclusion_table[name_indx]]
accidentals[inclusion_table[name_indx]] = False
return [
(
names[name_indx]+
("n" if add_n else "")+
str((octave if octave != octaves[name_indx] else ""))
),
accidentals,
output_octaves
]
def dur2mod(dur, bpm_mod=1.0):
"""
This functions returns float representation of duration ready to be
added to the note after /
"""
mod = bpm_mod / dur
mod = round(mod, FLOAT_PRECISION)
return str(mod).rstrip('0').rstrip('.')
# END OF UTILITY FUNCTIONS
# CONVERSION FUNCTIONS
def obtain_midi_file():
"""
Asks user to select MIDI and returns this file opened in binary mode for reading
"""
file = egui.fileopenbox(msg='Choose MIDI file to convert',
title='MIDI file selection',
filetypes=[['*.mid', 'MID files']])
if not file:
return None
file = open(file, mode='rb').read()
return file
def midi2score_without_ticks(midi_file):
"""
Transforms aforementioned file into a score, truncates it and returns it
"""
opus = mi.midi2opus(midi_file)
opus = mi.to_millisecs(opus)
score = mi.opus2score(opus)
return score[1:] # Ticks don't matter anymore, it is always 1000
def filter_events_from_score(score):
"""
Filters out irrevelant events and returns new score
"""
return list(map( # For each score track
lambda score_track: list(filter( # Filter irrevelant events
condition,
score_track
)),
score
))
def filter_empty_tracks(score):
"""
Filters out empty tracks and returns new score
"""
return list(filter(
lambda score_track: score_track,
score))
def filter_start_time_and_note_num(score):
"""
Recreates score with only note numbers and start time of each note and returns new score
"""
return list(map(
lambda score_track: list(map(
lambda event: [event[1], event[4]],
score_track)),
score))
def merge_events(score):
"""Merges all tracks together and returns new score"""
return list(reduce(
lambda lst1, lst2: lst1+lst2,
score))
def sort_score_by_event_times(score):
"""Sorts events by start time and returns new score"""
return list(map(
lambda index: score[index],
sorted(
list(range(len(score))),
key=lambda indx: score[indx][0])
))
def convert_into_delta_times(score):
"""
Transform start_time into delta_time and returns new score
"""
return list(map(
lambda super_event: (
[
super_event[1][0]-super_event[0][0],
super_event[0][1]
]), # [ [1, 2], [3, 4] ] -> [ [2, 2] ]
zip(score[:-1], score[1:]) # Shifted association. [1, 2, 3] -> [ (1, 2), (2, 3) ]
))+[[1000, score[-1][1]]] # Add 1 second note to the end
def perform_roundation(score):
"""
Rounds delta times to the nearest multiple of 100 ms as BYOND can't
process duration less than that and returns new score
"""
return list(map(
lambda event: [100*round(event[0]/100), event[1]],
score))
def obtain_common_duration(score):
"""
Returns the most frequent duration throughout the whole melody
"""
# Parse durations and filter out 0s
durs = list(filter(lambda x: x, list(map(lambda event: event[0], score))))
unique_durs = []
for dur in durs:
if dur not in unique_durs:
unique_durs.append(dur)
# How many such durations occur throughout the melody?
counter = [durs.count(dur) for dur in unique_durs]
highest_counter = max(counter) # Highest counter
dur_n_count = list(zip(durs, counter))
dur_n_count = list(filter(lambda e: e[1] == highest_counter, dur_n_count))
return dur_n_count[0][0] # Will be there
def reduce_score_to_chords(score):
"""
Reforms score into a chord-duration list:
[[chord_notes], duration_of_chord]
and returns it
"""
new_score = []
new_chord = [[], 0]
# [ [chord notes], duration of chord ]
for event in score:
new_chord[0].append(event[1]) # Append new note to the chord
if event[0] == 0:
continue # Add new notes to the chord until non-zero duration is hit
new_chord[1] = event[0] # This is the duration of chord
new_score.append(new_chord) # Append chord to the list
new_chord = [[], 0] # Reset the chord
return new_score
def obtain_sheet_music(score, most_frequent_dur):
"""
Returns unformated sheet music from score
"""
result = ""
octaves = [3 for i in range(12)]
accidentals = [False for i in range(7)]
for event in score:
for note_indx in range(len(event[0])):
data = notenum2string(event[0][note_indx], accidentals, octaves)
result += data[0]
accidentals = data[1]
octaves = data[2]
if note_indx != len(event[0])-1:
result += '-'
if event[1] != most_frequent_dur: # Quarters are default
result += '/'
result += dur2mod(event[1], most_frequent_dur)
result += ','
return result
def explode_sheet_music(sheet_music):
"""
Splits unformatted sheet music into formated lines of LINE_LEN_LIM
and such and returns a list of such lines
"""
split_music = sheet_music.split(',')
split_music = list(map(lambda note: note+',', split_music))
split_list = []
counter = 0
line_counter = 1
for note in split_music:
if line_counter > LINES_LIMIT-1:
break
if counter+len(note) > LINE_LENGTH_LIM-2:
last_note_num = len(split_list)-1
split_list[last_note_num] = split_list[last_note_num].rstrip(',')
split_list[last_note_num] += END_OF_LINE_CHAR
counter = 0
line_counter += 1
split_list.append(note)
counter += len(note)
return split_list
def finalize_sheet_music(split_music, most_frequent_dur):
"""
Recreates sheet music from exploded sheet music, truncates it and returns it
"""
sheet_music = ""
for note in split_music:
sheet_music += note
sheet_music = sheet_music.rstrip(',') # Trim the last ,
sheet_music = "BPM: " + str(int(60000 / most_frequent_dur)) + END_OF_LINE_CHAR + sheet_music
return sheet_music[:min(len(sheet_music), OVERALL_IMPORT_LIM)]
# END OF CONVERSION FUNCTIONS
def main_cycle():
"""
Activate the script
"""
while True:
midi_file = obtain_midi_file()
if not midi_file:
return # Cancel
score = midi2score_without_ticks(midi_file)
score = filter_events_from_score(score)
score = filter_start_time_and_note_num(score)
score = filter_empty_tracks(score)
score = merge_events(score)
score = sort_score_by_event_times(score)
score = convert_into_delta_times(score)
score = perform_roundation(score)
most_frequent_dur = obtain_common_duration(score)
score = reduce_score_to_chords(score)
sheet_music = obtain_sheet_music(score, most_frequent_dur)
split_music = explode_sheet_music(sheet_music)
sheet_music = finalize_sheet_music(split_music, most_frequent_dur)
pclip.copy(sheet_music)
main_cycle()

View File

@@ -0,0 +1,103 @@
"""
Pyperclip
A cross-platform clipboard module for Python. (only handles plain text for now)
By Al Sweigart al@inventwithpython.com
BSD License
Usage:
import pyperclip
pyperclip.copy('The text to be copied to the clipboard.')
spam = pyperclip.paste()
if not pyperclip.copy:
print("Copy functionality unavailable!")
On Windows, no additional modules are needed.
On Mac, the module uses pbcopy and pbpaste, which should come with the os.
On Linux, install xclip or xsel via package manager. For example, in Debian:
sudo apt-get install xclip
Otherwise on Linux, you will need the gtk or PyQt4 modules installed.
gtk and PyQt4 modules are not available for Python 3,
and this module does not work with PyGObject yet.
"""
__version__ = '1.5.27'
import platform
import os
import subprocess
from .clipboards import (init_osx_clipboard,
init_gtk_clipboard, init_qt_clipboard,
init_xclip_clipboard, init_xsel_clipboard,
init_klipper_clipboard, init_no_clipboard)
from .windows import init_windows_clipboard
# `import PyQt4` sys.exit()s if DISPLAY is not in the environment.
# Thus, we need to detect the presence of $DISPLAY manually
# and not load PyQt4 if it is absent.
HAS_DISPLAY = os.getenv("DISPLAY", False)
CHECK_CMD = "where" if platform.system() == "Windows" else "which"
def _executable_exists(name):
return subprocess.call([CHECK_CMD, name],
stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0
def determine_clipboard():
# Determine the OS/platform and set
# the copy() and paste() functions accordingly.
if 'cygwin' in platform.system().lower():
# FIXME: pyperclip currently does not support Cygwin,
# see https://github.com/asweigart/pyperclip/issues/55
pass
elif os.name == 'nt' or platform.system() == 'Windows':
return init_windows_clipboard()
if os.name == 'mac' or platform.system() == 'Darwin':
return init_osx_clipboard()
if HAS_DISPLAY:
# Determine which command/module is installed, if any.
try:
import gtk # check if gtk is installed
except ImportError:
pass
else:
return init_gtk_clipboard()
try:
import PyQt4 # check if PyQt4 is installed
except ImportError:
pass
else:
return init_qt_clipboard()
if _executable_exists("xclip"):
return init_xclip_clipboard()
if _executable_exists("xsel"):
return init_xsel_clipboard()
if _executable_exists("klipper") and _executable_exists("qdbus"):
return init_klipper_clipboard()
return init_no_clipboard()
def set_clipboard(clipboard):
global copy, paste
clipboard_types = {'osx': init_osx_clipboard,
'gtk': init_gtk_clipboard,
'qt': init_qt_clipboard,
'xclip': init_xclip_clipboard,
'xsel': init_xsel_clipboard,
'klipper': init_klipper_clipboard,
'windows': init_windows_clipboard,
'no': init_no_clipboard}
copy, paste = clipboard_types[clipboard]()
copy, paste = determine_clipboard()
__all__ = ["copy", "paste"]

View File

@@ -0,0 +1,134 @@
import sys
import subprocess
from .exceptions import PyperclipException
EXCEPT_MSG = """
Pyperclip could not find a copy/paste mechanism for your system.
For more information, please visit https://pyperclip.readthedocs.org """
PY2 = sys.version_info[0] == 2
text_type = unicode if PY2 else str
def init_osx_clipboard():
def copy_osx(text):
p = subprocess.Popen(['pbcopy', 'w'],
stdin=subprocess.PIPE, close_fds=True)
p.communicate(input=text.encode('utf-8'))
def paste_osx():
p = subprocess.Popen(['pbpaste', 'r'],
stdout=subprocess.PIPE, close_fds=True)
stdout, stderr = p.communicate()
return stdout.decode('utf-8')
return copy_osx, paste_osx
def init_gtk_clipboard():
import gtk
def copy_gtk(text):
global cb
cb = gtk.Clipboard()
cb.set_text(text)
cb.store()
def paste_gtk():
clipboardContents = gtk.Clipboard().wait_for_text()
# for python 2, returns None if the clipboard is blank.
if clipboardContents is None:
return ''
else:
return clipboardContents
return copy_gtk, paste_gtk
def init_qt_clipboard():
# $DISPLAY should exist
from PyQt4.QtGui import QApplication
app = QApplication([])
def copy_qt(text):
cb = app.clipboard()
cb.setText(text)
def paste_qt():
cb = app.clipboard()
return text_type(cb.text())
return copy_qt, paste_qt
def init_xclip_clipboard():
def copy_xclip(text):
p = subprocess.Popen(['xclip', '-selection', 'c'],
stdin=subprocess.PIPE, close_fds=True)
p.communicate(input=text.encode('utf-8'))
def paste_xclip():
p = subprocess.Popen(['xclip', '-selection', 'c', '-o'],
stdout=subprocess.PIPE, close_fds=True)
stdout, stderr = p.communicate()
return stdout.decode('utf-8')
return copy_xclip, paste_xclip
def init_xsel_clipboard():
def copy_xsel(text):
p = subprocess.Popen(['xsel', '-b', '-i'],
stdin=subprocess.PIPE, close_fds=True)
p.communicate(input=text.encode('utf-8'))
def paste_xsel():
p = subprocess.Popen(['xsel', '-b', '-o'],
stdout=subprocess.PIPE, close_fds=True)
stdout, stderr = p.communicate()
return stdout.decode('utf-8')
return copy_xsel, paste_xsel
def init_klipper_clipboard():
def copy_klipper(text):
p = subprocess.Popen(
['qdbus', 'org.kde.klipper', '/klipper', 'setClipboardContents',
text.encode('utf-8')],
stdin=subprocess.PIPE, close_fds=True)
p.communicate(input=None)
def paste_klipper():
p = subprocess.Popen(
['qdbus', 'org.kde.klipper', '/klipper', 'getClipboardContents'],
stdout=subprocess.PIPE, close_fds=True)
stdout, stderr = p.communicate()
# Workaround for https://bugs.kde.org/show_bug.cgi?id=342874
# TODO: https://github.com/asweigart/pyperclip/issues/43
clipboardContents = stdout.decode('utf-8')
# even if blank, Klipper will append a newline at the end
assert len(clipboardContents) > 0
# make sure that newline is there
assert clipboardContents.endswith('\n')
if clipboardContents.endswith('\n'):
clipboardContents = clipboardContents[:-1]
return clipboardContents
return copy_klipper, paste_klipper
def init_no_clipboard():
class ClipboardUnavailable(object):
def __call__(self, *args, **kwargs):
raise PyperclipException(EXCEPT_MSG)
if PY2:
def __nonzero__(self):
return False
else:
def __bool__(self):
return False
return ClipboardUnavailable(), ClipboardUnavailable()

View File

@@ -0,0 +1,11 @@
import ctypes
class PyperclipException(RuntimeError):
pass
class PyperclipWindowsException(PyperclipException):
def __init__(self, message):
message += " (%s)" % ctypes.WinError()
super(PyperclipWindowsException, self).__init__(message)

View File

@@ -0,0 +1,151 @@
"""
This module implements clipboard handling on Windows using ctypes.
"""
import time
import contextlib
import ctypes
from ctypes import c_size_t, sizeof, c_wchar_p, get_errno, c_wchar
from .exceptions import PyperclipWindowsException
class CheckedCall(object):
def __init__(self, f):
super(CheckedCall, self).__setattr__("f", f)
def __call__(self, *args):
ret = self.f(*args)
if not ret and get_errno():
raise PyperclipWindowsException("Error calling " + self.f.__name__)
return ret
def __setattr__(self, key, value):
setattr(self.f, key, value)
def init_windows_clipboard():
from ctypes.wintypes import (HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND,
HINSTANCE, HMENU, BOOL, UINT, HANDLE)
windll = ctypes.windll
safeCreateWindowExA = CheckedCall(windll.user32.CreateWindowExA)
safeCreateWindowExA.argtypes = [DWORD, LPCSTR, LPCSTR, DWORD, INT, INT,
INT, INT, HWND, HMENU, HINSTANCE, LPVOID]
safeCreateWindowExA.restype = HWND
safeDestroyWindow = CheckedCall(windll.user32.DestroyWindow)
safeDestroyWindow.argtypes = [HWND]
safeDestroyWindow.restype = BOOL
OpenClipboard = windll.user32.OpenClipboard
OpenClipboard.argtypes = [HWND]
OpenClipboard.restype = BOOL
safeCloseClipboard = CheckedCall(windll.user32.CloseClipboard)
safeCloseClipboard.argtypes = []
safeCloseClipboard.restype = BOOL
safeEmptyClipboard = CheckedCall(windll.user32.EmptyClipboard)
safeEmptyClipboard.argtypes = []
safeEmptyClipboard.restype = BOOL
safeGetClipboardData = CheckedCall(windll.user32.GetClipboardData)
safeGetClipboardData.argtypes = [UINT]
safeGetClipboardData.restype = HANDLE
safeSetClipboardData = CheckedCall(windll.user32.SetClipboardData)
safeSetClipboardData.argtypes = [UINT, HANDLE]
safeSetClipboardData.restype = HANDLE
safeGlobalAlloc = CheckedCall(windll.kernel32.GlobalAlloc)
safeGlobalAlloc.argtypes = [UINT, c_size_t]
safeGlobalAlloc.restype = HGLOBAL
safeGlobalLock = CheckedCall(windll.kernel32.GlobalLock)
safeGlobalLock.argtypes = [HGLOBAL]
safeGlobalLock.restype = LPVOID
safeGlobalUnlock = CheckedCall(windll.kernel32.GlobalUnlock)
safeGlobalUnlock.argtypes = [HGLOBAL]
safeGlobalUnlock.restype = BOOL
GMEM_MOVEABLE = 0x0002
CF_UNICODETEXT = 13
@contextlib.contextmanager
def window():
"""
Context that provides a valid Windows hwnd.
"""
# we really just need the hwnd, so setting "STATIC"
# as predefined lpClass is just fine.
hwnd = safeCreateWindowExA(0, b"STATIC", None, 0, 0, 0, 0, 0,
None, None, None, None)
try:
yield hwnd
finally:
safeDestroyWindow(hwnd)
@contextlib.contextmanager
def clipboard(hwnd):
"""
Context manager that opens the clipboard and prevents
other applications from modifying the clipboard content.
"""
# We may not get the clipboard handle immediately because
# some other application is accessing it (?)
# We try for at least 500ms to get the clipboard.
t = time.time() + 0.5
success = False
while time.time() < t:
success = OpenClipboard(hwnd)
if success:
break
time.sleep(0.01)
if not success:
raise PyperclipWindowsException("Error calling OpenClipboard")
try:
yield
finally:
safeCloseClipboard()
def copy_windows(text):
# This function is heavily based on
# http://msdn.com/ms649016#_win32_Copying_Information_to_the_Clipboard
with window() as hwnd:
# http://msdn.com/ms649048
# If an application calls OpenClipboard with hwnd set to NULL,
# EmptyClipboard sets the clipboard owner to NULL;
# this causes SetClipboardData to fail.
# => We need a valid hwnd to copy something.
with clipboard(hwnd):
safeEmptyClipboard()
if text:
# http://msdn.com/ms649051
# If the hMem parameter identifies a memory object,
# the object must have been allocated using the
# function with the GMEM_MOVEABLE flag.
count = len(text) + 1
handle = safeGlobalAlloc(GMEM_MOVEABLE,
count * sizeof(c_wchar))
locked_handle = safeGlobalLock(handle)
ctypes.memmove(c_wchar_p(locked_handle), c_wchar_p(text), count * sizeof(c_wchar))
safeGlobalUnlock(handle)
safeSetClipboardData(CF_UNICODETEXT, handle)
def paste_windows():
with clipboard(None):
handle = safeGetClipboardData(CF_UNICODETEXT)
if not handle:
# GetClipboardData may return NULL with errno == NO_ERROR
# if the clipboard is empty.
# (Also, it may return a handle to an empty buffer,
# but technically that's not empty)
return ""
return c_wchar_p(handle).value
return copy_windows, paste_windows