/*
* Created by: Leslie Sanford
*
* Contact: jabberdabber@hotmail.com
*
* Last modified: 05/08/2004
*/
using System;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Multimedia;
namespace Multimedia.Midi
{
/// <summary>
/// Represents Midi output device capabilities.
/// </summary>
public struct MidiOutCaps
{
#region MidiOutCaps Members
/// <summary>
/// Manufacturer identifier of the device driver for the Midi output
/// device.
/// </summary>
public short mid;
/// <summary>
/// Product identifier of the Midi output device.
/// </summary>
public short pid;
/// <summary>
/// Version number of the device driver for the Midi output device. The
/// high-order byte is the major version number, and the low-order byte
/// is the minor version number.
/// </summary>
public int driverVersion;
/// <summary>
/// Product name.
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst=32)]
public byte[] name;
/// <summary>
/// Flags describing the type of the Midi output device.
/// </summary>
public short technology;
/// <summary>
/// Number of voices supported by an internal synthesizer device. If
/// the device is a port, this member is not meaningful and is set
/// to 0.
/// </summary>
public short voices;
/// <summary>
/// Maximum number of simultaneous notes that can be played by an
/// internal synthesizer device. If the device is a port, this member
/// is not meaningful and is set to 0.
/// </summary>
public short notes;
/// <summary>
/// Channels that an internal synthesizer device responds to, where the
/// least significant bit refers to channel 0 and the most significant
/// bit to channel 15. Port devices that transmit on all channels set
/// this member to 0xFFFF.
/// </summary>
public short channelMask;
/// <summary>
/// Optional functionality supported by the device.
/// </summary>
public int support;
#endregion
}
/// <summary>
/// Represents Midi output devices.
/// </summary>
public class OutputDevice : Component, IDevice, IMidiSender
{
#region OutputDevice Members
#region Delegates
// Represents the method handles messages from Windows.
private delegate void MidiOutProc(int handle, int msg, int instance,
int param1, int param2);
#endregion
#region Win32 Midi Output Functions and Constants
[DllImport("winmm.dll")]
private static extern int midiOutOpen(ref int handle, int deviceId,
MidiOutProc proc, int instance, int flags);
[DllImport("winmm.dll")]
private static extern int midiOutClose(int handle);
[DllImport("winmm.dll")]
private static extern int midiOutReset(int handle);
[DllImport("winmm.dll")]
private static extern int midiOutShortMsg(int handle, int message);
[DllImport("winmm.dll")]
private static extern int midiOutPrepareHeader(int handle,
ref MidiHeader midiHeader, int sizeOfmidiHeader);
[DllImport("winmm.dll")]
private static extern int midiOutUnprepareHeader(int handle,
ref MidiHeader midiHeader, int sizeOfmidiHeader);
[DllImport("winmm.dll")]
private static extern int midiOutLongMsg(int handle,
ref MidiHeader midiHeader, int sizeOfmidiHeader);
[DllImport("winmm.dll")]
private static extern int midiOutGetDevCaps(int handle,
ref MidiOutCaps caps, int sizeOfmidiOutCaps);
[DllImport("winmm.dll")]
private static extern int midiOutGetNumDevs();
private const int MMSYSERR_NOERROR = 0;
private const int CALLBACK_FUNCTION = 0x30000;
private const int MOM_DONE = 0x3C9;
#endregion
#region Constants
// The amount to shift packed Midi short messages to omit the status
// value.
private const int StatusShift = 8;
#endregion
#region Fields
// Device handle.
private int handle;
// device Identifier.
private int deviceId;
// Indicates whether or not the device is open.
private bool opened;
// Represents the method that handles messages from Windows.
private MidiOutProc messageHandler;
// Thread for managing headers.
private Thread headerManager;
// Event used to signal when the device is done with a header.
private AutoResetEvent resetEvent = new AutoResetEvent(false);
// Queue for storing headers.
private Queue headerQueue = new Queue();
// Synchronized queue.
private Queue syncHeaderQueue;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
#endregion
#region Construction/Destruction
/// <summary>
/// Initializes a new instance of the OutputDevice class.
/// </summary>
/// <remarks>
/// This constructor initializes a new instance of the OutputDevice
/// class in a closed state.
/// </remarks>
public OutputDevice()
{
//
// Required for Windows.Forms Class Composition Designer support
//
InitializeComponent();
InitializeOutputDevice();
}
/// <summary>
/// Initializes a new instance of the OutputDevice class with the
/// specified IContainer.
/// </summary>
/// <param name="container">
/// The container to add this component to.
/// </param>
/// <remarks>
/// This constructor initializes a new instance of the OutputDevice
/// class in a closed state.
/// </remarks>
public OutputDevice(IContainer container)
{
//
// Required for Windows.Forms Class Composition Designer support
//
container.Add(this);
InitializeComponent();
InitializeOutputDevice();
}
/// <summary>
/// Initializes a new instance of the OutputDevice class with the
/// specified device Identifier.
/// </summary>
/// <param name="deviceId">
/// The device Identifier.
/// </param>
/// <exception cref="OutputDeviceException">
/// Thrown if an error occurred while opening the output device.
/// </exception>
/// <remarks>
/// This constructor initializes a new instance of the OutputDevice
/// class and opens it with the specified device Identifier.
/// </remarks>
public OutputDevice(int deviceId)
{
//
// Required for Windows.Forms Class Composition Designer support
//
InitializeComponent();
InitializeOutputDevice();
// Open device.
Open(deviceId);
}
/// <summary>
/// Initializes a new instance of the OutputDevice class with the
/// specified device Identifier.
/// </summary>
/// <param name="container">
/// The container to add this component to.
/// </param>
/// <param name="deviceId">
/// The device Identifier.
/// </param>
/// <exception cref="OutputDeviceException">
/// Thrown if an error occurred while opening the output device.
/// </exception>
/// <remarks>
/// This constructor initializes a new instance of the OutputDevice
/// class and opens it with the specified device Identifier.
/// </remarks>
public OutputDevice(IContainer container, int deviceId)
{
//
// Required for Windows.Forms Class Composition Designer support
//
container.Add(this);
InitializeComponent();
InitializeOutputDevice();
// Open device.
Open(deviceId);
}
/// <summary>
/// Destructor.
/// </summary>
~OutputDevice()
{
// If the device is still open.
if(IsOpen())
{
midiOutClose(handle);
}
}
#endregion
#region Methods
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
if(IsOpen())
{
Close();
}
}
base.Dispose( disposing );
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
/// <summary>
/// Gets the output device capabilities.
/// </summary>
/// <param name="deviceId">
/// The device Identifier.
/// </param>
/// <exception cref="OutputDeviceException">
/// Thrown if an error occurred while retrieving the output device
/// capabilities.
/// </exception>
/// <returns>
/// The Midi output device's capabilities.
/// </returns>
public static MidiOutCaps GetCapabilities(int deviceId)
{
MidiOutCaps caps = new MidiOutCaps();
ThrowOnError(midiOutGetDevCaps(deviceId, ref caps,
Marshal.SizeOf(caps)));
return caps;
}
/// <summary>
/// Initializes output device.
/// </summary>
private void InitializeOutputDevice()
{
// Create delegate for handling messages from Windows.
messageHandler = new MidiOutProc(OnMessage);
// Create synchronized queue for holding headers.
syncHeaderQueue = Queue.Synchronized(headerQueue);
}
/// <summary>
/// Handles messages from Windows.
/// </summary>
private void OnMessage(int handle, int msg, int instance, int param1,
int param2)
{
// If the device has finished sending a system exclusive message
if(msg == MOM_DONE)
{
// If the device is open
if(IsOpen())
{
// Signal header thread that the device has finished with
// a header.
resetEvent.Set();
}
}
}
/// <summary>
/// Throws exception on error.
/// </summary>
/// <param name="errCode">
/// The error code.
/// </param>
private static void ThrowOnError(int errCode)
{
// If an error occurred
if(errCode != MMSYSERR_NOERROR)
{
// Throw exception.
throw new OutputDeviceException(errCode);
}
}
/// <summary>
/// Thread method for managing headers.
/// </summary>
private void ManageHeaders()
{
// While the device is open.
while(IsOpen())
{
// Wait to be signalled when a header had finished being used.
resetEvent.WaitOne();
// While there are still headers in the queue.
while(syncHeaderQueue.Count > 0)
{
// Get header from the front of the queue.
MidiHeader header = (MidiHeader)syncHeaderQueue.Dequeue();
// Unprepare header.
ThrowOnError(midiOutUnprepareHeader(handle, ref header,
Marshal.SizeOf(header)));
// Free memory allocated for the system exclusive data.
Marshal.FreeHGlobal(header.data);
}
}
}
/// <summary>
/// Empties header queue.
/// </summary>
private void EmptyHeaderQueue()
{
IEnumerator en = syncHeaderQueue.GetEnumerator();
// While there are still headers in the queue.
while(en.MoveNext())
{
// Get header.
MidiHeader header = (MidiHeader)en.Current;
// Unprepare header.
ThrowOnError(midiOutUnprepareHeader(handle, ref header,
Marshal.SizeOf(header)));
// Free memory allocated for the system exclusive data.
Marshal.FreeHGlobal(header.data);
}
// Clear queue.
syncHeaderQueue.Clear();
}
#endregion
#region Properties
/// <summary>
/// Gets the number of output devices present in the system.
/// </summary>
public static int DeviceCount
{
get
{
return midiOutGetNumDevs();
}
}
#endregion
#region IDevice Members
#region Methods
/// <summary>
/// Opens the OutputDevice with the specified device Identifier.
/// </summary>
/// <param name="deviceId">
/// The device Identifier.
/// </param>
/// <exception cref="OutputDeviceException">
/// Thrown if an error occurred while opening the output device.
/// </exception>
public void Open(int deviceId)
{
// If the device is already open.
if(IsOpen())
{
// Close device before attempting to open it again.
Close();
}
// Open the device.
ThrowOnError(midiOutOpen(ref handle, deviceId, messageHandler,
0, CALLBACK_FUNCTION));
// Indicate that the device is open.
opened = true;
// Create thread for managing headers.
headerManager = new Thread(new ThreadStart(ManageHeaders));
// Start thread.
headerManager.Start();
// Wait for thread to become active.
while(!headerManager.IsAlive)
continue;
// Keep track of device Identifier.
this.deviceId = deviceId;
}
/// <summary>
/// Closes the OutputDevice.
/// </summary>
/// <exception cref="OutputDeviceException">
/// Thrown if an error occurred while closing the output device.
/// </exception>
public void Close()
{
// If the device is open.
if(IsOpen())
{
// Indicate that the device is closed.
opened = false;
// Reset device.
ThrowOnError(midiOutReset(handle));
// Signal header thread that it is finished.
resetEvent.Set();
// Wait for header thread to finish.
headerManager.Join();
// Empty header queue.
EmptyHeaderQueue();
// Close device.
ThrowOnError(midiOutClose(handle));
}
}
/// <summary>
/// Indicates whether or not the OutputDevice is open.
/// </summary>
/// <returns>
/// true if the device is open; otherwise, false.
/// </returns>
public bool IsOpen()
{
return opened;
}
#endregion
#region Properties
/// <summary>
/// Gets the device handle.
/// </summary>
public int DeviceHandle
{
get
{
return handle;
}
}
/// <summary>
/// Gets the device Identifier.
/// </summary>
public int DeviceId
{
get
{
return deviceId;
}
}
#endregion
#endregion
#region IMidiSender Members
/// <summary>
/// Send short Midi message.
/// </summary>
/// <param name="message">
/// The short Midi message to send.
/// </param>
/// <param name="status">
/// A flag indicating whether or not the status value should be sent.
/// </param>
/// <exception cref="OutputDeviceException">
/// Thrown if an error occurred while sending the message.
/// </exception>
public void Send(ShortMessage message, bool status)
{
// If the device is open.
if(opened)
{
// If status value is to be sent with message.
if(status)
{
// Send message with status value.
ThrowOnError(midiOutShortMsg(handle, message.Message));
}
// Else running status is used.
else
{
// Remove status value.
int msg = message.Message >> StatusShift;
// Send message.
ThrowOnError(midiOutShortMsg(handle, msg));
}
}
}
/// <summary>
/// Sends system exclusive messages.
/// </summary>
/// <param name="message">
/// The system exclusive message to send.
/// </param>
public void Send(SysExMessage message)
{
// If the device is open.
if(IsOpen())
{
// Create header.
MidiHeader header = new MidiHeader();
// System exclusive message data.
string msg = message.Message;
//
// Initialize header.
//
header.data = Marshal.StringToHGlobalAnsi(msg);
header.bufferLength = msg.Length;
header.flags = 0;
// Prepare header.
ThrowOnError(midiOutPrepareHeader(handle, ref header,
Marshal.SizeOf(header)));
// Place header in queue to be retrieved later.
syncHeaderQueue.Enqueue(header);
// Send message.
ThrowOnError(midiOutLongMsg(handle, ref header,
Marshal.SizeOf(header)));
}
}
/// <summary>
/// Turns off all Note on all Midi channels for the output device.
/// </summary>
/// <exception cref="OutputDeviceException">
/// Thrown if an error occurred while resetting the output device.
/// </exception>
public void Reset()
{
// If the device is open.
if(IsOpen())
{
// Reset device.
ThrowOnError(midiOutReset(handle));
// Empty header queue. Any system exclusive messages in the
// queue will be marked done at this point.
EmptyHeaderQueue();
}
}
#endregion
#endregion
}
/// <summary>
/// The exception that is thrown when a error occurs with the OutputDevice
/// class.
/// </summary>
public class OutputDeviceException : ApplicationException
{
#region OutputDeviceException Members
#region Win32 Midi Output Error Function
[DllImport("winmm.dll")]
private static extern int midiOutGetErrorText(int errCode,
StringBuilder message, int sizeOfMessage);
#endregion
#region Fields
// Error message.
private StringBuilder message = new StringBuilder(128);
#endregion
#region Construction
/// <summary>
/// Initializes a new instance of the OutputDeviceException class with
/// the specified error code.
/// </summary>
/// <param name="errCode">
/// The error code.
/// </param>
public OutputDeviceException(int errCode)
{
// Get error message.
midiOutGetErrorText(errCode, message, message.Capacity);
}
#endregion
#region Properties
/// <summary>
/// Gets a message that describes the current exception.
/// </summary>
public override string Message
{
get
{
return message.ToString();
}
}
#endregion
#endregion
}
}