using System;
using System.Runtime.InteropServices;
namespace NAudio.Midi
{
///
/// Represents a MIDI in device
///
public class MidiIn : IDisposable
{
private IntPtr hMidiIn = IntPtr.Zero;
private bool disposeIsRunning = false; // true while the Dispose() method run.
private bool disposed = false;
private MidiInterop.MidiInCallback callback;
// Buffer headers created and marshalled to recive incoming Sysex mesages
private IntPtr[] SysexBufferHeaders = new IntPtr[0];
///
/// Called when a MIDI message is received
///
public event EventHandler MessageReceived;
///
/// An invalid MIDI message
///
public event EventHandler ErrorReceived;
///
/// Called when a Sysex MIDI message is received
///
public event EventHandler SysexMessageReceived;
///
/// Gets the number of MIDI input devices available in the system
///
public static int NumberOfDevices
{
get
{
return MidiInterop.midiInGetNumDevs();
}
}
///
/// Opens a specified MIDI in device
///
/// The device number
public MidiIn(int deviceNo)
{
this.callback = new MidiInterop.MidiInCallback(Callback);
MmException.Try(MidiInterop.midiInOpen(out hMidiIn, (IntPtr) deviceNo,this.callback,IntPtr.Zero,MidiInterop.CALLBACK_FUNCTION),"midiInOpen");
}
///
/// Closes this MIDI in device
///
public void Close()
{
Dispose();
}
///
/// Closes this MIDI in device
///
public void Dispose()
{
GC.KeepAlive(callback);
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Start the MIDI in device
///
public void Start()
{
MmException.Try(MidiInterop.midiInStart(hMidiIn), "midiInStart");
}
///
/// Stop the MIDI in device
///
public void Stop()
{
MmException.Try(MidiInterop.midiInStop(hMidiIn), "midiInStop");
}
///
/// Reset the MIDI in device
///
public void Reset()
{
MmException.Try(MidiInterop.midiInReset(hMidiIn), "midiInReset");
}
///
/// Create a number of buffers and make them available to receive incoming Sysex messages
///
/// The size of each buffer, ideally large enough to hold a complete message from the device
/// The number of buffers needed to handle incoming Midi while busy
public void CreateSysexBuffers(int bufferSize, int numberOfBuffers)
{
SysexBufferHeaders = new IntPtr[numberOfBuffers];
var hdrSize = Marshal.SizeOf(typeof(MidiInterop.MIDIHDR));
for (var i = 0; i < numberOfBuffers; i++)
{
var hdr = new MidiInterop.MIDIHDR();
hdr.dwBufferLength = bufferSize;
hdr.dwBytesRecorded = 0;
hdr.lpData = Marshal.AllocHGlobal(bufferSize);
hdr.dwFlags = 0;
var lpHeader = Marshal.AllocHGlobal(hdrSize);
Marshal.StructureToPtr(hdr, lpHeader, false);
MmException.Try(MidiInterop.midiInPrepareHeader(hMidiIn, lpHeader, Marshal.SizeOf(typeof(MidiInterop.MIDIHDR))), "midiInPrepareHeader");
MmException.Try(MidiInterop.midiInAddBuffer(hMidiIn, lpHeader, Marshal.SizeOf(typeof(MidiInterop.MIDIHDR))), "midiInAddBuffer");
SysexBufferHeaders[i] = lpHeader;
}
}
private void Callback(IntPtr midiInHandle, MidiInterop.MidiInMessage message, IntPtr userData, IntPtr messageParameter1, IntPtr messageParameter2)
{
switch(message)
{
case MidiInterop.MidiInMessage.Open:
// message Parameter 1 & 2 are not used
break;
case MidiInterop.MidiInMessage.Data:
// parameter 1 is packed MIDI message
// parameter 2 is milliseconds since MidiInStart
if (MessageReceived != null)
{
MessageReceived(this, new MidiInMessageEventArgs(messageParameter1.ToInt32(), messageParameter2.ToInt32()));
}
break;
case MidiInterop.MidiInMessage.Error:
// parameter 1 is invalid MIDI message
if (ErrorReceived != null)
{
ErrorReceived(this, new MidiInMessageEventArgs(messageParameter1.ToInt32(), messageParameter2.ToInt32()));
}
break;
case MidiInterop.MidiInMessage.Close:
// message Parameter 1 & 2 are not used
break;
case MidiInterop.MidiInMessage.LongData:
// parameter 1 is pointer to MIDI header
// parameter 2 is milliseconds since MidiInStart
if (SysexMessageReceived != null)
{
MidiInterop.MIDIHDR hdr = (MidiInterop.MIDIHDR)Marshal.PtrToStructure(messageParameter1, typeof(MidiInterop.MIDIHDR));
// Copy the bytes received into an array so that the buffer is immediately available for re-use
var sysexBytes = new byte[hdr.dwBytesRecorded];
Marshal.Copy(hdr.lpData, sysexBytes, 0, hdr.dwBytesRecorded);
if (sysexBytes.Length!=0) // do not trigger the sysex event if no data in SYSEX message
SysexMessageReceived(this, new MidiInSysexMessageEventArgs(sysexBytes, messageParameter2.ToInt32()));
// Re-use the buffer - but not if we have no event handler registered as we are closing
// BUT When disposing the (resetting the MidiIn port), LONGDATA midi message are fired with a zero length.
// In that case, buffer should no be ReAdd to avoid an inifinite loop of callback as buffer are reused forever.
if (!disposeIsRunning)
MidiInterop.midiInAddBuffer(hMidiIn, messageParameter1, Marshal.SizeOf(typeof(MidiInterop.MIDIHDR)));
}
break;
case MidiInterop.MidiInMessage.LongError:
// parameter 1 is pointer to MIDI header
// parameter 2 is milliseconds since MidiInStart
break;
case MidiInterop.MidiInMessage.MoreData:
// parameter 1 is packed MIDI message
// parameter 2 is milliseconds since MidiInStart
break;
}
}
///
/// Gets the MIDI in device info
///
public static MidiInCapabilities DeviceInfo(int midiInDeviceNumber)
{
MidiInCapabilities caps = new MidiInCapabilities();
int structSize = Marshal.SizeOf(caps);
MmException.Try(MidiInterop.midiInGetDevCaps((IntPtr)midiInDeviceNumber,out caps,structSize),"midiInGetDevCaps");
return caps;
}
///
/// Closes the MIDI in device
///
/// True if called from Dispose
protected virtual void Dispose(bool disposing)
{
if(!this.disposed)
{
disposeIsRunning = true;
//if(disposing) Components.Dispose();
if (SysexBufferHeaders.Length > 0)
{
//// When SysexMessageReceived contains event handlers (!=null) , the 'midiInReset' call generate a infinit loop of CallBack call with LONGDATA message having a zero length.
//SysexMessageReceived = null; // removin all event handler to avoir the infinit loop.
// Reset in order to release any Sysex buffers
// We can't Unprepare and free them until they are flushed out. Neither can we close the handle.
MmException.Try(MidiInterop.midiInReset(hMidiIn), "midiInReset");
// Free up all created and allocated buffers for incoming Sysex messages
foreach (var lpHeader in SysexBufferHeaders)
{
MidiInterop.MIDIHDR hdr = (MidiInterop.MIDIHDR)Marshal.PtrToStructure(lpHeader, typeof(MidiInterop.MIDIHDR));
MmException.Try(MidiInterop.midiInUnprepareHeader(hMidiIn, lpHeader, Marshal.SizeOf(typeof(MidiInterop.MIDIHDR))), "midiInPrepareHeader");
Marshal.FreeHGlobal(hdr.lpData);
Marshal.FreeHGlobal(lpHeader);
}
// Defensive protection against double disposal
SysexBufferHeaders = new IntPtr[0];
}
MidiInterop.midiInClose(hMidiIn);
}
disposed = true;
disposeIsRunning = false;
}
///
/// Cleanup
///
~MidiIn()
{
System.Diagnostics.Debug.Assert(false,"MIDI In was not finalised");
Dispose(false);
}
}
}