Example #1
0
        private static bool _TryReadChunk(Stream stream, out KeyValuePair <string, byte[]> chunk)
        {
            chunk = default(KeyValuePair <string, byte[]>);
            var buf = new byte[4];

            if (4 != stream.Read(buf, 0, 4))
            {
                return(false);
            }
            var name = Encoding.ASCII.GetString(buf);

            if (4 != stream.Read(buf, 0, 4))
            {
                throw new EndOfStreamException();
            }
            var len = BitConverter.ToInt32(buf, 0);

            if (BitConverter.IsLittleEndian)
            {
                len = MidiUtility.Swap(len);
            }
            buf = new byte[len];
            if (len != stream.Read(buf, 0, len))
            {
                throw new EndOfStreamException();
            }
            chunk = new KeyValuePair <string, byte[]>(name, buf);
            return(true);
        }
Example #2
0
        /// <summary>
        /// Reads a MIDI file from the specified stream
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <returns>A MIDI file object representing the MIDI data in the stream</returns>
        public static MidiFile ReadFrom(Stream stream)
        {
            KeyValuePair <string, byte[]> chunk;

            if (!_TryReadChunk(stream, out chunk) || "MThd" != chunk.Key || 6 > chunk.Value.Length)
            {
                throw new InvalidDataException("The stream is not a MIDI file format.");
            }
            var type       = BitConverter.ToInt16(chunk.Value, 0);
            var trackCount = BitConverter.ToInt16(chunk.Value, 2);
            var timeBase   = BitConverter.ToInt16(chunk.Value, 4);

            if (BitConverter.IsLittleEndian)
            {
                type       = MidiUtility.Swap(type);
                trackCount = MidiUtility.Swap(trackCount);
                timeBase   = MidiUtility.Swap(timeBase);
            }
            var result = new MidiFile(type, timeBase);

            while (_TryReadChunk(stream, out chunk))
            {
                if ("MTrk" == chunk.Key)
                {
                    var tstm = new MemoryStream(chunk.Value, false);
                    var trk  = MidiSequence.ReadFrom(tstm);
                    result.Tracks.Add(trk);
                }
            }
            return(result);
        }
Example #3
0
        /// <summary>
        /// Writes the MIDI file to the specified stream
        /// </summary>
        /// <param name="stream">The stream to write to</param>
        public void WriteTo(Stream stream)
        {
            var fileStream = stream as FileStream;

            if (null != fileStream)
            {
                FilePath = fileStream.Name;
            }
            try
            {
                stream.SetLength(0);
            }
            catch { }
            var buf = new byte[4];

            buf[0] = (byte)'M'; buf[1] = (byte)'T'; buf[2] = (byte)'h'; buf[3] = (byte)'d';
            stream.Write(buf, 0, 4);
            var len = 6;

            if (BitConverter.IsLittleEndian)
            {
                len = MidiUtility.Swap(len);
            }
            stream.Write(BitConverter.GetBytes(len), 0, 4);
            var type       = Type;
            var trackCount = (short)Tracks.Count;
            var timeBase   = TimeBase;

            if (BitConverter.IsLittleEndian)
            {
                type       = MidiUtility.Swap(type);
                trackCount = MidiUtility.Swap(trackCount);
                timeBase   = MidiUtility.Swap(timeBase);
            }
            stream.Write(BitConverter.GetBytes(type), 0, 2);
            stream.Write(BitConverter.GetBytes(trackCount), 0, 2);
            stream.Write(BitConverter.GetBytes(timeBase), 0, 2);
            foreach (var trk in Tracks)
            {
                buf[0] = (byte)'M'; buf[1] = (byte)'T'; buf[2] = (byte)'r'; buf[3] = (byte)'k';
                stream.Write(buf, 0, 4);
                var tstm = new MemoryStream();
                trk.WriteTo(tstm);
                tstm.Position = 0;
                len           = (int)tstm.Length;
                if (BitConverter.IsLittleEndian)
                {
                    len = MidiUtility.Swap(len);
                }
                stream.Write(BitConverter.GetBytes(len), 0, 4);
                tstm.CopyTo(stream);
                tstm.Close();
            }
        }
Example #4
0
 /// <summary>
 /// Sends a message immediately to the device
 /// </summary>
 /// <param name="message">The message to send</param>
 /// <remarks>The message is not queued. Tempo change messages are not honored.</remarks>
 public void Send(MidiMessage message)
 {
     if (IntPtr.Zero == _handle)
     {
         throw new InvalidOperationException("The device is closed.");
     }
     if (null == message)
     {
         throw new ArgumentNullException("message");
     }
     if (0xF0 == (message.Status & 0xF0))
     {
         if (0xF != message.Channel)
         {
             var data = MidiUtility.ToMessageBytes(message);
             if (null == data)
             {
                 return;
             }
             if (254 < data.Length)
             {
                 var len = 254;
                 for (var i = 0; i < data.Length; i += len)
                 {
                     if (data.Length <= i + len)
                     {
                         len = data.Length - i;
                     }
                     _SendRaw(data, i, len);
                 }
             }
             else
             {
                 _SendRaw(data, 0, data.Length);
             }
         }
     }
     else
     {
         _CheckOutResult(midiOutShortMsg(_handle, MidiUtility.PackMessage(message)));
     }
 }
Example #5
0
        void _MidiInProc(IntPtr handle, int msg, int instance, int lparam, int wparam)
        {
            switch (msg)
            {
            case MIM_OPEN:
                Opened?.Invoke(this, EventArgs.Empty);
                break;

            case MIM_CLOSE:
                Closed?.Invoke(this, EventArgs.Empty);
                break;

            case MIM_DATA:
                MidiMessage m;
                if (0 != _tempoSynchEnabled && 0xF8 == (0xFF & lparam))
                {
                    if (0 != _timingTimestamp)
                    {
                        var dif      = (_PreciseUtcNowTicks - _timingTimestamp) * 24;
                        var tpm      = TimeSpan.TicksPerMillisecond * 60000;
                        var newTempo = (tpm / (double)dif);
                        if (newTempo < _tempoSynchMininumTempo)
                        {
                            Interlocked.Exchange(ref _timingTimestamp, 0);
                        }
                        else
                        {
                            var timeNow = _PreciseUtcNowTicks;

                            if (0L == _tempoSyncTimestamp || 0L == _tempoSyncFrequency || (timeNow - _tempoSyncTimestamp > _tempoSyncFrequency))
                            {
                                var tmp = Tempo;
                                var ta  = (tmp + newTempo) / 2;
                                Tempo = ta;
                            }
                            Interlocked.Exchange(ref _timingTimestamp, timeNow);
                        }
                    }
                    else
                    {
                        var timeNow = _PreciseUtcNowTicks;
                        Interlocked.Exchange(ref _timingTimestamp, timeNow);
                    }
                }
                else
                {
                    m = MidiUtility.UnpackMessage(lparam);
                    _ProcessRecording(m);
                    Input?.Invoke(this, new MidiInputEventArgs(new TimeSpan(0, 0, 0, 0, wparam), m));
                }
                break;

            case MIM_ERROR:
                Error?.Invoke(this, new MidiInputEventArgs(new TimeSpan(0, 0, 0, 0, wparam), MidiUtility.UnpackMessage(lparam)));
                break;

            case MIM_LONGDATA:
            case MIM_LONGERROR:
                // TODO: Semi tested
                var hdr = (MIDIHDR)Marshal.PtrToStructure(new IntPtr(lparam), typeof(MIDIHDR));
                if (0 == hdr.dwBytesRecorded)
                {
                    return;                             // no message
                }
                // this code assumes it's a sysex message but I should probably check it.
                var status  = Marshal.ReadByte(hdr.lpData, 0);
                var payload = new byte[hdr.dwBytesRecorded - 1];
                Marshal.Copy(new IntPtr((int)hdr.lpData + 1), payload, 0, payload.Length);
                m = new MidiMessageSysex(payload);
                var sz = Marshal.SizeOf(typeof(MIDIHDR));
                _inHeader.dwBufferLength = _inHeader.dwBytesRecorded = 65536u;
                _inHeader.lpData         = _buffer;
                _CheckInResult(midiInPrepareHeader(_handle, ref _inHeader, sz));
                _CheckInResult(midiInAddBuffer(_handle, ref _inHeader, sz));
                _ProcessRecording(m);
                if (MIM_LONGDATA == msg)
                {
                    Input?.Invoke(this, new MidiInputEventArgs(new TimeSpan(0, 0, 0, 0, wparam), m));
                }
                else
                {
                    Error?.Invoke(this, new MidiInputEventArgs(new TimeSpan(0, 0, 0, 0, wparam), m));
                }
                break;

            case MIM_MOREDATA:
                break;

            default:
                break;
            }
        }
Example #6
0
 /// <summary>
 /// Adjusts the tempo of a MIDI file
 /// </summary>
 /// <param name="tempo">The new tempo</param>
 /// <returns>A new file with an adjusted tempo. All other tempo messages are adjusted relatively to the first one</returns>
 public MidiFile AdjustTempo(double tempo)
 => AdjustTempo(MidiUtility.TempoToMicroTempo(tempo));
Example #7
0
        /// <summary>
        /// Sends events directly to the event queue without buffering
        /// </summary>
        /// <param name="events">The events to send</param>
        /// <remarks>The total size of the events must be less than 64kb</remarks>
        public void SendDirect(IEnumerable <MidiEvent> events)
        {
            if (null == events)
            {
                throw new ArgumentNullException("events");
            }
            if (IntPtr.Zero == _handle)
            {
                throw new InvalidOperationException("The stream is closed.");
            }
            if (IntPtr.Zero != _sendHeader.lpData)
            {
                throw new InvalidOperationException("The stream is busy playing.");
            }
            int    baseEventSize = Marshal.SizeOf(typeof(MIDIEVENT));
            int    blockSize     = 0;
            IntPtr eventPointer  = _sendEventBuffer;
            var    ofs           = 0;
            var    ptrOfs        = 0;
            var    hasEvents     = false;

            foreach (var @event in events)
            {
                hasEvents = true;
                if (0xF0 != (@event.Message.Status & 0xF0))
                {
                    blockSize += baseEventSize;
                    if (_SendBufferSize <= blockSize)
                    {
                        throw new ArgumentException("There are too many events in the event buffer - maximum size must be 64k", "events");
                    }
                    var se = new MIDIEVENT();
                    se.dwDeltaTime = @event.Position + ofs;
                    se.dwStreamId  = 0;
                    se.dwEvent     = MidiUtility.PackMessage(@event.Message);
                    Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false);
                    ptrOfs += baseEventSize;
                    ofs     = 0;
                }
                else if (0xFF == @event.Message.Status)
                {
                    var mm = @event.Message as MidiMessageMeta;
                    if (0x51 == mm.Data1)                     // tempo
                    {
                        blockSize += baseEventSize;
                        if (_SendBufferSize <= blockSize)
                        {
                            throw new ArgumentException("There are too many events in the event buffer - maximum size must be 64k", "events");
                        }

                        var se = new MIDIEVENT();
                        se.dwDeltaTime = @event.Position + ofs;
                        se.dwStreamId  = 0;
                        se.dwEvent     = (mm.Data[0] << 16) | (mm.Data[1] << 8) | mm.Data[2] | (MEVT_TEMPO << 24);
                        Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false);
                        ptrOfs += baseEventSize;
                        ofs     = 0;
                        // TODO: This signal is sent too early. It should really wait until after the
                        // MEVT_TEMPO message is processed by the driver, but i have no easy way to
                        // do that. All we can do is hope, here
                        Interlocked.Exchange(ref _tempoSyncMessagesSentCount, 0);
                    }
                    else if (0x2f == mm.Data1)                     // end track
                    {
                        blockSize += baseEventSize;
                        if (_SendBufferSize <= blockSize)
                        {
                            throw new ArgumentException("There are too many events in the event buffer - maximum size must be 64k", "events");
                        }

                        // add a NOP message to it just to pad our output in case we're looping
                        var se = new MIDIEVENT();
                        se.dwDeltaTime = @event.Position + ofs;
                        se.dwStreamId  = 0;
                        se.dwEvent     = (MEVT_NOP << 24);
                        Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false);
                        ptrOfs += baseEventSize;
                        ofs     = 0;
                    }
                    else
                    {
                        ofs = @event.Position;
                    }
                }
                else                 // sysex
                {
                    var msx = @event.Message as MidiMessageSysex;
                    var dl  = msx.Data.Length + 1;
                    if (0 != (dl % 4))
                    {
                        dl += 4 - (dl % 4);
                    }
                    blockSize += baseEventSize + dl;
                    if (_SendBufferSize <= blockSize)
                    {
                        throw new ArgumentException("There are too many events in the event buffer - maximum size must be 64k", "events");
                    }

                    var se = new MIDIEVENT();
                    se.dwDeltaTime = @event.Position + ofs;
                    se.dwStreamId  = 0;
                    se.dwEvent     = MEVT_F_LONG | (msx.Data.Length + 1);
                    Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false);
                    ptrOfs += baseEventSize;
                    Marshal.WriteByte(new IntPtr(ptrOfs + eventPointer.ToInt64()), msx.Status);
                    Marshal.Copy(msx.Data, 0, new IntPtr(ptrOfs + eventPointer.ToInt64() + 1), msx.Data.Length);

                    ptrOfs += dl;
                    ofs     = 0;
                }
            }
            if (hasEvents)
            {
                _sendHeader = default(MIDIHDR);
                Interlocked.Exchange(ref _sendHeader.lpData, eventPointer);
                _sendHeader.dwBufferLength = _sendHeader.dwBytesRecorded = unchecked ((uint)blockSize);
                _sendEventBuffer           = eventPointer;
                int headerSize = Marshal.SizeOf(typeof(MIDIHDR));
                _CheckOutResult(midiOutPrepareHeader(_handle, ref _sendHeader, headerSize));
                _CheckOutResult(midiStreamOut(_handle, ref _sendHeader, headerSize));
            }
        }
Example #8
0
 /// <summary>
 /// Creates a new MIDI channel pitch message
 /// </summary>
 /// <param name="pitch">The MIDI pressure (0-16383)</param>
 /// <param name="channel">The MIDI channel (0-15)</param>
 public MidiMessageChannelPitch(short pitch, byte channel) : base(unchecked ((byte)(0xE0 | channel)), BitConverter.IsLittleEndian?MidiUtility.Swap(pitch):pitch)
 {
 }
Example #9
0
        void _SendBlock()
        {
            if (null == _sendQueue)
            {
                return;
            }
            if (IntPtr.Zero == _handle)
            {
                throw new InvalidOperationException("The stream is closed.");
            }

            if (IntPtr.Zero != Interlocked.CompareExchange(ref _sendHeader.lpData, _sendEventBuffer, IntPtr.Zero))
            {
                throw new InvalidOperationException("The stream is busy playing.");
            }

            int    baseEventSize = Marshal.SizeOf(typeof(MIDIEVENT));
            int    blockSize     = 0;
            IntPtr eventPointer  = _sendEventBuffer;
            var    ofs           = 0;
            var    ptrOfs        = 0;

            for (; _sendQueuePosition < _sendQueue.Count; Interlocked.Exchange(ref _sendQueuePosition, _sendQueuePosition + 1))
            {
                var @event = _sendQueue[_sendQueuePosition];
                if (0x00 != @event.Message.Status && 0xF0 != (@event.Message.Status & 0xF0))
                {
                    if (_SendBufferSize < blockSize + baseEventSize)
                    {
                        break;
                    }
                    blockSize += baseEventSize;
                    var se = new MIDIEVENT();
                    se.dwDeltaTime = @event.Position + ofs;
                    se.dwStreamId  = 0;
                    se.dwEvent     = MidiUtility.PackMessage(@event.Message);
                    var gch = GCHandle.Alloc(se, GCHandleType.Pinned);
                    CopyMemory(new IntPtr(ptrOfs + eventPointer.ToInt64()), gch.AddrOfPinnedObject(), Marshal.SizeOf(typeof(MIDIEVENT)));
                    gch.Free();
                    ptrOfs += baseEventSize;
                    ofs     = 0;
                }
                else if (0xFF == @event.Message.Status)
                {
                    var mm = @event.Message as MidiMessageMeta;
                    if (0x51 == mm.Data1)                     // tempo
                    {
                        if (_SendBufferSize < blockSize + baseEventSize)
                        {
                            break;
                        }
                        blockSize += baseEventSize;
                        var se = new MIDIEVENT();
                        se.dwDeltaTime = @event.Position + ofs;
                        se.dwStreamId  = 0;
                        se.dwEvent     = (mm.Data[0] << 16) | (mm.Data[1] << 8) | mm.Data[2] | (MEVT_TEMPO << 24);
                        var gch = GCHandle.Alloc(se, GCHandleType.Pinned);
                        CopyMemory(new IntPtr(ptrOfs + eventPointer.ToInt64()), gch.AddrOfPinnedObject(), Marshal.SizeOf(typeof(MIDIEVENT)));
                        gch.Free();
                        ptrOfs += baseEventSize;
                        ofs     = 0;
                    }
                    else if (0x2f == mm.Data1)                     // end track
                    {
                        if (_SendBufferSize < blockSize + baseEventSize)
                        {
                            break;
                        }
                        blockSize += baseEventSize;

                        // add a NOP message to it just to pad our output in case we're looping
                        var se = new MIDIEVENT();
                        se.dwDeltaTime = @event.Position + ofs;
                        se.dwStreamId  = 0;
                        se.dwEvent     = (MEVT_NOP << 24);
                        var gch = GCHandle.Alloc(se, GCHandleType.Pinned);
                        CopyMemory(new IntPtr(ptrOfs + eventPointer.ToInt64()), gch.AddrOfPinnedObject(), Marshal.SizeOf(typeof(MIDIEVENT)));
                        gch.Free();
                        ptrOfs += baseEventSize;
                        ofs     = 0;
                    }
                    else
                    {
                        ofs = @event.Position;
                    }
                }
                else                 // sysex or sysex part
                {
                    byte[] data;
                    if (0 == @event.Message.Status)
                    {
                        data = (@event.Message as MidiMessageSysexPart).Data;
                    }
                    else
                    {
                        data = MidiUtility.ToMessageBytes(@event.Message);
                    }


                    var dl = data.Length;
                    if (0 != (dl % 4))
                    {
                        dl += 4 - (dl % 4);
                    }
                    if (_SendBufferSize < blockSize + baseEventSize + dl)
                    {
                        break;
                    }

                    blockSize += baseEventSize + dl;

                    var se = new MIDIEVENT();
                    se.dwDeltaTime = @event.Position + ofs;
                    se.dwStreamId  = 0;
                    se.dwEvent     = MEVT_F_LONG | data.Length;
                    var gch = GCHandle.Alloc(se, GCHandleType.Pinned);
                    CopyMemory(new IntPtr(ptrOfs + eventPointer.ToInt64()), gch.AddrOfPinnedObject(), Marshal.SizeOf(typeof(MIDIEVENT)));
                    gch.Free();
                    ptrOfs += baseEventSize;
                    Marshal.Copy(data, 0, new IntPtr(ptrOfs + eventPointer.ToInt64()), data.Length);

                    ptrOfs += dl;
                    ofs     = 0;
                }
            }
            _sendHeader = default(MIDIHDR);
            _sendHeader.dwBufferLength = _sendHeader.dwBytesRecorded = unchecked ((uint)blockSize);
            _sendHeader.lpData         = _sendEventBuffer;
            int headerSize = Marshal.SizeOf(typeof(MIDIHDR));

            _CheckOutResult(midiOutPrepareHeader(_handle, ref _sendHeader, headerSize));
            _CheckOutResult(midiStreamOut(_handle, ref _sendHeader, headerSize));
        }
Example #10
0
 /// <summary>
 /// Creates a new instance with the specified tempo
 /// </summary>
 /// <param name="tempo">The tempo</param>
 public MidiMessageMetaTempo(double tempo) : this(MidiUtility.TempoToMicroTempo(tempo))
 {
 }
Example #11
0
 /// <summary>
 /// Gets a string representation of this message
 /// </summary>
 /// <returns></returns>
 public override string ToString()
 {
     return("Key Pressure: " + MidiUtility.NoteIdToNote(NoteId) + ", Pressure: " + Pressure.ToString() + ", Channel: " + Channel.ToString());
 }
Example #12
0
 /// <summary>
 /// Gets a string representation of this message
 /// </summary>
 /// <returns></returns>
 public override string ToString()
 {
     return("Note Off: " + MidiUtility.NoteIdToNote(NoteId) + ", Velocity: " + Velocity.ToString() + ", Channel: " + Channel.ToString());
 }
Example #13
0
        /// <summary>
        /// Plays the sequence to the specified MIDI device using the specified timebase
        /// </summary>
        /// <param name="timeBase">The timebase to use, in pulses/ticks per quarter note</param>
        /// <param name="deviceIndex">The MIDI device to output to</param>
        /// <param name="loop">Indicates whether to loop playback or not</param>
        public void Preview(short timeBase = 480, int deviceIndex = 0, bool loop = false)
        {
            var handle = MidiUtility.OpenOutputDevice(deviceIndex);
            var ppq    = timeBase;
            var mt     = MidiUtility.TempoToMicroTempo(120d);

            try
            {
                while (loop)
                {
                    var ticksusec    = mt / (double)timeBase;
                    var tickspertick = ticksusec / (TimeSpan.TicksPerMillisecond / 1000) * 100;
                    var tickStart    = MidiUtility.PreciseUtcNowTicks;
                    var tickCurrent  = tickStart;

                    var end = (long)(Length * tickspertick + tickStart);
                    var tpm = TimeSpan.TicksPerMillisecond;

                    using (var e = AbsoluteEvents.GetEnumerator())
                    {
                        if (!e.MoveNext())
                        {
                            return;
                        }
                        var done = false;
                        while (!done && tickCurrent <= end)
                        {
                            tickCurrent = MidiUtility.PreciseUtcNowTicks;
                            var ce = (long)((tickCurrent - tickStart) / tickspertick);
                            while (!done && e.Current.Position <= ce)
                            {
                                if (0xFF == e.Current.Message.Status)
                                {
                                    var mbs = e.Current.Message as MidiMessageMeta;
                                    if (0x51 == mbs.Data1)
                                    {
                                        if (BitConverter.IsLittleEndian)
                                        {
                                            mt = (mbs.Data[0] << 16) | (mbs.Data[1] << 8) | mbs.Data[2];
                                        }
                                        else
                                        {
                                            mt = (mbs.Data[2] << 16) | (mbs.Data[1] << 8) | mbs.Data[0];
                                        }
                                        ticksusec    = mt / (double)ppq;
                                        tickspertick = ticksusec / (tpm / 1000) * 100;
                                        end          = (long)(Length * tickspertick + tickStart);
                                    }
                                    else if (0x2F == mbs.Data1)
                                    {
                                        done = true;
                                    }
                                }
                                MidiUtility.Send(handle, e.Current.Message);
                                if (!e.MoveNext())
                                {
                                    done = true;
                                }
                            }
                        }
                    }
                }
            }
            finally
            {
                MidiUtility.CloseOutputDevice(handle);
            }
        }
Example #14
0
        void _SendBlock()
        {
            if (null == _sendQueue)
            {
                return;
            }
            if (IntPtr.Zero == Handle)
            {
                throw new InvalidOperationException("The stream is closed.");
            }

            int    blockSize     = 0;
            IntPtr headerPointer = Marshal.AllocHGlobal(MIDIHDR_SIZE + MAX_EVENTBLOCK_SIZE);

            try
            {
                IntPtr eventPointer = new IntPtr(headerPointer.ToInt64() + MIDIHDR_SIZE);
                var    ofs          = 0;
                var    ptrOfs       = 0;
                for (; _sendQueuePosition < _sendQueue.Count; Interlocked.Exchange(ref _sendQueuePosition, _sendQueuePosition + 1))
                {
                    var @event = _sendQueue[_sendQueuePosition];
                    if (0x00 != @event.Message.Status && 0xF0 != (@event.Message.Status & 0xF0))
                    {
                        if (MAX_EVENTBLOCK_SIZE < blockSize + MIDIEVENT_SIZE)
                        {
                            break;
                        }
                        blockSize += MIDIEVENT_SIZE;
                        var se = default(MIDIEVENT);
                        se.dwDeltaTime = @event.Position + ofs;
                        se.dwStreamId  = 0;
                        se.dwEvent     = MidiUtility.PackMessage(@event.Message);
                        Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false);
                        ptrOfs += MIDIEVENT_SIZE;
                        ofs     = 0;
                    }
                    else if (0xFF == @event.Message.Status)
                    {
                        var mm = @event.Message as MidiMessageMeta;
                        if (0x51 == mm.Data1)                         // tempo
                        {
                            if (MAX_EVENTBLOCK_SIZE < blockSize + MIDIEVENT_SIZE)
                            {
                                break;
                            }
                            blockSize += MIDIEVENT_SIZE;
                            var se = default(MIDIEVENT);
                            se.dwDeltaTime = @event.Position + ofs;
                            se.dwStreamId  = 0;
                            se.dwEvent     = (mm.Data[0] << 16) | (mm.Data[1] << 8) | mm.Data[2] | (MEVT_TEMPO << 24);
                            Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false);
                            ptrOfs += MIDIEVENT_SIZE;
                            ofs     = 0;
                        }
                        else if (0x2f == mm.Data1)                         // end track
                        {
                            if (MAX_EVENTBLOCK_SIZE < blockSize + MIDIEVENT_SIZE)
                            {
                                break;
                            }
                            blockSize += MIDIEVENT_SIZE;

                            // add a NOP message to it just to pad our output in case we're looping
                            var se = default(MIDIEVENT);
                            se.dwDeltaTime = @event.Position + ofs;
                            se.dwStreamId  = 0;
                            se.dwEvent     = (MEVT_NOP << 24);
                            Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false);
                            ptrOfs += MIDIEVENT_SIZE;
                            ofs     = 0;
                        }
                        else
                        {
                            ofs = @event.Position;
                        }
                    }
                    else                     // sysex or sysex part
                    {
                        byte[] data;
                        if (0 == @event.Message.Status)
                        {
                            data = (@event.Message as MidiMessageSysexPart).Data;
                        }
                        else
                        {
                            data = MidiUtility.ToMessageBytes(@event.Message);
                        }


                        var dl = data.Length;
                        if (0 != (dl % 4))
                        {
                            dl += 4 - (dl % 4);
                        }
                        if (MAX_EVENTBLOCK_SIZE < blockSize + MIDIEVENT_SIZE + dl)
                        {
                            break;
                        }

                        blockSize += MIDIEVENT_SIZE + dl;

                        var se = default(MIDIEVENT);
                        se.dwDeltaTime = @event.Position + ofs;
                        se.dwStreamId  = 0;
                        se.dwEvent     = MEVT_F_LONG | data.Length;
                        Marshal.StructureToPtr(se, new IntPtr(ptrOfs + eventPointer.ToInt64()), false);
                        ptrOfs += MIDIEVENT_SIZE;
                        Marshal.Copy(data, 0, new IntPtr(ptrOfs + eventPointer.ToInt64()), data.Length);

                        ptrOfs += dl;
                        ofs     = 0;
                    }
                }
                var header = default(MIDIHDR);
                header.dwBufferLength = header.dwBytesRecorded = unchecked ((uint)blockSize);
                header.lpData         = eventPointer;
                Marshal.StructureToPtr(header, headerPointer, false);
                _CheckOutResult(midiOutPrepareHeader(Handle, headerPointer, MIDIHDR_SIZE));
                _CheckOutResult(midiStreamOut(Handle, headerPointer, MIDIHDR_SIZE));
                headerPointer = IntPtr.Zero;
            }
            finally
            {
                if (IntPtr.Zero != headerPointer)
                {
                    Marshal.FreeHGlobal(headerPointer);
                }
            }
        }