This commit is contained in:
2025-05-13 00:34:26 +03:00
parent ba0eb53334
commit f2b9d7c0db
12 changed files with 707 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
/*.sln
/bin
/obj
/.vs
+163
View File
@@ -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<MIDIINCAPS>());
}
[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;
}
}
}
+26
View File
@@ -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
};
}
+28
View File
@@ -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
}
}
+19
View File
@@ -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());
}
}
}
}
+13
View File
@@ -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) {
}
}
}
+135
View File
@@ -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<Message> _messageQueue = new BlockingCollection<Message>();
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");
}
}
}
+154
View File
@@ -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
};
}
+35
View File
@@ -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];
}
}
}
}
+17
View File
@@ -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
}
}
+106
View File
@@ -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;
}
}
}
+7
View File
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>