Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move MIDI parsing up from ALSA driver to platform independent driver. #90485

Merged
merged 1 commit into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 139 additions & 60 deletions core/os/midi_driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
68 changes: 61 additions & 7 deletions core/os/midi_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading
Loading