#region License
/* Copyright (c) 2006 Leslie Sanford
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Contact
/*
* Leslie Sanford
* Email: jabberdabber@hotmail.com
*/
#endregion
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Multimedia;
namespace Multimedia.Midi
{
public class OutputStream : OutputDeviceBase
{
[DllImport("winmm.dll")]
private static extern int midiStreamOpen(ref int handle, ref int deviceID, int reserved,
OutputDevice.MidiOutProc proc, int instance, uint flag);
[DllImport("winmm.dll")]
private static extern int midiStreamClose(int handle);
[DllImport("winmm.dll")]
private static extern int midiStreamOut(int handle, IntPtr headerPtr, int sizeOfMidiHeader);
[DllImport("winmm.dll")]
private static extern int midiStreamPause(int handle);
[DllImport("winmm.dll")]
private static extern int midiStreamPosition(int handle, ref Time t, int sizeOfTime);
[DllImport("winmm.dll")]
private static extern int midiStreamProperty(int handle, ref Property p, uint flags);
[DllImport("winmm.dll")]
private static extern int midiStreamRestart(int handle);
[DllImport("winmm.dll")]
private static extern int midiStreamStop(int handle);
[StructLayout(LayoutKind.Sequential)]
private struct Property
{
public int sizeOfProperty;
public int property;
}
private const uint MIDIPROP_SET = 0x80000000;
private const uint MIDIPROP_GET = 0x40000000;
private const uint MIDIPROP_TIMEDIV = 0x00000001;
private const uint MIDIPROP_TEMPO = 0x00000002;
private const byte MEVT_CALLBACK = 0x40;
private const byte MEVT_SHORTMSG = 0x00;
private const byte MEVT_TEMPO = 0x01;
private const byte MEVT_NOP = 0x02;
private const byte MEVT_LONGMSG = 0x80;
private const byte MEVT_COMMENT = 0x82;
private const byte MEVT_VERSION = 0x84;
private const int MOM_POSITIONCB = 0x3CA;
private const int SizeOfMidiEvent = 12;
private const int EventTypeIndex = 11;
private const int EventCodeOffset = 8;
private MidiOutProc midiOutProc;
private int offsetTicks = 0;
private byte[] streamID = new byte[4];
private List<byte> events = new List<byte>();
private MidiHeaderBuilder headerBuilder = new MidiHeaderBuilder();
public event EventHandler<NoOpEventArgs> NoOpOccurred;
public OutputStream(int deviceID)
{
midiOutProc = HandleMessage;
int result = midiStreamOpen(ref hndle, ref deviceID, 1, midiOutProc, 0, CALLBACK_FUNCTION);
if(result != DeviceException.MMSYSERR_NOERROR)
{
throw new OutputDeviceException(result);
}
}
protected override void Dispose(bool disposing)
{
if(disposing)
{
int result = midiStreamClose(Handle);
if(result != DeviceException.MMSYSERR_NOERROR)
{
throw new OutputDeviceException(result);
}
}
else
{
if(!IsDisposed)
{
midiStreamClose(Handle);
}
}
base.Dispose(disposing);
}
public override void Close()
{
#region Guard
if(!IsDisposed)
{
return;
}
#endregion
Dispose();
}
public void StartPlaying()
{
#region Require
if(IsDisposed)
{
throw new ObjectDisposedException("OutputStream");
}
#endregion
int result = midiStreamRestart(Handle);
if(result != DeviceException.MMSYSERR_NOERROR)
{
throw new OutputDeviceException(result);
}
}
public void PausePlaying()
{
#region Require
if(IsDisposed)
{
throw new ObjectDisposedException("OutputStream");
}
#endregion
int result = midiStreamPause(Handle);
if(result != DeviceException.MMSYSERR_NOERROR)
{
throw new OutputDeviceException(result);
}
}
public void StopPlaying()
{
#region Require
if(IsDisposed)
{
throw new ObjectDisposedException("OutputStream");
}
#endregion
int result = midiStreamStop(Handle);
if(result != DeviceException.MMSYSERR_NOERROR)
{
throw new OutputDeviceException(result);
}
}
public override void Reset()
{
#region Require
if(IsDisposed)
{
throw new ObjectDisposedException("OutputStream");
}
#endregion
offsetTicks = 0;
events.Clear();
base.Reset();
}
public void Write(MidiEvent e)
{
switch(e.MidiMessage.MessageType)
{
case MessageType.Channel:
case MessageType.SystemCommon:
case MessageType.SystemRealtime:
Write(e.DeltaTicks, (ShortMessage)e.MidiMessage);
break;
case MessageType.SystemExclusive:
Write(e.DeltaTicks, (SysExMessage)e.MidiMessage);
break;
case MessageType.Meta:
Write(e.DeltaTicks, (MetaMessage)e.MidiMessage);
break;
}
}
private void Write(int deltaTicks, ShortMessage message)
{
#region Require
if(IsDisposed)
{
throw new ObjectDisposedException("OutputStream");
}
#endregion
// Delta time.
events.AddRange(BitConverter.GetBytes(deltaTicks + offsetTicks));
// Stream ID.
events.AddRange(streamID);
// Event code.
byte[] eventCode = message.GetBytes();
eventCode[eventCode.Length - 1] = MEVT_SHORTMSG;
events.AddRange(eventCode);
offsetTicks = 0;
}
private void Write(int deltaTicks, SysExMessage message)
{
#region Require
if(IsDisposed)
{
throw new ObjectDisposedException("OutputStream");
}
#endregion
// Delta time.
events.AddRange(BitConverter.GetBytes(deltaTicks + offsetTicks));
// Stream ID.
events.AddRange(streamID);
// Event code.
byte[] eventCode = BitConverter.GetBytes(message.Length);
eventCode[eventCode.Length - 1] = MEVT_LONGMSG;
events.AddRange(eventCode);
byte[] sysExData;
if(message.Length % 4 != 0)
{
sysExData = new byte[message.Length + (message.Length % 4)];
message.GetBytes().CopyTo(sysExData, 0);
}
else
{
sysExData = message.GetBytes();
}
// SysEx data.
events.AddRange(sysExData);
offsetTicks = 0;
}
private void Write(int deltaTicks, MetaMessage message)
{
if(message.MetaType == MetaType.Tempo)
{
// Delta time.
events.AddRange(BitConverter.GetBytes(deltaTicks + offsetTicks));
// Stream ID.
events.AddRange(streamID);
TempoChangeBuilder builder = new TempoChangeBuilder(message);
byte[] t = BitConverter.GetBytes(builder.Tempo);
t[t.Length - 1] = MEVT_SHORTMSG | MEVT_TEMPO;
// Event code.
events.AddRange(t);
offsetTicks = 0;
}
else
{
offsetTicks += deltaTicks;
}
}
public void WriteNoOp(int deltaTicks, int data)
{
// Delta time.
events.AddRange(BitConverter.GetBytes(deltaTicks + offsetTicks));
// Stream ID.
events.AddRange(streamID);
// Event code.
byte[] eventCode = BitConverter.GetBytes(data);
eventCode[eventCode.Length - 1] = (byte)(MEVT_NOP | MEVT_CALLBACK);
events.AddRange(eventCode);
offsetTicks = 0;
}
public void Flush()
{
#region Require
if(IsDisposed)
{
throw new ObjectDisposedException("OutputStream");
}
#endregion
headerBuilder.InitializeBuffer(events);
headerBuilder.Build();
events.Clear();
int result = midiOutPrepareHeader(Handle, headerBuilder.Result, SizeOfMidiHeader);
if(result != DeviceException.MMSYSERR_NOERROR)
{
headerBuilder.Destroy();
throw new OutputDeviceException(result);
}
result = midiStreamOut(Handle, headerBuilder.Result, SizeOfMidiHeader);
if(result != DeviceException.MMSYSERR_NOERROR)
{
midiOutUnprepareHeader(Handle, headerBuilder.Result, SizeOfMidiHeader);
headerBuilder.Destroy();
throw new OutputDeviceException(result);
}
}
public Time GetTime(TimeType type)
{
#region Require
if(IsDisposed)
{
throw new ObjectDisposedException("OutputStream");
}
#endregion
Time t = new Time();
t.type = (int)type;
int result = midiStreamPosition(Handle, ref t, Marshal.SizeOf(typeof(Time)));
if(result != DeviceException.MMSYSERR_NOERROR)
{
throw new OutputDeviceException(result);
}
return t;
}
protected virtual void OnNoOpOccurred(NoOpEventArgs e)
{
EventHandler<NoOpEventArgs> handler = NoOpOccurred;
if(handler != null)
{
handler(this, e);
}
}
protected override void HandleMessage(int handle, int msg, int instance, int param1, int param2)
{
if(msg == MOM_POSITIONCB)
{
IntPtr headerPtr = new IntPtr(param1);
MidiHeader header = (MidiHeader)Marshal.PtrToStructure(headerPtr, typeof(MidiHeader));
byte[] midiEvent = new byte[SizeOfMidiEvent];
for(int i = 0; i < midiEvent.Length; i++)
{
midiEvent[i] = Marshal.ReadByte(header.data, header.offset + i);
}
// If this is a NoOp event.
if((midiEvent[EventTypeIndex] & MEVT_NOP) == MEVT_NOP)
{
// Clear the event type byte.
midiEvent[EventTypeIndex] = 0;
NoOpEventArgs e = new NoOpEventArgs(BitConverter.ToInt32(midiEvent, EventCodeOffset));
if(SynchronizingObject != null)
{
SynchronizingObject.BeginInvoke(
new GenericDelegate<NoOpEventArgs>(OnNoOpOccurred),
new object[] { e });
}
else
{
OnNoOpOccurred(e);
}
}
}
else
{
base.HandleMessage(handle, msg, instance, param1, param2);
}
}
public int Division
{
get
{
#region Require
if(IsDisposed)
{
throw new ObjectDisposedException("OutputStream");
}
#endregion
Property d = new Property();
d.sizeOfProperty = Marshal.SizeOf(typeof(Property));
int result = midiStreamProperty(Handle, ref d, MIDIPROP_GET | MIDIPROP_TIMEDIV);
if(result != DeviceException.MMSYSERR_NOERROR)
{
throw new OutputDeviceException(result);
}
return d.property;
}
set
{
#region Require
if(IsDisposed)
{
throw new ObjectDisposedException("OutputStream");
}
else if((value % PpqnClock.PpqnMinValue) != 0)
{
throw new ArgumentException();
}
#endregion
Property d = new Property();
d.sizeOfProperty = Marshal.SizeOf(typeof(Property));
d.property = value;
int result = midiStreamProperty(Handle, ref d, MIDIPROP_SET | MIDIPROP_TIMEDIV);
if(result != DeviceException.MMSYSERR_NOERROR)
{
throw new OutputDeviceException(result);
}
}
}
public int Tempo
{
get
{
#region Require
if(IsDisposed)
{
throw new ObjectDisposedException("OutputStream");
}
#endregion
Property t = new Property();
t.sizeOfProperty = Marshal.SizeOf(typeof(Property));
int result = midiStreamProperty(Handle, ref t, MIDIPROP_GET | MIDIPROP_TEMPO);
if(result != DeviceException.MMSYSERR_NOERROR)
{
throw new OutputDeviceException(result);
}
return t.property;
}
set
{
#region Require
if(IsDisposed)
{
throw new ObjectDisposedException("OutputStream");
}
else if(value < 0)
{
throw new ArgumentOutOfRangeException("Tempo", value,
"Tempo out of range.");
}
#endregion
Property t = new Property();
t.sizeOfProperty = Marshal.SizeOf(typeof(Property));
t.property = value;
int result = midiStreamProperty(Handle, ref t, MIDIPROP_SET | MIDIPROP_TEMPO);
if(result != DeviceException.MMSYSERR_NOERROR)
{
throw new OutputDeviceException(result);
}
}
}
}
}