4

C# から古代の Windows マルチメディア API (WinMM.dll の midiXyz 関数) を使用しています。

Midi Out デバイス/ポートを非ストリーミングモード ( midiOutOpen) で開いた後、( ) を使用した SysEx の送信は正常にmidiOutLongMsg機能します。

Midi Out デバイス/ポートをストリーミングモード ( midiStreamOpen) で開いた後、SysEx を送信しても機能しmidiOutLongMsgません。

代わりに、エラー(= 8)でmidiOutLongMsg失敗します。MMSYSERR_NOTSUPPORTEDエラー テキストは次のとおりです。「この機能はサポートされていません。Capabilities 関数を使用して、ドライバーがサポートしている機能とメッセージを確認してください。

ただし、MSDN によると、( midiOutLongMsg) はストリーム ハンドルでも動作するはずです。 Jeff Glatt の優れた MIDI 情報ページでも、SysEx とストリーミングを一緒に使用できると主張しています (ページの最後を参照)。

( ) midiStreamOut でエンキューしてバッファリングされた SysEx メッセージを送信すると、問題midiStreamOutなく動作します。しかし、私も を使用して SysEx を直接送信する必要がありmidiOutLongMsgます。

さまざまなオープン ソース Midi ライブラリ (マネージドおよびアンマネージド)、いくつかの Midi ドライバー ソース、さらには WINE の WinMM.dll ソースを既にチェックアウトしましたが、私が間違っていることのヒントを見つけることができませんでした。

可能な限り小さなコードで問題を再現するために、すべてのコールバック、準備解除、クリーンアップ、およびリリースを取り除き、いくつかのクラスを 1 つの関数に凝縮しました。次のコードは、最初の 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 デバイス/ポートがストリーミング モード ( ) で開かれている場合、SysEx を経由して送信することはできmidiStreamOpenますか?

質問 2:はいの場合、何が欠けているか分かりますか?

質問 3:ストリーミング モードで MIDI を使用しているソースはあまり見つかりませんでした。したがって、ストリーミング モードで MIDI 出力を使用するオープン ソース ライブラリを知っている場合は、比較できるようにリンクを提供してください。

ありがとう

更新 (2013 年 10 月 19 日):

CLさん、こんにちは。

ご回答有難うございます。はい、おそらく Microsoft の誰かが WinMM.dll を維持する際に何かを台無しにしました - しかし、何かが欠けている可能性は高いと思います。

a) 古いマニュアル「Windows NT DDK - マルチメディア ドライバー」(ここから入手可能) があり、現在の MSDN ページよりも詳細に WinMM の内容が説明されています。ページ 56 は、WinMM.dll をアプリケーションと MIDI/オーディオ ドライバ間の中間層として使用した図を示しています。WinMM の主な仕事は、MIDI データをアプリケーションから MIDI/オーディオ ドライバーの 1 つに渡すことです。MIDIドライバーが外部キーボード/シンセサイザー/トーンジェネレーターのポートドライバーの場合、WinMMはMIDIデータをそれほど変更できません。ページ 94 は、ドライバー メッセージ MODM_LONGDATA とそのパラメーターについて説明しています。これは、midiOutLongMsg のパ​​ラメーターとほとんど同じです。これにより、WinMM.dll 内で何かを台無しにする機会が制限されます。

b) 呼び出された midiOutLongMsg から MODM_LONGDATA でドライバを呼び出すまでの WinMM.dll のコード パスは、midiOutOpen の後では正常に機能しますが、midiStreamOpen の後では機能しません。結果コードは MMSYSERR_NOTSUPPORTED です。これは、WinMM.dll のコード パスの先頭にあるサニティ チェックによって平手打ちされていることを示しています。

if (whatever_weird_condition) return MMSYSERR_NOTSUPPORTED;

そして、whatever_weird_condition は、私がすべきだったのに、していないことに関するものである可能性が最も高い..

c) MIDI ドライバーがそれ自体でストリーミング出力をサポートしていない場合 (オプション)、WinMM は、バッファー/エンキューされた出力 (midiStreamOut) から、より単純な非ストリーミング ドライバー呼び出し (オプションではない) への変換を行います。私のマシンの 8 つの MIDI ドライバーはどれもストリーミング自体をサポートしておらず、すべて WinMM に依存しています。短いメッセージと長いメッセージのストリーミングは正常に機能します。

d) 私のテスト コードは、Windows 7 と Windows XP でまったく同じように動作します。Microsoft が何かを台無しにした場合、そのバグはかなり前 (XP より前) に作成されたに違いありません。そして、私が(何年も経って)最初にそれを見つけたのか、他の誰もがそれを極秘にしてグーグルで検索できないようにしていたのかのどちらかです.

e) テスト コードは、マシン上の 8 つの MIDI ドライバーすべてでまったく同じように動作します。これは、ドライバーの問題ではない可能性が高いことを示しています。

f) 何年にもわたるデバッグの経験から、何かがうまくいかない場合、問題は画面の私の側にある可能性が高い.. ;-P

4

5 に答える 5

4

更新: すぐに戻ってこないことをお詫びします。仕事に忙殺されました。はい、そうです、今は失敗しています。その夜遅く起きすぎたのかもしれませんが、それがどのように機能していたのか、あるいは機能していたとしても理解できません. また、ストリーム モードで sysex を送信する必要がありますが、アプリは非ストリーム モード (midiOutOpen) でのみそれを行っていません。私はそれを回避する方法を見つけることができるかどうかを確認するためにそれを続けます. sysex マスター ボリュームを使用する必要がありますか、それとも CC:7 ボリューム コントロールを使用できますか? もちろん、これは sysex には役に立ちませんが、短いメッセージはストリーム モードで通過できます。ああ、更新してくれてありがとう。x86 または x64 (AnyCPU) でコンパイルして実行するコードも取得していました。

元のメッセージ: あなたがまだ興味を持っているかどうかはわかりませんが、以下のコードがあなたの質問に答えるかもしれません. 古いコードをコメントの下に置き//PREVIOUS CODE、新しいコードをコメントの下に置きます//NEW CODE

さらに、x86 の場合、ヘッダーのサイズにデータを含めないでください。サイズが x86 の場合は 0x40 であることはわかっていますが、これをコーディングする最善の方法をまだ見つけようとしているので、アイデアがあれば教えてください。

別のアプリケーションでこれを自分で見つけたので、まだすべてを肉付けしていませんが、このコードを実行したところ、うまくいくようです。この古い dll のストリーミング モードが大好きです。これは非常に正確で、ダブル バッファリングを使用している場合はリアルタイムにすることができます...また、midiout と同じ方法でストリーム モードでショート メッセージを送信することもできます。

ヒント: 以下のコードはバージョン 2 で、x86/x64 32/64 ビットと部分的に互換性があります。(ミルカ)

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
于 2014-01-11T00:20:50.800 に答える
3

MIDI ストリームを使用する人はほとんどいません。

現在、midi* 機能はハードウェア ベンダーのドライバーではなく、Microsoft の MM WDM 互換ドライバーによって実装されています。この詳細は、書き直しで見落とされたようです。

于 2013-10-18T10:39:45.810 に答える
1

テストコードのバージョン 3 は次のとおりです。

public static class MidiTest { // version 3 - x68/x64 32/64-bit compatible

  public static void Test () {
    int moID = 0; // midi out device/port ID
    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
    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

    int shdr = Marshal.SizeOf(typeof(MidiHdr)); // hdr size
    IntPtr x = Marshal.OffsetOf (typeof (MidiHdr), "data");   // ptr; size: 4/8, offset: 0
    x = Marshal.OffsetOf (typeof (MidiHdr), "bufferLength");  // int; size: 4  , offset: 4/8
    x = Marshal.OffsetOf (typeof (MidiHdr), "bytesRecorded"); // int; size: 4  , offset: 8/12
    x = Marshal.OffsetOf (typeof (MidiHdr), "user");          // ptr; size: 4/8, offset: 12/16
    x = Marshal.OffsetOf (typeof (MidiHdr), "flags");         // int; size: 4  , offset: 16/24; followed by 4 byte padding
    x = Marshal.OffsetOf (typeof (MidiHdr), "next");          // ptr; size: 4/8, offset: 20/32
    x = Marshal.OffsetOf (typeof (MidiHdr), "reserved");      // ptr; size: 4/8, offset: 24/40
    x = Marshal.OffsetOf (typeof (MidiHdr), "offset");        // int; size: 4  , offset: 28/48; followed by 4 byte padding
    x = Marshal.OffsetOf (typeof (MidiHdr), "reservedArray"); // ptr; size: 4/8 x 8 = 32/64, offset: 32/56
    // total size: 64/120
    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
    int r = WinMM.midiOutLongMsg (moHdl, nhdr, shdr); // send native message bytes
    Chk (r); // 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 IntPtr user; // custom user data
    public int flags; // information flags about buffer
    public IntPtr next; // reserved
    public IntPtr reserved; // reserved
    public int offset; // buffer offset on callback
    [MarshalAs (UnmanagedType.ByValArray, SizeConst = 8)]
    public IntPtr[] reservedArray; // reserved
  } // struct MidiHdr

  internal sealed class WinMM { // native MIDI calls from WinMM.dll
    public delegate void CB (IntPtr hdl, int msg, IntPtr inst, int p1, int p2); // callback
    [DllImport ("winmm.dll")] public static extern int midiStreamOpen (out IntPtr hdl, ref int devID, int reserved, CB proc, IntPtr inst, uint flags);
    [DllImport ("winmm.dll")] public static extern int midiOutOpen (out IntPtr hdl, int devID, CB proc, IntPtr instance, int flags);
    [DllImport ("winmm.dll")] public static extern int midiOutPrepareHeader (IntPtr hdl, IntPtr pHdr, int sHdr);
    [DllImport ("winmm.dll")] public static extern int midiOutLongMsg (IntPtr hdl, IntPtr pHdr, int sHdr);
    [DllImport ("winmm.dll")] public static extern int midiOutGetErrorText (int err, StringBuilder msg, int sMsg);
  } // class WinMM

} // class MidiTest

上記の Mark のバージョン 2 と同様にIntPtr、32 ビットと 64 ビットとの互換性が必要な場合に使用します。

MidiHdr構造体の宣言も変更しました。

  • ポインターはIntPtr32/64 ビットのために宣言されます
  • ヘッダーの最後の配列のサイズが間違っていました (4)、現在は正しいです (8)

変更された宣言によりMarshal.SizeOf、正しいサイズを計算するようになりました:

  • 0x40 = 32 ビットで 64
  • 0x78 = 64 ビットで 120

ポインター フィールドnextとを揃えるために、コンパイラは int フィールドとreservedArrayの後に 4 バイトのパディングを追加します。フィールドのサイズとオフセットはコメントに示されています。flagsoffset

残念ながら、私のバージョン 3 と Mark のバージョン 2 にはまだ元の問題がありmidiOutLongMsgます。エラー コード 8 が返されます。Windows 7 Ultimate、64 ビット、SP1 で x86 (32 ビット) および x64 (64 ビット) としてコンパイルされたコードをテストしました。 . 32 ビット バージョンは、Windows XP Pro、SP3、32 ビットでもテストされています。すべてのプラットフォームで同じ結果。

ストリーミング中に Sysex メッセージを送信する必要がある理由を知りたい場合: WinMM ストリーミングを使用して midi ファイルを再生し、ユーザーがボリューム スライダーを変更した場合、ストリーミングを停止することなく、新しいボリュームを Sysex 経由で送信する必要があります。

于 2014-01-12T17:23:48.057 に答える
1

これはテスト コードの 4 番目のバージョンです。今回はネイティブ C++ で、私の問題が相互運用性やマネージ コードに関連していないことを確認します。問題 (エラー コード 8) が続く:

#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <MMSystem.h>

#pragma comment ( lib, "winmm.lib" )

static void Chk (UINT r) {
  static _TCHAR errmsg[256];
  if (!r) return;
  UINT rr = midiOutGetErrorText (r, errmsg, sizeof (errmsg));
  printf ("MIDI Error %d: '%S'\n", r, errmsg);
}

int _tmain (int argc, _TCHAR* argv[]) {
  UINT moID = 0; // first midi out device/port ID
  DWORD_PTR inst = NULL; // no instance
  DWORD_PTR clbk = NULL; // no callback
  DWORD flgs = CALLBACK_NULL; // flags, no callback
#if 0
  // SysEx via midiOutLongMsg works
  HMIDIOUT hmo = 0; // midi out device/port handle
  Chk (midiOutOpen (&hmo, moID, clbk, inst, flgs)); // open midi out in non-stream mode
#else
  // SysEx via midiOutLongMsg fails
  HMIDISTRM hms = 0; // midi out device/port handle
  Chk (midiStreamOpen (&hms, &moID, 1, clbk, inst, flgs)); // open midi out in stream mode
  HMIDIOUT hmo = (HMIDIOUT) hms;
#endif
  Chk (midiOutShortMsg (hmo, 0x00404090)); // note on
  Sleep (200); // ms
  Chk (midiOutShortMsg (hmo, 0x00004090)); // note off
  static unsigned char sx [] = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 }; // GM On sysex
  static MIDIHDR mhdr; // midi header describes long msg
  UINT shdr = sizeof (mhdr);
  memset (&mhdr, 0, shdr); // clear header
  mhdr.lpData = (LPSTR) sx; // point to sysex
  mhdr.dwBufferLength = mhdr.dwBytesRecorded = sizeof (sx); // length of message bytes
  Chk (midiOutPrepareHeader (hmo, &mhdr, shdr)); // prepare hdr
  UINT r = midiOutLongMsg (hmo, &mhdr, shdr); // send message bytes
  Chk (r);
  // unprepare header, close etc. omitted ...
  return 0;
}
于 2014-01-17T18:27:34.073 に答える