Move MIDI parsing up from ALSA driver to platform independent driver.
Aims for more consistent MIDI support across Windows, MacOS, Linux and to provide a base for adding MIDI drivers for other platforms. Reworks the MIDIDriverALSAMidi MIDI parsing implementation as a platform independent version in MIDIDriver::Parser. Uses MIDIDriver::Parser to provide running status support in MacOS MIDIDriverCoreMidi. Collects connected input names at open, ensuring devices indices reported in events match names in array returned from get_connected_inputs. Fixes #77035. Fixes #79811. With code review changes by: A Thousand Ships (she/her) <96648715+AThousandShips@users.noreply.github.com>
This commit is contained in:
@ -37,137 +37,36 @@
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
MIDIDriverALSAMidi::MessageCategory MIDIDriverALSAMidi::msg_category(uint8_t msg_part) {
|
||||
if (msg_part >= 0xf8) {
|
||||
return MessageCategory::RealTime;
|
||||
} else if (msg_part >= 0xf0) {
|
||||
// System Exclusive begin/end are specified as System Common Category messages,
|
||||
// but we separate them here and give them their own categories as their
|
||||
// behavior is significantly different.
|
||||
if (msg_part == 0xf0) {
|
||||
return MessageCategory::SysExBegin;
|
||||
} else if (msg_part == 0xf7) {
|
||||
return MessageCategory::SysExEnd;
|
||||
}
|
||||
return MessageCategory::SystemCommon;
|
||||
} else if (msg_part >= 0x80) {
|
||||
return MessageCategory::Voice;
|
||||
}
|
||||
return MessageCategory::Data;
|
||||
}
|
||||
MIDIDriverALSAMidi::InputConnection::InputConnection(int p_device_index,
|
||||
snd_rawmidi_t *p_rawmidi) :
|
||||
parser(p_device_index), rawmidi_ptr(p_rawmidi) {}
|
||||
|
||||
size_t MIDIDriverALSAMidi::msg_expected_data(uint8_t status_byte) {
|
||||
if (msg_category(status_byte) == MessageCategory::Voice) {
|
||||
// Voice messages have a channel number in the status byte, mask it out.
|
||||
status_byte &= 0xf0;
|
||||
}
|
||||
|
||||
switch (status_byte) {
|
||||
case 0x80: // Note Off
|
||||
case 0x90: // Note On
|
||||
case 0xA0: // Polyphonic Key Pressure (Aftertouch)
|
||||
case 0xB0: // Control Change (CC)
|
||||
case 0xE0: // Pitch Bend Change
|
||||
case 0xF2: // Song Position Pointer
|
||||
return 2;
|
||||
|
||||
case 0xC0: // Program Change
|
||||
case 0xD0: // Channel Pressure (Aftertouch)
|
||||
case 0xF1: // MIDI Time Code Quarter Frame
|
||||
case 0xF3: // Song Select
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MIDIDriverALSAMidi::InputConnection::parse_byte(uint8_t byte, MIDIDriverALSAMidi &driver,
|
||||
uint64_t timestamp, int device_index) {
|
||||
switch (msg_category(byte)) {
|
||||
case MessageCategory::RealTime:
|
||||
// Real-Time messages are single byte messages that can
|
||||
// occur at any point.
|
||||
// We pass them straight through.
|
||||
driver.receive_input_packet(device_index, timestamp, &byte, 1);
|
||||
break;
|
||||
|
||||
case MessageCategory::Data:
|
||||
// We don't currently forward System Exclusive messages so skip their data.
|
||||
// Collect any expected data for other message types.
|
||||
if (!skipping_sys_ex && expected_data > received_data) {
|
||||
buffer[received_data + 1] = byte;
|
||||
received_data++;
|
||||
|
||||
// Forward a complete message and reset relevant state.
|
||||
if (received_data == expected_data) {
|
||||
driver.receive_input_packet(device_index, timestamp, buffer, received_data + 1);
|
||||
received_data = 0;
|
||||
|
||||
if (msg_category(buffer[0]) != MessageCategory::Voice) {
|
||||
// Voice Category messages can be sent with "running status".
|
||||
// This means they don't resend the status byte until it changes.
|
||||
// For other categories, we reset expected data, to require a new status byte.
|
||||
expected_data = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MessageCategory::SysExBegin:
|
||||
buffer[0] = byte;
|
||||
skipping_sys_ex = true;
|
||||
break;
|
||||
|
||||
case MessageCategory::SysExEnd:
|
||||
expected_data = 0;
|
||||
skipping_sys_ex = false;
|
||||
break;
|
||||
|
||||
case MessageCategory::Voice:
|
||||
case MessageCategory::SystemCommon:
|
||||
buffer[0] = byte;
|
||||
received_data = 0;
|
||||
expected_data = msg_expected_data(byte);
|
||||
skipping_sys_ex = false;
|
||||
if (expected_data == 0) {
|
||||
driver.receive_input_packet(device_index, timestamp, &byte, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int MIDIDriverALSAMidi::InputConnection::read_in(MIDIDriverALSAMidi &driver, uint64_t timestamp, int device_index) {
|
||||
int ret;
|
||||
void MIDIDriverALSAMidi::InputConnection::read() {
|
||||
int read_count;
|
||||
do {
|
||||
uint8_t byte = 0;
|
||||
ret = snd_rawmidi_read(rawmidi_ptr, &byte, 1);
|
||||
uint8_t buffer[32];
|
||||
read_count = snd_rawmidi_read(rawmidi_ptr, buffer, sizeof(buffer));
|
||||
|
||||
if (ret < 0) {
|
||||
if (ret != -EAGAIN) {
|
||||
ERR_PRINT("snd_rawmidi_read error: " + String(snd_strerror(ret)));
|
||||
if (read_count < 0) {
|
||||
if (read_count != -EAGAIN) {
|
||||
ERR_PRINT("snd_rawmidi_read error: " + String(snd_strerror(read_count)));
|
||||
}
|
||||
} else {
|
||||
parse_byte(byte, driver, timestamp, device_index);
|
||||
for (int i = 0; i < read_count; i++) {
|
||||
parser.parse_fragment(buffer[i]);
|
||||
}
|
||||
}
|
||||
} while (ret > 0);
|
||||
|
||||
return ret;
|
||||
} while (read_count > 0);
|
||||
}
|
||||
|
||||
void MIDIDriverALSAMidi::thread_func(void *p_udata) {
|
||||
MIDIDriverALSAMidi *md = static_cast<MIDIDriverALSAMidi *>(p_udata);
|
||||
uint64_t timestamp = 0;
|
||||
|
||||
while (!md->exit_thread.is_set()) {
|
||||
md->lock();
|
||||
|
||||
InputConnection *connections = md->connected_inputs.ptrw();
|
||||
size_t connection_count = md->connected_inputs.size();
|
||||
|
||||
for (size_t i = 0; i < connection_count; i++) {
|
||||
connections[i].read_in(*md, timestamp, (int)i);
|
||||
for (InputConnection &conn : md->connected_inputs) {
|
||||
conn.read();
|
||||
}
|
||||
|
||||
md->unlock();
|
||||
|
||||
OS::get_singleton()->delay_usec(1000);
|
||||
@ -181,15 +80,25 @@ Error MIDIDriverALSAMidi::open() {
|
||||
return ERR_CANT_OPEN;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (void **n = hints; *n != nullptr; n++) {
|
||||
char *name = snd_device_name_get_hint(*n, "NAME");
|
||||
lock();
|
||||
int device_index = 0;
|
||||
for (void **h = hints; *h != nullptr; h++) {
|
||||
char *name = snd_device_name_get_hint(*h, "NAME");
|
||||
|
||||
if (name != nullptr) {
|
||||
snd_rawmidi_t *midi_in;
|
||||
int ret = snd_rawmidi_open(&midi_in, nullptr, name, SND_RAWMIDI_NONBLOCK);
|
||||
if (ret >= 0) {
|
||||
connected_inputs.insert(i++, InputConnection(midi_in));
|
||||
// Get display name.
|
||||
snd_rawmidi_info_t *info;
|
||||
snd_rawmidi_info_malloc(&info);
|
||||
snd_rawmidi_info(midi_in, info);
|
||||
connected_input_names.push_back(snd_rawmidi_info_get_name(info));
|
||||
snd_rawmidi_info_free(info);
|
||||
|
||||
connected_inputs.push_back(InputConnection(device_index, midi_in));
|
||||
// Only increment device_index for successfully connected devices.
|
||||
device_index++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,6 +107,7 @@ Error MIDIDriverALSAMidi::open() {
|
||||
}
|
||||
}
|
||||
snd_device_name_free_hint(hints);
|
||||
unlock();
|
||||
|
||||
exit_thread.clear();
|
||||
thread.start(MIDIDriverALSAMidi::thread_func, this);
|
||||
@ -211,11 +121,12 @@ void MIDIDriverALSAMidi::close() {
|
||||
thread.wait_to_finish();
|
||||
}
|
||||
|
||||
for (int i = 0; i < connected_inputs.size(); i++) {
|
||||
snd_rawmidi_t *midi_in = connected_inputs[i].rawmidi_ptr;
|
||||
snd_rawmidi_close(midi_in);
|
||||
for (const InputConnection &conn : connected_inputs) {
|
||||
snd_rawmidi_close(conn.rawmidi_ptr);
|
||||
}
|
||||
|
||||
connected_inputs.clear();
|
||||
connected_input_names.clear();
|
||||
}
|
||||
|
||||
void MIDIDriverALSAMidi::lock() const {
|
||||
@ -226,24 +137,6 @@ void MIDIDriverALSAMidi::unlock() const {
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
PackedStringArray MIDIDriverALSAMidi::get_connected_inputs() {
|
||||
PackedStringArray list;
|
||||
|
||||
lock();
|
||||
for (int i = 0; i < connected_inputs.size(); i++) {
|
||||
snd_rawmidi_t *midi_in = connected_inputs[i].rawmidi_ptr;
|
||||
snd_rawmidi_info_t *info;
|
||||
|
||||
snd_rawmidi_info_malloc(&info);
|
||||
snd_rawmidi_info(midi_in, info);
|
||||
list.push_back(snd_rawmidi_info_get_name(info));
|
||||
snd_rawmidi_info_free(info);
|
||||
}
|
||||
unlock();
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
MIDIDriverALSAMidi::MIDIDriverALSAMidi() {
|
||||
exit_thread.clear();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user