ALSA RawMIDI 问题

ALSA RawMIDI 问题

我正在尝试使用 ALSA RawMIDI 接口编写一个应用程序,通过 Raspberry PI 上的 USB 与硬件合成器交换 sysex 数据。 RawMIDI 库随 libasound2-dev 一起提供。

在我的测试应用程序中,我向设备发送 sysex 请求,如下所示:

uint8_t req[] = {0xF0, 0x00, 0x20, 0x3C, 0x07, 0x00, type, 0x01, 0x01, position, 0x00, 0x00, 0x00, 0x05, 0xF7};
if ((status = snd_rawmidi_write(midiout, req, 15)) < 0)
{
    errormessage("Problem sending request: %s", snd_strerror(status));
    exit(1);
}

snd_rawmidi_drain(midiout);

然后设备以 sysex 数据结构进行响应。

它可以工作,但有时似乎存在 MIDI 驱动程序的初始化问题。当我启动应用程序时,大约十分之一的请求/响应根本不会成功。然后我重新启动该应用程序,它有时会起作用,有时则不起作用。

如果有效的话,那就很好了。如果第一个请求成功,我可以发送数千个更多的请求,并且与合成器的通信是可靠的。

所以我认为这与 MIDI 库的初始化/拆卸有关。该库在初始化或发送数据期间不会报告任何错误。

也许我在初始化或拆卸过程中遗漏了一些东西?有没有办法在启动应用程序时重置 MIDI 驱动程序?

这是我的初始化代码:

if ((status = snd_rawmidi_open(&midiin, &midiout, portname, mode)) < 0)
{
    errormessage("Problem opening MIDI connection: %s", snd_strerror(status));
    exit(1);
}

这是我的拆解代码:

snd_rawmidi_close(midiin);
snd_rawmidi_close(midiout);
midiin  = NULL;
midiout = NULL;

看起来很容易,对吧?

编辑:这是我的 main.cpp

#include <signal.h>
#include <thread>
#include "MIDI.hpp"

using namespace std;

static MIDI midi;

void sighandler(int dum)
{
    midi.quit();
    exit(0);
}

void midiRead()
{
    while(1)
        midi.read();
}

int main()
{
    signal(SIGINT,sighandler);
    thread midiReadThread(midiRead);
    midiReadThread.join();
    return 0;
}

这是 midiRead 方法:

void MIDI::read()
{
    uint8_t readByte;

    if ((status = snd_rawmidi_read(midiin, &readByte, 1)) < 0) {
        errormessage("Problem reading MIDI input: %s", snd_strerror(status));
    }

    // if status byte other than sysex end, reset read buffer:
    if(readByte & 0x80 && readByte != 0xF7)
    {
        if(readByte == 0xF0)
            printf("syx");

        argsLeft = getArgsExpected(readByte);
        bufIdx = 0;
        currentCommand = readByte;
        inBuffer[bufIdx++] = readByte;
    }
    // if it's a data byte or sysex end:
    else if(argsLeft || currentCommand == 0xF0)
    {
        inBuffer[bufIdx++] = readByte;
        argsLeft--;
        // handle the sysex message:
        if(readByte == 0xF7)
        {
            printf(" done\n");
            handleSysex(inBuffer, bufIdx);
        }
    }

    // if we don't expect any more data bytes from non-sysex:
    if(!argsLeft && currentCommand != 0xF0)
    {
        switch (currentCommand & 0xF0) {
            case 0x90:
            {
                handleNoteOn(inBuffer[0] & 0x0F, inBuffer[1] & 0x7F, inBuffer[2] & 0x7F);
                break;
            }
            case 0x80:
            {
                handleNoteOff(inBuffer[0] & 0x0F, inBuffer[1] & 0x7F, inBuffer[2] & 0x7F);
                break;
            }
            case 0xA0:
            {
                handleAftertouch(inBuffer[0] & 0x0F, inBuffer[1] & 0x7F, inBuffer[2] & 0x7F);
                break;
            }
            case 0xB0:
            {
                handleController(inBuffer[0] & 0x0F, inBuffer[1] & 0x7F, inBuffer[2] & 0x7F);
                break;
            }
            default:
                break;
        }
    }
}

澄清一下:如果我只是启动进程,我可以从机器手动将 sysex 或其他 MIDI 数据发送到 PI,这通常是有效的。

如果在进程启动后立即向计算机发送请求,它有时会响应,有时不会。如果我稍等一下,请求/响应机制更有可能起作用。

目前我认为失败的原因是我必须等待MIDI初始化完成。看起来当snd_rawmidi_open返回时,它实际上并没有完全初始化。我不知道我要等多久?

更多编辑:

看来问题不仅仅限于sysex。如果我启动进程并开始读取,然后将 MIDI 音符事件从合成器发送到进程,有时第一个音符不会被读取。以下注释和所有后续事件均已正确读取。

例如,如果我将其放在printf("reading...\n");读取函数的顶部并启动该进程,则日志输出如下所示:

Init MIDI...
reading...

通常,如果我然后从合成器发送一个音符开始,然后发送一个音符结束,它看起来像这样:

Init MIDI...
reading...
reading...
reading...
note on chn: 0 note: 36 vel: 100
reading...
reading...
reading...
note off chn: 0 note: 36 vel: 0
reading...

但有时,第一个音符没有收到:

Init MIDI...
reading...
reading...
reading...
note off chn: 0 note: 36 vel: 0
reading...

答案1

snd_rawmidi_read()ALSA 原始 MIDI 设备在被调用之前实际上不会开始从硬件读取数据。这意味着您必须snd_rawmidi_read()尽早调用,否则响应的第一个字节可能会丢失。

最安全的方法是致电snd_rawmidi_read() 请求已发送。 (参见,例如,阿米迪.)

相关内容