我正在使用来自C#的古老Windows Multimedia API(来自WinMM.dll的midiXyz函数)。
以非流模式( midiOutOpen
)打开Midi Out设备/端口后,使用( midiOutLongMsg
)发送SysEx可以正常工作。
在流模式( midiStreamOpen
)中打开Midi Out设备/端口后,使用midiOutLongMsg
发送SysEx不起作用。
而是,midiOutLongMsg
失败,错误为MMSYSERR_NOTSUPPORTED
(= 8)。
错误文本为:“不支持此功能。使用功能功能来确定驱动程序支持哪些功能和消息。”
但是,根据MSDN,( midiOutLongMsg
)也应与流句柄一起使用。
Jeff Glatt's excellent MIDI information pages 还声称SysEx和流技术可以一起使用(see end of page)。
通过使用( midiStreamOut
)midiStreamOut入队来发送缓冲的SysEx消息,效果很好。
但是,我也需要/想要直接使用midiOutLongMsg
发送SysEx。
我已经检查了各种开源Midi库(托管的和非托管的),几个Midi驱动程序源,甚至是WINE的WinMM.dll源,但是找不到任何提示我在做什么错。
为了用最小的代码重现我的问题,我删除了所有回调,准备不足,清理和发布的内容,并在一个函数中压缩了几个类。
以下代码打开第一个Midi设备/端口,并尝试发送“GM Mode On” SysEx消息:
2014年1月12日更新:请参阅下面的代码版本3!
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
问题1:当以流模式(
midiOutLongMsg
)打开Midi设备/端口时,是否可以通过midiStreamOpen
发送SysEx?问题2:如果可以,我有什么想法吗?
问题3:我没有在流模式下使用MIDI找到很多音源。因此,如果您知道一些在流模式下使用MIDI输出的开源库,请给我一个链接,以便我进行比较。
谢谢
更新(2013年10月19日):
嗨,CL,
感谢您的回答。是的,也许Microsoft的某人在维护WinMM.dll时搞砸了一些,但是我认为丢失某些东西的可能性更高,因为
a)有一个旧的手册“Windows NT DDK-多媒体驱动程序”(可用here),比当前的MSDN页面更详细地描述了WinMM。页面56显示了一个图,其中WinMM.dll作为应用程序和MIDI/音频驱动程序之间的中间层。 WinMM的主要工作是将MIDI数据从应用程序传递到MIDI/音频驱动程序之一。当MIDI驱动程序是外部键盘/合成器/音源的端口驱动程序时,WinMM不能改变MIDI数据。第94页描述了驱动程序消息MODM_LONGDATA及其参数-它与midiOutLongMsg的参数几乎相同。这限制了在WinMM.dll中弄乱某些东西的机会。
b)从midiOutLongMsg调用WinMM.dll到使用MODM_LONGDATA调用驱动程序的代码路径在midiOutOpen之后可以正常工作,但在midiStreamOpen之后不能正常工作。结果代码是MMSYSERR_NOTSUPPORTED-告诉我在WinMM.dll的代码路径开头,我被一些健全性检查打了头,例如
if (whatever_weird_condition) return MMSYSERR_NOTSUPPORTED;
而且what_weird_condition最有可能是我应该做但还没有做的事情..
c)如果MIDI驱动程序本身不支持流输出(它是可选的),则WinMM将从缓冲/排队的输出(midiStreamOut)转换为更简单的非流驱动程序调用(这不是可选的)。我的机器上的8个MIDI驱动程序都不支持流本身,都依靠WinMM来完成。流短消息和长消息工作正常。
d)我的测试代码在Windows 7和Windows XP上的行为完全相同。如果Microsoft搞砸了,则该错误一定是在很久以前(在XP之前)已经完成的。我是(几年后)第一个找到它的人,或者其他所有人都把它保密了,
不可谷歌
e)我的测试代码与计算机上所有8个Midi驱动程序的行为完全相同。这告诉我,很可能不是驱动程序问题。
f)数年的调试经验告诉我,如果某事无法正常进行,则问题很可能出在我的屏幕上.. ;-P
最佳答案
更新:很抱歉不早回来。我上类了。是的,您是对的,它现在失败了。也许那天晚上我起得太晚了,但我不知道它是如何工作的,即使是这样。我还需要以流模式发送sysex,但是我的应用程序还没有仅以非流模式(midiOutOpen)进行发送。我将继续研究它,看看是否可以找到解决方法。您必须使用sysex主卷还是可以使用CC:7音量控件?当然,这对sysex没有帮助,但是短消息可以以流模式通过。噢,感谢您的更新,我也得到了要在x86或x64(AnyCPU)中编译和运行的代码。
原始消息:我不知道您是否仍然感兴趣,但是我认为下面的代码可以回答您的问题。我将您的旧代码放在//PREVIOUS CODE
注释下,将新代码放在//NEW CODE
注释下。
此外,对于x86, header 的大小不应包含数据。我知道x86的大小是0x40,但我仍在尝试找出编写此代码的最佳方法,因此,如果您有任何想法请告诉我。
我只是自己为另一个应用程序弄清楚了,所以我还没有充实它,但是我运行了这段代码,它似乎对您有用。我喜欢这个旧dll中的流模式。这非常精确,如果您正在使用双缓冲,则可以使其实时...也可以像在midiout中一样,以流模式发送短消息。
提示:下面的代码是版本2,部分与x86/x64 32/64位兼容。 (MillKa)
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
public static class MidiTest
{
public static void Test()
{
int moID = 0; // midi out device/port ID
//PREVIOUS CODE
//int moHdl; // midi out device/port handle
//NEW CODE
IntPtr moHdl = IntPtr.Zero;
#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
//PREVIOUS CODE
//Chk(WinMM.midiStreamOpen(out moHdl, ref moID, 1, null, 0, 0)); // open midi out in stream mode
//NEW CODE
IntPtr instance = IntPtr.Zero;
Chk(WinMM.midiStreamOpen(out moHdl, ref moID, 1, null, instance, 0)); // open midi out in stream mode
#endif
byte[] sx = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 }; // GM On sysex
//PREVIOUS CODE
//int shdr = Marshal.SizeOf(typeof(MidiHdr)); // hdr size
//NEW CODE
int shdr = 0x40; // 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 == WinMM.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
//PREVIOUS CODE
//[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);
//NEW CODE
#region winmm declarations
[DllImport("winmm.dll")]
public static extern int midiOutPrepareHeader(IntPtr handle,
IntPtr headerPtr, int sizeOfMidiHeader);
[DllImport("winmm.dll")]
public static extern int midiOutUnprepareHeader(IntPtr handle,
IntPtr headerPtr, int sizeOfMidiHeader);
[DllImport("winmm.dll")]
public static extern int midiOutOpen(out IntPtr handle, int deviceID,
CB proc, IntPtr instance, int flags);
[DllImport("winmm.dll")]
public static extern int midiOutGetErrorText(int errCode,
StringBuilder message, int sizeOfMessage);
[DllImport("winmm.dll")]
public static extern int midiOutClose(IntPtr handle);
[DllImport("winmm.dll")]
public static extern int midiStreamOpen(out IntPtr handle, ref int deviceID, int reserved,
CB proc, IntPtr instance, uint flag);
[DllImport("winmm.dll")]
public static extern int midiStreamClose(IntPtr handle);
[DllImport("winmm.dll")]
public static extern int midiStreamOut(IntPtr handle, IntPtr headerPtr, int sizeOfMidiHeader);
[DllImport("winmm.dll")]
public static extern int midiOutLongMsg(IntPtr handle,
IntPtr headerPtr, int sizeOfMidiHeader);
#endregion
} // class WinMM
} // class MidiTest
关于c# - Windows MIDI流和SysEx,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/19446403/