private void ReadThreadStart() { byte[] buf = new byte[512]; byte[] overflowBuf = null; // Compute number of pollfd structures required to poll int numPollStructs = AlsaNativeMethods.Snd_rawmidi_poll_descriptors_count(handle); // Allocate space for the poll structures IntPtr pollAreaPtr = Marshal.AllocHGlobal(numPollStructs * AlsaNativeMethods.POLLFDSZ); int st = AlsaNativeMethods.Snd_rawmidi_poll_descriptors(handle, pollAreaPtr, (uint)numPollStructs); while (!stop) { st = AlsaNativeMethods.Poll(pollAreaPtr, numPollStructs, 200); if (st < 0) { var errno = Marshal.GetLastWin32Error(); if (errno == 4 /* EINTR */) { continue; } Console.WriteLine("MidiIn: Cannot poll - errno = " + errno); break; } if (st == 0) { continue; } // We got something - scan the poll struct ushort revent = 0; st = AlsaNativeMethods.Snd_rawmidi_poll_descriptors_revents( handle, pollAreaPtr, (uint)numPollStructs, ref revent); if (st < 0) { Console.WriteLine("MidiIn: Cannot parse poll - " + AlsaUtils.StrError(st)); break; } AlsaNativeMethods.EPoll evt = (AlsaNativeMethods.EPoll)revent; if (evt.HasFlag(AlsaNativeMethods.EPoll.POLLERR) || evt.HasFlag(AlsaNativeMethods.EPoll.POLLHUP)) { break; } if (evt.HasFlag(AlsaNativeMethods.EPoll.POLLIN)) { // We have something to read - read it int nRead = (int)AlsaNativeMethods.Snd_rawmidi_read(handle, buf, (uint)buf.Length); if (nRead < 0) { int errno = Marshal.GetLastWin32Error(); if (errno == 4 /* EINTR */) { continue; } Console.WriteLine("MidiIn: Cannot parse poll - " + AlsaUtils.StrError(st)); break; } // Combine with overflow buffer if we had an overflow last time if (overflowBuf != null) { var newBuf = new byte[buf.Length + overflowBuf.Length]; Array.Copy(overflowBuf, 0, newBuf, 0, overflowBuf.Length); Array.Copy(buf, 0, newBuf, overflowBuf.Length, buf.Length); buf = newBuf; overflowBuf = null; } // Translate the input to midi message int nParsed = 0; while (true) { st = ParseBuffer(buf, nParsed, nRead, out MidiMessage midiMessage); if (st < 0) { // buffer contains an incomplete message overflowBuf = buf; buf = new byte[overflowBuf.Length]; } else if (st == 0) { // Message not supported - drop everything. break; } else // st > 0 { // Send message var midiEvent = new MidiEvent(midiMessage, 0); var midiEventArg = new WindowsMultiMedia.WindowsMidiEventArgs(midiEvent, IntPtr.Zero, IntPtr.Zero); MidiInputReceived?.Invoke(this, midiEventArg); // Update counters nParsed += st; nRead -= st; if (nRead <= 0) { // Normal end break; } } } } } Marshal.FreeHGlobal(pollAreaPtr); }
// Forward callback to user-provided function private void MidiProc(IntPtr hMidiIn, EMMMidiMessages wMsg, IntPtr dwInstance, IntPtr dwParam1, IntPtr dwParam2) { if (wMsg == EMMMidiMessages.MIM_DATA) { if (MidiInputReceived != null) { // Make a midi event out of the incoming params MidiEvent e = MidiInParser.ParseMimDataMessage((uint)dwParam1, (uint)dwParam2); // Give it to the user MidiInputReceived.Invoke(this, new WindowsMidiEventArgs(e, dwParam1, dwParam2)); } } else if (wMsg == EMMMidiMessages.MIMLONG_DATA) { // The first parameter is a pointer to a buffer descriptor var header = dwParam1; // Find this buffer in our buffer set int index = (int)MidiBuffer.GetUserData(header); if (index < 0 || index >= buffers.Length) { throw new InvalidOperationException("midiInputPort: MIMLONG_DATA buffer not found - index " + index); } var buffer = buffers[index]; if (buffer != null) { // Get data from buffer var data = buffer.GetRecordedData(); if (data.Length == 0) { // This happens on Reset(), all the buffers are freed by windows and we get a MIMLONG_DATA for each of them. // Free the buffer. buffer.RemoveMidiInBuffer(handle); buffer.Dispose(); buffers[index] = null; } else { // Give the buffer back to windows, we are done with it buffer.AddMidiInBuffer(handle); // If we have a callback, either generate a sysex or // memorize the data if we don't have a full sysex if (MidiInputReceived != null) { MidiSysexMessage sysex = null; if (data[0] == (byte)EMidiCommand.SystemExclusive) { // This buffer contains the beginning of the sysex - check we don't have an ongoing sysex if (this.partialSysex != null) { throw new InvalidOperationException("midiInputPort: Beginning of new sysex received while previous one still on-going"); } if (data[data.Length - 1] == (byte)EMidiCommand.EndOfSystemExclusive) { // This buffer contains a full sysex sysex = new MidiSysexMessage(data); } else { // This buffer contains the beginning of a sysex this.partialSysex = data; } } else { // This buffer is a continuation of a sysex. Verify we have an ongoing sysex if (this.partialSysex == null) { throw new InvalidOperationException("midiInputPort: Continuation sysex without beginning"); } // Concatenate the partial data and the new data var dataSoFar = new byte[partialSysex.Length + data.Length]; partialSysex.CopyTo(dataSoFar, 0); data.CopyTo(dataSoFar, dataSoFar.Length); if (data[data.Length - 1] == (byte)EMidiCommand.EndOfSystemExclusive) { // This buffer concludes the sysex sysex = new MidiSysexMessage(dataSoFar); this.partialSysex = null; } else { // This buffer contains a partial sysex, add it to the partial sysex this.partialSysex = dataSoFar; } } // Create sysex event if we have a sysex if (sysex != null) { // Make a midi event out of the sysex var e = new MidiEvent(sysex, (uint)dwParam2); // Give it to the user MidiInputReceived.Invoke(this, new WindowsMidiEventArgs(e, dwParam1, dwParam2)); } } } } } }