/// <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)); } }
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)); }