【问题标题】:Trouble using Windows MIDI API (no callbacks when playing)使用 Windows MIDI API 出现问题(播放时没有回调)
【发布时间】:2011-04-20 07:56:24
【问题描述】:

我有一个 USB 连接的 MIDI 键盘。它在其他应用程序中运行良好。但是,在我自己的程序中却没有。 midiInOpen() 调用通过,我收到一个回调(来自打开设备),但在弹奏键盘时我没有收到任何回调。

通过使用 midiInGetDevCaps(),我可以看到我正在使用正确的设备。

有什么想法吗?其他(商业)应用程序是否可以使用其他 API?

static void CALLBACK myFunc(HMIDIIN handle, UINT uMsg,
                            DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) {
   printf("Callback!"\n);
}

int main() {

   unsigned long result;
   HMIDIIN       inHandle;

   result = midiInOpen(&inHandle, 0, (DWORD)myFunc, 0, CALLBACK_FUNCTION);
   if (result)
   {
      printf("There was an error opening MIDI\n");
   }

   while(1) { Sleep(1); }
}

【问题讨论】:

  • 您确定它没有被调用是因为您设置了断点,还是因为 printf() 没有显示任何内容?当接收到 MIDI 数据时,您可以在此回调中执行有限的操作列表——API 文档建议使用 OutputDebugString();我不确定printf 在这种情况下是否应该工作。

标签: c windows midi


【解决方案1】:

您需要致电midiInstart。您还需要发送消息。如果您调用 Sleep 并且不为输入提供服务,则不会发生任何事情。

这里有一点tutorial on Windows MIDI

这是我为 Win MIDI 输入编写的一个类 (WinMidiIn) 的摘录(删除了许多错误处理,可以从它们作为参数传递的调用中推断出类成员类型):

{
    MMRESULT res = ::midiInOpen(&mMidiIn, mDeviceIdx, (DWORD_PTR)MidiInCallbackProc, (DWORD_PTR)this, 
        CALLBACK_FUNCTION | MIDI_IO_STATUS);
    if (MMSYSERR_NOERROR != res)
        return;

    const int kDataBufLen = 512;
    int idx;
    for (idx = 0; idx < MIDIHDR_CNT; ++idx)
    {
        mMidiHdrs[idx].lpData = (LPSTR) ::malloc(kDataBufLen);
        mMidiHdrs[idx].dwBufferLength = kDataBufLen;

        res = ::midiInPrepareHeader(mMidiIn, &mMidiHdrs[idx], (UINT)sizeof(MIDIHDR));
        res = ::midiInAddBuffer(mMidiIn, &mMidiHdrs[idx], sizeof(MIDIHDR));
    }

    res = ::midiInStart(mMidiIn);

    for (;;)
    {
        DWORD result;
        MSG msg;

        // Read all of the messages in this next loop, 
        // removing each message as we read it.
        while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            // If it is a quit message, exit.
            if (msg.message == WM_QUIT)
                break;

            // Otherwise, dispatch the message.
            ::DispatchMessage(&msg);
        }

        // Wait for any message sent or posted to this queue 
        // or for one of the passed handles be set to signaled.
        result = ::MsgWaitForMultipleObjects(1, &mDoneEvent, FALSE, INFINITE, QS_ALLINPUT);

        // The result tells us the type of event we have.
        if (result == (WAIT_OBJECT_0 + 1))
        {
            // New messages have arrived. 
            // Continue to the top of the always while loop to 
            // dispatch them and resume waiting.
            continue;
        }
        else if (WAIT_TIMEOUT == result)
            continue;
        else if (WAIT_OBJECT_0 == result)
            break; // done event fired
        else
            break; // ??
    }

    res = ::midiInReset(mMidiIn);

    for (idx = 0; idx < MIDIHDR_CNT; ++idx)
    {
        res = ::midiInUnprepareHeader(mMidiIn, &mMidiHdrs[idx], (UINT)sizeof(MIDIHDR));
        ::free(mMidiHdrs[idx].lpData);
        mMidiHdrs[idx].lpData = NULL;
    }

    res = ::midiInClose(mMidiIn);
    mMidiIn = NULL;
}



void CALLBACK 
MidiInCallbackProc(HMIDIIN hmi, 
                          UINT wMsg, 
                          DWORD dwInstance, 
                          DWORD dwParam1, 
                          DWORD dwParam2)
{
    MMRESULT res;
    LPMIDIHDR hdr;
    WinMidiIn * _this = (WinMidiIn *) dwInstance;

    switch (wMsg)
    {
    case MIM_DATA:
        //  dwParam1 is the midi event with status in the low byte of the low word
        //  dwParam2 is the event time in ms since the start of midi in
        // data: LOBYTE(dwParam1), HIBYTE(dwParam1), LOBYTE(HIWORD(dwParam1))
        break;
    case MIM_ERROR:
        break;
    case MIM_LONGDATA:
        //  dwParam1 is the lpMidiHdr
        //  dwParam2 is the event time in ms since the start of midi in
        hdr = (LPMIDIHDR) dwParam1;
        // sysex: (byte*)hdr->lpData, (int)hdr->dwBytesRecorded
        res = ::midiInAddBuffer(_this->mMidiIn, hdr, sizeof(MIDIHDR));
        break;
    case MIM_LONGERROR:
        hdr = (LPMIDIHDR) dwParam1;
        res = ::midiInAddBuffer(_this->mMidiIn, hdr, sizeof(MIDIHDR));
        break;
    }
}

【讨论】:

  • 所以我迟到了六年,但我自己还是第一次应对其中的一些问题。我看到您在回拨中致电midiInAddBuffer。这是有道理的,因为驱动程序不能再次使用您的缓冲区,直到您执行 DRWE。但是MidiInProc 上的 MSDN 文档说:“应用程序不应从回调函数内部调用任何多媒体函数,因为这样做会导致死锁。其他系统函数可以安全地从回调函数中调用。”打电话midiInAddBuffer 不违反这个限制吗? (另一方面,你还会在哪里称呼它?)
  • @StevensMiller 看起来有人和你有同样的问题:social.msdn.microsoft.com/Forums/vstudio/en-US/…。并不是说这是一个答案,但我从来没有遇到过在回调中调用 midiInAddBuffer 以响应 sysex 数据的问题(自 1996 年左右以来)
  • 嘿,感谢您回复我!我自己一直在回调中调用它,没有任何问题。但似乎有些人认为这是不安全的,至少对于某些驱动程序而言(可以想象调用导致对回调的无休止的递归系列调用,我想)。 github.com/jonathanslenders/fluidsynth/blob/master/src/drivers/… 的 cmets 提到了这一点。为了完全兼容,该代码(和其他一些代码)将缓冲区发布到工作线程,然后调用 midiInAddBuffer。 (哎呀,但它就在那里。)
【解决方案2】:

你可以在这里找到一个例子https://gist.github.com/yoggy/1485181 我在这里发布代码以防链接失效

您可能缺少 midiInStart()

#include <SDKDDKVer.h>

#include <Windows.h>

#include <stdio.h>
#include <conio.h>

#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")

void PrintMidiDevices()
{
    UINT nMidiDeviceNum;
    MIDIINCAPS caps;

    nMidiDeviceNum = midiInGetNumDevs();
    if (nMidiDeviceNum == 0) {
        fprintf(stderr, "midiInGetNumDevs() return 0...");
        return;
    }

    printf("== PrintMidiDevices() == \n");
    for (unsigned int i = 0; i < nMidiDeviceNum; ++i) {
        midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS));
        printf("\t%d : name = %s\n", i, caps.szPname);
    }
    printf("=====\n");
}

void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
    switch(wMsg) {
    case MIM_OPEN:
        printf("wMsg=MIM_OPEN\n");
        break;
    case MIM_CLOSE:
        printf("wMsg=MIM_CLOSE\n");
        break;
    case MIM_DATA:
        printf("wMsg=MIM_DATA, dwInstance=%08x, dwParam1=%08x, dwParam2=%08x\n", dwInstance, dwParam1, dwParam2);
        break;
    case MIM_LONGDATA:
        printf("wMsg=MIM_LONGDATA\n"); 
        break;
    case MIM_ERROR:
        printf("wMsg=MIM_ERROR\n");
        break;
    case MIM_LONGERROR:
        printf("wMsg=MIM_LONGERROR\n");
        break;
    case MIM_MOREDATA:
        printf("wMsg=MIM_MOREDATA\n");
        break;
    default:
        printf("wMsg = unknown\n");
        break;
    }
    return;
}

int main(int argc, char* argv[])
{
    HMIDIIN hMidiDevice = NULL;;
    DWORD nMidiPort = 0;
    UINT nMidiDeviceNum;
    MMRESULT rv;

    PrintMidiDevices();

    nMidiDeviceNum = midiInGetNumDevs();
    if (nMidiDeviceNum == 0) {
        fprintf(stderr, "midiInGetNumDevs() return 0...");
        return -1;
    }

    rv = midiInOpen(&hMidiDevice, nMidiPort, (DWORD)(void*)MidiInProc, 0, CALLBACK_FUNCTION);
    if (rv != MMSYSERR_NOERROR) {
        fprintf(stderr, "midiInOpen() failed...rv=%d", rv);
        return -1;
    }

    midiInStart(hMidiDevice);

    while(true) {
        if (!_kbhit()) {
            Sleep(100);
            continue;
        }
        int c = _getch();
        if (c == VK_ESCAPE) break;
        if (c == 'q') break;
    }

    midiInStop(hMidiDevice);
    midiInClose(hMidiDevice);
    hMidiDevice = NULL;

    return 0;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-31
    • 2015-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多