diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e1639d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/*.sln +/bin +/obj +/.vs diff --git a/MidiApiWindows.cs b/MidiApiWindows.cs new file mode 100644 index 0000000..fe54f6d --- /dev/null +++ b/MidiApiWindows.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace RushMidiLibrary { + public static class MidiApiWindows { + [DllImport("winmm.dll")] + public static extern uint midiOutGetNumDevs(); + [DllImport("winmm.dll")] + public static extern uint midiInGetNumDevs(); + + public enum MMRESULT : uint { + MMSYSERR_BASE = 0, + MMSYSERR_NOERROR = MMSYSERR_BASE + 0, + MMSYSERR_ERROR = MMSYSERR_BASE + 1, + MMSYSERR_BADDEVICEID = MMSYSERR_BASE + 2, + MMSYSERR_NOTENABLED = MMSYSERR_BASE + 3, + MMSYSERR_ALLOCATED = MMSYSERR_BASE + 4, + MMSYSERR_INVALHANDLE = MMSYSERR_BASE + 5, + MMSYSERR_NODRIVER = MMSYSERR_BASE + 6, + MMSYSERR_NOMEM = MMSYSERR_BASE + 7, + MMSYSERR_NOTSUPPORTED = MMSYSERR_BASE + 8, + MMSYSERR_BADERRNUM = MMSYSERR_BASE + 9, + MMSYSERR_INVALFLAG = MMSYSERR_BASE + 10, + MMSYSERR_INVALPARAM = MMSYSERR_BASE + 11, + MMSYSERR_HANDLEBUSY = MMSYSERR_BASE + 12, + MMSYSERR_INVALIDALIAS = MMSYSERR_BASE + 13, + MMSYSERR_BADDB = MMSYSERR_BASE + 14, + MMSYSERR_KEYNOTFOUND = MMSYSERR_BASE + 15, + MMSYSERR_READERROR = MMSYSERR_BASE + 16, + MMSYSERR_WRITEERROR = MMSYSERR_BASE + 17, + MMSYSERR_DELETEERROR = MMSYSERR_BASE + 18, + MMSYSERR_VALNOTFOUND = MMSYSERR_BASE + 19, + MMSYSERR_NODRIVERCB = MMSYSERR_BASE + 20, + MMSYSERR_MOREDATA = MMSYSERR_BASE + 21, + MMSYSERR_LASTERROR = MMSYSERR_BASE + 21, + + + MIDIERR_BASE = 64, + MIDIERR_UNPREPARED = MIDIERR_BASE + 0, + MIDIERR_STILLPLAYING = MIDIERR_BASE + 1, + MIDIERR_NOMAP = MIDIERR_BASE + 2, + MIDIERR_NOTREADY = MIDIERR_BASE + 3, + MIDIERR_NODEVICE = MIDIERR_BASE + 4, + MIDIERR_INVALIDSETUP = MIDIERR_BASE + 5, + MIDIERR_BADOPENMODE = MIDIERR_BASE + 6, + MIDIERR_DONT_CONTINUE = MIDIERR_BASE + 7, + MIDIERR_LASTERROR = MIDIERR_BASE + 7 + } + + + [DllImport("winmm.dll")] + public static extern MMRESULT midiInGetErrorText(MMRESULT errCode, StringBuilder message, uint sizeOfMessage); + + public static MMRESULT midiInGetErrorText(MMRESULT mmrError, StringBuilder lpText) { + return midiInGetErrorText(mmrError, lpText, (UInt32)lpText.Capacity); + } + + + + [DllImport("winmm.dll")] + private static extern MMRESULT midiInGetDevCaps(UIntPtr uDeviceID, out MIDIINCAPS caps, uint sizeOfMidiInCaps); + public static MMRESULT midiInGetDevCaps(UIntPtr uDeviceID, out MIDIINCAPS caps) { + return midiInGetDevCaps(uDeviceID, out caps, (uint)Marshal.SizeOf()); + } + + + [DllImport("winmm.dll")] + public static extern MMRESULT midiInOpen(out HMIDIIN phmi, UIntPtr uDeviceID, MidiInProc dwCallback, UIntPtr dwInstance, MidiOpenFlags fdwOpen); + + public static MMRESULT midiInOpen(out HMIDIIN phmi, UIntPtr uDeviceID, MidiInProc dwCallback, UIntPtr dwInstance) { + return midiInOpen(out phmi, uDeviceID, dwCallback, dwInstance, dwCallback == null ? MidiOpenFlags.CALLBACK_NULL : MidiOpenFlags.CALLBACK_FUNCTION); + } + + + [DllImport("winmm.dll")] + public static extern MMRESULT midiInClose(HMIDIIN hmi); + [DllImport("winmm.dll")] + public static extern MMRESULT midiInStart(HMIDIIN hmi); + [DllImport("winmm.dll")] + public static extern MMRESULT midiInStop(HMIDIIN hmi); + [DllImport("winmm.dll")] + public static extern MMRESULT midiInReset(HMIDIIN hmi); + [DllImport("winmm.dll")] + public static extern MMRESULT midiInAddBuffer(HMIDIIN hmi, ref MIDIHDR lpMidiHeader, uint wSize); + [DllImport("winmm.dll")] + public static extern MMRESULT midiInPrepareHeader(HMIDIIN handle, ref MIDIHDR lpMidiHeader, uint wSize); + [DllImport("winmm.dll")] + public static extern MMRESULT midiInUnprepareHeader(HMIDIIN handle, ref MIDIHDR lpMidiHeader, uint wSize); + + + [DllImport("Kernel32.dll", CallingConvention = CallingConvention.Winapi)] + public static extern void GetSystemTimePreciseAsFileTime(out long filetime); + + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate void MidiInProc(HMIDIIN handle, MidiInMessage wMsg, UIntPtr dwInstance, UIntPtr dwParam1, UIntPtr dwParam2); + + + public struct HMIDIIN { + public IntPtr handle; + } + + public struct HMIDIOUT { + public IntPtr handle; + } + + public enum MidiInMessage : uint { + MIM_OPEN = 0x3C1, + MIM_CLOSE = 0x3C2, + MIM_DATA = 0x3C3, + MIM_LONGDATA = 0x3C4, + MIM_ERROR = 0x3C5, + MIM_LONGERROR = 0x3C6, + MIM_MOREDATA = 0x3CC + } + + public enum MidiOpenFlags : uint { + CALLBACK_TYPEMASK = 0x70000, + CALLBACK_NULL = 0x00000, + CALLBACK_WINDOW = 0x10000, + CALLBACK_TASK = 0x20000, + CALLBACK_FUNCTION = 0x30000, + CALLBACK_THREAD = CALLBACK_TASK, + CALLBACK_EVENT = 0x50000, + MIDI_IO_STATUS = 0x00020 + } + + + [StructLayout(LayoutKind.Sequential)] + public struct MIDIINCAPS { + public ushort wMid; + public ushort wPid; + public uint vDriverVersion; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string szPname; + public uint dwSupport; + } + + [StructLayout(LayoutKind.Sequential)] + public struct MIDIHDR { + public UIntPtr lpData; + public uint dwBufferLength; + public uint dwBytesRecorded; + public UIntPtr dwUser; + public uint dwFlags; + public UIntPtr lpNext; + public UIntPtr reserved; + public uint dwOffset; + public UIntPtr dwReserved0; + public UIntPtr dwReserved1; + public UIntPtr dwReserved2; + public UIntPtr dwReserved3; + public UIntPtr dwReserved4; + public UIntPtr dwReserved5; + public UIntPtr dwReserved6; + public UIntPtr dwReserved7; + } + } +} diff --git a/MidiChannel.cs b/MidiChannel.cs new file mode 100644 index 0000000..8afd50c --- /dev/null +++ b/MidiChannel.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RushMidiLibrary { + public enum MidiChannel { + Channel1 = 0, + Channel2 = 1, + Channel3 = 2, + Channel4 = 3, + Channel5 = 4, + Channel6 = 5, + Channel7 = 6, + Channel8 = 7, + Channel9 = 8, + Channel10 = 9, + Channel11 = 10, + Channel12 = 11, + Channel13 = 12, + Channel14 = 13, + Channel15 = 14, + Channel16 = 15 + }; +} diff --git a/MidiControl.cs b/MidiControl.cs new file mode 100644 index 0000000..bc12a77 --- /dev/null +++ b/MidiControl.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RushMidiLibrary { + public enum MidiControl { + ModulationWheel = 1, + DataEntryMSB = 6, + Volume = 7, + Pan = 10, + Expression = 11, + DataEntryLSB = 38, + SustainPedal = 64, + ReverbLevel = 91, + TremoloLevel = 92, + ChorusLevel = 93, + CelesteLevel = 94, + PhaserLevel = 95, + NonRegisteredParameterLSB = 98, + NonRegisteredParameterMSB = 99, + RegisteredParameterNumberLSB = 100, + RegisteredParameterNumberMSB = 101, + AllControllersOff = 121, + AllNotesOff = 123 + } +} diff --git a/MidiDevice.cs b/MidiDevice.cs new file mode 100644 index 0000000..a942554 --- /dev/null +++ b/MidiDevice.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace RushMidiLibrary { + public class MidiDevice { + + + protected void CheckMMResult(MidiApiWindows.MMRESULT errorCode) { + if(errorCode != MidiApiWindows.MMRESULT.MMSYSERR_NOERROR) { + StringBuilder errorMessage = new StringBuilder(128); + errorCode = MidiApiWindows.midiInGetErrorText(errorCode, errorMessage); + if (errorCode != MidiApiWindows.MMRESULT.MMSYSERR_NOERROR) { + throw new MidiDeviceException("Unknown error"); + } + throw new MidiDeviceException(errorMessage.ToString()); + } + } + } +} diff --git a/MidiDeviceException.cs b/MidiDeviceException.cs new file mode 100644 index 0000000..fa0a1a5 --- /dev/null +++ b/MidiDeviceException.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RushMidiLibrary { + public class MidiDeviceException : Exception { + public MidiDeviceException(string message) : base(message) { + + } + } +} diff --git a/MidiInputDevice.cs b/MidiInputDevice.cs new file mode 100644 index 0000000..5c4c7df --- /dev/null +++ b/MidiInputDevice.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; +using static RushMidiLibrary.MidiApiWindows; + +namespace RushMidiLibrary { + public class MidiInputDevice : MidiDevice { + private MidiApiWindows.MidiInProc _inputCallback; + private UIntPtr _deviceId; + private MidiApiWindows.HMIDIIN _deviceHandle; + private Thread _messageProcessingThread; + private bool _messageProcessingThreadExitRequest = false; + + readonly BlockingCollection _messageQueue = new BlockingCollection(); + + private struct Message { + public MidiApiWindows.HMIDIIN hMidiIn; + public MidiApiWindows.MidiInMessage wMsg; + public UIntPtr dwInstance; + public UIntPtr dwParam1; + public UIntPtr dwParam2; + } + + public MidiInputDevice(UIntPtr deviceId, MidiApiWindows.MIDIINCAPS caps) { + _deviceId = deviceId; + } + + public static MidiInputDevice[] Enumerate() { + var devs = MidiApiWindows.midiInGetNumDevs(); + MidiInputDevice[] result = new MidiInputDevice[devs]; + for (uint deviceId = 0; deviceId < devs; deviceId++) { + MidiApiWindows.MIDIINCAPS caps; + MidiApiWindows.midiInGetDevCaps((UIntPtr)deviceId, out caps); + result[deviceId] = new MidiInputDevice((UIntPtr)deviceId, caps); + } + return result; + } + + public void Open() { + _inputCallback = this.InputCallback; + + BeginMessageProcessing(); + try { + CheckMMResult(MidiApiWindows.midiInOpen(out _deviceHandle, _deviceId, _inputCallback, (UIntPtr)0)); + } + catch (Exception) { + EndMessageProcessing(); + throw; + } + + } + + public void Start() { + CheckMMResult(MidiApiWindows.midiInStart(_deviceHandle)); + } + + private void InputCallback(MidiApiWindows.HMIDIIN hMidiIn, MidiApiWindows.MidiInMessage wMsg, UIntPtr dwInstance, UIntPtr dwParam1, UIntPtr dwParam2) { + Message message = new Message(); + message.hMidiIn = hMidiIn; + message.wMsg = wMsg; + message.dwInstance = dwInstance; + message.dwParam1 = dwParam1; + message.dwParam2 = dwParam2; + _messageQueue.Add(message); + } + + + private void BeginMessageProcessing() { + _messageProcessingThreadExitRequest = false; + _messageProcessingThread = new Thread(MessageProcessingThreadHandler); + _messageProcessingThread.Start(); + } + + private void MessageProcessingThreadHandler() { + while (!_messageProcessingThreadExitRequest) { + Message message; + if (_messageQueue.TryTake(out message, 100)) { + HandleMessage(message); + } + } + } + + private void EndMessageProcessing() { + _messageProcessingThreadExitRequest = true; + _messageProcessingThread.Join(); + } + + private void HandleMessage(Message message) { + + switch (message.wMsg) { + case MidiInMessage.MIM_DATA: + HandleMessageData(new MidiShortMessage(message.dwParam1, message.dwParam2)); + break; + case MidiInMessage.MIM_LONGDATA: + HandleMessageLongData(message.dwParam1, message.dwParam2); + break; + default: + break; + } + } + + private void HandleMessageData(MidiShortMessage message) { + if (message.IsNoteOn) { + MidiChannel channel; + int noteId; + int velocity; + uint timestamp; + message.GetNoteOn(out channel, out noteId, out velocity, out timestamp); + + var note = new MidiNote(noteId); + + Console.WriteLine($"Received short message NoteOn: Name={note.Name} Octave={note.OctaveName} Channel={channel.ToString()}, NoteId={noteId}, Velocity={velocity}, Timestapm={timestamp}"); + + } else if (message.IsNoteOff) { + MidiChannel channel; + int noteId; + int velocity; + uint timestamp; + message.GetNoteOff(out channel, out noteId, out velocity, out timestamp); + + var note = new MidiNote(noteId); + + Console.WriteLine($"Received short message NoteOff: Name={note.Name} Octave={note.OctaveName} Channel={channel.ToString()}, NoteId={noteId}, Velocity={velocity}, Timestapm={timestamp}"); + + } else { + Console.WriteLine($"Received short message"); + } + + } + + private void HandleMessageLongData(UIntPtr dwParam1, UIntPtr dwParam2) { + Console.WriteLine($"Received long message"); + } + } +} diff --git a/MidiInstrument.cs b/MidiInstrument.cs new file mode 100644 index 0000000..133fe8e --- /dev/null +++ b/MidiInstrument.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RushMidiLibrary { + public enum MidiInstrument { + AcousticGrandPiano = 0, + BrightAcousticPiano = 1, + ElectricGrandPiano = 2, + HonkyTonkPiano = 3, + ElectricPiano1 = 4, + ElectricPiano2 = 5, + Harpsichord = 6, + Clavinet = 7, + + + Celesta = 8, + Glockenspiel = 9, + MusicBox = 10, + Vibraphone = 11, + Marimba = 12, + Xylophone = 13, + TubularBells = 14, + Dulcimer = 15, + + DrawbarOrgan = 16, + PercussiveOrgan = 17, + RockOrgan = 18, + ChurchOrgan = 19, + ReedOrgan = 20, + Accordion = 21, + Harmonica = 22, + TangoAccordion = 23, + + AcousticGuitarNylon = 24, + AcousticGuitarSteel = 25, + ElectricGuitarJazz = 26, + ElectricGuitarClean = 27, + ElectricGuitarMuted = 28, + OverdrivenGuitar = 29, + DistortionGuitar = 30, + GuitarHarmonics = 31, + + AcousticBass = 32, + ElectricBassFinger = 33, + ElectricBassPick = 34, + FretlessBass = 35, + SlapBass1 = 36, + SlapBass2 = 37, + SynthBass1 = 38, + SynthBass2 = 39, + + Violin = 40, + Viola = 41, + Cello = 42, + Contrabass = 43, + TremoloStrings = 44, + PizzicatoStrings = 45, + OrchestralHarp = 46, + Timpani = 47, + + StringEnsemble1 = 48, + StringEnsemble2 = 49, + SynthStrings1 = 50, + SynthStrings2 = 51, + ChoirAahs = 52, + VoiceOohs = 53, + SynthVoice = 54, + OrchestraHit = 55, + + Trumpet = 56, + Trombone = 57, + Tuba = 58, + MutedTrumpet = 59, + FrenchHorn = 60, + BrassSection = 61, + SynthBrass1 = 62, + SynthBrass2 = 63, + + SopranoSax = 64, + AltoSax = 65, + TenorSax = 66, + BaritoneSax = 67, + Oboe = 68, + EnglishHorn = 69, + Bassoon = 70, + Clarinet = 71, + + Piccolo = 72, + Flute = 73, + Recorder = 74, + PanFlute = 75, + BlownBottle = 76, + Shakuhachi = 77, + Whistle = 78, + Ocarina = 79, + + Lead1Square = 80, + Lead2Sawtooth = 81, + Lead3Calliope = 82, + Lead4Chiff = 83, + Lead5Charang = 84, + Lead6Voice = 85, + Lead7Fifths = 86, + Lead8BassPlusLead = 87, + + Pad1NewAge = 88, + Pad2Warm = 89, + Pad3Polysynth = 90, + Pad4Choir = 91, + Pad5Bowed = 92, + Pad6Metallic = 93, + Pad7Halo = 94, + Pad8Sweep = 95, + + FX1Rain = 96, + FX2Soundtrack = 97, + FX3Crystal = 98, + FX4Atmosphere = 99, + FX5Brightness = 100, + FX6Goblins = 101, + FX7Echoes = 102, + FX8SciFi = 103, + + Sitar = 104, + Banjo = 105, + Shamisen = 106, + Koto = 107, + Kalimba = 108, + Bagpipe = 109, + Fiddle = 110, + Shanai = 111, + + TinkleBell = 112, + Agogo = 113, + SteelDrums = 114, + Woodblock = 115, + TaikoDrum = 116, + MelodicTom = 117, + SynthDrum = 118, + ReverseCymbal = 119, + + GuitarFretNoise = 120, + BreathNoise = 121, + Seashore = 122, + BirdTweet = 123, + TelephoneRing = 124, + Helicopter = 125, + Applause = 126, + Gunshot = 127 + }; +} diff --git a/MidiNote.cs b/MidiNote.cs new file mode 100644 index 0000000..3041e95 --- /dev/null +++ b/MidiNote.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RushMidiLibrary { + public class MidiNote { + private static string[] _noteNames = new string[] { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; + private static string[] _octaveNames = new string[] { "Sub Contra", "Contra", "Great", "Small", "1 Line", "2 Line", "3 Line", "4 Line", "5 Line", "6 Line" }; + public int NoteId { get; init; } + + public MidiNote(int noteId) { + NoteId = noteId; + } + + public string Name { + get { + return _noteNames[NoteId % 12]; + } + } + + public int OctaveIndex { + get { + return NoteId / 12 - 1; + } + } + + public string OctaveName { + get { + return _octaveNames[OctaveIndex]; + } + } + } +} diff --git a/MidiRealTimeMessageType.cs b/MidiRealTimeMessageType.cs new file mode 100644 index 0000000..a71b7d4 --- /dev/null +++ b/MidiRealTimeMessageType.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RushMidiLibrary { + public enum MidiRealTimeMessageType { + TimingClock, + Start, + Continue, + Stop, + Undefined, + ActiveSensing, + SystemReset + } +} diff --git a/MidiShortMessage.cs b/MidiShortMessage.cs new file mode 100644 index 0000000..885e82b --- /dev/null +++ b/MidiShortMessage.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; +using System.Linq; +using System.Text; +using System.Threading.Channels; +using System.Threading.Tasks; + +namespace RushMidiLibrary { + public struct MidiShortMessage { + public UIntPtr dwParam1; + public UIntPtr dwParam2; + public MidiShortMessage(UIntPtr dwParam1, UIntPtr dwParam2) { + this.dwParam1 = dwParam1; + this.dwParam2 = dwParam2; + } + + public bool IsNoteOn { + get { return ((int)dwParam1 & 0xf0) == 0x90; } + } + + public void GetNoteOn(out MidiChannel channel, out int noteId, out int velocity, out uint timestamp) { + channel = (MidiChannel)((int)dwParam1 & 0x0f); + noteId = ((int)dwParam1 & 0xff00) >> 8; + velocity = ((int)dwParam1 & 0xff0000) >> 16; + timestamp = (uint)dwParam2; + } + + public bool IsNoteOff { + get { return ((int)dwParam1 & 0xf0) == 0x80; } + } + + public void GetNoteOff(out MidiChannel channel, out int noteId, out int velocity, out uint timestamp) { + channel = (MidiChannel)((int)dwParam1 & 0x0f); + noteId = ((int)dwParam1 & 0xff00) >> 8; + velocity = ((int)dwParam1 & 0xff0000) >> 16; + timestamp = (uint)dwParam2; + } + + public bool IsControlChange { + get { return ((int)dwParam1 & 0xf0) == 0xB0; } + } + + public void GetControlChange(out MidiChannel channel, out MidiControl control, out int value, out uint timestamp) { + channel = (MidiChannel)((int)dwParam1 & 0x0f); + control = (MidiControl)(((int)dwParam1 & 0xff00) >> 8); + value = ((int)dwParam1 & 0xff0000) >> 16; + timestamp = (uint)dwParam2; + } + + public bool IsProgramChange { + get { return ((int)dwParam1 & 0xf0) == 0xC0; } + } + + public void DecodeProgramChange(out MidiChannel channel, out MidiInstrument instrument, out uint timestamp) { + channel = (MidiChannel)((int)dwParam1 & 0x0f); + instrument = (MidiInstrument)(((int)dwParam1 & 0xff00) >> 8); + timestamp = (uint)dwParam2; + } + + public bool IsPitchBend { + get { return ((int)dwParam1 & 0xf0) == 0xE0; } + } + + public void GetPitchBend(out MidiChannel channel, out int value, out uint timestamp) { + channel = (MidiChannel)((int)dwParam1 & 0x0f); + value = ((((int)dwParam1 >> 9) & 0x3f80) | (((int)dwParam1 >> 8) & 0x7f)); + timestamp = (uint)dwParam2; + } + + public bool IsRealTime { + get { return ((int)dwParam1 & 0xf8) == 0xF8; } + } + + public void GetRealTime(out MidiRealTimeMessageType msgType, out uint timestamp) { + int messageType = (int)dwParam1 & 0x07; + switch (messageType) { + case 0: + msgType = MidiRealTimeMessageType.TimingClock; + break; + case 2: + msgType = MidiRealTimeMessageType.Start; + break; + case 3: + msgType = MidiRealTimeMessageType.Continue; + break; + case 4: + msgType = MidiRealTimeMessageType.Stop; + break; + case 6: + msgType = MidiRealTimeMessageType.ActiveSensing; + break; + case 7: + msgType = MidiRealTimeMessageType.SystemReset; + break; + case 1: + case 5: + msgType = MidiRealTimeMessageType.Undefined; + break; + default: + throw new ArgumentException("Unknown realtime message"); + } + timestamp = (uint)dwParam2; + } + } +} diff --git a/RushMidiLibrary.csproj b/RushMidiLibrary.csproj new file mode 100644 index 0000000..fff7812 --- /dev/null +++ b/RushMidiLibrary.csproj @@ -0,0 +1,7 @@ + + + + net6.0 + + +