diff options
author | Ibrahn Sahir <ibrahn.sahir@gmail.com> | 2024-04-10 10:50:28 +0100 |
---|---|---|
committer | Ibrahn Sahir <ibrahn.sahir@gmail.com> | 2024-06-25 16:46:30 +0100 |
commit | 607c5ec49f605966c6870e34cebcd2f967be12d3 (patch) | |
tree | 5a23b3a590a8518fe6ec4cd0d667adb3afa72fdb /core/os/midi_driver.cpp | |
parent | 6b281c0c07b07f2142b1fc8a6b3158091a9b124c (diff) | |
download | redot-engine-607c5ec49f605966c6870e34cebcd2f967be12d3.tar.gz |
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>
Diffstat (limited to 'core/os/midi_driver.cpp')
-rw-r--r-- | core/os/midi_driver.cpp | 199 |
1 files changed, 139 insertions, 60 deletions
diff --git a/core/os/midi_driver.cpp b/core/os/midi_driver.cpp index 6870c84b49..6c748b1498 100644 --- a/core/os/midi_driver.cpp +++ b/core/os/midi_driver.cpp @@ -38,88 +38,167 @@ MIDIDriver *MIDIDriver::get_singleton() { return singleton; } -void MIDIDriver::set_singleton() { +MIDIDriver::MIDIDriver() { singleton = this; } -void MIDIDriver::receive_input_packet(int device_index, uint64_t timestamp, uint8_t *data, uint32_t length) { - Ref<InputEventMIDI> event; - event.instantiate(); - event->set_device(device_index); - uint32_t param_position = 1; - - if (length >= 1) { - if (data[0] >= 0xF0) { - // channel does not apply to system common messages - event->set_channel(0); - event->set_message(MIDIMessage(data[0])); - last_received_message = data[0]; - } else if ((data[0] & 0x80) == 0x00) { - // running status - event->set_channel(last_received_message & 0xF); - event->set_message(MIDIMessage(last_received_message >> 4)); - param_position = 0; +MIDIDriver::MessageCategory MIDIDriver::Parser::category(uint8_t p_midi_fragment) { + if (p_midi_fragment >= 0xf8) { + return MessageCategory::RealTime; + } else if (p_midi_fragment >= 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 (p_midi_fragment == 0xf0) { + return MessageCategory::SysExBegin; + } else if (p_midi_fragment == 0xf7) { + return MessageCategory::SysExEnd; + } + return MessageCategory::SystemCommon; + } else if (p_midi_fragment >= 0x80) { + return MessageCategory::Voice; + } + return MessageCategory::Data; +} + +MIDIMessage MIDIDriver::Parser::status_to_msg_enum(uint8_t p_status_byte) { + if (p_status_byte & 0x80) { + if (p_status_byte < 0xf0) { + return MIDIMessage(p_status_byte >> 4); } else { - event->set_channel(data[0] & 0xF); - event->set_message(MIDIMessage(data[0] >> 4)); - param_position = 1; - last_received_message = data[0]; + return MIDIMessage(p_status_byte); } } + return MIDIMessage::NONE; +} - switch (event->get_message()) { - case MIDIMessage::AFTERTOUCH: - if (length >= 2 + param_position) { - event->set_pitch(data[param_position]); - event->set_pressure(data[param_position + 1]); - } - break; +size_t MIDIDriver::Parser::expected_data(uint8_t p_status_byte) { + return expected_data(status_to_msg_enum(p_status_byte)); +} +size_t MIDIDriver::Parser::expected_data(MIDIMessage p_msg_type) { + switch (p_msg_type) { + case MIDIMessage::NOTE_OFF: + case MIDIMessage::NOTE_ON: + case MIDIMessage::AFTERTOUCH: case MIDIMessage::CONTROL_CHANGE: - if (length >= 2 + param_position) { - event->set_controller_number(data[param_position]); - event->set_controller_value(data[param_position + 1]); - } - break; + case MIDIMessage::PITCH_BEND: + case MIDIMessage::SONG_POSITION_POINTER: + return 2; + case MIDIMessage::PROGRAM_CHANGE: + case MIDIMessage::CHANNEL_PRESSURE: + case MIDIMessage::QUARTER_FRAME: + case MIDIMessage::SONG_SELECT: + return 1; + default: + return 0; + } +} - case MIDIMessage::NOTE_ON: +uint8_t MIDIDriver::Parser::channel(uint8_t p_status_byte) { + if (category(p_status_byte) == MessageCategory::Voice) { + return p_status_byte & 0x0f; + } + return 0; +} + +void MIDIDriver::send_event(int p_device_index, uint8_t p_status, + const uint8_t *p_data, size_t p_data_len) { + const MIDIMessage msg = Parser::status_to_msg_enum(p_status); + ERR_FAIL_COND(p_data_len < Parser::expected_data(msg)); + + Ref<InputEventMIDI> event; + event.instantiate(); + event->set_device(p_device_index); + event->set_channel(Parser::channel(p_status)); + event->set_message(msg); + switch (msg) { case MIDIMessage::NOTE_OFF: - if (length >= 2 + param_position) { - event->set_pitch(data[param_position]); - event->set_velocity(data[param_position + 1]); - } + case MIDIMessage::NOTE_ON: + event->set_pitch(p_data[0]); + event->set_velocity(p_data[1]); break; - - case MIDIMessage::PITCH_BEND: - if (length >= 2 + param_position) { - event->set_pitch((data[param_position + 1] << 7) | data[param_position]); - } + case MIDIMessage::AFTERTOUCH: + event->set_pitch(p_data[0]); + event->set_pressure(p_data[1]); + break; + case MIDIMessage::CONTROL_CHANGE: + event->set_controller_number(p_data[0]); + event->set_controller_value(p_data[1]); break; - case MIDIMessage::PROGRAM_CHANGE: - if (length >= 1 + param_position) { - event->set_instrument(data[param_position]); - } + event->set_instrument(p_data[0]); break; - case MIDIMessage::CHANNEL_PRESSURE: - if (length >= 1 + param_position) { - event->set_pressure(data[param_position]); - } + event->set_pressure(p_data[0]); + break; + case MIDIMessage::PITCH_BEND: + event->set_pitch((p_data[1] << 7) | p_data[0]); break; + // QUARTER_FRAME, SONG_POSITION_POINTER, and SONG_SELECT not yet implemented. default: break; } - - Input *id = Input::get_singleton(); - id->parse_input_event(event); + Input::get_singleton()->parse_input_event(event); } -PackedStringArray MIDIDriver::get_connected_inputs() { - PackedStringArray list; - return list; +void MIDIDriver::Parser::parse_fragment(uint8_t p_fragment) { + switch (category(p_fragment)) { + case MessageCategory::RealTime: + // Real-Time messages are single byte messages that can + // occur at any point and do not interrupt other messages. + // We pass them straight through. + MIDIDriver::send_event(device_index, p_fragment); + break; + + case MessageCategory::SysExBegin: + status_byte = p_fragment; + skipping_sys_ex = true; + break; + + case MessageCategory::SysExEnd: + status_byte = 0; + skipping_sys_ex = false; + break; + + case MessageCategory::Voice: + case MessageCategory::SystemCommon: + skipping_sys_ex = false; // If we were in SysEx, assume it was aborted. + received_data_len = 0; + status_byte = 0; + ERR_FAIL_COND(expected_data(p_fragment) > DATA_BUFFER_SIZE); + if (expected_data(p_fragment) == 0) { + // No data bytes needed, post it now. + MIDIDriver::send_event(device_index, p_fragment); + } else { + status_byte = p_fragment; + } + break; + + case MessageCategory::Data: + // We don't currently process SysEx messages, so ignore their data. + if (!skipping_sys_ex) { + const size_t expected = expected_data(status_byte); + if (received_data_len < expected) { + data_buffer[received_data_len] = p_fragment; + received_data_len++; + if (received_data_len == expected) { + MIDIDriver::send_event(device_index, status_byte, + data_buffer, expected); + received_data_len = 0; + // Voice messages can use 'running status', sending further + // messages without resending their status byte. + // For other messages types we clear the cached status byte. + if (category(status_byte) != MessageCategory::Voice) { + status_byte = 0; + } + } + } + } + break; + } } -MIDIDriver::MIDIDriver() { - set_singleton(); +PackedStringArray MIDIDriver::get_connected_inputs() const { + return connected_input_names; } |