我正在尝试使用 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()
前请求已发送。 (参见,例如,阿米迪.)