summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/os/midi_driver.cpp199
-rw-r--r--core/os/midi_driver.h68
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