I am using the ancient Windows Multimedia API (midiXyz functions from WinMM.dll) from C#.
After opening a Midi Out device/port in non-streaming mode (midiOutOpen
), sending SysEx with (midiOutLongMsg
) works fine.
After opening a Midi Out device/port in streaming mode (midiStreamOpen
), sending SysEx with midiOutLongMsg
does not work.
Instead, midiOutLongMsg
fails with error MMSYSERR_NOTSUPPORTED
(= 8).
The error text is: "This function is not supported. Use the Capabilities function to determine which functions and messages the driver supports."
However, according to MSDN, (midiOutLongMsg
) should also work with stream handles.
Jeff Glatt's excellent MIDI information pages also claim, that SysEx and streaming can be used together (see end of page).
Sending buffered SysEx messages by enqueueing them with (midiStreamOut
) midiStreamOut works fine.
However, I need/want to send SysEx directly using midiOutLongMsg
, too.
I have already checked out various open source Midi libraries (managed as well as unmanaged), several Midi driver sources and even the WinMM.dll sources of WINE, but could not find any hints what I am doing wrong.
To reproduce my problem in the smallest possible code, I ripped out all callback, unprepare, cleanup and release stuff, and condensed several classes in just one function. The following code opens the first Midi device/port and tries to send a "GM Mode On" SysEx message:
Update 2014 January 12th: Please see code version 3 below !
public static class MidiTest { // version 1 - x86/32 bit only
public static void Test () {
int moID = 0; // midi out device/port ID
int moHdl; // midi out device/port handle
#if !true
// SysEx via midiOutLongMsg works
Chk (WinMM.midiOutOpen (out moHdl, moID, null, 0, 0)); // open midi out in non-stream mode
#else
// SysEx via midiOutLongMsg fails
Chk (WinMM.midiStreamOpen (out moHdl, ref moID, 1, null, 0, 0)); // open midi out in stream mode
#endif
byte [] sx = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 }; // GM On sysex
int shdr = Marshal.SizeOf (typeof (MidiHdr)); // hdr size
var mhdr = new MidiHdr (); // allocate managed hdr
mhdr.bufferLength = mhdr.bytesRecorded = sx.Length; // length of message bytes
mhdr.data = Marshal.AllocHGlobal (mhdr.bufferLength); // allocate native message bytes
Marshal.Copy (sx, 0, mhdr.data, mhdr.bufferLength); // copy message bytes from managed to native memory
IntPtr nhdr = Marshal.AllocHGlobal (shdr); // allocate native hdr
Marshal.StructureToPtr (mhdr, nhdr, false); // copy managed hdr to native hdr
Chk (WinMM.midiOutPrepareHeader (moHdl, nhdr, shdr)); // prepare native hdr
Chk (WinMM.midiOutLongMsg (moHdl, nhdr, shdr)); // send native message bytes
} // Test
static void Chk (int f) {
if (0 == f) return;
var sb = new StringBuilder (256); // MAXERRORLENGTH
var s = 0 == WMM.midiOutGetErrorText (f, sb, sb.Capacity) ? sb.ToString () : String.Format ("MIDI Error {0}.", f);
System.Diagnostics.Trace.WriteLine (s);
}
[StructLayout (LayoutKind.Sequential)]
internal struct MidiHdr { // sending long MIDI messages requires a header
public IntPtr data; // native pointer to message bytes, allocated on native heap
public int bufferLength; // length of buffer 'data'
public int bytesRecorded; // actual amount of data in buffer 'data'
public int user; // custom user data
public int flags; // information flags about buffer
public IntPtr next; // reserved
public int reserved; // reserved
public int offset; // buffer offset on callback
[MarshalAs (UnmanagedType.ByValArray, SizeConst = 4)]
public int[] reservedArray; // reserved
} // struct MidiHdr
internal sealed class WinMM { // native MIDI calls from WinMM.dll
public delegate void CB (int hdl, int msg, int inst, int p1, int p2); // callback
[DllImport ("winmm.dll")] public static extern int midiStreamOpen (out int hdl, ref int devID, int reserved, CB proc, int inst, int flags);
[DllImport ("winmm.dll")] public static extern int midiOutOpen (out int hdl, int devID, CB proc, int inst, int flags);
[DllImport ("winmm.dll")] public static extern int midiOutPrepareHeader (int hdl, IntPtr pHdr, int sHdr);
[DllImport ("winmm.dll")] public static extern int midiOutLongMsg (int hdl, IntPtr pHdr, int sHdr);
[DllImport ("winmm.dll")] public static extern int midiOutGetErrorText (int err, StringBuilder msg, int sMsg);
} // class WinMM
} // class MidiTest
Question 1: Is it possible to send SysEx via midiOutLongMsg
when the Midi device/port has been opened in streaming mode (midiStreamOpen
) ?
Question 2: If yes, any idea what I am missing ?
Question 3: I did not found many sources using MIDI in streaming mode. So, if you know some open source library using MIDI output in streaming mode, please throw me a link so I can compare ..
Thanks
Update (2013 Oct 19):
Hi CL,
thanks for your answer. Yes, maybe someone at Microsoft messed something up in maintaining WinMM.dll - but I think the probability that I am missing something is higher, because
a) There is an old manual "Windows NT DDK - Multimedia Drivers" (available here), that describes the WinMM stuff in more detail than the current MSDN pages. Page 56 shows a diagram with WinMM.dll as an intermediate layer between the application and the MIDI/Audio driver(s). WinMM's main job is to pass the MIDI data from the application to one of the MIDI/Audio drivers. When the MIDI driver is a port driver for an external keyboard/synthesizer/tone generator, WinMM cant change the MIDI data that much. Page 94 describes the driver message MODM_LONGDATA and its parameters - its pretty much identical to the parameters of midiOutLongMsg. That limits the opportunities to mess something up inside WinMM.dll.
b) The code path in WinMM.dll from midiOutLongMsg being called to calling the driver with MODM_LONGDATA works fine after midiOutOpen, but not after midiStreamOpen. The result code is MMSYSERR_NOTSUPPORTED - that tells me that I am getting slapped by some sanity check at the beginning of the code path in WinMM.dll, like
if (whatever_weird_condition) return MMSYSERR_NOTSUPPORTED;
And that whatever_weird_condition is most likely about something I should have done, but have not done ..
c) If the MIDI driver doesnt support streaming output by itself (its optional), WinMM does the translation from buffered/enqueued output (midiStreamOut) to the simpler non-streaming driver calls (which are not optional). None of the 8 MIDI drivers on my machine supports streaming itself, all rely on WinMM to do it. Streaming short and long messages works fine.
d) My test code behaves exactly the same on Windows 7 and Windows XP. If Microsoft messed something up, the bug must have been made quite a while ago (before XP). And I am the either the first one to find it (after years), or everybody else kept it top secret and ungoogleable.
e) My test code behaves exactly the same with all 8 midi drivers on my machine. That tells me, that it is most likely not a driver issue.
f) Years of debugging have taught me, that if something doesnt work as it should, the problem is most likely on my side of the screen .. ;-P