diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/os/midi_driver.cpp | 199 | ||||
-rw-r--r-- | core/os/midi_driver.h | 68 |
2 files changed, 200 insertions, 67 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; } diff --git a/core/os/midi_driver.h b/core/os/midi_driver.h index cad3d8189e..ddce63f9c8 100644 --- a/core/os/midi_driver.h +++ b/core/os/midi_driver.h @@ -42,19 +42,73 @@ class MIDIDriver { static MIDIDriver *singleton; static uint8_t last_received_message; +protected: + // Categories of message for parser logic. + enum class MessageCategory { + Data, + Voice, + SysExBegin, + SystemCommon, // excluding System Exclusive Begin/End + SysExEnd, + RealTime, + }; + + // Convert midi data to InputEventMIDI and send it to Input. + // p_data_len is the length of the buffer passed at p_data, this must be + // at least equal to the data required by the passed message type, but + // may be larger. Only the required data will be read. + static void send_event(int p_device_index, uint8_t p_status, + const uint8_t *p_data = nullptr, size_t p_data_len = 0); + + class Parser { + public: + Parser() = default; + Parser(int p_device_index) : + device_index{ p_device_index } {} + virtual ~Parser() = default; + + // Push a byte of MIDI stream. Any completed messages will be + // forwarded to MIDIDriver::send_event. + void parse_fragment(uint8_t p_fragment); + + static MessageCategory category(uint8_t p_midi_fragment); + + // If the byte is a Voice Message status byte return the contained + // channel number, otherwise zero. + static uint8_t channel(uint8_t p_status_byte); + + // If the byte is a status byte for a message with a fixed number of + // additional data bytes, return the number expected, otherwise zero. + static size_t expected_data(uint8_t p_status_byte); + static size_t expected_data(MIDIMessage p_msg_type); + + // If the fragment is a status byte return the message type + // represented, otherwise MIDIMessage::NONE. + static MIDIMessage status_to_msg_enum(uint8_t p_status_byte); + + private: + int device_index = 0; + + static constexpr size_t DATA_BUFFER_SIZE = 2; + + uint8_t status_byte = 0; + uint8_t data_buffer[DATA_BUFFER_SIZE] = { 0 }; + size_t received_data_len = 0; + bool skipping_sys_ex = false; + }; + + PackedStringArray connected_input_names; + public: static MIDIDriver *get_singleton(); - void set_singleton(); + + MIDIDriver(); + virtual ~MIDIDriver() = default; virtual Error open() = 0; virtual void close() = 0; - virtual PackedStringArray get_connected_inputs(); - - static void receive_input_packet(int device_index, uint64_t timestamp, uint8_t *data, uint32_t length); - - MIDIDriver(); - virtual ~MIDIDriver() {} + PackedStringArray get_connected_inputs() const; }; #endif // MIDI_DRIVER_H |