首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Windows流和SysEx

Windows流和SysEx
EN

Stack Overflow用户
提问于 2013-10-18 09:40:43
回答 5查看 4K关注 0票数 4

我正在使用古老的(来自WinMM.dll的WinMM.dll函数)来自C#。

非流式模式(midiOutOpen)中打开Midi Out设备/端口后,发送SysEx with (midiOutLongMsg)工作正常。

模式(midiStreamOpen)中打开Midi Out设备/端口后,使用midiOutLongMsg发送SysEx无法工作。

相反,midiOutLongMsg在错误MMSYSERR_NOTSUPPORTED (= 8)中失败。错误文本是:“此函数不受支持。请使用此功能函数确定驱动程序支持哪些函数和消息。”

但是,根据MSDN,(midiOutLongMsg)也应该使用流句柄。杰夫·格拉特优秀的MIDI信息页面还声称,SysEx和流可以一起使用(见页尾)。

通过将缓冲的SysEx消息与(midiStreamOut) midiStreamOut一起排队来发送缓冲消息,效果很好。但是,我也需要/希望直接使用SysEx发送midiOutLongMsg

我已经检查了各种开源Midi库(管理的和非托管的)、几个Midi驱动程序源,甚至还有葡萄酒的WinMM.dll源代码,但是找不到我做错了什么的任何提示。

为了在尽可能小的代码中重现我的问题,我删除了所有回调、不准备、清理和发布内容,并在一个函数中压缩了几个类。以下代码打开第一个Midi设备/端口,并尝试发送"GM模式On“SysEx消息:

2014年1月12日更新:请参阅下面的代码版本3!

代码语言:javascript
复制
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:当Midi设备/端口以流式模式(midiStreamOpen)打开时,是否可以通过midiOutLongMsg发送SysEx?

问题2:如果是,知道我错过了什么吗?

问题3: I没有在流模式下找到许多使用MIDI的源。因此,如果您知道在流模式下使用MIDI输出的开源库,请给我一个链接,以便我可以比较。

谢谢

更新(2013年10月19日):

嗨,CL,

谢谢你的回答。是的,也许是微软的某个人在维护WinMM.dll方面搞砸了--但我认为我遗漏某些东西的可能性更高,因为

a)有一个旧的手册"Windows多媒体驱动程序“(可用的这里),它比当前的MSDN页面更详细地描述了WinMM内容。第56页显示了以WinMM.dll作为应用程序和MIDI/音频驱动程序之间的中间层的图表。WinMM的主要工作是将MIDI数据从应用程序传递到MIDI/音频驱动程序之一。当MIDI驱动程序是外部键盘/合成器/音调发生器的端口驱动程序时,WinMM无法对MIDI数据进行太多的更改。第94页描述了驱动程序消息MODM_LONGDATA及其参数--它与midiOutLongMsg的参数基本相同。这限制了在WinMM.dll内部搞砸东西的机会。

( b)在WinMM.dll中,从被调用的midiOutLongMsg到使用MODM_LONGDATA调用驱动程序的代码路径在midiOutOpen之后运行良好,但在midiStreamOpen之后工作正常。结果代码是MMSYSERR_NOTSUPPORTED --这告诉我,在WinMM.dll代码路径的开头,我正受到一些理智检查的影响,比如

代码语言:javascript
复制
if (whatever_weird_condition) return MMSYSERR_NOTSUPPORTED;

而且whatever_weird_condition很可能是关于我应该做但没有做的事情。

c)如果MIDI驱动程序本身不支持流输出(它是可选的),WinMM将从缓冲/排队输出(midiStreamOut)转换为更简单的非流驱动程序调用(这不是可选的)。我的机器上的8个MIDI驱动程序都不支持流本身,所有这些都依赖于WinMM。流短消息和长消息可以正常工作。

( d)我的测试代码在Windows 7和Windows上的行为完全相同。如果微软搞砸了什么,那么这个bug一定是在很久以前(在XP之前)制造的。我要么是第一个找到它的人(几年后),要么是其他人都把它保密,而且是无法搜索的。

e)我的测试代码与我的机器上的所有8个midi驱动程序的行为完全相同。这告诉我,这很可能不是司机的问题。

( f)多年的调试告诉我,如果某些东西不能正常工作,问题很可能发生在我这一边的屏幕上。;-P

EN

回答 5

Stack Overflow用户

发布于 2014-01-11 00:20:50

更新:我很抱歉没有早点回来。我工作忙得不可开交。是的,你是对的-现在正在失败。也许那天晚上我睡得太晚了,但我不明白它是怎么工作的,即使它是。我还需要在流模式下发送sysex,但是我的应用程序还没有在非流模式(midiOutOpen)中这样做。我会继续看它,看看我是否能找到一个绕过它的方法。您必须使用男用母卷还是可以使用CC:7卷控件?当然,这将无助于同性,但短消息可以通过流模式。哦,谢谢你的更新,我还得到了在x86或x64 (AnyCPU)中编译和运行的代码。

原文:我不知道你是否还感兴趣,但我认为下面的代码可能会回答你的问题。我将您的旧代码放在注释//PREVIOUS CODE下,新代码放在注释//NEW CODE下。

此外,对于x86,头的大小不应该包括数据。我知道x86的大小是0x40,但是我仍然在努力找出最好的编码方法,所以如果您有任何想法,请告诉我。

我只是自己为另一个应用程序解决了这个问题,所以我还没有全部完成,但是我运行了这段代码,它似乎适合您。我喜欢这个旧dll中的流模式。它非常精确,如果你使用双缓冲,你可以使它实时.您也可以在流模式下发送短消息,就像对midiout一样。

提示:下面的代码是版本2,部分与x86/x64 32/64位兼容。(MillKa)

代码语言:javascript
复制
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
票数 4
EN

Stack Overflow用户

发布于 2013-10-18 10:39:45

几乎没有人使用MIDI流。

现在,midi*功能不再是由硬件厂商的驱动程序实现的,而是由微软的MM兼容性驱动程序实现的。在重写过程中,这个细节似乎被忽略了。

票数 3
EN

Stack Overflow用户

发布于 2014-01-16 17:07:07

我不知道这是否有帮助,但是如果你把你的女同性恋包在这样的MidiEvent结构中(r -相对勾号,s-流ID,e-事件代码,d- data,p- pad):

代码语言:javascript
复制
//MidiEvent - r, r, r, r, s, s, s, s, e, e, e,   e,   d,   d,   d, d, d,   d, p, p
byte[] sx = { 9, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 128, 240, 126, 127, 9, 1, 247, 0, 0 }; // GM On sysex

并在准备标题之后添加以下行:

代码语言:javascript
复制
Chk(WinMM.midiStreamOut(moHdl, nhdr, shdr));
int r = WinMM.midiOutLongMsg(moHdl, nhdr, shdr);
Chk(r); // send native message bytes
Chk(WinMM.midiStreamRestart(moHdl));

您将看到midiStreamOut系统从MidiEvent的相对滴答字段略为延迟之后,就会离开端口。您仍将收到midiOutLongMessage的错误,但错误代码为65,或者“在媒体数据仍在播放时无法执行此操作。请重置设备,或等到数据播放完毕”。

我不知道这是否有用,但它不同于错误代码8。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19446403

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档