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