Path: blob/master/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/OutputStream.cs
180 views
#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: [email protected] */ #endregion using System; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; using System.Threading; using Sanford.Multimedia.Timers; namespace Sanford.Multimedia.Midi { public sealed 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) : base(deviceID) { midiOutProc = HandleMessage; int result = midiStreamOpen(ref hndle, ref deviceID, 1, midiOutProc, 0, CALLBACK_FUNCTION); if(result != MidiDeviceException.MMSYSERR_NOERROR) { throw new OutputDeviceException(result); } } protected override void Dispose(bool disposing) { if(disposing) { lock(lockObject) { Reset(); int result = midiStreamClose(Handle); if(result != MidiDeviceException.MMSYSERR_NOERROR) { throw new OutputDeviceException(result); } } } else { midiOutReset(Handle); midiStreamClose(Handle); } base.Dispose(disposing); } public override void Close() { #region Guard if(!IsDisposed) { return; } #endregion Dispose(true); } public void StartPlaying() { #region Require if(IsDisposed) { throw new ObjectDisposedException("OutputStream"); } #endregion lock(lockObject) { int result = midiStreamRestart(Handle); if(result != MidiDeviceException.MMSYSERR_NOERROR) { throw new OutputDeviceException(result); } } } public void PausePlaying() { #region Require if(IsDisposed) { throw new ObjectDisposedException("OutputStream"); } #endregion lock(lockObject) { int result = midiStreamPause(Handle); if(result != MidiDeviceException.MMSYSERR_NOERROR) { throw new OutputDeviceException(result); } } } public void StopPlaying() { #region Require if(IsDisposed) { throw new ObjectDisposedException("OutputStream"); } #endregion lock(lockObject) { int result = midiStreamStop(Handle); if(result != MidiDeviceException.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 lock(lockObject) { headerBuilder.InitializeBuffer(events); headerBuilder.Build(); events.Clear(); int result = midiOutPrepareHeader(Handle, headerBuilder.Result, SizeOfMidiHeader); if(result == MidiDeviceException.MMSYSERR_NOERROR) { bufferCount++; } else { headerBuilder.Destroy(); throw new OutputDeviceException(result); } result = midiStreamOut(Handle, headerBuilder.Result, SizeOfMidiHeader); if(result != MidiDeviceException.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; lock(lockObject) { int result = midiStreamPosition(Handle, ref t, Marshal.SizeOf(typeof(Time))); if(result != MidiDeviceException.MMSYSERR_NOERROR) { throw new OutputDeviceException(result); } } return t; } private 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) { delegateQueue.Post(HandleNoOp, new IntPtr(param1)); } else { base.HandleMessage(handle, msg, instance, param1, param2); } } private void HandleNoOp(object state) { IntPtr headerPtr = (IntPtr)state; 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)); context.Post(new SendOrPostCallback(delegate(object s) { OnNoOpOccurred(e); }), null); } } public int Division { get { #region Require if(IsDisposed) { throw new ObjectDisposedException("OutputStream"); } #endregion Property d = new Property(); d.sizeOfProperty = Marshal.SizeOf(typeof(Property)); lock(lockObject) { int result = midiStreamProperty(Handle, ref d, MIDIPROP_GET | MIDIPROP_TIMEDIV); if(result != MidiDeviceException.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; lock(lockObject) { int result = midiStreamProperty(Handle, ref d, MIDIPROP_SET | MIDIPROP_TIMEDIV); if(result != MidiDeviceException.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)); lock(lockObject) { int result = midiStreamProperty(Handle, ref t, MIDIPROP_GET | MIDIPROP_TEMPO); if(result != MidiDeviceException.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; lock(lockObject) { int result = midiStreamProperty(Handle, ref t, MIDIPROP_SET | MIDIPROP_TEMPO); if(result != MidiDeviceException.MMSYSERR_NOERROR) { throw new OutputDeviceException(result); } } } } } }