├── ChartConverter
├── Icon.bmp
├── Icon.ico
├── runtimeconfig.template.json
├── .config
│ └── dotnet-tools.json
├── ChartConverter.csproj
└── app.manifest
├── .gitmodules
├── ChartConverterShared
├── Program.cs
├── ChartConverterHost.cs
├── Midi
│ ├── MidiOutTechnology.cs
│ ├── MidiInMessageEventArgs.cs
│ ├── MidiCommandCode.cs
│ ├── MidiController.cs
│ ├── MidiEventComparer.cs
│ ├── MetaEventType.cs
│ ├── RawMetaEvent.cs
│ ├── MidiInCapabilities.cs
│ ├── TrackSequenceNumberEvent.cs
│ ├── ChannelAfterTouchEvent.cs
│ ├── SysexEvent.cs
│ ├── SequencerSpecificEvent.cs
│ ├── KeySignatureEvent.cs
│ ├── TextEvent.cs
│ ├── TempoEvent.cs
│ ├── PitchWheelChangeEvent.cs
│ ├── SmpteOffsetEvent.cs
│ ├── ControlChangeEvent.cs
│ ├── MidiMessage.cs
│ ├── NoteOnEvent.cs
│ ├── TimeSignatureEvent.cs
│ ├── MidiOutCapabilities.cs
│ ├── PatchChangeEvent.cs
│ ├── MetaEvent.cs
│ ├── MidiOut.cs
│ ├── MidiIn.cs
│ ├── NoteEvent.cs
│ ├── MidiFile.cs
│ ├── MidiInterop.cs
│ ├── MidiEventCollection.cs
│ └── MidiEvent.cs
├── ChartConverterShared.shproj
├── ConvertOptions.cs
├── ChartUtil.cs
├── ChartConverterShared.projitems
└── MainInterface.cs
├── .github
└── workflows
│ ├── build.yml
│ └── release.yml
├── README.md
├── .gitattributes
├── .gitignore
└── ChartConverter.sln
/ChartConverter/Icon.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeoliphant/ChartConverter/HEAD/ChartConverter/Icon.bmp
--------------------------------------------------------------------------------
/ChartConverter/Icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeoliphant/ChartConverter/HEAD/ChartConverter/Icon.ico
--------------------------------------------------------------------------------
/ChartConverter/runtimeconfig.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "configProperties": {
3 | "System.Drawing.EnableUnixSupport": true
4 | }
5 | }
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Dependencies/UILayout"]
2 | path = Dependencies/UILayout
3 | url = https://github.com/mikeoliphant/UILayout
4 | [submodule "Dependencies/PsarcUtil"]
5 | path = Dependencies/PsarcUtil
6 | url = https://github.com/mikeoliphant/PsarcUtil
7 | [submodule "Dependencies/OpenSongChart"]
8 | path = Dependencies/OpenSongChart
9 | url = https://github.com/mikeoliphant/OpenSongChart
10 |
--------------------------------------------------------------------------------
/ChartConverterShared/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UILayout;
3 | using ChartConverter;
4 |
5 | namespace ChartPlayer
6 | {
7 | class Program
8 | {
9 | [STAThread]
10 | static void Main(string[] args)
11 | {
12 | using var host = new ChartConverterHost(1024, 720, isFullscreen: false);
13 |
14 | MonoGameLayout layout = new MonoGameLayout();
15 |
16 | host.StartGame(layout);
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | [workflow_dispatch, push, pull_request]
5 |
6 | jobs:
7 | build-windows:
8 | name: Build Windows
9 | runs-on: windows-latest
10 | steps:
11 | - uses: actions/checkout@v3.3.0
12 | with:
13 | submodules: recursive
14 |
15 | - name: Build
16 | run: dotnet build -c Release ChartConverter
17 |
18 | - name: Create Archive
19 | uses: actions/upload-artifact@v4
20 | with:
21 | name: ChartConverter
22 | path: ${{github.workspace}}\ChartConverter\bin\Release\net8.0
23 |
--------------------------------------------------------------------------------
/ChartConverterShared/ChartConverterHost.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Xml.Serialization;
8 | using UILayout;
9 |
10 | namespace ChartConverter
11 | {
12 | public class ChartConverterHost : MonoGameHost
13 | {
14 | public ChartConverterHost(int screenWidth, int screenHeight, bool isFullscreen)
15 | : base(screenWidth, screenHeight, isFullscreen)
16 | {
17 | UsePremultipliedAlpha = false;
18 | Window.Title = "ChartConverter v0.1.11";
19 | }
20 |
21 | protected override void LoadContent()
22 | {
23 | Layout.RootUIElement = new MainInterface();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ChartConverter/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "dotnet-mgcb": {
6 | "version": "3.8.1.303",
7 | "commands": [
8 | "mgcb"
9 | ]
10 | },
11 | "dotnet-mgcb-editor": {
12 | "version": "3.8.1.303",
13 | "commands": [
14 | "mgcb-editor"
15 | ]
16 | },
17 | "dotnet-mgcb-editor-linux": {
18 | "version": "3.8.1.303",
19 | "commands": [
20 | "mgcb-editor-linux"
21 | ]
22 | },
23 | "dotnet-mgcb-editor-windows": {
24 | "version": "3.8.1.303",
25 | "commands": [
26 | "mgcb-editor-windows"
27 | ]
28 | },
29 | "dotnet-mgcb-editor-mac": {
30 | "version": "3.8.1.303",
31 | "commands": [
32 | "mgcb-editor-mac"
33 | ]
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiOutTechnology.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NAudio;
3 |
4 | namespace Midi
5 | {
6 | ///
7 | /// Represents the different types of technology used by a MIDI out device
8 | ///
9 | /// from mmsystem.h
10 | public enum MidiOutTechnology
11 | {
12 | /// The device is a MIDI port
13 | MidiPort = 1,
14 | /// The device is a MIDI synth
15 | Synth = 2,
16 | /// The device is a square wave synth
17 | SquareWaveSynth = 3,
18 | /// The device is an FM synth
19 | FMSynth = 4,
20 | /// The device is a MIDI mapper
21 | MidiMapper = 5,
22 | /// The device is a WaveTable synth
23 | WaveTableSynth = 6,
24 | /// The device is a software synth
25 | SoftwareSynth = 7
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ChartConverterShared/ChartConverterShared.shproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | eadcd423-38ce-4d03-82a4-eb8c40c54ace
5 | 14.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ChartConverterShared/ConvertOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Xml.Serialization;
5 |
6 |
7 | namespace ChartConverter
8 | {
9 | public class ConvertOptions
10 | {
11 | public string SongOutputPath { get; set; } = "";
12 | public List PsarcFiles { get; private set; } = new();
13 | public List PsarcFolders { get; private set; } = new();
14 | public bool CopyRockBandAudio { get; set; } = true;
15 | public List RockBandFolders { get; private set; } = new();
16 | public bool ConvertPsarc { get; set; } = true;
17 | public bool ConvertRockBand { get; set; } = true;
18 |
19 | public static ConvertOptions Load(string path)
20 | {
21 | XmlSerializer serializer = new XmlSerializer(typeof(ConvertOptions));
22 |
23 | using (Stream inputStream = File.OpenRead(path))
24 | {
25 | return serializer.Deserialize(inputStream) as ConvertOptions;
26 | }
27 | }
28 |
29 | public void Save(string path)
30 | {
31 | XmlSerializer serializer = new XmlSerializer(typeof(ConvertOptions));
32 |
33 | using (Stream outputStream = File.Create(path))
34 | {
35 | serializer.Serialize(outputStream, this);
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiInMessageEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NAudio;
3 |
4 | namespace Midi
5 | {
6 | ///
7 | /// MIDI In Message Information
8 | ///
9 | public class MidiInMessageEventArgs : EventArgs
10 | {
11 | ///
12 | /// Create a new MIDI In Message EventArgs
13 | ///
14 | ///
15 | ///
16 | public MidiInMessageEventArgs(int message, int timestamp)
17 | {
18 | this.RawMessage = message;
19 | this.Timestamp = timestamp;
20 | try
21 | {
22 | this.MidiEvent = MidiEvent.FromRawMessage(message);
23 | }
24 | catch (Exception)
25 | {
26 | // don't worry too much - might be an invalid message
27 | }
28 | }
29 |
30 | ///
31 | /// The Raw message received from the MIDI In API
32 | ///
33 | public int RawMessage { get; private set; }
34 |
35 | ///
36 | /// The raw message interpreted as a MidiEvent
37 | ///
38 | public MidiEvent MidiEvent { get; private set; }
39 |
40 | ///
41 | /// The timestamp in milliseconds for this message
42 | ///
43 | public int Timestamp { get; private set; }
44 | }
45 | }
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiCommandCode.cs:
--------------------------------------------------------------------------------
1 | using NAudio;
2 |
3 | namespace Midi
4 | {
5 | ///
6 | /// MIDI command codes
7 | ///
8 | public enum MidiCommandCode : byte
9 | {
10 | /// Note Off
11 | NoteOff = 0x80,
12 | /// Note On
13 | NoteOn = 0x90,
14 | /// Key After-touch
15 | KeyAfterTouch = 0xA0,
16 | /// Control change
17 | ControlChange = 0xB0,
18 | /// Patch change
19 | PatchChange = 0xC0,
20 | /// Channel after-touch
21 | ChannelAfterTouch = 0xD0,
22 | /// Pitch wheel change
23 | PitchWheelChange = 0xE0,
24 | /// Sysex message
25 | Sysex = 0xF0,
26 | /// Eox (comes at end of a sysex message)
27 | Eox = 0xF7,
28 | /// Timing clock (used when synchronization is required)
29 | TimingClock = 0xF8,
30 | /// Start sequence
31 | StartSequence = 0xFA,
32 | /// Continue sequence
33 | ContinueSequence = 0xFB,
34 | /// Stop sequence
35 | StopSequence = 0xFC,
36 | /// Auto-Sensing
37 | AutoSensing = 0xFE,
38 | /// Meta-event
39 | MetaEvent = 0xFF,
40 | }
41 | }
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NAudio;
3 |
4 | namespace Midi
5 | {
6 | ///
7 | /// MidiController enumeration
8 | /// http://www.midi.org/techspecs/midimessages.php#3
9 | ///
10 | public enum MidiController : byte
11 | {
12 | /// Bank Select (MSB)
13 | BankSelect = 0,
14 | /// Modulation (MSB)
15 | Modulation = 1,
16 | /// Breath Controller
17 | BreathController = 2,
18 | /// Foot controller (MSB)
19 | FootController = 4,
20 | /// Main volume
21 | MainVolume = 7,
22 | /// Pan
23 | Pan = 10,
24 | /// Expression
25 | Expression = 11,
26 | /// Bank Select LSB
27 | BankSelectLsb = 32,
28 | /// Sustain
29 | Sustain = 64,
30 | /// Portamento On/Off
31 | Portamento = 65,
32 | /// Sostenuto On/Off
33 | Sostenuto = 66,
34 | /// Soft Pedal On/Off
35 | SoftPedal = 67,
36 | /// Legato Footswitch
37 | LegatoFootswitch = 68,
38 | /// Reset all controllers
39 | ResetAllControllers = 121,
40 | /// All notes off
41 | AllNotesOff = 123,
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiEventComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using System.Collections.Generic;
5 | using NAudio;
6 |
7 | namespace Midi
8 | {
9 | ///
10 | /// Utility class for comparing MidiEvent objects
11 | ///
12 | public class MidiEventComparer : IComparer
13 | {
14 | #region IComparer Members
15 |
16 | ///
17 | /// Compares two MidiEvents
18 | /// Sorts by time, with EndTrack always sorted to the end
19 | ///
20 | public int Compare(MidiEvent x, MidiEvent y)
21 | {
22 | long xTime = x.AbsoluteTime;
23 | long yTime = y.AbsoluteTime;
24 |
25 | if (xTime == yTime)
26 | {
27 | // sort meta events before note events, except end track
28 | MetaEvent xMeta = x as MetaEvent;
29 | MetaEvent yMeta = y as MetaEvent;
30 |
31 | if (xMeta != null)
32 | {
33 | if (xMeta.MetaEventType == MetaEventType.EndTrack)
34 | xTime = Int64.MaxValue;
35 | else
36 | xTime = Int64.MinValue;
37 | }
38 | if (yMeta != null)
39 | {
40 | if (yMeta.MetaEventType == MetaEventType.EndTrack)
41 | yTime = Int64.MaxValue;
42 | else
43 | yTime = Int64.MinValue;
44 | }
45 | }
46 | return xTime.CompareTo(yTime);
47 | }
48 |
49 | #endregion
50 | }
51 | }
--------------------------------------------------------------------------------
/ChartConverter/ChartConverter.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | net8.0
5 | Major
6 | false
7 | false
8 | true
9 | disable
10 |
11 |
12 | app.manifest
13 | Icon.ico
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MetaEventType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NAudio;
3 |
4 | namespace Midi
5 | {
6 | ///
7 | /// MIDI MetaEvent Type
8 | ///
9 | public enum MetaEventType : byte
10 | {
11 | /// Track sequence number
12 | TrackSequenceNumber = 0x00,
13 | /// Text event
14 | TextEvent = 0x01,
15 | /// Copyright
16 | Copyright = 0x02,
17 | /// Sequence track name
18 | SequenceTrackName = 0x03,
19 | /// Track instrument name
20 | TrackInstrumentName = 0x04,
21 | /// Lyric
22 | Lyric = 0x05,
23 | /// Marker
24 | Marker = 0x06,
25 | /// Cue point
26 | CuePoint = 0x07,
27 | /// Program (patch) name
28 | ProgramName = 0x08,
29 | /// Device (port) name
30 | DeviceName = 0x09,
31 | /// MIDI Channel (not official?)
32 | MidiChannel = 0x20,
33 | /// MIDI Port (not official?)
34 | MidiPort = 0x21,
35 | /// End track
36 | EndTrack = 0x2F,
37 | /// Set tempo
38 | SetTempo = 0x51,
39 | /// SMPTE offset
40 | SmpteOffset = 0x54,
41 | /// Time signature
42 | TimeSignature = 0x58,
43 | /// Key signature
44 | KeySignature = 0x59,
45 | /// Sequencer specific
46 | SequencerSpecific = 0x7F,
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/RawMetaEvent.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text;
3 | using NAudio;
4 |
5 | namespace Midi
6 | {
7 | ///
8 | /// Represents a MIDI meta event with raw data
9 | ///
10 | public class RawMetaEvent : MetaEvent
11 | {
12 | ///
13 | /// Raw data contained in the meta event
14 | ///
15 | public byte[] Data { get; set; }
16 |
17 | ///
18 | /// Creates a meta event with raw data
19 | ///
20 | public RawMetaEvent(MetaEventType metaEventType, long absoluteTime, byte[] data) : base(metaEventType, data?.Length ?? 0, absoluteTime)
21 | {
22 | Data = data;
23 | }
24 |
25 | ///
26 | /// Creates a deep clone of this MIDI event.
27 | ///
28 | public override MidiEvent Clone() => new RawMetaEvent(MetaEventType, AbsoluteTime, (byte[])Data?.Clone());
29 |
30 | ///
31 | /// Describes this meta event
32 | ///
33 | public override string ToString()
34 | {
35 | var sb = new StringBuilder().Append(base.ToString());
36 | foreach (var b in Data)
37 | sb.AppendFormat(" {0:X2}", b);
38 | return sb.ToString();
39 | }
40 |
41 | ///
42 | ///
43 | ///
44 | public override void Export(ref long absoluteTime, BinaryWriter writer)
45 | {
46 | base.Export(ref absoluteTime, writer);
47 | if (Data == null) return;
48 | writer.Write(Data, 0, Data.Length);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ChartConverter/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | true/pm
39 | permonitorv2,permonitor
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ChartConverter
2 |
3 | This app provides a simple interface for converting Rocksmith PSARC and Rock Band files to [OpenSongChart](https://github.com/mikeoliphant/OpenSongChart) format.
4 |
5 | It uses [PsarcUtil](https://github.com/mikeoliphant/PsarcUtil) and [RockBandUtil](https://github.com/mikeoliphant/RockBandUtil) under the hood.
6 |
7 | ## Downloading
8 |
9 | The latest version can be found in the releases section, [here](https://github.com/mikeoliphant/ChartConverter/releases/latest).
10 |
11 | ## Supported chart formats
12 |
13 | ChartConverter currently supports the following formats:
14 |
15 | - Rocksmith (original and CDLC) PSARC files for Guitar, Bass and Vocals
16 | - RockBand (Phase Shift format) for Drums, Keys and Vocals
17 |
18 | ## Where To Get Charts
19 |
20 | If you own Rocksmith, you can covert the psarc charts from the game iteself.
21 |
22 | CDLC psarc charts are available from the [Ignition song database](https://ignition4.customsforge.com/).
23 |
24 | RockBand Phase Shift songs can be found in various places:
25 |
26 | - https://rhythmverse.co/songfiles/game/ps
27 | - https://www.fretsonfire.org/forums/viewforum.php?f=5&sid=6ca91ebdb016bc22de5c4d4a3f582e64
28 |
29 | ## What Is Phase Shift format?
30 |
31 | "Phase Shift" was a PC based rhythm game that playing Rock Band charts. Currently, the format used by Phase Shift is what is supported by ChartConverter.
32 |
33 | You can tell a song is in the Phase Shift format if it has a "notes.mid" file. Support for songs with "notes.chart" will likely come soon.
34 |
35 | ## Running
36 |
37 | The following external dependencies must be installed:
38 |
39 | - libogg
40 | - libvorbis
41 | - libgdiplus (on non-Windows platforms)
42 |
43 | On Windows, extract the downladed .zip file and run "ChartConverter".
44 |
45 | On other platforms, you will likely have to make it executable first:
46 |
47 | ```
48 | chmod u+x ChartConverter
49 | ```
50 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiInCapabilities.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Runtime.InteropServices;
5 | using NAudio;
6 |
7 | namespace Midi
8 | {
9 | ///
10 | /// MIDI In Device Capabilities
11 | ///
12 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
13 | public struct MidiInCapabilities
14 | {
15 | ///
16 | /// wMid
17 | ///
18 | UInt16 manufacturerId;
19 | ///
20 | /// wPid
21 | ///
22 | UInt16 productId;
23 | ///
24 | /// vDriverVersion
25 | ///
26 | UInt32 driverVersion;
27 | ///
28 | /// Product Name
29 | ///
30 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MaxProductNameLength)]
31 | string productName;
32 | ///
33 | /// Support - Reserved
34 | ///
35 | Int32 support;
36 |
37 | private const int MaxProductNameLength = 32;
38 |
39 | ///
40 | /// Gets the manufacturer of this device
41 | ///
42 | public Manufacturers Manufacturer
43 | {
44 | get
45 | {
46 | return (Manufacturers)manufacturerId;
47 | }
48 | }
49 |
50 | ///
51 | /// Gets the product identifier (manufacturer specific)
52 | ///
53 | public int ProductId
54 | {
55 | get
56 | {
57 | return productId;
58 | }
59 | }
60 |
61 | ///
62 | /// Gets the product name
63 | ///
64 | public string ProductName
65 | {
66 | get
67 | {
68 | return productName;
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/ChartConverterShared/ChartUtil.cs:
--------------------------------------------------------------------------------
1 | using SongFormat;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Globalization;
5 | using System.Text;
6 | using System.Text.RegularExpressions;
7 | using static System.Net.Mime.MediaTypeNames;
8 |
9 | namespace ChartConverter
10 | {
11 | public class ChartUtil
12 | {
13 | public static void FormatVocals(List vocals)
14 | {
15 | int maxCharsPerLine = 40;
16 |
17 | List formattedVocals = new List();
18 |
19 | int charsInLine = 0;
20 | int lastBreakPos = 0;
21 |
22 | for (int pos = 0; pos < vocals.Count; pos++)
23 | {
24 | if (vocals[pos].Vocal.EndsWith('\n'))
25 | {
26 | charsInLine = 0;
27 | lastBreakPos = pos;
28 |
29 | continue;
30 | }
31 |
32 | charsInLine += vocals[pos].Vocal.Length;
33 |
34 | if (charsInLine > maxCharsPerLine)
35 | {
36 | pos = lastBreakPos + 1;
37 | charsInLine = 0;
38 |
39 | for (; pos < vocals.Count; pos++)
40 | {
41 | charsInLine += vocals[pos].Vocal.Length;
42 |
43 | bool isGoodBreak = (pos < (vocals.Count - 2)) && (char.IsAsciiLetterUpper(vocals[pos + 1].Vocal[0]) || ((vocals[pos + 1].TimeOffset - vocals[pos].TimeOffset) > 0.5f));
44 |
45 | if ((isGoodBreak && charsInLine > 20) || (charsInLine > maxCharsPerLine))
46 | {
47 | vocals[pos] = new SongVocal()
48 | {
49 | TimeOffset = vocals[pos].TimeOffset,
50 | Vocal = vocals[pos].Vocal + "\n"
51 | };
52 |
53 | charsInLine = 0;
54 | lastBreakPos = pos;
55 |
56 | break;
57 | }
58 | }
59 | }
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/TrackSequenceNumberEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.IO;
5 | using NAudio;
6 |
7 | namespace Midi
8 | {
9 | ///
10 | /// Represents a MIDI track sequence number event event
11 | ///
12 | public class TrackSequenceNumberEvent : MetaEvent
13 | {
14 | private ushort sequenceNumber;
15 |
16 | ///
17 | /// Creates a new track sequence number event
18 | ///
19 | public TrackSequenceNumberEvent(ushort sequenceNumber)
20 | {
21 | this.sequenceNumber = sequenceNumber;
22 | }
23 |
24 | ///
25 | /// Reads a new track sequence number event from a MIDI stream
26 | ///
27 | /// The MIDI stream
28 | /// the data length
29 | public TrackSequenceNumberEvent(BinaryReader br, int length)
30 | {
31 | // TODO: there is a form of the TrackSequenceNumberEvent that
32 | // has a length of zero
33 | if(length != 2)
34 | {
35 | throw new FormatException("Invalid sequence number length");
36 | }
37 | sequenceNumber = (ushort) ((br.ReadByte() << 8) + br.ReadByte());
38 | }
39 |
40 | ///
41 | /// Creates a deep clone of this MIDI event.
42 | ///
43 | public override MidiEvent Clone() => (TrackSequenceNumberEvent)MemberwiseClone();
44 |
45 | ///
46 | /// Describes this event
47 | ///
48 | /// String describing the event
49 | public override string ToString()
50 | {
51 | return String.Format("{0} {1}", base.ToString(), sequenceNumber);
52 | }
53 |
54 | ///
55 | /// Calls base class export first, then exports the data
56 | /// specific to this event
57 | /// MidiEvent.Export
58 | ///
59 | public override void Export(ref long absoluteTime, BinaryWriter writer)
60 | {
61 | base.Export(ref absoluteTime, writer);
62 | writer.Write((byte)((sequenceNumber >> 8) & 0xFF));
63 | writer.Write((byte)(sequenceNumber & 0xFF));
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/ChannelAfterTouchEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using NAudio;
4 |
5 | namespace Midi
6 | {
7 | ///
8 | /// Represents a MIDI Channel AfterTouch Event.
9 | ///
10 | public class ChannelAfterTouchEvent : MidiEvent
11 | {
12 | private byte afterTouchPressure;
13 |
14 | ///
15 | /// Creates a new ChannelAfterTouchEvent from raw MIDI data
16 | ///
17 | /// A binary reader
18 | public ChannelAfterTouchEvent(BinaryReader br)
19 | {
20 | afterTouchPressure = br.ReadByte();
21 | if ((afterTouchPressure & 0x80) != 0)
22 | {
23 | // TODO: might be a follow-on
24 | throw new FormatException("Invalid afterTouchPressure");
25 | }
26 | }
27 |
28 | ///
29 | /// Creates a new Channel After-Touch Event
30 | ///
31 | /// Absolute time
32 | /// Channel
33 | /// After-touch pressure
34 | public ChannelAfterTouchEvent(long absoluteTime, int channel, int afterTouchPressure)
35 | : base(absoluteTime, channel, MidiCommandCode.ChannelAfterTouch)
36 | {
37 | AfterTouchPressure = afterTouchPressure;
38 | }
39 |
40 | ///
41 | /// Calls base class export first, then exports the data
42 | /// specific to this event
43 | /// MidiEvent.Export
44 | ///
45 | public override void Export(ref long absoluteTime, BinaryWriter writer)
46 | {
47 | base.Export(ref absoluteTime, writer);
48 | writer.Write(afterTouchPressure);
49 | }
50 |
51 | ///
52 | /// The aftertouch pressure value
53 | ///
54 | public int AfterTouchPressure
55 | {
56 | get { return afterTouchPressure; }
57 | set
58 | {
59 | if (value < 0 || value > 127)
60 | {
61 | throw new ArgumentOutOfRangeException("value", "After touch pressure must be in the range 0-127");
62 | }
63 | afterTouchPressure = (byte) value;
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/SysexEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using System.Collections.Generic;
5 | using NAudio;
6 |
7 | namespace Midi
8 | {
9 | ///
10 | /// Represents a MIDI sysex message
11 | ///
12 | public class SysexEvent : MidiEvent
13 | {
14 | private byte[] data;
15 | //private int length;
16 |
17 | ///
18 | /// Reads a sysex message from a MIDI stream
19 | ///
20 | /// Stream of MIDI data
21 | /// a new sysex message
22 | public static SysexEvent ReadSysexEvent(BinaryReader br)
23 | {
24 | SysexEvent se = new SysexEvent();
25 | //se.length = ReadVarInt(br);
26 | //se.data = br.ReadBytes(se.length);
27 |
28 | List sysexData = new List();
29 | bool loop = true;
30 | while(loop)
31 | {
32 | byte b = br.ReadByte();
33 | if(b == 0xF7)
34 | {
35 | loop = false;
36 | }
37 | else
38 | {
39 | sysexData.Add(b);
40 | }
41 | }
42 |
43 | se.data = sysexData.ToArray();
44 |
45 | return se;
46 | }
47 |
48 | ///
49 | /// Creates a deep clone of this MIDI event.
50 | ///
51 | public override MidiEvent Clone() => new SysexEvent { data = (byte[])data?.Clone() };
52 |
53 | ///
54 | /// Describes this sysex message
55 | ///
56 | /// A string describing the sysex message
57 | public override string ToString()
58 | {
59 | StringBuilder sb = new StringBuilder();
60 | foreach (byte b in data)
61 | {
62 | sb.AppendFormat("{0:X2} ", b);
63 | }
64 | return String.Format("{0} Sysex: {1} bytes\r\n{2}",this.AbsoluteTime,data.Length,sb.ToString());
65 | }
66 |
67 | ///
68 | /// Calls base class export first, then exports the data
69 | /// specific to this event
70 | /// MidiEvent.Export
71 | ///
72 | public override void Export(ref long absoluteTime, BinaryWriter writer)
73 | {
74 | base.Export(ref absoluteTime, writer);
75 | //WriteVarInt(writer,length);
76 | //writer.Write(data, 0, data.Length);
77 | writer.Write(data, 0, data.Length);
78 | writer.Write((byte)0xF7);
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/SequencerSpecificEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using NAudio;
5 |
6 | namespace Midi
7 | {
8 | ///
9 | /// Represents a Sequencer Specific event
10 | ///
11 | public class SequencerSpecificEvent : MetaEvent
12 | {
13 | private byte[] data;
14 |
15 | ///
16 | /// Reads a new sequencer specific event from a MIDI stream
17 | ///
18 | /// The MIDI stream
19 | /// The data length
20 | public SequencerSpecificEvent(BinaryReader br, int length)
21 | {
22 | this.data = br.ReadBytes(length);
23 | }
24 |
25 | ///
26 | /// Creates a new Sequencer Specific event
27 | ///
28 | /// The sequencer specific data
29 | /// Absolute time of this event
30 | public SequencerSpecificEvent(byte[] data, long absoluteTime)
31 | : base(MetaEventType.SequencerSpecific, data.Length, absoluteTime)
32 | {
33 | this.data = data;
34 | }
35 |
36 | ///
37 | /// Creates a deep clone of this MIDI event.
38 | ///
39 | public override MidiEvent Clone() => new SequencerSpecificEvent((byte[])data.Clone(), AbsoluteTime);
40 |
41 | ///
42 | /// The contents of this sequencer specific
43 | ///
44 | public byte[] Data
45 | {
46 | get
47 | {
48 | return this.data;
49 | }
50 | set
51 | {
52 | this.data = value;
53 | this.metaDataLength = this.data.Length;
54 | }
55 | }
56 |
57 | ///
58 | /// Describes this MIDI text event
59 | ///
60 | /// A string describing this event
61 | public override string ToString()
62 | {
63 | StringBuilder sb = new StringBuilder();
64 | sb.Append(base.ToString());
65 | sb.Append(" ");
66 | foreach (var b in data)
67 | {
68 | sb.AppendFormat("{0:X2} ", b);
69 | }
70 | sb.Length--;
71 | return sb.ToString();
72 | }
73 |
74 | ///
75 | /// Calls base class export first, then exports the data
76 | /// specific to this event
77 | /// MidiEvent.Export
78 | ///
79 | public override void Export(ref long absoluteTime, BinaryWriter writer)
80 | {
81 | base.Export(ref absoluteTime, writer);
82 | writer.Write(data);
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/KeySignatureEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace Midi
5 | {
6 | ///
7 | /// Represents a MIDI key signature event event
8 | ///
9 | public class KeySignatureEvent : MetaEvent
10 | {
11 | private byte sharpsFlats;
12 | private byte majorMinor;
13 |
14 | ///
15 | /// Reads a new track sequence number event from a MIDI stream
16 | ///
17 | /// The MIDI stream
18 | /// the data length
19 | public KeySignatureEvent(BinaryReader br, int length)
20 | {
21 | if (length != 2)
22 | {
23 | throw new FormatException("Invalid key signature length");
24 | }
25 | sharpsFlats = br.ReadByte(); // sf=sharps/flats (-7=7 flats, 0=key of C,7=7 sharps)
26 | majorMinor = br.ReadByte(); // mi=major/minor (0=major, 1=minor) }
27 | }
28 |
29 | ///
30 | /// Creates a new Key signature event with the specified data
31 | ///
32 | public KeySignatureEvent(int sharpsFlats, int majorMinor, long absoluteTime)
33 | : base(MetaEventType.KeySignature, 2, absoluteTime)
34 | {
35 | this.sharpsFlats = (byte) sharpsFlats;
36 | this.majorMinor = (byte) majorMinor;
37 | }
38 |
39 | ///
40 | /// Creates a deep clone of this MIDI event.
41 | ///
42 | public override MidiEvent Clone() => (KeySignatureEvent)MemberwiseClone();
43 |
44 | ///
45 | /// Number of sharps or flats
46 | ///
47 | public int SharpsFlats
48 | {
49 | get
50 | {
51 | return sharpsFlats;
52 | }
53 | }
54 |
55 | ///
56 | /// Major or Minor key
57 | ///
58 | public int MajorMinor
59 | {
60 | get
61 | {
62 | return majorMinor;
63 | }
64 | }
65 |
66 | ///
67 | /// Describes this event
68 | ///
69 | /// String describing the event
70 | public override string ToString()
71 | {
72 | return String.Format("{0} {1} {2}", base.ToString(), sharpsFlats, majorMinor);
73 | }
74 |
75 | ///
76 | /// Calls base class export first, then exports the data
77 | /// specific to this event
78 | /// MidiEvent.Export
79 | ///
80 | public override void Export(ref long absoluteTime, BinaryWriter writer)
81 | {
82 | base.Export(ref absoluteTime, writer);
83 | writer.Write(sharpsFlats);
84 | writer.Write(majorMinor);
85 | }
86 | }
87 | }
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/TextEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using NAudio;
5 |
6 | namespace Midi
7 | {
8 | ///
9 | /// Represents a MIDI text event
10 | ///
11 | public class TextEvent : MetaEvent
12 | {
13 | private byte[] data;
14 |
15 | ///
16 | /// Reads a new text event from a MIDI stream
17 | ///
18 | /// The MIDI stream
19 | /// The data length
20 | public TextEvent(BinaryReader br,int length)
21 | {
22 | data = br.ReadBytes(length);
23 | }
24 |
25 | ///
26 | /// Creates a new TextEvent
27 | ///
28 | /// The text in this type
29 | /// MetaEvent type (must be one that is
30 | /// associated with text data)
31 | /// Absolute time of this event
32 | public TextEvent(string text, MetaEventType metaEventType, long absoluteTime)
33 | : base(metaEventType, text.Length, absoluteTime)
34 | {
35 | Text = text;
36 | }
37 |
38 | ///
39 | /// Creates a deep clone of this MIDI event.
40 | ///
41 | public override MidiEvent Clone() => (TextEvent)MemberwiseClone();
42 |
43 | ///
44 | /// The contents of this text event
45 | ///
46 | public string Text
47 | {
48 | get
49 | {
50 | Encoding byteEncoding = NAudio.Utils.ByteEncoding.Instance;
51 | return byteEncoding.GetString(data);
52 | }
53 | set
54 | {
55 | Encoding byteEncoding = NAudio.Utils.ByteEncoding.Instance;
56 | data = byteEncoding.GetBytes(value);
57 | metaDataLength = data.Length;
58 | }
59 | }
60 |
61 | ///
62 | /// The raw contents of this text event
63 | ///
64 | public byte[] Data
65 | {
66 | get
67 | {
68 | return data;
69 | }
70 | set
71 | {
72 | data = value;
73 | metaDataLength = data.Length;
74 | }
75 | }
76 |
77 | ///
78 | /// Describes this MIDI text event
79 | ///
80 | /// A string describing this event
81 | public override string ToString()
82 | {
83 | return String.Format("{0} {1}",base.ToString(),Text);
84 | }
85 |
86 | ///
87 | /// Calls base class export first, then exports the data
88 | /// specific to this event
89 | /// MidiEvent.Export
90 | ///
91 | public override void Export(ref long absoluteTime, BinaryWriter writer)
92 | {
93 | base.Export(ref absoluteTime, writer);
94 | writer.Write(data);
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/TempoEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using NAudio;
5 |
6 | namespace Midi
7 | {
8 | ///
9 | /// Represents a MIDI tempo event
10 | ///
11 | public class TempoEvent : MetaEvent
12 | {
13 | private int microsecondsPerQuarterNote;
14 |
15 | ///
16 | /// Reads a new tempo event from a MIDI stream
17 | ///
18 | /// The MIDI stream
19 | /// the data length
20 | public TempoEvent(BinaryReader br,int length)
21 | {
22 | if(length != 3)
23 | {
24 | throw new FormatException("Invalid tempo length");
25 | }
26 | microsecondsPerQuarterNote = (br.ReadByte() << 16) + (br.ReadByte() << 8) + br.ReadByte();
27 | }
28 |
29 | ///
30 | /// Creates a new tempo event with specified settings
31 | ///
32 | /// Microseconds per quarter note
33 | /// Absolute time
34 | public TempoEvent(int microsecondsPerQuarterNote, long absoluteTime)
35 | : base(MetaEventType.SetTempo,3,absoluteTime)
36 | {
37 | this.microsecondsPerQuarterNote = microsecondsPerQuarterNote;
38 | }
39 |
40 | ///
41 | /// Creates a deep clone of this MIDI event.
42 | ///
43 | public override MidiEvent Clone() => (TempoEvent)MemberwiseClone();
44 |
45 | ///
46 | /// Describes this tempo event
47 | ///
48 | /// String describing the tempo event
49 | public override string ToString()
50 | {
51 | return String.Format("{0} {2}bpm ({1})",
52 | base.ToString(),
53 | microsecondsPerQuarterNote,
54 | (60000000 / microsecondsPerQuarterNote));
55 | }
56 |
57 | ///
58 | /// Microseconds per quarter note
59 | ///
60 | public int MicrosecondsPerQuarterNote
61 | {
62 | get { return microsecondsPerQuarterNote; }
63 | set { microsecondsPerQuarterNote = value; }
64 | }
65 |
66 | ///
67 | /// Tempo
68 | ///
69 | public double Tempo
70 | {
71 | get { return (60000000.0/microsecondsPerQuarterNote); }
72 | set { microsecondsPerQuarterNote = (int) (60000000.0/value); }
73 | }
74 |
75 | ///
76 | /// Calls base class export first, then exports the data
77 | /// specific to this event
78 | /// MidiEvent.Export
79 | ///
80 | public override void Export(ref long absoluteTime, BinaryWriter writer)
81 | {
82 | base.Export(ref absoluteTime, writer);
83 | writer.Write((byte) ((microsecondsPerQuarterNote >> 16) & 0xFF));
84 | writer.Write((byte) ((microsecondsPerQuarterNote >> 8) & 0xFF));
85 | writer.Write((byte) (microsecondsPerQuarterNote & 0xFF));
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/ChartConverterShared/ChartConverterShared.projitems:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
5 | true
6 | eadcd423-38ce-4d03-82a4-eb8c40c54ace
7 |
8 |
9 | PsarcConverterShared
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/PitchWheelChangeEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using NAudio;
5 |
6 | namespace Midi
7 | {
8 | ///
9 | /// Represents a MIDI pitch wheel change event
10 | ///
11 | public class PitchWheelChangeEvent : MidiEvent
12 | {
13 | private int pitch;
14 |
15 | ///
16 | /// Reads a pitch wheel change event from a MIDI stream
17 | ///
18 | /// The MIDI stream to read from
19 | public PitchWheelChangeEvent(BinaryReader br)
20 | {
21 | byte b1 = br.ReadByte();
22 | byte b2 = br.ReadByte();
23 | if((b1 & 0x80) != 0)
24 | {
25 | // TODO: might be a follow-on
26 | throw new FormatException("Invalid pitchwheelchange byte 1");
27 | }
28 | if((b2 & 0x80) != 0)
29 | {
30 | throw new FormatException("Invalid pitchwheelchange byte 2");
31 | }
32 |
33 | pitch = b1 + (b2 << 7); // 0x2000 is normal
34 | }
35 |
36 | ///
37 | /// Creates a new pitch wheel change event
38 | ///
39 | /// Absolute event time
40 | /// Channel
41 | /// Pitch wheel value
42 | public PitchWheelChangeEvent(long absoluteTime, int channel, int pitchWheel)
43 | : base(absoluteTime, channel, MidiCommandCode.PitchWheelChange)
44 | {
45 | Pitch = pitchWheel;
46 | }
47 |
48 | ///
49 | /// Describes this pitch wheel change event
50 | ///
51 | /// String describing this pitch wheel change event
52 | public override string ToString()
53 | {
54 | return String.Format("{0} Pitch {1} ({2})",
55 | base.ToString(),
56 | this.pitch,
57 | this.pitch - 0x2000);
58 | }
59 |
60 | ///
61 | /// Pitch Wheel Value 0 is minimum, 0x2000 (8192) is default, 0x3FFF (16383) is maximum
62 | ///
63 | public int Pitch
64 | {
65 | get
66 | {
67 | return pitch;
68 | }
69 | set
70 | {
71 | if (value < 0 || value >= 0x4000)
72 | {
73 | throw new ArgumentOutOfRangeException("value", "Pitch value must be in the range 0 - 0x3FFF");
74 | }
75 | pitch = value;
76 | }
77 | }
78 |
79 | ///
80 | /// Gets a short message
81 | ///
82 | /// Integer to sent as short message
83 | public override int GetAsShortMessage()
84 | {
85 | return base.GetAsShortMessage() + ((pitch & 0x7f) << 8) + (((pitch >> 7) & 0x7f) << 16);
86 | }
87 |
88 | ///
89 | /// Calls base class export first, then exports the data
90 | /// specific to this event
91 | /// MidiEvent.Export
92 | ///
93 | public override void Export(ref long absoluteTime, BinaryWriter writer)
94 | {
95 | base.Export(ref absoluteTime, writer);
96 | writer.Write((byte)(pitch & 0x7f));
97 | writer.Write((byte)((pitch >> 7) & 0x7f));
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/SmpteOffsetEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.IO;
5 | using NAudio;
6 |
7 | namespace Midi
8 | {
9 | class SmpteOffsetEvent : MetaEvent
10 | {
11 | private byte hours;
12 | private byte minutes;
13 | private byte seconds;
14 | private byte frames;
15 | private byte subFrames; // 100ths of a frame
16 |
17 | ///
18 | /// Creates a new time signature event
19 | ///
20 | public SmpteOffsetEvent(byte hours, byte minutes, byte seconds, byte frames, byte subFrames)
21 | {
22 | this.hours = hours;
23 | this.minutes = minutes;
24 | this.seconds = seconds;
25 | this.frames = frames;
26 | this.subFrames = subFrames;
27 | }
28 |
29 | ///
30 | /// Reads a new time signature event from a MIDI stream
31 | ///
32 | /// The MIDI stream
33 | /// The data length
34 | public SmpteOffsetEvent(BinaryReader br,int length)
35 | {
36 | if(length != 5)
37 | {
38 | throw new FormatException(String.Format("Invalid SMPTE Offset length: Got {0}, expected 5",length));
39 | }
40 | hours = br.ReadByte();
41 | minutes = br.ReadByte();
42 | seconds = br.ReadByte();
43 | frames = br.ReadByte();
44 | subFrames = br.ReadByte();
45 | }
46 |
47 | ///
48 | /// Creates a deep clone of this MIDI event.
49 | ///
50 | public override MidiEvent Clone() => (SmpteOffsetEvent)MemberwiseClone();
51 |
52 | ///
53 | /// Hours
54 | ///
55 | public int Hours
56 | {
57 | get { return hours; }
58 | }
59 |
60 | ///
61 | /// Minutes
62 | ///
63 | public int Minutes
64 | {
65 | get { return minutes; }
66 | }
67 |
68 | ///
69 | /// Seconds
70 | ///
71 | public int Seconds
72 | {
73 | get { return seconds; }
74 | }
75 |
76 | ///
77 | /// Frames
78 | ///
79 | public int Frames
80 | {
81 | get { return frames; }
82 | }
83 |
84 | ///
85 | /// SubFrames
86 | ///
87 | public int SubFrames
88 | {
89 | get { return subFrames; }
90 | }
91 |
92 |
93 | ///
94 | /// Describes this time signature event
95 | ///
96 | /// A string describing this event
97 | public override string ToString()
98 | {
99 | return String.Format("{0} {1}:{2}:{3}:{4}:{5}",
100 | base.ToString(),hours,minutes,seconds,frames,subFrames);
101 | }
102 |
103 | ///
104 | /// Calls base class export first, then exports the data
105 | /// specific to this event
106 | /// MidiEvent.Export
107 | ///
108 | public override void Export(ref long absoluteTime, BinaryWriter writer)
109 | {
110 | base.Export(ref absoluteTime, writer);
111 | writer.Write(hours);
112 | writer.Write(minutes);
113 | writer.Write(seconds);
114 | writer.Write(frames);
115 | writer.Write(subFrames);
116 | }
117 | }
118 | }
119 |
120 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/ControlChangeEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using NAudio;
5 |
6 | namespace Midi
7 | {
8 | ///
9 | /// Represents a MIDI control change event
10 | ///
11 | public class ControlChangeEvent : MidiEvent
12 | {
13 | private MidiController controller;
14 | private byte controllerValue;
15 |
16 | ///
17 | /// Reads a control change event from a MIDI stream
18 | ///
19 | /// Binary reader on the MIDI stream
20 | public ControlChangeEvent(BinaryReader br)
21 | {
22 | byte c = br.ReadByte();
23 | controllerValue = br.ReadByte();
24 | if((c & 0x80) != 0)
25 | {
26 | // TODO: might be a follow-on
27 | throw new InvalidDataException("Invalid controller");
28 | }
29 | controller = (MidiController) c;
30 | if((controllerValue & 0x80) != 0)
31 | {
32 | throw new InvalidDataException(String.Format("Invalid controllerValue {0} for controller {1}, Pos 0x{2:X}", controllerValue, controller, br.BaseStream.Position));
33 | }
34 | }
35 |
36 | ///
37 | /// Creates a control change event
38 | ///
39 | /// Time
40 | /// MIDI Channel Number
41 | /// The MIDI Controller
42 | /// Controller value
43 | public ControlChangeEvent(long absoluteTime, int channel, MidiController controller, int controllerValue)
44 | : base(absoluteTime,channel,MidiCommandCode.ControlChange)
45 | {
46 | this.Controller = controller;
47 | this.ControllerValue = controllerValue;
48 | }
49 |
50 | ///
51 | /// Describes this control change event
52 | ///
53 | /// A string describing this event
54 | public override string ToString()
55 | {
56 | return String.Format("{0} Controller {1} Value {2}",
57 | base.ToString(),
58 | this.controller,
59 | this.controllerValue);
60 | }
61 |
62 | ///
63 | ///
64 | ///
65 | public override int GetAsShortMessage()
66 | {
67 | byte c = (byte)controller;
68 | return base.GetAsShortMessage() + (c << 8) + (controllerValue << 16);
69 | }
70 |
71 | ///
72 | /// Calls base class export first, then exports the data
73 | /// specific to this event
74 | /// MidiEvent.Export
75 | ///
76 | public override void Export(ref long absoluteTime, BinaryWriter writer)
77 | {
78 | base.Export(ref absoluteTime, writer);
79 | writer.Write((byte)controller);
80 | writer.Write((byte)controllerValue);
81 | }
82 |
83 | ///
84 | /// The controller number
85 | ///
86 | public MidiController Controller
87 | {
88 | get
89 | {
90 | return controller;
91 | }
92 | set
93 | {
94 | if ((int) value < 0 || (int) value > 127)
95 | {
96 | throw new ArgumentOutOfRangeException("value", "Controller number must be in the range 0-127");
97 | }
98 | controller = value;
99 | }
100 | }
101 |
102 | ///
103 | /// The controller value
104 | ///
105 | public int ControllerValue
106 | {
107 | get
108 | {
109 | return controllerValue;
110 | }
111 | set
112 | {
113 | if (value < 0 || value > 127)
114 | {
115 | throw new ArgumentOutOfRangeException("value", "Controller Value must be in the range 0-127");
116 | }
117 | controllerValue = (byte) value;
118 | }
119 | }
120 | }
121 | }
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | create_release:
8 | name: Create release
9 | runs-on: ubuntu-latest
10 | outputs:
11 | upload_url: ${{steps.create_release.outputs.upload_url}}
12 | steps:
13 | - name: Check out repository
14 | uses: actions/checkout@v3.3.0
15 | with:
16 | submodules: recursive
17 |
18 | - name: Create release
19 | id: create_release
20 | uses: actions/create-release@v1
21 | env:
22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
23 | with:
24 | draft: true
25 | tag_name: ${{github.ref}}
26 | release_name: Release ${{github.ref}}
27 |
28 | build-windows:
29 | name: Build Windows
30 | needs: create_release
31 | runs-on: windows-latest
32 | steps:
33 | - uses: actions/checkout@v3.3.0
34 | with:
35 | submodules: recursive
36 |
37 | - name: Build
38 | run: dotnet build -c Release ChartConverter
39 |
40 | - name: Add Win-x64 Archive
41 | working-directory: ${{github.workspace}}
42 | run: |
43 | mkdir win-x64-build
44 | move ChartConverter\bin\Release\net8.0 win-x64-build\ChartConverter
45 | Compress-Archive -Path win-x64-build\* -Destination ChartConverterWindows.zip
46 |
47 | - name: Upload Release Asset
48 | uses: actions/upload-release-asset@v1
49 | env:
50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51 | with:
52 | upload_url: ${{ needs.create_release.outputs.upload_url }}
53 | asset_path: ./ChartConverterWindows.zip
54 | asset_name: ChartConverterWindows.zip
55 | asset_content_type: application/zip
56 |
57 | - name: Publish Linux-x64
58 | working-directory: ${{github.workspace}}
59 | run: dotnet publish .\ChartConverter\ChartConverter.csproj --runtime linux-x64 -p:PublishSingleFile=true --self-contained true
60 |
61 | - name: Add Linux-x64 Archive
62 | working-directory: ${{github.workspace}}
63 | run: |
64 | mkdir linux-x64-build
65 | move ChartConverter\bin\Release\net8.0\linux-x64\publish linux-x64-build\ChartConverter
66 | Compress-Archive -Path linux-x64-build\* -Destination ChartConverterLinux.zip
67 |
68 | - name: Upload Linux-x64 Asset
69 | uses: actions/upload-release-asset@v1
70 | env:
71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72 | with:
73 | upload_url: ${{ needs.create_release.outputs.upload_url }}
74 | asset_path: ./ChartConverterLinux.zip
75 | asset_name: ChartConverterLinux.zip
76 | asset_content_type: application/zip
77 |
78 | - name: Publish OSX-x64
79 | working-directory: ${{github.workspace}}
80 | run: dotnet publish .\ChartConverter\ChartConverter.csproj --runtime osx-x64 -p:PublishSingleFile=true --self-contained true
81 |
82 | - name: Add OSX-x64 Archive
83 | working-directory: ${{github.workspace}}
84 | run: |
85 | mkdir osx-x64-build
86 | move ChartConverter\bin\Release\net8.0\osx-x64\publish osx-x64-build\ChartConverter
87 | Compress-Archive -Path osx-x64-build\* -Destination ChartConverterMacX64.zip
88 |
89 | - name: Upload OSX-x64 Asset
90 | uses: actions/upload-release-asset@v1
91 | env:
92 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
93 | with:
94 | upload_url: ${{ needs.create_release.outputs.upload_url }}
95 | asset_path: ./ChartConverterMacX64.zip
96 | asset_name: ChartConverterMacX64.zip
97 | asset_content_type: application/zip
98 |
99 | - name: Publish OSX-arm64
100 | working-directory: ${{github.workspace}}
101 | run: dotnet publish .\ChartConverter\ChartConverter.csproj --runtime osx-arm64 -p:PublishSingleFile=true --self-contained true
102 |
103 | - name: Add OSX-arm64 Archive
104 | working-directory: ${{github.workspace}}
105 | run: |
106 | mkdir osx-arm64-build
107 | move ChartConverter\bin\Release\net8.0\osx-arm64\publish osx-arm64-build\ChartConverter
108 | Compress-Archive -Path osx-arm64-build\* -Destination ChartConverterMacArm64.zip
109 |
110 | - name: Upload OSX-arm64 Asset
111 | uses: actions/upload-release-asset@v1
112 | env:
113 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
114 | with:
115 | upload_url: ${{ needs.create_release.outputs.upload_url }}
116 | asset_path: ./ChartConverterMacArm64.zip
117 | asset_name: ChartConverterMacArm64.zip
118 | asset_content_type: application/zip
119 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiMessage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NAudio;
3 |
4 | namespace Midi
5 | {
6 | ///
7 | /// Represents a MIDI message
8 | ///
9 | public class MidiMessage
10 | {
11 | private int rawData;
12 |
13 | ///
14 | /// Creates a new MIDI message
15 | ///
16 | /// Status
17 | /// Data parameter 1
18 | /// Data parameter 2
19 | public MidiMessage(int status, int data1, int data2)
20 | {
21 | rawData = status + (data1 << 8) + (data2 << 16);
22 | }
23 |
24 | ///
25 | /// Creates a new MIDI message from a raw message
26 | ///
27 | /// A packed MIDI message from an MMIO function
28 | public MidiMessage(int rawData)
29 | {
30 | this.rawData = rawData;
31 | }
32 |
33 | ///
34 | /// Creates a Note On message
35 | ///
36 | /// Note number (0 to 127)
37 | /// Volume (0 to 127)
38 | /// MIDI channel (1 to 16)
39 | /// A new MidiMessage object
40 | public static MidiMessage StartNote(int note, int volume, int channel)
41 | {
42 | ValidateNoteParameters(note, volume, channel);
43 | return new MidiMessage((int)MidiCommandCode.NoteOn + channel - 1, note, volume);
44 | }
45 |
46 | private static void ValidateNoteParameters(int note, int volume, int channel)
47 | {
48 | ValidateChannel(channel);
49 | if (note < 0 || note > 127)
50 | {
51 | throw new ArgumentOutOfRangeException("note", "Note number must be in the range 0-127");
52 | }
53 | if (volume < 0 || volume > 127)
54 | {
55 | throw new ArgumentOutOfRangeException("volume", "Velocity must be in the range 0-127");
56 | }
57 | }
58 |
59 | private static void ValidateChannel(int channel)
60 | {
61 | if ((channel < 1) || (channel > 16))
62 | {
63 | throw new ArgumentOutOfRangeException("channel", channel,
64 | String.Format("Channel must be 1-16 (Got {0})", channel));
65 | }
66 | }
67 |
68 | ///
69 | /// Creates a Note Off message
70 | ///
71 | /// Note number
72 | /// Volume
73 | /// MIDI channel (1-16)
74 | /// A new MidiMessage object
75 | public static MidiMessage StopNote(int note, int volume, int channel)
76 | {
77 | ValidateNoteParameters(note, volume, channel);
78 | return new MidiMessage((int)MidiCommandCode.NoteOff + channel - 1, note, volume);
79 | }
80 |
81 | ///
82 | /// Creates a patch change message
83 | ///
84 | /// The patch number
85 | /// The MIDI channel number (1-16)
86 | /// A new MidiMessageObject
87 | public static MidiMessage ChangePatch(int patch, int channel)
88 | {
89 | ValidateChannel(channel);
90 | return new MidiMessage((int)MidiCommandCode.PatchChange + channel - 1, patch, 0);
91 | }
92 |
93 | ///
94 | /// Creates a Control Change message
95 | ///
96 | /// The controller number to change
97 | /// The value to set the controller to
98 | /// The MIDI channel number (1-16)
99 | /// A new MidiMessageObject
100 | public static MidiMessage ChangeControl(int controller, int value, int channel)
101 | {
102 | ValidateChannel(channel);
103 | return new MidiMessage((int)MidiCommandCode.ControlChange + channel - 1, controller, value);
104 | }
105 |
106 | ///
107 | /// Returns the raw MIDI message data
108 | ///
109 | public int RawData
110 | {
111 | get
112 | {
113 | return rawData;
114 | }
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/NoteOnEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using NAudio;
5 |
6 | namespace Midi
7 | {
8 | ///
9 | /// Represents a MIDI note on event
10 | ///
11 | public class NoteOnEvent : NoteEvent
12 | {
13 | private NoteEvent offEvent;
14 |
15 | ///
16 | /// Reads a new Note On event from a stream of MIDI data
17 | ///
18 | /// Binary reader on the MIDI data stream
19 | public NoteOnEvent(BinaryReader br)
20 | : base(br)
21 | {
22 | }
23 |
24 | ///
25 | /// Creates a NoteOn event with specified parameters
26 | ///
27 | /// Absolute time of this event
28 | /// MIDI channel number
29 | /// MIDI note number
30 | /// MIDI note velocity
31 | /// MIDI note duration
32 | public NoteOnEvent(long absoluteTime, int channel, int noteNumber,
33 | int velocity, int duration)
34 | : base(absoluteTime, channel, MidiCommandCode.NoteOn, noteNumber, velocity)
35 | {
36 | this.OffEvent = new NoteEvent(absoluteTime, channel, MidiCommandCode.NoteOff,
37 | noteNumber, 0);
38 | NoteLength = duration;
39 | }
40 |
41 | ///
42 | /// Creates a deep clone of this MIDI event.
43 | ///
44 | public override MidiEvent Clone() => new NoteOnEvent(AbsoluteTime, Channel, NoteNumber, Velocity, NoteLength);
45 |
46 | ///
47 | /// The associated Note off event
48 | ///
49 | public NoteEvent OffEvent
50 | {
51 | get
52 | {
53 | return offEvent;
54 | }
55 | set
56 | {
57 | if (!MidiEvent.IsNoteOff(value))
58 | {
59 | throw new ArgumentException("OffEvent must be a valid MIDI note off event");
60 | }
61 | if (value.NoteNumber != this.NoteNumber)
62 | {
63 | throw new ArgumentException("Note Off Event must be for the same note number");
64 | }
65 | if (value.Channel != this.Channel)
66 | {
67 | throw new ArgumentException("Note Off Event must be for the same channel");
68 | }
69 | offEvent = value;
70 |
71 | }
72 | }
73 |
74 | ///
75 | /// Get or set the Note Number, updating the off event at the same time
76 | ///
77 | public override int NoteNumber
78 | {
79 | get
80 | {
81 | return base.NoteNumber;
82 | }
83 | set
84 | {
85 | base.NoteNumber = value;
86 | if (OffEvent != null)
87 | {
88 | OffEvent.NoteNumber = NoteNumber;
89 | }
90 | }
91 | }
92 |
93 | ///
94 | /// Get or set the channel, updating the off event at the same time
95 | ///
96 | public override int Channel
97 | {
98 | get
99 | {
100 | return base.Channel;
101 | }
102 | set
103 | {
104 | base.Channel = value;
105 | if (OffEvent != null)
106 | {
107 | OffEvent.Channel = Channel;
108 | }
109 | }
110 | }
111 |
112 | ///
113 | /// The duration of this note
114 | ///
115 | ///
116 | /// There must be a note off event
117 | ///
118 | public int NoteLength
119 | {
120 | get
121 | {
122 | return (int)(offEvent.AbsoluteTime - this.AbsoluteTime);
123 | }
124 | set
125 | {
126 | if (value < 0)
127 | {
128 | throw new ArgumentException("NoteLength must be 0 or greater");
129 | }
130 | offEvent.AbsoluteTime = this.AbsoluteTime + value;
131 | }
132 | }
133 |
134 | ///
135 | /// Calls base class export first, then exports the data
136 | /// specific to this event
137 | /// MidiEvent.Export
138 | ///
139 | public override string ToString()
140 | {
141 | if ((this.Velocity == 0) && (OffEvent == null))
142 | {
143 | return String.Format("{0} (Note Off)",
144 | base.ToString());
145 | }
146 | return String.Format("{0} Len: {1}",
147 | base.ToString(),
148 | (this.OffEvent == null) ? "?" : this.NoteLength.ToString());
149 | }
150 | }
151 | }
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/TimeSignatureEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using NAudio;
5 |
6 | namespace Midi
7 | {
8 | ///
9 | /// Represents a MIDI time signature event
10 | ///
11 | public class TimeSignatureEvent : MetaEvent
12 | {
13 | private byte numerator;
14 | private byte denominator;
15 | private byte ticksInMetronomeClick;
16 | private byte no32ndNotesInQuarterNote;
17 |
18 | ///
19 | /// Reads a new time signature event from a MIDI stream
20 | ///
21 | /// The MIDI stream
22 | /// The data length
23 | public TimeSignatureEvent(BinaryReader br,int length)
24 | {
25 | if(length != 4)
26 | {
27 | throw new FormatException(String.Format("Invalid time signature length: Got {0}, expected 4", length));
28 | }
29 | numerator = br.ReadByte();
30 | denominator = br.ReadByte(); //2=quarter, 3=eigth etc
31 | ticksInMetronomeClick = br.ReadByte();
32 | no32ndNotesInQuarterNote = br.ReadByte();
33 | }
34 |
35 | ///
36 | /// Creates a new TimeSignatureEvent
37 | ///
38 | /// Time at which to create this event
39 | /// Numerator
40 | /// Denominator
41 | /// Ticks in Metronome Click
42 | /// No of 32nd Notes in Quarter Click
43 | public TimeSignatureEvent(long absoluteTime, int numerator, int denominator, int ticksInMetronomeClick, int no32ndNotesInQuarterNote)
44 | :
45 | base(MetaEventType.TimeSignature, 4, absoluteTime)
46 | {
47 | this.numerator = (byte)numerator;
48 | this.denominator = (byte)denominator;
49 | this.ticksInMetronomeClick = (byte)ticksInMetronomeClick;
50 | this.no32ndNotesInQuarterNote = (byte)no32ndNotesInQuarterNote;
51 | }
52 |
53 | ///
54 | /// Creates a deep clone of this MIDI event.
55 | ///
56 | public override MidiEvent Clone() => (TimeSignatureEvent)MemberwiseClone();
57 |
58 | ///
59 | /// Numerator (number of beats in a bar)
60 | ///
61 | public int Numerator
62 | {
63 | get { return numerator; }
64 | }
65 |
66 | ///
67 | /// Denominator (Beat unit),
68 | /// 1 means 2, 2 means 4 (crochet), 3 means 8 (quaver), 4 means 16 and 5 means 32
69 | ///
70 | public int Denominator
71 | {
72 | get { return denominator; }
73 | }
74 |
75 | ///
76 | /// Ticks in a metronome click
77 | ///
78 | public int TicksInMetronomeClick
79 | {
80 | get { return ticksInMetronomeClick; }
81 | }
82 |
83 | ///
84 | /// Number of 32nd notes in a quarter note
85 | ///
86 | public int No32ndNotesInQuarterNote
87 | {
88 | get { return no32ndNotesInQuarterNote; }
89 | }
90 |
91 | ///
92 | /// The time signature
93 | ///
94 | public string TimeSignature
95 | {
96 | get
97 | {
98 | string den = String.Format("Unknown ({0})",denominator);
99 | switch(denominator)
100 | {
101 | case 1:
102 | den = "2";
103 | break;
104 | case 2:
105 | den = "4";
106 | break;
107 | case 3:
108 | den = "8";
109 | break;
110 | case 4:
111 | den = "16";
112 | break;
113 | case 5:
114 | den = "32";
115 | break;
116 | }
117 | return String.Format("{0}/{1}",numerator,den);
118 | }
119 | }
120 |
121 | ///
122 | /// Describes this time signature event
123 | ///
124 | /// A string describing this event
125 | public override string ToString()
126 | {
127 | return String.Format("{0} {1} TicksInClick:{2} 32ndsInQuarterNote:{3}",
128 | base.ToString(),TimeSignature,ticksInMetronomeClick,no32ndNotesInQuarterNote);
129 | }
130 |
131 | ///
132 | /// Calls base class export first, then exports the data
133 | /// specific to this event
134 | /// MidiEvent.Export
135 | ///
136 | public override void Export(ref long absoluteTime, BinaryWriter writer)
137 | {
138 | base.Export(ref absoluteTime, writer);
139 | writer.Write(numerator);
140 | writer.Write(denominator);
141 | writer.Write(ticksInMetronomeClick);
142 | writer.Write(no32ndNotesInQuarterNote);
143 | }
144 | }
145 | }
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiOutCapabilities.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using NAudio;
4 |
5 | namespace Midi
6 | {
7 | ///
8 | /// class representing the capabilities of a MIDI out device
9 | /// MIDIOUTCAPS: http://msdn.microsoft.com/en-us/library/dd798467%28VS.85%29.aspx
10 | ///
11 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
12 | public struct MidiOutCapabilities
13 | {
14 | Int16 manufacturerId;
15 | Int16 productId;
16 | int driverVersion;
17 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MaxProductNameLength)]
18 | string productName;
19 | Int16 wTechnology;
20 | Int16 wVoices;
21 | Int16 wNotes;
22 | UInt16 wChannelMask;
23 | MidiOutCapabilityFlags dwSupport;
24 |
25 | const int MaxProductNameLength = 32; // max product name length (including NULL)
26 |
27 | [Flags]
28 | enum MidiOutCapabilityFlags
29 | {
30 | ///
31 | /// MIDICAPS_VOLUME
32 | ///
33 | Volume = 1,
34 | ///
35 | /// separate left-right volume control
36 | /// MIDICAPS_LRVOLUME
37 | ///
38 | LeftRightVolume = 2,
39 | ///
40 | /// MIDICAPS_CACHE
41 | ///
42 | PatchCaching = 4,
43 | ///
44 | /// MIDICAPS_STREAM
45 | /// driver supports midiStreamOut directly
46 | ///
47 | Stream = 8,
48 | }
49 |
50 | ///
51 | /// Gets the manufacturer of this device
52 | ///
53 | public Manufacturers Manufacturer
54 | {
55 | get
56 | {
57 | return (Manufacturers)manufacturerId;
58 | }
59 | }
60 |
61 | ///
62 | /// Gets the product identifier (manufacturer specific)
63 | ///
64 | public short ProductId
65 | {
66 | get
67 | {
68 | return productId;
69 | }
70 | }
71 |
72 | ///
73 | /// Gets the product name
74 | ///
75 | public String ProductName
76 | {
77 | get
78 | {
79 | return productName;
80 | }
81 | }
82 |
83 | ///
84 | /// Returns the number of supported voices
85 | ///
86 | public int Voices
87 | {
88 | get
89 | {
90 | return wVoices;
91 | }
92 | }
93 |
94 | ///
95 | /// Gets the polyphony of the device
96 | ///
97 | public int Notes
98 | {
99 | get
100 | {
101 | return wNotes;
102 | }
103 | }
104 |
105 | ///
106 | /// Returns true if the device supports all channels
107 | ///
108 | public bool SupportsAllChannels
109 | {
110 | get
111 | {
112 | return wChannelMask == 0xFFFF;
113 | }
114 | }
115 |
116 | ///
117 | /// Queries whether a particular channel is supported
118 | ///
119 | /// Channel number to test
120 | /// True if the channel is supported
121 | public bool SupportsChannel(int channel)
122 | {
123 | return (wChannelMask & (1 << (channel - 1))) > 0;
124 | }
125 |
126 | ///
127 | /// Returns true if the device supports patch caching
128 | ///
129 | public bool SupportsPatchCaching
130 | {
131 | get
132 | {
133 | return (dwSupport & MidiOutCapabilityFlags.PatchCaching) != 0;
134 | }
135 | }
136 |
137 | ///
138 | /// Returns true if the device supports separate left and right volume
139 | ///
140 | public bool SupportsSeparateLeftAndRightVolume
141 | {
142 | get
143 | {
144 | return (dwSupport & MidiOutCapabilityFlags.LeftRightVolume) != 0;
145 | }
146 | }
147 |
148 | ///
149 | /// Returns true if the device supports MIDI stream out
150 | ///
151 | public bool SupportsMidiStreamOut
152 | {
153 | get
154 | {
155 | return (dwSupport & MidiOutCapabilityFlags.Stream) != 0;
156 | }
157 | }
158 |
159 | ///
160 | /// Returns true if the device supports volume control
161 | ///
162 | public bool SupportsVolumeControl
163 | {
164 | get
165 | {
166 | return (dwSupport & MidiOutCapabilityFlags.Volume) != 0;
167 | }
168 | }
169 |
170 | ///
171 | /// Returns the type of technology used by this MIDI out device
172 | ///
173 | public MidiOutTechnology Technology
174 | {
175 | get
176 | {
177 | return (MidiOutTechnology)wTechnology;
178 | }
179 | }
180 |
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/PatchChangeEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using NAudio;
5 |
6 | namespace Midi
7 | {
8 | ///
9 | /// Represents a MIDI patch change event
10 | ///
11 | public class PatchChangeEvent : MidiEvent
12 | {
13 | private byte patch;
14 |
15 | ///
16 | /// Gets the default MIDI instrument names
17 | ///
18 | public static string GetPatchName(int patchNumber)
19 | {
20 | return patchNames[patchNumber];
21 | }
22 |
23 | // TODO: localize
24 | private static readonly string[] patchNames = new string[]
25 | {
26 | "Acoustic Grand","Bright Acoustic","Electric Grand","Honky-Tonk","Electric Piano 1","Electric Piano 2","Harpsichord","Clav",
27 | "Celesta","Glockenspiel","Music Box","Vibraphone","Marimba","Xylophone","Tubular Bells","Dulcimer",
28 | "Drawbar Organ","Percussive Organ","Rock Organ","Church Organ","Reed Organ","Accoridan","Harmonica","Tango Accordian",
29 | "Acoustic Guitar(nylon)","Acoustic Guitar(steel)","Electric Guitar(jazz)","Electric Guitar(clean)","Electric Guitar(muted)","Overdriven Guitar","Distortion Guitar","Guitar Harmonics",
30 | "Acoustic Bass","Electric Bass(finger)","Electric Bass(pick)","Fretless Bass","Slap Bass 1","Slap Bass 2","Synth Bass 1","Synth Bass 2",
31 | "Violin","Viola","Cello","Contrabass","Tremolo Strings","Pizzicato Strings","Orchestral Strings","Timpani",
32 | "String Ensemble 1","String Ensemble 2","SynthStrings 1","SynthStrings 2","Choir Aahs","Voice Oohs","Synth Voice","Orchestra Hit",
33 | "Trumpet","Trombone","Tuba","Muted Trumpet","French Horn","Brass Section","SynthBrass 1","SynthBrass 2",
34 | "Soprano Sax","Alto Sax","Tenor Sax","Baritone Sax","Oboe","English Horn","Bassoon","Clarinet",
35 | "Piccolo","Flute","Recorder","Pan Flute","Blown Bottle","Skakuhachi","Whistle","Ocarina",
36 | "Lead 1 (square)","Lead 2 (sawtooth)","Lead 3 (calliope)","Lead 4 (chiff)","Lead 5 (charang)","Lead 6 (voice)","Lead 7 (fifths)","Lead 8 (bass+lead)",
37 | "Pad 1 (new age)","Pad 2 (warm)","Pad 3 (polysynth)","Pad 4 (choir)","Pad 5 (bowed)","Pad 6 (metallic)","Pad 7 (halo)","Pad 8 (sweep)",
38 | "FX 1 (rain)","FX 2 (soundtrack)","FX 3 (crystal)","FX 4 (atmosphere)","FX 5 (brightness)","FX 6 (goblins)","FX 7 (echoes)","FX 8 (sci-fi)",
39 | "Sitar","Banjo","Shamisen","Koto","Kalimba","Bagpipe","Fiddle","Shanai",
40 | "Tinkle Bell","Agogo","Steel Drums","Woodblock","Taiko Drum","Melodic Tom","Synth Drum","Reverse Cymbal",
41 | "Guitar Fret Noise","Breath Noise","Seashore","Bird Tweet","Telephone Ring","Helicopter","Applause","Gunshot"
42 | };
43 |
44 | ///
45 | /// Reads a new patch change event from a MIDI stream
46 | ///
47 | /// Binary reader for the MIDI stream
48 | public PatchChangeEvent(BinaryReader br)
49 | {
50 | patch = br.ReadByte();
51 | if ((patch & 0x80) != 0)
52 | {
53 | // TODO: might be a follow-on
54 | throw new FormatException("Invalid patch");
55 | }
56 | }
57 |
58 | ///
59 | /// Creates a new patch change event
60 | ///
61 | /// Time of the event
62 | /// Channel number
63 | /// Patch number
64 | public PatchChangeEvent(long absoluteTime, int channel, int patchNumber)
65 | : base(absoluteTime, channel, MidiCommandCode.PatchChange)
66 | {
67 | this.Patch = patchNumber;
68 | }
69 |
70 | ///
71 | /// The Patch Number
72 | ///
73 | public int Patch
74 | {
75 | get
76 | {
77 | return patch;
78 | }
79 | set
80 | {
81 | if (value < 0 || value > 127)
82 | {
83 | throw new ArgumentOutOfRangeException("value", "Patch number must be in the range 0-127");
84 | }
85 | patch = (byte)value;
86 | }
87 | }
88 |
89 | ///
90 | /// Describes this patch change event
91 | ///
92 | /// String describing the patch change event
93 | public override string ToString()
94 | {
95 | return String.Format("{0} {1}",
96 | base.ToString(),
97 | GetPatchName(this.patch));
98 | }
99 |
100 | ///
101 | /// Gets as a short message for sending with the midiOutShortMsg API
102 | ///
103 | /// short message
104 | public override int GetAsShortMessage()
105 | {
106 | return base.GetAsShortMessage() + (this.patch << 8);
107 | }
108 |
109 | ///
110 | /// Calls base class export first, then exports the data
111 | /// specific to this event
112 | /// MidiEvent.Export
113 | ///
114 | public override void Export(ref long absoluteTime, BinaryWriter writer)
115 | {
116 | base.Export(ref absoluteTime, writer);
117 | writer.Write(patch);
118 | }
119 | }
120 | }
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MetaEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using NAudio;
5 |
6 | namespace Midi
7 | {
8 | ///
9 | /// Represents a MIDI meta event
10 | ///
11 | public class MetaEvent : MidiEvent
12 | {
13 | private MetaEventType metaEvent;
14 | internal int metaDataLength;
15 |
16 | ///
17 | /// Gets the type of this meta event
18 | ///
19 | public MetaEventType MetaEventType
20 | {
21 | get
22 | {
23 | return metaEvent;
24 | }
25 | }
26 |
27 | ///
28 | /// Empty constructor
29 | ///
30 | protected MetaEvent()
31 | {
32 | }
33 |
34 | ///
35 | /// Custom constructor for use by derived types, who will manage the data themselves
36 | ///
37 | /// Meta event type
38 | /// Meta data length
39 | /// Absolute time
40 | public MetaEvent(MetaEventType metaEventType, int metaDataLength, long absoluteTime)
41 | : base(absoluteTime,1,MidiCommandCode.MetaEvent)
42 | {
43 | this.metaEvent = metaEventType;
44 | this.metaDataLength = metaDataLength;
45 | }
46 |
47 | ///
48 | /// Creates a deep clone of this MIDI event.
49 | ///
50 | public override MidiEvent Clone() => new MetaEvent(metaEvent, metaDataLength, AbsoluteTime);
51 |
52 | ///
53 | /// Reads a meta-event from a stream
54 | ///
55 | /// A binary reader based on the stream of MIDI data
56 | /// A new MetaEvent object
57 | public static MetaEvent ReadMetaEvent(BinaryReader br)
58 | {
59 | MetaEventType metaEvent = (MetaEventType) br.ReadByte();
60 | int length = ReadVarInt(br);
61 |
62 | MetaEvent me = new MetaEvent();
63 |
64 | switch (metaEvent)
65 | {
66 | case MetaEventType.TrackSequenceNumber: // Sets the track's sequence number.
67 | me = new TrackSequenceNumberEvent(br, length);
68 | break;
69 | case MetaEventType.TextEvent: // Text event
70 | case MetaEventType.Copyright: // Copyright
71 | case MetaEventType.SequenceTrackName: // Sequence / Track Name
72 | case MetaEventType.TrackInstrumentName: // Track instrument name
73 | case MetaEventType.Lyric: // lyric
74 | case MetaEventType.Marker: // marker
75 | case MetaEventType.CuePoint: // cue point
76 | case MetaEventType.ProgramName:
77 | case MetaEventType.DeviceName:
78 | me = new TextEvent(br, length);
79 | break;
80 | case MetaEventType.EndTrack: // This event must come at the end of each track
81 | if (length != 0)
82 | {
83 | throw new FormatException("End track length");
84 | }
85 | break;
86 | case MetaEventType.SetTempo: // Set tempo
87 | me = new TempoEvent(br, length);
88 | break;
89 | case MetaEventType.TimeSignature: // Time signature
90 | me = new TimeSignatureEvent(br, length);
91 | break;
92 | case MetaEventType.KeySignature: // Key signature
93 | me = new KeySignatureEvent(br, length);
94 | break;
95 | case MetaEventType.SequencerSpecific: // Sequencer specific information
96 | me = new SequencerSpecificEvent(br, length);
97 | break;
98 | case MetaEventType.SmpteOffset:
99 | me = new SmpteOffsetEvent(br, length);
100 | break;
101 | default:
102 | //System.Windows.Forms.MessageBox.Show(String.Format("Unsupported MetaEvent {0} length {1} pos {2}",metaEvent,length,br.BaseStream.Position));
103 | var data = br.ReadBytes(length);
104 | if (data.Length != length)
105 | {
106 | throw new FormatException("Failed to read metaevent's data fully");
107 | }
108 | return new RawMetaEvent(metaEvent, default(long), data);
109 | }
110 |
111 | me.metaEvent = metaEvent;
112 | me.metaDataLength = length;
113 |
114 | return me;
115 | }
116 |
117 | ///
118 | /// Describes this meta event
119 | ///
120 | public override string ToString()
121 | {
122 | return $"{AbsoluteTime} {metaEvent}";
123 | }
124 |
125 | ///
126 | ///
127 | ///
128 | public override void Export(ref long absoluteTime, BinaryWriter writer)
129 | {
130 | base.Export(ref absoluteTime, writer);
131 | writer.Write((byte)metaEvent);
132 | WriteVarInt(writer, metaDataLength);
133 | }
134 | }
135 | }
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiOut.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using NAudio.Wave;
4 | using NAudio;
5 |
6 | namespace Midi
7 | {
8 | ///
9 | /// Represents a MIDI out device
10 | ///
11 | public class MidiOut : IDisposable
12 | {
13 | private IntPtr hMidiOut = IntPtr.Zero;
14 | private bool disposed = false;
15 | MidiInterop.MidiOutCallback callback;
16 |
17 | ///
18 | /// Gets the number of MIDI devices available in the system
19 | ///
20 | public static int NumberOfDevices
21 | {
22 | get
23 | {
24 | return MidiInterop.midiOutGetNumDevs();
25 | }
26 | }
27 |
28 | ///
29 | /// Gets the MIDI Out device info
30 | ///
31 | public static MidiOutCapabilities DeviceInfo(int midiOutDeviceNumber)
32 | {
33 | MidiOutCapabilities caps = new MidiOutCapabilities();
34 | int structSize = Marshal.SizeOf(caps);
35 | MmException.Try(MidiInterop.midiOutGetDevCaps((IntPtr)midiOutDeviceNumber, out caps, structSize), "midiOutGetDevCaps");
36 | return caps;
37 | }
38 |
39 |
40 | ///
41 | /// Opens a specified MIDI out device
42 | ///
43 | /// The device number
44 | public MidiOut(int deviceNo)
45 | {
46 | this.callback = new MidiInterop.MidiOutCallback(Callback);
47 | MmException.Try(MidiInterop.midiOutOpen(out hMidiOut, (IntPtr)deviceNo, callback, IntPtr.Zero, MidiInterop.CALLBACK_FUNCTION), "midiOutOpen");
48 | }
49 |
50 | ///
51 | /// Closes this MIDI out device
52 | ///
53 | public void Close()
54 | {
55 | Dispose();
56 | }
57 |
58 | ///
59 | /// Closes this MIDI out device
60 | ///
61 | public void Dispose()
62 | {
63 | GC.KeepAlive(callback);
64 | Dispose(true);
65 | GC.SuppressFinalize(this);
66 | }
67 |
68 | ///
69 | /// Gets or sets the volume for this MIDI out device
70 | ///
71 | public int Volume
72 | {
73 | // TODO: Volume can be accessed by device ID
74 | get
75 | {
76 | int volume = 0;
77 | MmException.Try(MidiInterop.midiOutGetVolume(hMidiOut,ref volume),"midiOutGetVolume");
78 | return volume;
79 | }
80 | set
81 | {
82 | MmException.Try(MidiInterop.midiOutSetVolume(hMidiOut,value),"midiOutSetVolume");
83 | }
84 | }
85 |
86 | ///
87 | /// Resets the MIDI out device
88 | ///
89 | public void Reset()
90 | {
91 | MmException.Try(MidiInterop.midiOutReset(hMidiOut),"midiOutReset");
92 | }
93 |
94 | ///
95 | /// Sends a MIDI out message
96 | ///
97 | /// Message
98 | /// Parameter 1
99 | /// Parameter 2
100 | public void SendDriverMessage(int message, int param1, int param2)
101 | {
102 | MmException.Try(MidiInterop.midiOutMessage(hMidiOut,message,(IntPtr)param1,(IntPtr)param2),"midiOutMessage");
103 | }
104 |
105 | ///
106 | /// Sends a MIDI message to the MIDI out device
107 | ///
108 | /// The message to send
109 | public void Send(int message)
110 | {
111 | MmException.Try(MidiInterop.midiOutShortMsg(hMidiOut,message),"midiOutShortMsg");
112 | }
113 |
114 | ///
115 | /// Closes the MIDI out device
116 | ///
117 | /// True if called from Dispose
118 | protected virtual void Dispose(bool disposing)
119 | {
120 | if(!this.disposed)
121 | {
122 | //if(disposing) Components.Dispose();
123 | MidiInterop.midiOutClose(hMidiOut);
124 | }
125 | disposed = true;
126 | }
127 |
128 | private void Callback(IntPtr midiInHandle, MidiInterop.MidiOutMessage message, IntPtr userData, IntPtr messageParameter1, IntPtr messageParameter2)
129 | {
130 | }
131 |
132 | ///
133 | /// Send a long message, for example sysex.
134 | ///
135 | /// The bytes to send.
136 | public void SendBuffer(byte[] byteBuffer)
137 | {
138 | var header = new MidiInterop.MIDIHDR();
139 | header.lpData = Marshal.AllocHGlobal(byteBuffer.Length);
140 | Marshal.Copy(byteBuffer, 0, header.lpData, byteBuffer.Length);
141 |
142 | header.dwBufferLength = byteBuffer.Length;
143 | header.dwBytesRecorded = byteBuffer.Length;
144 | int size = Marshal.SizeOf(header);
145 | MidiInterop.midiOutPrepareHeader(this.hMidiOut, ref header, size);
146 | var errcode = MidiInterop.midiOutLongMsg(this.hMidiOut, ref header, size);
147 | if (errcode != MmResult.NoError)
148 | {
149 | MidiInterop.midiOutUnprepareHeader(this.hMidiOut, ref header, size);
150 | }
151 | Marshal.FreeHGlobal(header.lpData);
152 | }
153 |
154 | ///
155 | /// Cleanup
156 | ///
157 | ~MidiOut()
158 | {
159 | System.Diagnostics.Debug.Assert(false);
160 | Dispose(false);
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiIn.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using NAudio;
4 |
5 | namespace Midi
6 | {
7 | ///
8 | /// Represents a MIDI in device
9 | ///
10 | public class MidiIn : IDisposable
11 | {
12 | private IntPtr hMidiIn = IntPtr.Zero;
13 | private bool disposed = false;
14 | private MidiInterop.MidiInCallback callback;
15 |
16 | ///
17 | /// Called when a MIDI message is received
18 | ///
19 | public event EventHandler MessageReceived;
20 |
21 | ///
22 | /// An invalid MIDI message
23 | ///
24 | public event EventHandler ErrorReceived;
25 |
26 | ///
27 | /// Gets the number of MIDI input devices available in the system
28 | ///
29 | public static int NumberOfDevices
30 | {
31 | get
32 | {
33 | return MidiInterop.midiInGetNumDevs();
34 | }
35 | }
36 |
37 | ///
38 | /// Opens a specified MIDI in device
39 | ///
40 | /// The device number
41 | public MidiIn(int deviceNo)
42 | {
43 | this.callback = new MidiInterop.MidiInCallback(Callback);
44 | MmException.Try(MidiInterop.midiInOpen(out hMidiIn, (IntPtr) deviceNo,this.callback,IntPtr.Zero,MidiInterop.CALLBACK_FUNCTION),"midiInOpen");
45 | }
46 |
47 | ///
48 | /// Closes this MIDI in device
49 | ///
50 | public void Close()
51 | {
52 | Dispose();
53 | }
54 |
55 | ///
56 | /// Closes this MIDI in device
57 | ///
58 | public void Dispose()
59 | {
60 | GC.KeepAlive(callback);
61 | Dispose(true);
62 | GC.SuppressFinalize(this);
63 | }
64 |
65 | ///
66 | /// Start the MIDI in device
67 | ///
68 | public void Start()
69 | {
70 | MmException.Try(MidiInterop.midiInStart(hMidiIn), "midiInStart");
71 | }
72 |
73 | ///
74 | /// Stop the MIDI in device
75 | ///
76 | public void Stop()
77 | {
78 | MmException.Try(MidiInterop.midiInStop(hMidiIn), "midiInStop");
79 | }
80 |
81 | ///
82 | /// Reset the MIDI in device
83 | ///
84 | public void Reset()
85 | {
86 | MmException.Try(MidiInterop.midiInReset(hMidiIn), "midiInReset");
87 | }
88 |
89 | private void Callback(IntPtr midiInHandle, MidiInterop.MidiInMessage message, IntPtr userData, IntPtr messageParameter1, IntPtr messageParameter2)
90 | {
91 | switch(message)
92 | {
93 | case MidiInterop.MidiInMessage.Open:
94 | // message Parameter 1 & 2 are not used
95 | break;
96 | case MidiInterop.MidiInMessage.Data:
97 | // parameter 1 is packed MIDI message
98 | // parameter 2 is milliseconds since MidiInStart
99 | if (MessageReceived != null)
100 | {
101 | MessageReceived(this, new MidiInMessageEventArgs(messageParameter1.ToInt32(), messageParameter2.ToInt32()));
102 | }
103 | break;
104 | case MidiInterop.MidiInMessage.Error:
105 | // parameter 1 is invalid MIDI message
106 | if (ErrorReceived != null)
107 | {
108 | ErrorReceived(this, new MidiInMessageEventArgs(messageParameter1.ToInt32(), messageParameter2.ToInt32()));
109 | }
110 | break;
111 | case MidiInterop.MidiInMessage.Close:
112 | // message Parameter 1 & 2 are not used
113 | break;
114 | case MidiInterop.MidiInMessage.LongData:
115 | // parameter 1 is pointer to MIDI header
116 | // parameter 2 is milliseconds since MidiInStart
117 | break;
118 | case MidiInterop.MidiInMessage.LongError:
119 | // parameter 1 is pointer to MIDI header
120 | // parameter 2 is milliseconds since MidiInStart
121 | break;
122 | case MidiInterop.MidiInMessage.MoreData:
123 | // parameter 1 is packed MIDI message
124 | // parameter 2 is milliseconds since MidiInStart
125 | break;
126 | }
127 | }
128 |
129 | ///
130 | /// Gets the MIDI in device info
131 | ///
132 | public static MidiInCapabilities DeviceInfo(int midiInDeviceNumber)
133 | {
134 | MidiInCapabilities caps = new MidiInCapabilities();
135 | int structSize = Marshal.SizeOf(caps);
136 | MmException.Try(MidiInterop.midiInGetDevCaps((IntPtr)midiInDeviceNumber,out caps,structSize),"midiInGetDevCaps");
137 | return caps;
138 | }
139 |
140 | ///
141 | /// Closes the MIDI out device
142 | ///
143 | /// True if called from Dispose
144 | protected virtual void Dispose(bool disposing)
145 | {
146 | if(!this.disposed)
147 | {
148 | //if(disposing) Components.Dispose();
149 | MidiInterop.midiInClose(hMidiIn);
150 | }
151 | disposed = true;
152 | }
153 |
154 | ///
155 | /// Cleanup
156 | ///
157 | ~MidiIn()
158 | {
159 | System.Diagnostics.Debug.Assert(false,"MIDI In was not finalised");
160 | Dispose(false);
161 | }
162 | }
163 | }
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/NoteEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using NAudio;
5 |
6 | namespace Midi
7 | {
8 | ///
9 | /// Represents a note MIDI event
10 | ///
11 | public class NoteEvent : MidiEvent
12 | {
13 | private int noteNumber;
14 | private int velocity;
15 |
16 | ///
17 | /// Reads a NoteEvent from a stream of MIDI data
18 | ///
19 | /// Binary Reader for the stream
20 | public NoteEvent(BinaryReader br)
21 | {
22 | NoteNumber = br.ReadByte();
23 | velocity = br.ReadByte();
24 | // it seems it is possible for cubase
25 | // to output some notes with a NoteOff velocity > 127
26 | if (velocity > 127)
27 | {
28 | velocity = 127;
29 | }
30 | }
31 |
32 | ///
33 | /// Creates a MIDI Note Event with specified parameters
34 | ///
35 | /// Absolute time of this event
36 | /// MIDI channel number
37 | /// MIDI command code
38 | /// MIDI Note Number
39 | /// MIDI Note Velocity
40 | public NoteEvent(long absoluteTime, int channel, MidiCommandCode commandCode, int noteNumber, int velocity)
41 | : base(absoluteTime, channel, commandCode)
42 | {
43 | this.NoteNumber = noteNumber;
44 | this.Velocity = velocity;
45 | }
46 |
47 | private static readonly string[] NoteNames = new string[] { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
48 |
49 | ///
50 | ///
51 | ///
52 | public override int GetAsShortMessage()
53 | {
54 | return base.GetAsShortMessage() + (noteNumber << 8) + (velocity << 16);
55 | }
56 |
57 | ///
58 | /// The MIDI note number
59 | ///
60 | public virtual int NoteNumber
61 | {
62 | get
63 | {
64 | return noteNumber;
65 | }
66 | set
67 | {
68 | if (value < 0 || value > 127)
69 | {
70 | throw new ArgumentOutOfRangeException("value", "Note number must be in the range 0-127");
71 | }
72 | noteNumber = value;
73 | }
74 | }
75 |
76 | ///
77 | /// The note velocity
78 | ///
79 | public int Velocity
80 | {
81 | get
82 | {
83 | return velocity;
84 | }
85 | set
86 | {
87 | if (value < 0 || value > 127)
88 | {
89 | throw new ArgumentOutOfRangeException("value", "Velocity must be in the range 0-127");
90 | }
91 | velocity = value;
92 | }
93 | }
94 |
95 | ///
96 | /// The note name
97 | ///
98 | public string NoteName
99 | {
100 | get
101 | {
102 | if ((Channel == 16) || (Channel == 10))
103 | {
104 | switch (noteNumber)
105 | {
106 | case 35: return "Acoustic Bass Drum";
107 | case 36: return "Bass Drum 1";
108 | case 37: return "Side Stick";
109 | case 38: return "Acoustic Snare";
110 | case 39: return "Hand Clap";
111 | case 40: return "Electric Snare";
112 | case 41: return "Low Floor Tom";
113 | case 42: return "Closed Hi-Hat";
114 | case 43: return "High Floor Tom";
115 | case 44: return "Pedal Hi-Hat";
116 | case 45: return "Low Tom";
117 | case 46: return "Open Hi-Hat";
118 | case 47: return "Low-Mid Tom";
119 | case 48: return "Hi-Mid Tom";
120 | case 49: return "Crash Cymbal 1";
121 | case 50: return "High Tom";
122 | case 51: return "Ride Cymbal 1";
123 | case 52: return "Chinese Cymbal";
124 | case 53: return "Ride Bell";
125 | case 54: return "Tambourine";
126 | case 55: return "Splash Cymbal";
127 | case 56: return "Cowbell";
128 | case 57: return "Crash Cymbal 2";
129 | case 58: return "Vibraslap";
130 | case 59: return "Ride Cymbal 2";
131 | case 60: return "Hi Bongo";
132 | case 61: return "Low Bongo";
133 | case 62: return "Mute Hi Conga";
134 | case 63: return "Open Hi Conga";
135 | case 64: return "Low Conga";
136 | case 65: return "High Timbale";
137 | case 66: return "Low Timbale";
138 | case 67: return "High Agogo";
139 | case 68: return "Low Agogo";
140 | case 69: return "Cabasa";
141 | case 70: return "Maracas";
142 | case 71: return "Short Whistle";
143 | case 72: return "Long Whistle";
144 | case 73: return "Short Guiro";
145 | case 74: return "Long Guiro";
146 | case 75: return "Claves";
147 | case 76: return "Hi Wood Block";
148 | case 77: return "Low Wood Block";
149 | case 78: return "Mute Cuica";
150 | case 79: return "Open Cuica";
151 | case 80: return "Mute Triangle";
152 | case 81: return "Open Triangle";
153 | default: return String.Format("Drum {0}", noteNumber);
154 | }
155 | }
156 | else
157 | {
158 | int octave = noteNumber / 12;
159 | return String.Format("{0}{1}", NoteNames[noteNumber % 12], octave);
160 | }
161 | }
162 | }
163 |
164 | ///
165 | /// Describes the Note Event
166 | ///
167 | /// Note event as a string
168 | public override string ToString()
169 | {
170 | return String.Format("{0} {1} Vel:{2}",
171 | base.ToString(),
172 | this.NoteName,
173 | this.Velocity);
174 | }
175 |
176 | ///
177 | ///
178 | ///
179 | public override void Export(ref long absoluteTime, BinaryWriter writer)
180 | {
181 | base.Export(ref absoluteTime, writer);
182 | writer.Write((byte)noteNumber);
183 | writer.Write((byte)velocity);
184 | }
185 | }
186 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/ChartConverter.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.7.34024.191
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PsarcUtil", "Dependencies\PsarcUtil\PsarcUtil\PsarcUtil.csproj", "{5E4EFF4B-5A94-4940-8055-6DFB7B974171}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BnkExtractor", "Dependencies\PsarcUtil\Dependencies\BnkExtractor\BnkExtractor\BnkExtractor.csproj", "{CD1D23EF-2D72-443B-B70F-C68BE05A5EBB}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rocksmith2014PsarcLib", "Dependencies\PsarcUtil\Dependencies\Rocksmith2014PsarcLib\Rocksmith2014PsarcLib.csproj", "{7B43C4EC-55A3-40B8-9640-04147049C0E3}"
11 | EndProject
12 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "ChartConverterShared", "ChartConverterShared\ChartConverterShared.shproj", "{EADCD423-38CE-4D03-82A4-EB8C40C54ACE}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChartConverter", "ChartConverter\ChartConverter.csproj", "{206A5643-0B91-46AB-AC79-744F4DFD47DD}"
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UILayout.MonoGame.DesktopGL", "Dependencies\UILayout\UILayout.MonoGame.DesktopGL\UILayout.MonoGame.DesktopGL.csproj", "{F05802D8-6CBC-4F7F-82C1-CD8175C85CD8}"
17 | EndProject
18 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SongFormat", "Dependencies\OpenSongChart\SongFormat\SongFormat.shproj", "{595D9376-81D5-4163-9045-BD1D72A45B16}"
19 | EndProject
20 | Global
21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
22 | Debug|Any CPU = Debug|Any CPU
23 | Debug|x64 = Debug|x64
24 | Debug|x86 = Debug|x86
25 | Release|Any CPU = Release|Any CPU
26 | Release|x64 = Release|x64
27 | Release|x86 = Release|x86
28 | EndGlobalSection
29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
30 | {5E4EFF4B-5A94-4940-8055-6DFB7B974171}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {5E4EFF4B-5A94-4940-8055-6DFB7B974171}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {5E4EFF4B-5A94-4940-8055-6DFB7B974171}.Debug|x64.ActiveCfg = Debug|Any CPU
33 | {5E4EFF4B-5A94-4940-8055-6DFB7B974171}.Debug|x64.Build.0 = Debug|Any CPU
34 | {5E4EFF4B-5A94-4940-8055-6DFB7B974171}.Debug|x86.ActiveCfg = Debug|Any CPU
35 | {5E4EFF4B-5A94-4940-8055-6DFB7B974171}.Debug|x86.Build.0 = Debug|Any CPU
36 | {5E4EFF4B-5A94-4940-8055-6DFB7B974171}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {5E4EFF4B-5A94-4940-8055-6DFB7B974171}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {5E4EFF4B-5A94-4940-8055-6DFB7B974171}.Release|x64.ActiveCfg = Release|Any CPU
39 | {5E4EFF4B-5A94-4940-8055-6DFB7B974171}.Release|x64.Build.0 = Release|Any CPU
40 | {5E4EFF4B-5A94-4940-8055-6DFB7B974171}.Release|x86.ActiveCfg = Release|Any CPU
41 | {5E4EFF4B-5A94-4940-8055-6DFB7B974171}.Release|x86.Build.0 = Release|Any CPU
42 | {CD1D23EF-2D72-443B-B70F-C68BE05A5EBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | {CD1D23EF-2D72-443B-B70F-C68BE05A5EBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | {CD1D23EF-2D72-443B-B70F-C68BE05A5EBB}.Debug|x64.ActiveCfg = Debug|Any CPU
45 | {CD1D23EF-2D72-443B-B70F-C68BE05A5EBB}.Debug|x64.Build.0 = Debug|Any CPU
46 | {CD1D23EF-2D72-443B-B70F-C68BE05A5EBB}.Debug|x86.ActiveCfg = Debug|Any CPU
47 | {CD1D23EF-2D72-443B-B70F-C68BE05A5EBB}.Debug|x86.Build.0 = Debug|Any CPU
48 | {CD1D23EF-2D72-443B-B70F-C68BE05A5EBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
49 | {CD1D23EF-2D72-443B-B70F-C68BE05A5EBB}.Release|Any CPU.Build.0 = Release|Any CPU
50 | {CD1D23EF-2D72-443B-B70F-C68BE05A5EBB}.Release|x64.ActiveCfg = Release|Any CPU
51 | {CD1D23EF-2D72-443B-B70F-C68BE05A5EBB}.Release|x64.Build.0 = Release|Any CPU
52 | {CD1D23EF-2D72-443B-B70F-C68BE05A5EBB}.Release|x86.ActiveCfg = Release|Any CPU
53 | {CD1D23EF-2D72-443B-B70F-C68BE05A5EBB}.Release|x86.Build.0 = Release|Any CPU
54 | {7B43C4EC-55A3-40B8-9640-04147049C0E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55 | {7B43C4EC-55A3-40B8-9640-04147049C0E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
56 | {7B43C4EC-55A3-40B8-9640-04147049C0E3}.Debug|x64.ActiveCfg = Debug|x64
57 | {7B43C4EC-55A3-40B8-9640-04147049C0E3}.Debug|x64.Build.0 = Debug|x64
58 | {7B43C4EC-55A3-40B8-9640-04147049C0E3}.Debug|x86.ActiveCfg = Debug|Any CPU
59 | {7B43C4EC-55A3-40B8-9640-04147049C0E3}.Debug|x86.Build.0 = Debug|Any CPU
60 | {7B43C4EC-55A3-40B8-9640-04147049C0E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
61 | {7B43C4EC-55A3-40B8-9640-04147049C0E3}.Release|Any CPU.Build.0 = Release|Any CPU
62 | {7B43C4EC-55A3-40B8-9640-04147049C0E3}.Release|x64.ActiveCfg = Release|x64
63 | {7B43C4EC-55A3-40B8-9640-04147049C0E3}.Release|x64.Build.0 = Release|x64
64 | {7B43C4EC-55A3-40B8-9640-04147049C0E3}.Release|x86.ActiveCfg = Release|Any CPU
65 | {7B43C4EC-55A3-40B8-9640-04147049C0E3}.Release|x86.Build.0 = Release|Any CPU
66 | {206A5643-0B91-46AB-AC79-744F4DFD47DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
67 | {206A5643-0B91-46AB-AC79-744F4DFD47DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
68 | {206A5643-0B91-46AB-AC79-744F4DFD47DD}.Debug|x64.ActiveCfg = Debug|Any CPU
69 | {206A5643-0B91-46AB-AC79-744F4DFD47DD}.Debug|x64.Build.0 = Debug|Any CPU
70 | {206A5643-0B91-46AB-AC79-744F4DFD47DD}.Debug|x86.ActiveCfg = Debug|Any CPU
71 | {206A5643-0B91-46AB-AC79-744F4DFD47DD}.Debug|x86.Build.0 = Debug|Any CPU
72 | {206A5643-0B91-46AB-AC79-744F4DFD47DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
73 | {206A5643-0B91-46AB-AC79-744F4DFD47DD}.Release|Any CPU.Build.0 = Release|Any CPU
74 | {206A5643-0B91-46AB-AC79-744F4DFD47DD}.Release|x64.ActiveCfg = Release|Any CPU
75 | {206A5643-0B91-46AB-AC79-744F4DFD47DD}.Release|x64.Build.0 = Release|Any CPU
76 | {206A5643-0B91-46AB-AC79-744F4DFD47DD}.Release|x86.ActiveCfg = Release|Any CPU
77 | {206A5643-0B91-46AB-AC79-744F4DFD47DD}.Release|x86.Build.0 = Release|Any CPU
78 | {F05802D8-6CBC-4F7F-82C1-CD8175C85CD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
79 | {F05802D8-6CBC-4F7F-82C1-CD8175C85CD8}.Debug|Any CPU.Build.0 = Debug|Any CPU
80 | {F05802D8-6CBC-4F7F-82C1-CD8175C85CD8}.Debug|x64.ActiveCfg = Debug|Any CPU
81 | {F05802D8-6CBC-4F7F-82C1-CD8175C85CD8}.Debug|x64.Build.0 = Debug|Any CPU
82 | {F05802D8-6CBC-4F7F-82C1-CD8175C85CD8}.Debug|x86.ActiveCfg = Debug|Any CPU
83 | {F05802D8-6CBC-4F7F-82C1-CD8175C85CD8}.Debug|x86.Build.0 = Debug|Any CPU
84 | {F05802D8-6CBC-4F7F-82C1-CD8175C85CD8}.Release|Any CPU.ActiveCfg = Release|Any CPU
85 | {F05802D8-6CBC-4F7F-82C1-CD8175C85CD8}.Release|Any CPU.Build.0 = Release|Any CPU
86 | {F05802D8-6CBC-4F7F-82C1-CD8175C85CD8}.Release|x64.ActiveCfg = Release|Any CPU
87 | {F05802D8-6CBC-4F7F-82C1-CD8175C85CD8}.Release|x64.Build.0 = Release|Any CPU
88 | {F05802D8-6CBC-4F7F-82C1-CD8175C85CD8}.Release|x86.ActiveCfg = Release|Any CPU
89 | {F05802D8-6CBC-4F7F-82C1-CD8175C85CD8}.Release|x86.Build.0 = Release|Any CPU
90 | EndGlobalSection
91 | GlobalSection(SolutionProperties) = preSolution
92 | HideSolutionNode = FALSE
93 | EndGlobalSection
94 | GlobalSection(ExtensibilityGlobals) = postSolution
95 | SolutionGuid = {D6BF81B9-DA6D-4E2F-8790-3E6DEF860F81}
96 | EndGlobalSection
97 | GlobalSection(SharedMSBuildProjectFiles) = preSolution
98 | ChartConverterShared\ChartConverterShared.projitems*{206a5643-0b91-46ab-ac79-744f4dfd47dd}*SharedItemsImports = 5
99 | Dependencies\OpenSongChart\SongFormat\SongFormat.projitems*{206a5643-0b91-46ab-ac79-744f4dfd47dd}*SharedItemsImports = 5
100 | Dependencies\OpenSongChart\SongFormat\SongFormat.projitems*{595d9376-81d5-4163-9045-bd1d72a45b16}*SharedItemsImports = 13
101 | ChartConverterShared\ChartConverterShared.projitems*{eadcd423-38ce-4d03-82a4-eb8c40c54ace}*SharedItemsImports = 13
102 | Dependencies\UILayout\UILayout.CrossPlatform\UILayout.CrossPlatform.projitems*{f05802d8-6cbc-4f7f-82c1-cd8175c85cd8}*SharedItemsImports = 5
103 | Dependencies\UILayout\UILayout.DefaultTextures\UILayout.DefaultTextures.projitems*{f05802d8-6cbc-4f7f-82c1-cd8175c85cd8}*SharedItemsImports = 5
104 | Dependencies\UILayout\UILayout.MonoGame\UILayout.MonoGame.projitems*{f05802d8-6cbc-4f7f-82c1-cd8175c85cd8}*SharedItemsImports = 5
105 | Dependencies\UILayout\UILayout\UILayout.projitems*{f05802d8-6cbc-4f7f-82c1-cd8175c85cd8}*SharedItemsImports = 5
106 | EndGlobalSection
107 | EndGlobal
108 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiFile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using System.Collections.Generic;
5 | using NAudio.Utils;
6 | using NAudio;
7 |
8 | namespace Midi
9 | {
10 | ///
11 | /// Class able to read a MIDI file
12 | ///
13 | public class MidiFile
14 | {
15 | private MidiEventCollection events;
16 | private ushort fileFormat;
17 | //private ushort tracks;
18 | private ushort deltaTicksPerQuarterNote;
19 | private bool strictChecking;
20 |
21 | ///
22 | /// Opens a MIDI file for reading
23 | ///
24 | /// Name of MIDI file
25 | public MidiFile(string filename)
26 | : this(filename,true)
27 | {
28 | }
29 |
30 | ///
31 | /// MIDI File format
32 | ///
33 | public int FileFormat
34 | {
35 | get { return fileFormat; }
36 | }
37 |
38 | ///
39 | /// Opens a MIDI file for reading
40 | ///
41 | /// Name of MIDI file
42 | /// If true will error on non-paired note events
43 | public MidiFile(string filename, bool strictChecking) :
44 | this(File.OpenRead(filename), strictChecking, true)
45 | {
46 | }
47 |
48 | ///
49 | /// Opens a MIDI file stream for reading
50 | ///
51 | /// The input stream containing a MIDI file
52 | /// If true will error on non-paired note events
53 | public MidiFile(Stream inputStream, bool strictChecking) :
54 | this(inputStream, strictChecking, false)
55 | {
56 | }
57 |
58 | private MidiFile(Stream inputStream, bool strictChecking, bool ownInputStream)
59 | {
60 | this.strictChecking = strictChecking;
61 |
62 | var br = new BinaryReader(inputStream);
63 | try
64 | {
65 | string chunkHeader = Encoding.UTF8.GetString(br.ReadBytes(4));
66 | if(chunkHeader != "MThd")
67 | {
68 | throw new FormatException("Not a MIDI file - header chunk missing");
69 | }
70 | uint chunkSize = SwapUInt32(br.ReadUInt32());
71 |
72 | if(chunkSize != 6)
73 | {
74 | throw new FormatException("Unexpected header chunk length");
75 | }
76 | // 0 = single track, 1 = multi-track synchronous, 2 = multi-track asynchronous
77 | fileFormat = SwapUInt16(br.ReadUInt16());
78 | int tracks = SwapUInt16(br.ReadUInt16());
79 | deltaTicksPerQuarterNote = SwapUInt16(br.ReadUInt16());
80 |
81 | events = new MidiEventCollection((fileFormat == 0) ? 0 : 1, deltaTicksPerQuarterNote);
82 | for (int n = 0; n < tracks; n++)
83 | {
84 | events.AddTrack();
85 | }
86 |
87 | long absoluteTime = 0;
88 |
89 | for(int track = 0; track < tracks; track++)
90 | {
91 | if(fileFormat == 1)
92 | {
93 | absoluteTime = 0;
94 | }
95 | chunkHeader = Encoding.UTF8.GetString(br.ReadBytes(4));
96 | if(chunkHeader != "MTrk")
97 | {
98 | throw new FormatException("Invalid chunk header");
99 | }
100 | chunkSize = SwapUInt32(br.ReadUInt32());
101 |
102 | long startPos = br.BaseStream.Position;
103 | MidiEvent me = null;
104 | var outstandingNoteOns = new List();
105 | MidiEvent previous = null;
106 |
107 | while(br.BaseStream.Position < startPos + chunkSize)
108 | {
109 | me = MidiEvent.ReadNextEvent(br, previous);
110 | absoluteTime += me.DeltaTime;
111 | me.AbsoluteTime = absoluteTime;
112 | events[track].Add(me);
113 | if (me.CommandCode == MidiCommandCode.NoteOn)
114 | {
115 | NoteEvent ne = (NoteEvent) me;
116 | if(ne.Velocity > 0)
117 | {
118 | outstandingNoteOns.Add((NoteOnEvent) ne);
119 | }
120 | else
121 | {
122 | // don't remove the note offs, even though
123 | // they are annoying
124 | // events[track].Remove(me);
125 | FindNoteOn(ne,outstandingNoteOns);
126 | }
127 | }
128 | else if(me.CommandCode == MidiCommandCode.NoteOff)
129 | {
130 | FindNoteOn((NoteEvent) me,outstandingNoteOns);
131 | }
132 | else if(me.CommandCode == MidiCommandCode.MetaEvent)
133 | {
134 | MetaEvent metaEvent = (MetaEvent) me;
135 | if(metaEvent.MetaEventType == MetaEventType.EndTrack)
136 | {
137 | //break;
138 | // some dodgy MIDI files have an event after end track
139 | if (strictChecking)
140 | {
141 | if (br.BaseStream.Position < startPos + chunkSize)
142 | {
143 | throw new FormatException(String.Format("End Track event was not the last MIDI event on track {0}", track));
144 | }
145 | }
146 | }
147 | }
148 |
149 | // Only update previous event if it is a simple midi event
150 | if (me.CommandCode < MidiCommandCode.Sysex)
151 | {
152 | previous = me;
153 | }
154 | }
155 | if(outstandingNoteOns.Count > 0)
156 | {
157 | if (strictChecking)
158 | {
159 | throw new FormatException(String.Format("Note ons without note offs {0} (file format {1})", outstandingNoteOns.Count, fileFormat));
160 | }
161 | }
162 | if(br.BaseStream.Position != startPos + chunkSize)
163 | {
164 | //throw new FormatException(String.Format("Read too far {0}+{1}!={2}", chunkSize, startPos, br.BaseStream.Position));
165 | }
166 | }
167 | }
168 | finally
169 | {
170 | if (ownInputStream)
171 | br.Close();
172 | }
173 | }
174 |
175 | ///
176 | /// The collection of events in this MIDI file
177 | ///
178 | public MidiEventCollection Events
179 | {
180 | get { return events; }
181 | }
182 |
183 | ///
184 | /// Number of tracks in this MIDI file
185 | ///
186 | public int Tracks
187 | {
188 | get { return events.Tracks; }
189 | }
190 |
191 | ///
192 | /// Delta Ticks Per Quarter Note
193 | ///
194 | public int DeltaTicksPerQuarterNote
195 | {
196 | get { return deltaTicksPerQuarterNote; }
197 | }
198 |
199 | private void FindNoteOn(NoteEvent offEvent, List outstandingNoteOns)
200 | {
201 | bool found = false;
202 | foreach(NoteOnEvent noteOnEvent in outstandingNoteOns)
203 | {
204 | if ((noteOnEvent.Channel == offEvent.Channel) && (noteOnEvent.NoteNumber == offEvent.NoteNumber))
205 | {
206 | noteOnEvent.OffEvent = offEvent;
207 | outstandingNoteOns.Remove(noteOnEvent);
208 | found = true;
209 | break;
210 | }
211 | }
212 | if(!found)
213 | {
214 | if (strictChecking)
215 | {
216 | throw new FormatException(String.Format("Got an off without an on {0}", offEvent));
217 | }
218 | }
219 | }
220 |
221 | private static uint SwapUInt32(uint i)
222 | {
223 | return ((i & 0xFF000000) >> 24) | ((i & 0x00FF0000) >> 8) | ((i & 0x0000FF00) << 8) | ((i & 0x000000FF) << 24);
224 | }
225 |
226 | private static ushort SwapUInt16(ushort i)
227 | {
228 | return (ushort) (((i & 0xFF00) >> 8) | ((i & 0x00FF) << 8));
229 | }
230 |
231 | ///
232 | /// Describes the MIDI file
233 | ///
234 | /// A string describing the MIDI file and its events
235 | public override string ToString()
236 | {
237 | var sb = new StringBuilder();
238 | sb.AppendFormat("Format {0}, Tracks {1}, Delta Ticks Per Quarter Note {2}\r\n",
239 | fileFormat,Tracks,deltaTicksPerQuarterNote);
240 | for (int n = 0; n < Tracks; n++)
241 | {
242 | foreach (MidiEvent midiEvent in events[n])
243 | {
244 | sb.AppendFormat("{0}\r\n", midiEvent);
245 | }
246 | }
247 | return sb.ToString();
248 | }
249 |
250 | ///
251 | /// Exports a MIDI file
252 | ///
253 | /// Filename to export to
254 | /// Events to export
255 | public static void Export(string filename, MidiEventCollection events)
256 | {
257 | if (events.MidiFileType == 0 && events.Tracks > 1)
258 | {
259 | throw new ArgumentException("Can't export more than one track to a type 0 file");
260 | }
261 | using (var writer = new BinaryWriter(File.Create(filename)))
262 | {
263 | writer.Write(Encoding.UTF8.GetBytes("MThd"));
264 | writer.Write(SwapUInt32((uint)6)); // chunk size
265 | writer.Write(SwapUInt16((ushort)events.MidiFileType));
266 | writer.Write(SwapUInt16((ushort)events.Tracks));
267 | writer.Write(SwapUInt16((ushort)events.DeltaTicksPerQuarterNote));
268 |
269 | for (int track = 0; track < events.Tracks; track++ )
270 | {
271 | IList eventList = events[track];
272 |
273 | writer.Write(Encoding.UTF8.GetBytes("MTrk"));
274 | long trackSizePosition = writer.BaseStream.Position;
275 | writer.Write(SwapUInt32((uint)0));
276 |
277 | long absoluteTime = events.StartAbsoluteTime;
278 |
279 | // use a stable sort to preserve ordering of MIDI events whose
280 | // absolute times are the same
281 | MergeSort.Sort(eventList, new MidiEventComparer());
282 | if (eventList.Count > 0)
283 | {
284 | System.Diagnostics.Debug.Assert(MidiEvent.IsEndTrack(eventList[eventList.Count - 1]), "Exporting a track with a missing end track");
285 | }
286 | foreach (MidiEvent midiEvent in eventList)
287 | {
288 | midiEvent.Export(ref absoluteTime, writer);
289 | }
290 |
291 | uint trackChunkLength = (uint)(writer.BaseStream.Position - trackSizePosition) - 4;
292 | writer.BaseStream.Position = trackSizePosition;
293 | writer.Write(SwapUInt32(trackChunkLength));
294 | writer.BaseStream.Position += trackChunkLength;
295 | }
296 | }
297 | }
298 | }
299 | }
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiInterop.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using NAudio.Wave;
4 | using NAudio;
5 |
6 | namespace Midi
7 | {
8 | internal class MidiInterop
9 | {
10 |
11 | public enum MidiInMessage
12 | {
13 | ///
14 | /// MIM_OPEN
15 | ///
16 | Open = 0x3C1,
17 | ///
18 | /// MIM_CLOSE
19 | ///
20 | Close = 0x3C2,
21 | ///
22 | /// MIM_DATA
23 | ///
24 | Data = 0x3C3,
25 | ///
26 | /// MIM_LONGDATA
27 | ///
28 | LongData = 0x3C4,
29 | ///
30 | /// MIM_ERROR
31 | ///
32 | Error = 0x3C5,
33 | ///
34 | /// MIM_LONGERROR
35 | ///
36 | LongError = 0x3C6,
37 | ///
38 | /// MIM_MOREDATA
39 | ///
40 | MoreData = 0x3CC,
41 | }
42 |
43 |
44 |
45 | public enum MidiOutMessage
46 | {
47 | ///
48 | /// MOM_OPEN
49 | ///
50 | Open = 0x3C7,
51 | ///
52 | /// MOM_CLOSE
53 | ///
54 | Close = 0x3C8,
55 | ///
56 | /// MOM_DONE
57 | ///
58 | Done = 0x3C9
59 | }
60 |
61 | // http://msdn.microsoft.com/en-us/library/dd798460%28VS.85%29.aspx
62 | public delegate void MidiInCallback(IntPtr midiInHandle, MidiInMessage message, IntPtr userData, IntPtr messageParameter1, IntPtr messageParameter2);
63 |
64 | // http://msdn.microsoft.com/en-us/library/dd798478%28VS.85%29.aspx
65 | public delegate void MidiOutCallback(IntPtr midiInHandle, MidiOutMessage message, IntPtr userData, IntPtr messageParameter1, IntPtr messageParameter2);
66 |
67 | // http://msdn.microsoft.com/en-us/library/dd798446%28VS.85%29.aspx
68 | [DllImport("winmm.dll")]
69 | public static extern MmResult midiConnect(IntPtr hMidiIn, IntPtr hMidiOut, IntPtr pReserved);
70 |
71 | // http://msdn.microsoft.com/en-us/library/dd798447%28VS.85%29.aspx
72 | [DllImport("winmm.dll")]
73 | public static extern MmResult midiDisconnect(IntPtr hMidiIn, IntPtr hMidiOut, IntPtr pReserved);
74 |
75 | // http://msdn.microsoft.com/en-us/library/dd798450%28VS.85%29.aspx
76 | [DllImport("winmm.dll")]
77 | public static extern MmResult midiInAddBuffer(IntPtr hMidiIn, [MarshalAs(UnmanagedType.Struct)] ref MIDIHDR lpMidiInHdr, int uSize);
78 |
79 | // http://msdn.microsoft.com/en-us/library/dd798452%28VS.85%29.aspx
80 | [DllImport("winmm.dll")]
81 | public static extern MmResult midiInClose(IntPtr hMidiIn);
82 |
83 | // http://msdn.microsoft.com/en-us/library/dd798453%28VS.85%29.aspx
84 | [DllImport("winmm.dll", CharSet = CharSet.Auto)]
85 | public static extern MmResult midiInGetDevCaps(IntPtr deviceId, out MidiInCapabilities capabilities, int size);
86 |
87 | // http://msdn.microsoft.com/en-us/library/dd798454%28VS.85%29.aspx
88 | // TODO: review this, probably doesn't work
89 | [DllImport("winmm.dll")]
90 | public static extern MmResult midiInGetErrorText(int err, string lpText, int uSize);
91 |
92 | // http://msdn.microsoft.com/en-us/library/dd798455%28VS.85%29.aspx
93 | [DllImport("winmm.dll")]
94 | public static extern MmResult midiInGetID(IntPtr hMidiIn, out int lpuDeviceId);
95 |
96 | // http://msdn.microsoft.com/en-us/library/dd798456%28VS.85%29.aspx
97 | [DllImport("winmm.dll")]
98 | public static extern int midiInGetNumDevs();
99 |
100 | // http://msdn.microsoft.com/en-us/library/dd798457%28VS.85%29.aspx
101 | [DllImport("winmm.dll")]
102 | public static extern MmResult midiInMessage(IntPtr hMidiIn, int msg, IntPtr dw1, IntPtr dw2);
103 |
104 | // http://msdn.microsoft.com/en-us/library/dd798458%28VS.85%29.aspx
105 | [DllImport("winmm.dll", EntryPoint = "midiInOpen")]
106 | public static extern MmResult midiInOpen(out IntPtr hMidiIn, IntPtr uDeviceID, MidiInCallback callback, IntPtr dwInstance, int dwFlags);
107 |
108 | // http://msdn.microsoft.com/en-us/library/dd798458%28VS.85%29.aspx
109 | [DllImport("winmm.dll", EntryPoint = "midiInOpen")]
110 | public static extern MmResult midiInOpenWindow(out IntPtr hMidiIn, IntPtr uDeviceID, IntPtr callbackWindowHandle, IntPtr dwInstance, int dwFlags);
111 |
112 | // http://msdn.microsoft.com/en-us/library/dd798459%28VS.85%29.aspx
113 | [DllImport("winmm.dll")]
114 | public static extern MmResult midiInPrepareHeader(IntPtr hMidiIn, [MarshalAs(UnmanagedType.Struct)] ref MIDIHDR lpMidiInHdr, int uSize);
115 |
116 | // http://msdn.microsoft.com/en-us/library/dd798461%28VS.85%29.aspx
117 | [DllImport("winmm.dll")]
118 | public static extern MmResult midiInReset(IntPtr hMidiIn);
119 |
120 | // http://msdn.microsoft.com/en-us/library/dd798462%28VS.85%29.aspx
121 | [DllImport("winmm.dll")]
122 | public static extern MmResult midiInStart(IntPtr hMidiIn);
123 |
124 | // http://msdn.microsoft.com/en-us/library/dd798463%28VS.85%29.aspx
125 | [DllImport("winmm.dll")]
126 | public static extern MmResult midiInStop(IntPtr hMidiIn);
127 |
128 | // http://msdn.microsoft.com/en-us/library/dd798464%28VS.85%29.aspx
129 | [DllImport("winmm.dll")]
130 | public static extern MmResult midiInUnprepareHeader(IntPtr hMidiIn, [MarshalAs(UnmanagedType.Struct)] ref MIDIHDR lpMidiInHdr, int uSize);
131 |
132 | // http://msdn.microsoft.com/en-us/library/dd798465%28VS.85%29.aspx
133 | [DllImport("winmm.dll")]
134 | public static extern MmResult midiOutCacheDrumPatches(IntPtr hMidiOut, int uPatch, IntPtr lpKeyArray, int uFlags);
135 |
136 | // http://msdn.microsoft.com/en-us/library/dd798466%28VS.85%29.aspx
137 | [DllImport("winmm.dll")]
138 | public static extern MmResult midiOutCachePatches(IntPtr hMidiOut, int uBank, IntPtr lpPatchArray, int uFlags);
139 |
140 | // http://msdn.microsoft.com/en-us/library/dd798468%28VS.85%29.aspx
141 | [DllImport("winmm.dll")]
142 | public static extern MmResult midiOutClose(IntPtr hMidiOut);
143 |
144 | // http://msdn.microsoft.com/en-us/library/dd798469%28VS.85%29.aspx
145 | [DllImport("winmm.dll", CharSet = CharSet.Auto)]
146 | public static extern MmResult midiOutGetDevCaps(IntPtr deviceNumber, out MidiOutCapabilities caps, int uSize);
147 |
148 | // http://msdn.microsoft.com/en-us/library/dd798470%28VS.85%29.aspx
149 | // TODO: review, probably doesn't work
150 | [DllImport("winmm.dll")]
151 | public static extern MmResult midiOutGetErrorText(IntPtr err, string lpText, int uSize);
152 |
153 | // http://msdn.microsoft.com/en-us/library/dd798471%28VS.85%29.aspx
154 | [DllImport("winmm.dll")]
155 | public static extern MmResult midiOutGetID(IntPtr hMidiOut, out int lpuDeviceID);
156 |
157 | // http://msdn.microsoft.com/en-us/library/dd798472%28VS.85%29.aspx
158 | [DllImport("winmm.dll")]
159 | public static extern int midiOutGetNumDevs();
160 |
161 | // http://msdn.microsoft.com/en-us/library/dd798473%28VS.85%29.aspx
162 | [DllImport("winmm.dll")]
163 | public static extern MmResult midiOutGetVolume(IntPtr uDeviceID, ref int lpdwVolume);
164 |
165 | // http://msdn.microsoft.com/en-us/library/dd798474%28VS.85%29.aspx
166 | [DllImport("winmm.dll")]
167 | public static extern MmResult midiOutLongMsg(IntPtr hMidiOut, [MarshalAs(UnmanagedType.Struct)] ref MIDIHDR lpMidiOutHdr, int uSize);
168 |
169 | // http://msdn.microsoft.com/en-us/library/dd798475%28VS.85%29.aspx
170 | [DllImport("winmm.dll")]
171 | public static extern MmResult midiOutMessage(IntPtr hMidiOut, int msg, IntPtr dw1, IntPtr dw2);
172 |
173 | // http://msdn.microsoft.com/en-us/library/dd798476%28VS.85%29.aspx
174 | [DllImport("winmm.dll")]
175 | public static extern MmResult midiOutOpen(out IntPtr lphMidiOut, IntPtr uDeviceID, MidiOutCallback dwCallback, IntPtr dwInstance, int dwFlags);
176 |
177 | // http://msdn.microsoft.com/en-us/library/dd798477%28VS.85%29.aspx
178 | [DllImport("winmm.dll")]
179 | public static extern MmResult midiOutPrepareHeader(IntPtr hMidiOut, [MarshalAs(UnmanagedType.Struct)] ref MIDIHDR lpMidiOutHdr, int uSize);
180 |
181 | // http://msdn.microsoft.com/en-us/library/dd798479%28VS.85%29.aspx
182 | [DllImport("winmm.dll")]
183 | public static extern MmResult midiOutReset(IntPtr hMidiOut);
184 |
185 | // http://msdn.microsoft.com/en-us/library/dd798480%28VS.85%29.aspx
186 | [DllImport("winmm.dll")]
187 | public static extern MmResult midiOutSetVolume(IntPtr hMidiOut, int dwVolume);
188 |
189 | // http://msdn.microsoft.com/en-us/library/dd798481%28VS.85%29.aspx
190 | [DllImport("winmm.dll")]
191 | public static extern MmResult midiOutShortMsg(IntPtr hMidiOut, int dwMsg);
192 |
193 | // http://msdn.microsoft.com/en-us/library/dd798482%28VS.85%29.aspx
194 | [DllImport("winmm.dll")]
195 | public static extern MmResult midiOutUnprepareHeader(IntPtr hMidiOut, [MarshalAs(UnmanagedType.Struct)] ref MIDIHDR lpMidiOutHdr, int uSize);
196 |
197 | // http://msdn.microsoft.com/en-us/library/dd798485%28VS.85%29.aspx
198 | [DllImport("winmm.dll")]
199 | public static extern MmResult midiStreamClose(IntPtr hMidiStream);
200 |
201 | // http://msdn.microsoft.com/en-us/library/dd798486%28VS.85%29.aspx
202 | [DllImport("winmm.dll")]
203 | public static extern MmResult midiStreamOpen(out IntPtr hMidiStream, IntPtr puDeviceID, int cMidi, IntPtr dwCallback, IntPtr dwInstance, int fdwOpen);
204 |
205 | // http://msdn.microsoft.com/en-us/library/dd798487%28VS.85%29.aspx
206 | [DllImport("winmm.dll")]
207 | public static extern MmResult midiStreamOut(IntPtr hMidiStream, [MarshalAs(UnmanagedType.Struct)] ref MIDIHDR pmh, int cbmh);
208 |
209 | // http://msdn.microsoft.com/en-us/library/dd798488%28VS.85%29.aspx
210 | [DllImport("winmm.dll")]
211 | public static extern MmResult midiStreamPause(IntPtr hMidiStream);
212 |
213 | // http://msdn.microsoft.com/en-us/library/dd798489%28VS.85%29.aspx
214 | [DllImport("winmm.dll")]
215 | public static extern MmResult midiStreamPosition(IntPtr hMidiStream, [MarshalAs(UnmanagedType.Struct)] ref MMTIME lpmmt, int cbmmt);
216 |
217 | // http://msdn.microsoft.com/en-us/library/dd798490%28VS.85%29.aspx
218 | [DllImport("winmm.dll")]
219 | public static extern MmResult midiStreamProperty(IntPtr hMidiStream, IntPtr lppropdata, int dwProperty);
220 |
221 | // http://msdn.microsoft.com/en-us/library/dd798491%28VS.85%29.aspx
222 | [DllImport("winmm.dll")]
223 | public static extern MmResult midiStreamRestart(IntPtr hMidiStream);
224 |
225 | // http://msdn.microsoft.com/en-us/library/dd798492%28VS.85%29.aspx
226 | [DllImport("winmm.dll")]
227 | public static extern MmResult midiStreamStop(IntPtr hMidiStream);
228 |
229 | // TODO: this is general MM interop
230 | public const int CALLBACK_FUNCTION = 0x30000;
231 | public const int CALLBACK_NULL = 0;
232 |
233 | // http://msdn.microsoft.com/en-us/library/dd757347%28VS.85%29.aspx
234 | // TODO: not sure this is right
235 | [StructLayout(LayoutKind.Sequential)]
236 | public struct MMTIME
237 | {
238 | public int wType;
239 | public int u;
240 | }
241 |
242 | // TODO: check for ANSI strings in these structs
243 | // TODO: check for WORD params
244 | [StructLayout(LayoutKind.Sequential)]
245 | public struct MIDIEVENT
246 | {
247 | public int dwDeltaTime;
248 | public int dwStreamID;
249 | public int dwEvent;
250 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
251 | public int dwParms;
252 | }
253 |
254 | // http://msdn.microsoft.com/en-us/library/dd798449%28VS.85%29.aspx
255 | [StructLayout(LayoutKind.Sequential)]
256 | public struct MIDIHDR
257 | {
258 | public IntPtr lpData; // LPSTR
259 | public int dwBufferLength; // DWORD
260 | public int dwBytesRecorded; // DWORD
261 | public IntPtr dwUser; // DWORD_PTR
262 | public int dwFlags; // DWORD
263 | public IntPtr lpNext; // struct mididhdr_tag *
264 | public IntPtr reserved; // DWORD_PTR
265 | public int dwOffset; // DWORD
266 | // n.b. MSDN documentation incorrect, see mmsystem.h
267 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
268 | public IntPtr[] dwReserved; // DWORD_PTR dwReserved[8]
269 | }
270 |
271 | [StructLayout(LayoutKind.Sequential)]
272 | public struct MIDIPROPTEMPO
273 | {
274 | public int cbStruct;
275 | public int dwTempo;
276 | }
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiEventCollection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using NAudio.Utils;
5 | using NAudio;
6 |
7 | namespace Midi
8 | {
9 | class MergeSort
10 | {
11 | ///
12 | /// In-place and stable implementation of MergeSort
13 | ///
14 | static void Sort(IList list, int lowIndex, int highIndex, IComparer comparer)
15 | {
16 | if (lowIndex >= highIndex)
17 | {
18 | return;
19 | }
20 |
21 |
22 | int midIndex = (lowIndex + highIndex) / 2;
23 |
24 |
25 | // Partition the list into two lists and Sort them recursively
26 | Sort(list, lowIndex, midIndex, comparer);
27 | Sort(list, midIndex + 1, highIndex, comparer);
28 |
29 | // Merge the two sorted lists
30 | int endLow = midIndex;
31 | int startHigh = midIndex + 1;
32 |
33 |
34 | while ((lowIndex <= endLow) && (startHigh <= highIndex))
35 | {
36 | // MRH, if use < 0 sort is not stable
37 | if (comparer.Compare(list[lowIndex], list[startHigh]) <= 0)
38 | {
39 | lowIndex++;
40 | }
41 | else
42 | {
43 | // list[lowIndex] > list[startHigh]
44 | // The next element comes from the second list,
45 | // move the list[start_hi] element into the next
46 | // position and shuffle all the other elements up.
47 | T t = list[startHigh];
48 |
49 | for (int k = startHigh - 1; k >= lowIndex; k--)
50 | {
51 | list[k + 1] = list[k];
52 | }
53 |
54 | list[lowIndex] = t;
55 | lowIndex++;
56 | endLow++;
57 | startHigh++;
58 | }
59 | }
60 | }
61 |
62 | ///
63 | /// MergeSort a list of comparable items
64 | ///
65 | public static void Sort(IList list) where T : IComparable
66 | {
67 | Sort(list, 0, list.Count - 1, Comparer.Default);
68 | }
69 |
70 | ///
71 | /// MergeSort a list
72 | ///
73 | public static void Sort(IList list, IComparer comparer)
74 | {
75 | Sort(list, 0, list.Count - 1, comparer);
76 | }
77 | }
78 |
79 | ///
80 | /// A helper class to manage collection of MIDI events
81 | /// It has the ability to organise them in tracks
82 | ///
83 | public class MidiEventCollection : IEnumerable>
84 | {
85 | int midiFileType;
86 | List> trackEvents;
87 | int deltaTicksPerQuarterNote;
88 | long startAbsoluteTime;
89 |
90 | ///
91 | /// Creates a new Midi Event collection
92 | ///
93 | /// Initial file type
94 | /// Delta Ticks Per Quarter Note
95 | public MidiEventCollection(int midiFileType, int deltaTicksPerQuarterNote)
96 | {
97 | this.midiFileType = midiFileType;
98 | this.deltaTicksPerQuarterNote = deltaTicksPerQuarterNote;
99 | this.startAbsoluteTime = 0;
100 | trackEvents = new List>();
101 | }
102 |
103 | ///
104 | /// The number of tracks
105 | ///
106 | public int Tracks
107 | {
108 | get
109 | {
110 | return trackEvents.Count;
111 | }
112 | }
113 |
114 | ///
115 | /// The absolute time that should be considered as time zero
116 | /// Not directly used here, but useful for timeshifting applications
117 | ///
118 | public long StartAbsoluteTime
119 | {
120 | get
121 | {
122 | return startAbsoluteTime;
123 | }
124 | set
125 | {
126 | startAbsoluteTime = value;
127 | }
128 | }
129 |
130 | ///
131 | /// The number of ticks per quarter note
132 | ///
133 | public int DeltaTicksPerQuarterNote
134 | {
135 | get { return deltaTicksPerQuarterNote; }
136 | }
137 |
138 | ///
139 | /// Gets events on a specified track
140 | ///
141 | /// Track number
142 | /// The list of events
143 | public IList GetTrackEvents(int trackNumber)
144 | {
145 | return trackEvents[trackNumber];
146 | }
147 |
148 | ///
149 | /// Gets events on a specific track
150 | ///
151 | /// Track number
152 | /// The list of events
153 | public IList this[int trackNumber]
154 | {
155 | get { return trackEvents[trackNumber]; }
156 | }
157 |
158 | ///
159 | /// Adds a new track
160 | ///
161 | /// The new track event list
162 | public IList AddTrack()
163 | {
164 | return AddTrack(null);
165 | }
166 |
167 | ///
168 | /// Adds a new track
169 | ///
170 | /// Initial events to add to the new track
171 | /// The new track event list
172 | public IList AddTrack(IList initialEvents)
173 | {
174 | List events = new List();
175 | if (initialEvents != null)
176 | {
177 | events.AddRange(initialEvents);
178 | }
179 | trackEvents.Add(events);
180 | return events;
181 | }
182 |
183 | ///
184 | /// Removes a track
185 | ///
186 | /// Track number to remove
187 | public void RemoveTrack(int track)
188 | {
189 | trackEvents.RemoveAt(track);
190 | }
191 |
192 | ///
193 | /// Clears all events
194 | ///
195 | public void Clear()
196 | {
197 | trackEvents.Clear();
198 | }
199 |
200 | ///
201 | /// The MIDI file type
202 | ///
203 | public int MidiFileType
204 | {
205 | get
206 | {
207 | return midiFileType;
208 | }
209 | set
210 | {
211 | if (midiFileType != value)
212 | {
213 | // set MIDI file type before calling flatten or explode functions
214 | midiFileType = value;
215 |
216 | if (value == 0)
217 | {
218 | FlattenToOneTrack();
219 | }
220 | else
221 | {
222 | ExplodeToManyTracks();
223 | }
224 | }
225 | }
226 | }
227 |
228 | ///
229 | /// Adds an event to the appropriate track depending on file type
230 | ///
231 | /// The event to be added
232 | /// The original (or desired) track number
233 | /// When adding events in type 0 mode, the originalTrack parameter
234 | /// is ignored. If in type 1 mode, it will use the original track number to
235 | /// store the new events. If the original track was 0 and this is a channel based
236 | /// event, it will create new tracks if necessary and put it on the track corresponding
237 | /// to its channel number
238 | public void AddEvent(MidiEvent midiEvent, int originalTrack)
239 | {
240 | if (midiFileType == 0)
241 | {
242 | EnsureTracks(1);
243 | trackEvents[0].Add(midiEvent);
244 | }
245 | else
246 | {
247 | if(originalTrack == 0)
248 | {
249 | // if its a channel based event, lets move it off to
250 | // a channel track of its own
251 | switch (midiEvent.CommandCode)
252 | {
253 | case MidiCommandCode.NoteOff:
254 | case MidiCommandCode.NoteOn:
255 | case MidiCommandCode.KeyAfterTouch:
256 | case MidiCommandCode.ControlChange:
257 | case MidiCommandCode.PatchChange:
258 | case MidiCommandCode.ChannelAfterTouch:
259 | case MidiCommandCode.PitchWheelChange:
260 | EnsureTracks(midiEvent.Channel + 1);
261 | trackEvents[midiEvent.Channel].Add(midiEvent);
262 | break;
263 | default:
264 | EnsureTracks(1);
265 | trackEvents[0].Add(midiEvent);
266 | break;
267 | }
268 |
269 | }
270 | else
271 | {
272 | // put it on the track it was originally on
273 | EnsureTracks(originalTrack + 1);
274 | trackEvents[originalTrack].Add(midiEvent);
275 | }
276 | }
277 | }
278 |
279 |
280 | private void EnsureTracks(int count)
281 | {
282 | for (int n = trackEvents.Count; n < count; n++)
283 | {
284 | trackEvents.Add(new List());
285 | }
286 | }
287 |
288 | private void ExplodeToManyTracks()
289 | {
290 | IList originalList = trackEvents[0];
291 | Clear();
292 | foreach (MidiEvent midiEvent in originalList)
293 | {
294 | AddEvent(midiEvent, 0);
295 | }
296 | PrepareForExport();
297 | }
298 |
299 | private void FlattenToOneTrack()
300 | {
301 | bool eventsAdded = false;
302 | for (int track = 1; track < trackEvents.Count; track++)
303 | {
304 | foreach (MidiEvent midiEvent in trackEvents[track])
305 | {
306 | if (!MidiEvent.IsEndTrack(midiEvent))
307 | {
308 | trackEvents[0].Add(midiEvent);
309 | eventsAdded = true;
310 | }
311 | }
312 | }
313 | for (int track = trackEvents.Count - 1; track > 0; track--)
314 | {
315 | RemoveTrack(track);
316 | }
317 | if (eventsAdded)
318 | {
319 | PrepareForExport();
320 | }
321 | }
322 |
323 | ///
324 | /// Sorts, removes empty tracks and adds end track markers
325 | ///
326 | public void PrepareForExport()
327 | {
328 | var comparer = new MidiEventComparer();
329 | // 1. sort each track
330 | foreach (List list in trackEvents)
331 | {
332 | MergeSort.Sort(list, comparer);
333 |
334 | // 2. remove all End track events except one at the very end
335 | int index = 0;
336 | while (index < list.Count - 1)
337 | {
338 | if(MidiEvent.IsEndTrack(list[index]))
339 | {
340 | list.RemoveAt(index);
341 | }
342 | else
343 | {
344 | index++;
345 | }
346 | }
347 | }
348 |
349 | int track = 0;
350 | // 3. remove empty tracks and add missing
351 | while (track < trackEvents.Count)
352 | {
353 | IList list = trackEvents[track];
354 | if (list.Count == 0)
355 | {
356 | RemoveTrack(track);
357 | }
358 | else
359 | {
360 | if(list.Count == 1 && MidiEvent.IsEndTrack(list[0]))
361 | {
362 | RemoveTrack(track);
363 | }
364 | else
365 | {
366 | if(!MidiEvent.IsEndTrack(list[list.Count-1]))
367 | {
368 | list.Add(new MetaEvent(MetaEventType.EndTrack, 0, list[list.Count - 1].AbsoluteTime));
369 | }
370 | track++;
371 | }
372 | }
373 | }
374 | }
375 |
376 | ///
377 | /// Gets an enumerator for the lists of track events
378 | ///
379 | public IEnumerator> GetEnumerator()
380 | {
381 | return trackEvents.GetEnumerator();
382 |
383 | }
384 |
385 | ///
386 | /// Gets an enumerator for the lists of track events
387 | ///
388 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
389 | {
390 | return trackEvents.GetEnumerator();
391 | }
392 | }
393 | }
394 |
--------------------------------------------------------------------------------
/ChartConverterShared/Midi/MidiEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using NAudio;
5 |
6 | namespace Midi
7 | {
8 | ///
9 | /// Represents an individual MIDI event
10 | ///
11 | public class MidiEvent : ICloneable
12 | {
13 | /// The MIDI command code
14 | private MidiCommandCode commandCode;
15 | private int channel;
16 | private int deltaTime;
17 | private long absoluteTime;
18 |
19 | ///
20 | /// Creates a MidiEvent from a raw message received using
21 | /// the MME MIDI In APIs
22 | ///
23 | /// The short MIDI message
24 | /// A new MIDI Event
25 | public static MidiEvent FromRawMessage(int rawMessage)
26 | {
27 | long absoluteTime = 0;
28 | int b = rawMessage & 0xFF;
29 | int data1 = (rawMessage >> 8) & 0xFF;
30 | int data2 = (rawMessage >> 16) & 0xFF;
31 | MidiCommandCode commandCode;
32 | int channel = 1;
33 |
34 | if ((b & 0xF0) == 0xF0)
35 | {
36 | // both bytes are used for command code in this case
37 | commandCode = (MidiCommandCode)b;
38 | }
39 | else
40 | {
41 | commandCode = (MidiCommandCode)(b & 0xF0);
42 | channel = (b & 0x0F) + 1;
43 | }
44 |
45 | MidiEvent me;
46 | switch (commandCode)
47 | {
48 | case MidiCommandCode.NoteOn:
49 | case MidiCommandCode.NoteOff:
50 | case MidiCommandCode.KeyAfterTouch:
51 | if (data2 > 0 && commandCode == MidiCommandCode.NoteOn)
52 | {
53 | me = new NoteOnEvent(absoluteTime, channel, data1, data2, 0);
54 | }
55 | else
56 | {
57 | me = new NoteEvent(absoluteTime, channel, commandCode, data1, data2);
58 | }
59 | break;
60 | case MidiCommandCode.ControlChange:
61 | me = new ControlChangeEvent(absoluteTime,channel,(MidiController)data1,data2);
62 | break;
63 | case MidiCommandCode.PatchChange:
64 | me = new PatchChangeEvent(absoluteTime,channel,data1);
65 | break;
66 | case MidiCommandCode.ChannelAfterTouch:
67 | me = new ChannelAfterTouchEvent(absoluteTime,channel,data1);
68 | break;
69 | case MidiCommandCode.PitchWheelChange:
70 | me = new PitchWheelChangeEvent(absoluteTime, channel, data1 + (data2 << 7));
71 | break;
72 | case MidiCommandCode.TimingClock:
73 | case MidiCommandCode.StartSequence:
74 | case MidiCommandCode.ContinueSequence:
75 | case MidiCommandCode.StopSequence:
76 | case MidiCommandCode.AutoSensing:
77 | me = new MidiEvent(absoluteTime,channel,commandCode);
78 | break;
79 | case MidiCommandCode.MetaEvent:
80 | case MidiCommandCode.Sysex:
81 | default:
82 | throw new FormatException(String.Format("Unsupported MIDI Command Code for Raw Message {0}", commandCode));
83 | }
84 | return me;
85 |
86 | }
87 |
88 | ///
89 | /// Constructs a MidiEvent from a BinaryStream
90 | ///
91 | /// The binary stream of MIDI data
92 | /// The previous MIDI event (pass null for first event)
93 | /// A new MidiEvent
94 | public static MidiEvent ReadNextEvent(BinaryReader br, MidiEvent previous)
95 | {
96 | int deltaTime = MidiEvent.ReadVarInt(br);
97 | MidiCommandCode commandCode;
98 | int channel = 1;
99 | byte b = br.ReadByte();
100 | if((b & 0x80) == 0)
101 | {
102 | // a running command - command & channel are same as previous
103 |
104 | commandCode = previous.CommandCode;
105 | channel = previous.Channel;
106 | br.BaseStream.Position--; // need to push this back
107 | }
108 | else
109 | {
110 | if((b & 0xF0) == 0xF0)
111 | {
112 | // both bytes are used for command code in this case
113 | commandCode = (MidiCommandCode) b;
114 | }
115 | else
116 | {
117 | commandCode = (MidiCommandCode) (b & 0xF0);
118 | channel = (b & 0x0F) + 1;
119 | }
120 | }
121 |
122 | MidiEvent me;
123 | switch(commandCode)
124 | {
125 | case MidiCommandCode.NoteOn:
126 | me = new NoteOnEvent(br);
127 | break;
128 | case MidiCommandCode.NoteOff:
129 | case MidiCommandCode.KeyAfterTouch:
130 | me = new NoteEvent(br);
131 | break;
132 | case MidiCommandCode.ControlChange:
133 | me = new ControlChangeEvent(br);
134 | break;
135 | case MidiCommandCode.PatchChange:
136 | me = new PatchChangeEvent(br);
137 | break;
138 | case MidiCommandCode.ChannelAfterTouch:
139 | me = new ChannelAfterTouchEvent(br);
140 | break;
141 | case MidiCommandCode.PitchWheelChange:
142 | me = new PitchWheelChangeEvent(br);
143 | break;
144 | case MidiCommandCode.TimingClock:
145 | case MidiCommandCode.StartSequence:
146 | case MidiCommandCode.ContinueSequence:
147 | case MidiCommandCode.StopSequence:
148 | me = new MidiEvent();
149 | break;
150 | case MidiCommandCode.Sysex:
151 | me = SysexEvent.ReadSysexEvent(br);
152 | break;
153 | case MidiCommandCode.MetaEvent:
154 | me = MetaEvent.ReadMetaEvent(br);
155 | break;
156 | default:
157 | throw new FormatException(String.Format("Unsupported MIDI Command Code {0:X2}",(byte) commandCode));
158 | }
159 | me.channel = channel;
160 | me.deltaTime = deltaTime;
161 | me.commandCode = commandCode;
162 | return me;
163 | }
164 |
165 | ///
166 | /// Converts this MIDI event to a short message (32 bit integer) that
167 | /// can be sent by the Windows MIDI out short message APIs
168 | /// Cannot be implemented for all MIDI messages
169 | ///
170 | /// A short message
171 | public virtual int GetAsShortMessage()
172 | {
173 | return (channel - 1) + (int)commandCode;
174 | }
175 |
176 | ///
177 | /// Default constructor
178 | ///
179 | protected MidiEvent()
180 | {
181 | }
182 |
183 | ///
184 | /// Creates a MIDI event with specified parameters
185 | ///
186 | /// Absolute time of this event
187 | /// MIDI channel number
188 | /// MIDI command code
189 | public MidiEvent(long absoluteTime, int channel, MidiCommandCode commandCode)
190 | {
191 | this.absoluteTime = absoluteTime;
192 | this.Channel = channel;
193 | this.commandCode = commandCode;
194 | }
195 |
196 | ///
197 | /// Creates a deep clone of this MIDI event.
198 | ///
199 | public virtual MidiEvent Clone() => (MidiEvent)MemberwiseClone();
200 |
201 | object ICloneable.Clone() => Clone();
202 |
203 | ///
204 | /// The MIDI Channel Number for this event (1-16)
205 | ///
206 | public virtual int Channel
207 | {
208 | get
209 | {
210 | return channel;
211 | }
212 | set
213 | {
214 | if ((value < 1) || (value > 16))
215 | {
216 | throw new ArgumentOutOfRangeException("value", value,
217 | String.Format("Channel must be 1-16 (Got {0})",value));
218 | }
219 | channel = value;
220 | }
221 | }
222 |
223 | ///
224 | /// The Delta time for this event
225 | ///
226 | public int DeltaTime
227 | {
228 | get
229 | {
230 | return deltaTime;
231 | }
232 | }
233 |
234 | ///
235 | /// The absolute time for this event
236 | ///
237 | public long AbsoluteTime
238 | {
239 | get
240 | {
241 | return absoluteTime;
242 | }
243 | set
244 | {
245 | absoluteTime = value;
246 | }
247 | }
248 |
249 | ///
250 | /// The command code for this event
251 | ///
252 | public MidiCommandCode CommandCode
253 | {
254 | get
255 | {
256 | return commandCode;
257 | }
258 | }
259 |
260 | ///
261 | /// Whether this is a note off event
262 | ///
263 | public static bool IsNoteOff(MidiEvent midiEvent)
264 | {
265 | if (midiEvent != null)
266 | {
267 | if (midiEvent.CommandCode == MidiCommandCode.NoteOn)
268 | {
269 | NoteEvent ne = (NoteEvent)midiEvent;
270 | return (ne.Velocity == 0);
271 | }
272 | return (midiEvent.CommandCode == MidiCommandCode.NoteOff);
273 | }
274 | return false;
275 | }
276 |
277 | ///
278 | /// Whether this is a note on event
279 | ///
280 | public static bool IsNoteOn(MidiEvent midiEvent)
281 | {
282 | if (midiEvent != null)
283 | {
284 | if (midiEvent.CommandCode == MidiCommandCode.NoteOn)
285 | {
286 | NoteEvent ne = (NoteEvent)midiEvent;
287 | return (ne.Velocity > 0);
288 | }
289 | }
290 | return false;
291 | }
292 |
293 | ///
294 | /// Determines if this is an end track event
295 | ///
296 | public static bool IsEndTrack(MidiEvent midiEvent)
297 | {
298 | if (midiEvent != null)
299 | {
300 | MetaEvent me = midiEvent as MetaEvent;
301 | if (me != null)
302 | {
303 | return me.MetaEventType == MetaEventType.EndTrack;
304 | }
305 | }
306 | return false;
307 | }
308 |
309 |
310 | ///
311 | /// Displays a summary of the MIDI event
312 | ///
313 | /// A string containing a brief description of this MIDI event
314 | public override string ToString()
315 | {
316 | if(commandCode >= MidiCommandCode.Sysex)
317 | return String.Format("{0} {1}",absoluteTime,commandCode);
318 | else
319 | return String.Format("{0} {1} Ch: {2}", absoluteTime, commandCode, channel);
320 | }
321 |
322 | ///
323 | /// Utility function that can read a variable length integer from a binary stream
324 | ///
325 | /// The binary stream
326 | /// The integer read
327 | public static int ReadVarInt(BinaryReader br)
328 | {
329 | int value = 0;
330 | byte b;
331 | for(int n = 0; n < 4; n++)
332 | {
333 | b = br.ReadByte();
334 | value <<= 7;
335 | value += (b & 0x7F);
336 | if((b & 0x80) == 0)
337 | {
338 | return value;
339 | }
340 | }
341 | throw new FormatException("Invalid Var Int");
342 | }
343 |
344 | ///
345 | /// Writes a variable length integer to a binary stream
346 | ///
347 | /// Binary stream
348 | /// The value to write
349 | public static void WriteVarInt(BinaryWriter writer, int value)
350 | {
351 | if (value < 0)
352 | {
353 | throw new ArgumentOutOfRangeException("value", value, "Cannot write a negative Var Int");
354 | }
355 | if (value > 0x0FFFFFFF)
356 | {
357 | throw new ArgumentOutOfRangeException("value", value, "Maximum allowed Var Int is 0x0FFFFFFF");
358 | }
359 |
360 | int n = 0;
361 | byte[] buffer = new byte[4];
362 | do
363 | {
364 | buffer[n++] = (byte)(value & 0x7F);
365 | value >>= 7;
366 | } while (value > 0);
367 |
368 | while (n > 0)
369 | {
370 | n--;
371 | if(n > 0)
372 | writer.Write((byte) (buffer[n] | 0x80));
373 | else
374 | writer.Write(buffer[n]);
375 | }
376 | }
377 |
378 | ///
379 | /// Exports this MIDI event's data
380 | /// Overriden in derived classes, but they should call this version
381 | ///
382 | /// Absolute time used to calculate delta.
383 | /// Is updated ready for the next delta calculation
384 | /// Stream to write to
385 | public virtual void Export(ref long absoluteTime, BinaryWriter writer)
386 | {
387 | if (this.absoluteTime < absoluteTime)
388 | {
389 | throw new FormatException("Can't export unsorted MIDI events");
390 | }
391 | WriteVarInt(writer,(int) (this.absoluteTime - absoluteTime));
392 | absoluteTime = this.absoluteTime;
393 | int output = (int) commandCode;
394 | if (commandCode != MidiCommandCode.MetaEvent)
395 | {
396 | output += (channel - 1);
397 | }
398 | writer.Write((byte)output);
399 | }
400 | }
401 | }
--------------------------------------------------------------------------------
/ChartConverterShared/MainInterface.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework.Input;
2 | using PsarcUtil;
3 | using SongFormat;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Reflection;
8 | using System.Threading.Tasks;
9 | using UILayout;
10 | using static System.Net.Mime.MediaTypeNames;
11 |
12 | namespace ChartConverter
13 | {
14 | public class MainInterface : VerticalStack
15 | {
16 | public static UIColor PanelBackgroundColor = new UIColor(50, 55, 65);
17 | public static UIColor PanelBackgroundColorDark = PanelBackgroundColor * 0.8f;
18 | public static UIColor PanelBackgroundColorDarkest = PanelBackgroundColor * 0.5f;
19 | public static UIColor PanelBackgroundColorLight = PanelBackgroundColor * 1.5f;
20 | public static UIColor PanelBackgroundColorLightest = PanelBackgroundColor * 3.0f;
21 | public static UIColor PanelForegroundColor = UIColor.Lerp(PanelBackgroundColor, UIColor.White, 0.75f);
22 |
23 |
24 | string saveFolder;
25 | string saveFile;
26 |
27 | ConvertOptions convertOptions;
28 |
29 | TextBlock songOutputText;
30 |
31 | VerticalStack convertStack;
32 | TextBlock currentlyConverting;
33 | int songsConverted;
34 | TextBlock songsConvertedText;
35 | TextButton convertButton;
36 | TextToggleButton convertPsarcButton;
37 | TextToggleButton convertRBButton;
38 |
39 | bool abortConversion;
40 | bool convertRunning;
41 | string oneOffPath;
42 |
43 | static MainInterface()
44 | {
45 | Layout.Current.DefaultForegroundColor = UIColor.White;
46 | }
47 |
48 | public MainInterface()
49 | {
50 | HorizontalAlignment = EHorizontalAlignment.Stretch;
51 | VerticalAlignment = EVerticalAlignment.Stretch;
52 | Padding = new LayoutPadding(5);
53 | BackgroundColor = UIColor.Black;
54 |
55 | saveFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ChartConverter");
56 | saveFile = Path.Combine(saveFolder, "Options.xml");
57 |
58 | try
59 | {
60 | if (!Directory.Exists(saveFolder))
61 | {
62 | Directory.CreateDirectory(saveFolder);
63 | }
64 |
65 | convertOptions = ConvertOptions.Load(saveFile);
66 | }
67 | catch { }
68 |
69 | if (convertOptions == null)
70 | {
71 | convertOptions = new ConvertOptions();
72 |
73 | convertOptions.PsarcFiles.Add(@"C:\Program Files (x86)\Steam\steamapps\common\Rocksmith2014\songs.psarc");
74 | convertOptions.PsarcFolders.Add(@"C:\Program Files (x86)\Steam\steamapps\common\Rocksmith2014\dlc");
75 | }
76 |
77 | TabPanel tabPanel = new TabPanel(PanelBackgroundColorDark, UIColor.White, Layout.Current.GetImage("TabPanelBackground"), Layout.Current.GetImage("TabForeground"), Layout.Current.GetImage("TabBackground"), 5, 5);
78 | Children.Add(tabPanel);
79 |
80 | tabPanel.AddTab("General", GeneralTab());
81 | tabPanel.AddTab("Psarc", PsarcTab());
82 | tabPanel.AddTab("RockBand", RockBandTab());
83 |
84 | NinePatchWrapper bottomSection = new NinePatchWrapper(Layout.Current.DefaultOutlineNinePatch)
85 | {
86 | HorizontalAlignment = EHorizontalAlignment.Stretch,
87 | VerticalAlignment = EVerticalAlignment.Stretch
88 | };
89 | Children.Add(bottomSection);
90 |
91 | Dock convertDock = new Dock();
92 | bottomSection.Child = convertDock;
93 |
94 | convertStack = new VerticalStack()
95 | {
96 | HorizontalAlignment = EHorizontalAlignment.Stretch,
97 | ChildSpacing = 10,
98 | };
99 | convertDock.Children.Add(convertStack);
100 |
101 | convertStack.Children.Add(new TextBlock("Conversion status:")
102 | {
103 | Padding = new LayoutPadding(0, 10)
104 | });
105 |
106 | songsConvertedText = new TextBlock("");
107 | convertStack.Children.Add(songsConvertedText);
108 |
109 | currentlyConverting = new TextBlock("") { Padding = new LayoutPadding(0, 3) };
110 | convertStack.Children.Add(currentlyConverting);
111 |
112 | HorizontalStack buttonStack = new HorizontalStack()
113 | {
114 | VerticalAlignment = EVerticalAlignment.Bottom,
115 | ChildSpacing = 5
116 | };
117 | convertDock.Children.Add(buttonStack);
118 |
119 | convertButton = new TextButton("Convert Files")
120 | {
121 | ClickAction = ConvertFiles
122 | };
123 |
124 | buttonStack.Children.Add(convertButton);
125 |
126 | buttonStack.Children.Add(new TextBlock("Convert Psarc: ") { Padding = (20, 0, 0, 0), VerticalAlignment = EVerticalAlignment.Center });
127 | buttonStack.Children.Add(convertPsarcButton = new TextToggleButton("Yes", "No")
128 | {
129 | PressAction = delegate
130 | {
131 | convertOptions.ConvertPsarc = convertPsarcButton.IsPressed;
132 | SaveOptions();
133 | }
134 | });
135 |
136 | buttonStack.Children.Add(new TextBlock("Convert RockBand: ") { VerticalAlignment = EVerticalAlignment.Center } );
137 | buttonStack.Children.Add(convertRBButton = new TextToggleButton("Yes", "No")
138 | {
139 | PressAction = delegate
140 | {
141 | convertOptions.ConvertRockBand = convertRBButton.IsPressed;
142 | SaveOptions();
143 | }
144 | });
145 |
146 | convertPsarcButton.SetPressed(convertOptions.ConvertPsarc);
147 | convertRBButton.SetPressed(convertOptions.ConvertRockBand);
148 | }
149 |
150 | UIElement GeneralTab()
151 | {
152 | VerticalStack vStack = new VerticalStack()
153 | {
154 | HorizontalAlignment = EHorizontalAlignment.Stretch,
155 | ChildSpacing = 5
156 | };
157 |
158 | vStack.Children.Add(new TextBlock("Song Output Folder:")
159 | {
160 | Padding = new LayoutPadding(0, 10)
161 | });
162 |
163 | HorizontalStack pathStack = new HorizontalStack()
164 | {
165 | ChildSpacing = 10
166 | };
167 | vStack.Children.Add(pathStack);
168 |
169 | pathStack.Children.Add(songOutputText = new TextBlock(convertOptions.SongOutputPath)
170 | {
171 | VerticalAlignment = EVerticalAlignment.Center
172 | });
173 |
174 | pathStack.Children.Add(new TextButton("Select")
175 | {
176 | ClickAction = SelectSongPath
177 | });
178 |
179 | return vStack;
180 | }
181 |
182 | UIElement PsarcTab()
183 | {
184 | VerticalStack vStack = new VerticalStack()
185 | {
186 | HorizontalAlignment = EHorizontalAlignment.Stretch,
187 | ChildSpacing = 5
188 | };
189 |
190 | FolderFileList psarcFiles = new FolderFileList("Psarc Files:", convertOptions.PsarcFiles, Update, isFolder: false);
191 | psarcFiles.SetFilter("Psarc Files", "psarc");
192 | vStack.Children.Add(psarcFiles);
193 |
194 | FolderFileList psarcFolders = new FolderFileList("Psarc Folders:", convertOptions.PsarcFolders, Update, isFolder: true);
195 | vStack.Children.Add(psarcFolders);
196 |
197 | TextButton oneShotButton = new TextButton("One Off")
198 | {
199 | ClickAction = delegate
200 | {
201 | oneOffPath = Layout.Current.GetFolder(null);
202 |
203 | if (!string.IsNullOrEmpty(oneOffPath))
204 | {
205 | ConvertFiles();
206 | }
207 | }
208 | };
209 | vStack.Children.Add(oneShotButton);
210 |
211 | return vStack;
212 | }
213 |
214 | UIElement RockBandTab()
215 | {
216 | VerticalStack vStack = new VerticalStack()
217 | {
218 | HorizontalAlignment = EHorizontalAlignment.Stretch,
219 | ChildSpacing = 5
220 | };
221 |
222 | HorizontalStack copyStack = new HorizontalStack();
223 | vStack.Children.Add(copyStack);
224 |
225 | copyStack.Children.Add(new TextBlock("Copy RockBand Audio: ")
226 | {
227 | VerticalAlignment = EVerticalAlignment.Center
228 | });
229 |
230 | TextToggleButton copyRockBandButton = null;
231 |
232 | copyStack.Children.Add(copyRockBandButton = new TextToggleButton("Yes", "No")
233 | {
234 | VerticalAlignment = EVerticalAlignment.Center,
235 | PressAction = delegate
236 | {
237 | convertOptions.CopyRockBandAudio = !convertOptions.CopyRockBandAudio;
238 | SaveOptions();
239 | }
240 | });
241 |
242 | copyRockBandButton.SetPressed(convertOptions.CopyRockBandAudio);
243 |
244 | FolderFileList psarcFolders = new FolderFileList("RockBand Folders:", convertOptions.RockBandFolders, Update, isFolder: true);
245 | vStack.Children.Add(psarcFolders);
246 |
247 | return vStack;
248 | }
249 |
250 |
251 | public void SaveOptions()
252 | {
253 | convertOptions.Save(saveFile);
254 | }
255 |
256 | void SelectSongPath()
257 | {
258 | string newPath = Layout.Current.GetFolder(convertOptions.SongOutputPath);
259 |
260 | if (!string.IsNullOrEmpty(newPath))
261 | {
262 | convertOptions.SongOutputPath = newPath;
263 |
264 | songOutputText.Text = convertOptions.SongOutputPath;
265 |
266 | UpdateContentLayout();
267 |
268 | SaveOptions();
269 | }
270 | }
271 |
272 | void Update()
273 | {
274 | UpdateContentLayout();
275 | SaveOptions();
276 | }
277 |
278 | HashSet convertedPaths = new();
279 |
280 | EConvertOption UpdateRocksmithConvert(string artistName, string songName, string songDir)
281 | {
282 | if (convertedPaths.Contains(songDir))
283 | {
284 | }
285 |
286 | convertedPaths.Add(songDir);
287 |
288 | currentlyConverting.Text = artistName + " - " + songName;
289 |
290 | songsConverted++;
291 | songsConvertedText.Text = songsConverted + " Songs";
292 |
293 | convertStack.UpdateContentLayout();
294 |
295 | if (abortConversion)
296 | {
297 | abortConversion = false;
298 |
299 | return EConvertOption.Abort;
300 | }
301 |
302 | return EConvertOption.Continue;
303 | }
304 |
305 | bool UpdateRockBandConvert(string text)
306 | {
307 | currentlyConverting.Text = text;
308 |
309 | songsConverted++;
310 | songsConvertedText.Text = songsConverted + " Songs";
311 |
312 | convertStack.UpdateContentLayout();
313 |
314 | if (abortConversion)
315 | {
316 | abortConversion = false;
317 |
318 | return false;
319 | }
320 |
321 | return true;
322 | }
323 |
324 | void ConvertFiles()
325 | {
326 | if (string.IsNullOrEmpty(convertOptions.SongOutputPath))
327 | {
328 | Layout.Current.ShowContinuePopup("Please select a song output path.");
329 |
330 | return;
331 | }
332 |
333 | if (convertRunning)
334 | {
335 | abortConversion = true;
336 | }
337 | else
338 | {
339 | convertRunning = true;
340 |
341 | convertButton.Text = "Abort Conversion";
342 | UpdateContentLayout();
343 |
344 | Task.Run(() =>
345 | {
346 | DoConvert();
347 |
348 | oneOffPath = null;
349 |
350 | convertRunning = false;
351 | convertButton.Text = "Convert Files";
352 |
353 | currentlyConverting.Text = "Finished";
354 |
355 | convertStack.UpdateContentLayout();
356 |
357 | convertButton.UpdateContentLayout();
358 | });
359 | }
360 | }
361 |
362 | void DoConvert()
363 | {
364 | convertedPaths.Clear();
365 |
366 | songsConverted = 0;
367 |
368 | if (convertOptions.ConvertPsarc)
369 | {
370 | if (!ConvertPsarc())
371 | return;
372 | }
373 |
374 | if (convertOptions.ConvertRockBand)
375 | {
376 | ConvertRockBand();
377 | }
378 | }
379 |
380 | bool ConvertPsarc()
381 | {
382 | PsarcConverter converter = new(convertOptions.SongOutputPath)
383 | {
384 | OverwriteAudio = false,
385 | OverwriteData = true,
386 | UpdateAction = UpdateRocksmithConvert
387 | };
388 |
389 | if (!string.IsNullOrEmpty(oneOffPath))
390 | {
391 | try
392 | {
393 | converter.ConvertFolder(oneOffPath);
394 | }
395 | catch { }
396 |
397 | return false;
398 | }
399 |
400 | foreach (string file in convertOptions.PsarcFiles)
401 | {
402 | try
403 | {
404 | if (!converter.ConvertPsarc(file))
405 | {
406 | return false;
407 | }
408 | }
409 | catch { }
410 | }
411 |
412 | foreach (string folder in convertOptions.PsarcFolders)
413 | {
414 | try
415 | {
416 | if (!converter.ConvertFolder(folder))
417 | return false;
418 | }
419 | catch { }
420 | }
421 |
422 | return true;
423 | }
424 |
425 | bool ConvertRockBand()
426 | {
427 | if (!string.IsNullOrEmpty(oneOffPath))
428 | return false;
429 |
430 | var converter = new RockBandConverter(convertOptions.SongOutputPath, convertOptions.CopyRockBandAudio);
431 | converter.UpdateAction = UpdateRockBandConvert;
432 |
433 | foreach (string folder in convertOptions.RockBandFolders)
434 | {
435 | try
436 | {
437 | if (!converter.ConvertAll(folder))
438 | return false;
439 | }
440 | catch { }
441 | }
442 |
443 | return true;
444 | }
445 | }
446 |
447 | class FolderFileList : VerticalStack
448 | {
449 | List itemList = null;
450 | VerticalStack itemStack = new VerticalStack();
451 | Action updateAction;
452 | bool isFolder = false;
453 | string filterName = null;
454 | string filterWildcard = null;
455 |
456 | public FolderFileList(string name, List folderList, Action updateAction, bool isFolder)
457 | {
458 | this.itemList = folderList;
459 | this.updateAction = updateAction;
460 | this.isFolder = isFolder;
461 |
462 | ChildSpacing = 5;
463 |
464 | Children.Add(new TextBlock(name)
465 | {
466 | Padding = new LayoutPadding(0, 10)
467 | });
468 |
469 | Children.Add(itemStack = new VerticalStack()
470 | {
471 | ChildSpacing = 5
472 | });
473 |
474 | Children.Add(new TextButton("Add " + (isFolder ? "Folder" : "File"))
475 | {
476 | ClickAction = Add
477 | });
478 |
479 | UpdateSources();
480 | }
481 |
482 | public void SetFilter(string filterName, string filterWildcard)
483 | {
484 | this.filterName = filterName;
485 | this.filterWildcard = filterWildcard;
486 | }
487 |
488 | void Add()
489 | {
490 | string newPath = isFolder ? Layout.Current.GetFolder(null) : Layout.Current.GetFile(null, filterName, filterWildcard);
491 |
492 | if (!string.IsNullOrEmpty(newPath) && !itemList.Contains(newPath))
493 | {
494 | itemList.Add(newPath);
495 |
496 | UpdateSources();
497 |
498 | if (updateAction != null)
499 | updateAction();
500 | }
501 | }
502 |
503 | void Delete(string path)
504 | {
505 | itemList.Remove(path);
506 |
507 | UpdateSources();
508 |
509 | if (updateAction != null)
510 | updateAction();
511 | }
512 |
513 | void UpdateSources()
514 | {
515 | itemStack.Children.Clear();
516 |
517 | foreach (string item in itemList)
518 | {
519 | HorizontalStack stack = new HorizontalStack()
520 | {
521 | ChildSpacing = 10
522 | };
523 | itemStack.Children.Add(stack);
524 |
525 | stack.Children.Add(new TextBlock(item)
526 | {
527 | VerticalAlignment = EVerticalAlignment.Center
528 | });
529 |
530 | stack.Children.Add(new TextButton("X")
531 | {
532 | TextColor = UIColor.Lerp(UIColor.Red, UIColor.Black, 0.5f),
533 | ClickAction = delegate { Delete(item); },
534 | });
535 | }
536 | }
537 | }
538 | }
539 |
--------------------------------------------------------------------------------