summaryrefslogtreecommitdiff
path: root/controller/libs/base_midi.c
diff options
context:
space:
mode:
authorDuncan Wilkie <antigravityd@gmail.com>2023-07-18 11:55:03 -0500
committerDuncan Wilkie <antigravityd@gmail.com>2023-07-18 11:55:03 -0500
commited60865743a5d65240c9da353edb4dc20cf7009f (patch)
treebc7f7bb4ef3d48a1dfae42100667a5698d0b011e /controller/libs/base_midi.c
parent18a2be0c24b68dbfb4667e08ab6e8912adf52e7b (diff)
Rename, add startup file.
Diffstat (limited to 'controller/libs/base_midi.c')
-rw-r--r--controller/libs/base_midi.c835
1 files changed, 835 insertions, 0 deletions
diff --git a/controller/libs/base_midi.c b/controller/libs/base_midi.c
new file mode 100644
index 0000000..e146ec0
--- /dev/null
+++ b/controller/libs/base_midi.c
@@ -0,0 +1,835 @@
+ // This file is written to be read side-along with the MIDI 1.0 spec.
+// Note the tables at its very end, with /absolutely no direct allusion in the text/.
+
+#include "midi.h"
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef RUNNING_STATUS
+uint8_t last_status = 0x00;
+#endif
+
+// Helper functions
+#define MAX_DATA_BYTE 0b01111111
+static inline void MIDI_bare_send(uint8_t status) {
+#ifdef DEBUG
+ if (status <= MAX_DATA_BYTE) {
+ while(true); // Illegal status.
+ }
+#endif
+
+ pfns.uart_write(status);
+
+}
+
+static inline void MIDI_single_send(uint8_t status, uint8_t byte) {
+#ifdef DEBUG
+ if (status <= MAX_DATA_BYTE || byte > MAX_DATA_BYTE) {
+ while(true); // Illegal status or data byte.
+ }
+#endif
+
+#ifdef RUNNING_STATUS
+ if (status != last_status) {
+ pfns.uart_write(status);
+ last_status = status;
+ }
+#else
+ pfns.uart_write(status);
+#endif
+ pfns.uart_write(byte);
+}
+
+static inline void MIDI_send(uint8_t status, uint8_t byte1, uint8_t byte2) {
+#ifdef DEBUG
+ if (status <= MAX_DATA_BYTE || byte1 > MAX_DATA_BYTE || byte2 > MAX_DATA_BYTE) {
+ while(true); // Illegal status or data byte.
+ }
+#endif
+
+#ifdef RUNNING_STATUS
+ if (status != last_status) {
+ pfns.uart_write(status);
+ last_status = status;
+ }
+#else
+ pfns.uart_write(status);
+#endif
+ pfns.uart_write(byte1);
+ pfns.uart_write(byte2);
+}
+
+
+#define MAX_NON_UNIVERSAL_ID (MAX_DATA_BYTE - 2)
+#define SYSEX_STATUS 0xf0
+#define EOX_STATUS 0xf7
+void sysex(uint16_t manufacturer_id, uint8_t* contents, size_t contents_len) {
+#ifdef DEBUG
+ if (manufacturer_id == 0 || (manufacturer_id >> 8) > MAX_DATA_BYTE || (manufacturer_id & 0x00ff) > MAX_DATA_BYTE) {
+ while(true); // Invalid ID.
+ }
+
+ // Pretty inefficient to iterate contents twice, but prevents writing partial SysEx messages to the bus.
+ for (size_t i = 0; i < contents_len; i++) {
+ if (contents[i] > MAX_DATA_BYTE) {
+ while(true);
+ }
+ }
+#endif
+
+ if ((manufacturer_id >> 8) != 0) {
+ pfns.uart_write(SYSEX_STATUS);
+ pfns.uart_write(0x00);
+ pfns.uart_write(manufacturer_id >> 8);
+ pfns.uart_write(manufacturer_id & 0x00ff);
+ } else {
+ if (manufacturer_id > MAX_NON_UNIVERSAL_ID) {
+ while(true); // Used universal sysex ID.
+ }
+
+ pfns.uart_write(manufacturer_id);
+ }
+
+ for (size_t i = 0; i < contents_len; i++) {
+ pfns.uart_write(contents[i]);
+ }
+
+ pfns.uart_write(EOX_STATUS);
+}
+
+#define UNIVERSAL_NONREALTIME_SUBID 0x7e
+#define UNIVERSAL_REALTIME_SUBID 0x7f
+void universal_nonrealtime(uint8_t device_id, uint16_t sub_id, uint8_t *contents, size_t contents_len) {
+#ifdef DEBUG
+ if ((sub_id >> 8) > MAX_DATA_BYTE || (sub_id & 0x00ff) > MAX_DATA_BYTE) {
+ while(true);
+ }
+
+ for (size_t i = 0; i < contents_len; i++) {
+ if (contents[i] > MAX_DATA_BYTE) {
+ while(true);
+ }
+ }
+#endif
+
+ pfns.uart_write(SYSEX_STATUS);
+ pfns.uart_write(UNIVERSAL_NONREALTIME_SUBID);
+ pfns.uart_write(device_id);
+ pfns.uart_write(sub_id >> 8);
+ pfns.uart_write(sub_id & 0x00ff);
+
+ for (size_t i = 0; i < contents_len; i++) {
+ pfns.uart_write(contents[i]);
+ }
+
+ pfns.uart_write(EOX_STATUS);
+}
+
+void universal_realtime(uint8_t device_id, uint16_t sub_id, uint8_t *contents, size_t contents_len) {
+#ifdef DEBUG
+ if ((sub_id >> 8) > MAX_DATA_BYTE || (sub_id & 0x00ff) > MAX_DATA_BYTE) {
+ while(true);
+ }
+
+ for (size_t i = 0; i < contents_len; i++) {
+ if (contents[i] > MAX_DATA_BYTE) {
+ while(true);
+ }
+ }
+#endif
+
+ pfns.uart_write(SYSEX_STATUS);
+ pfns.uart_write(UNIVERSAL_REALTIME_SUBID);
+ pfns.uart_write(device_id);
+ pfns.uart_write(sub_id >> 8);
+ pfns.uart_write(sub_id & 0x00ff);
+
+ for (size_t i = 0; i < contents_len; i++) {
+ pfns.uart_write(contents[i]);
+ }
+
+ pfns.uart_write(EOX_STATUS);
+}
+// Channel voice messages.
+
+#define MAX_CHANNEL 0x0f
+#define NOTE_OFF_MASK 0x80
+#define NOTE_ON_MASK 0x90
+#define POLY_KEY_PRESSURE_MASK 0xa0
+#define CONTROL_CHANGE_MASK 0xb0
+#define PROGRAM_CHANGE_MASK 0xc0
+#define AFTERTOUCH_MASK 0xd0
+#define PITCH_BEND_MASK 0xe0
+
+#define MIDDLE_VELOCITY 0x40
+
+#ifdef KEY_ON_VELOCITY
+void note_on(uint8_t channel, uint8_t note, uint8_t velocity) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL || note > MAX_DATA_BYTE || velocity > MAX_DATA_BYTE) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(NOTE_ON_MASK | channel, note, velocity);
+
+}
+
+#else
+
+void note_on(uint8_t channel, uint8_t note) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL || note > MAX_DATA_BYTE) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(NOTE_ON_MASK | channel, note, MIDDLE_VELOCITY);
+
+}
+
+
+#endif
+
+#ifdef RELEASE_VELOCITY
+
+void note_off(uint8_t channel, uint8_t note, uint8_t velocity) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL || note > MAX_DATA_BYTE || velocity > MAX_DATA_BYTE) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(NOTE_OFF_MASK | channel, note, velocity);
+
+}
+
+#else
+
+void note_off(uint8_t channel, uint8_t note) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL || note > MAX_DATA_BYTE) {
+ while(true);
+ }
+#endif
+
+#ifdef ACTUAL_OFF_MESSAGE
+ MIDI_send(NOTE_OFF_MASK | channel, note, MIDDLE_VELOCITY);
+#else
+ MIDI_send(NOTE_ON_MASK | channel, note, 0);
+#endif
+
+}
+
+#endif
+
+#define MAX_CONTROLLER 119
+void control_change(uint8_t channel, uint8_t controller_number, uint8_t control_value) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL || controller_number > MAX_CONTROLLER || control_value > MAX_DATA_BYTE) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(CONTROL_CHANGE_MASK | channel, controller_number, control_value);
+
+}
+
+void program_change(uint8_t channel, uint8_t program_number) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL || program_number > MAX_DATA_BYTE) {
+ while(true);
+ }
+#endif
+
+ MIDI_single_send(PROGRAM_CHANGE_MASK | channel, program_number);
+
+}
+
+void aftertouch(uint8_t channel, uint8_t pressure_value) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL || pressure_value > MAX_DATA_BYTE) {
+ while(true);
+ }
+#endif
+
+ MIDI_single_send(AFTERTOUCH_MASK | channel, pressure_value);
+
+}
+
+void pitch_bend_change(uint8_t channel, uint16_t pressure_value) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL || pressure_value > ((MAX_DATA_BYTE << 8) | MAX_DATA_BYTE)) {
+ while(true);
+ }
+#endif
+
+ MIDI_single_send(PITCH_BEND_MASK | channel, pressure_value);
+
+}
+
+
+
+// Channel mode messages.
+
+#define SOUND_OFF_MODE (MAX_CONTROLLER + 1)
+#define RESET_ALL_MODE (MAX_CONTROLLER + 2)
+#define LOCAL_CONTROL_MODE (MAX_CONTROLLER + 3)
+#define NOTES_OFF_MODE (MAX_CONTROLLER + 4)
+#define OMNI_OFF_MODE (MAX_CONTROLLER + 5)
+#define OMNI_ON_MODE (MAX_CONTROLLER + 6)
+#define MONO_ON_MODE (MAX_CONTROLLER + 7)
+#define POLY_ON_MODE (MAX_CONTROLLER + 8)
+
+void all_sound_off(uint8_t channel) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(CONTROL_CHANGE_MASK | channel, SOUND_OFF_MODE, 0);
+
+}
+
+void reset_all_controllers(uint8_t channel) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(CONTROL_CHANGE_MASK | channel, RESET_ALL_MODE, 0);
+
+}
+
+void local_control_on(uint8_t channel) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(CONTROL_CHANGE_MASK | channel, LOCAL_CONTROL_MODE, MAX_DATA_BYTE);
+
+}
+
+void local_control_off(uint8_t channel) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(CONTROL_CHANGE_MASK | channel, LOCAL_CONTROL_MODE, 0);
+
+}
+
+void all_notes_off(uint8_t channel) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(CONTROL_CHANGE_MASK | channel, NOTES_OFF_MODE, 0);
+
+}
+
+void omni_on(uint8_t channel) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(CONTROL_CHANGE_MASK | channel, OMNI_ON_MODE, 0);
+
+}
+
+void omni_off(uint8_t channel) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(CONTROL_CHANGE_MASK | channel, OMNI_OFF_MODE, 0);
+
+}
+
+#define RECEIVER_CHANNEL_COUNT 0
+void mono_on(uint8_t channel, uint8_t channel_count) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL || channel_count > MAX_DATA_BYTE) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(CONTROL_CHANGE_MASK | channel, MONO_ON_MODE, channel_count);
+
+}
+
+void poly_on(uint8_t channel) {
+#ifdef DEBUG
+ if (channel > MAX_CHANNEL) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(CONTROL_CHANGE_MASK | channel, POLY_ON_MODE, 0);
+
+}
+
+
+// System common messages.
+// Not implemented:
+// - MTC Quarter Frame
+// EOX is automatically sent in the functions that send exclusives.
+
+#define MTC_QUARTER_FRAME_STATUS 0xf1
+#define SONG_POSITION_POINTER_STATUS 0xf2
+// Undefined.
+#define SONG_SELECT_STATUS 0xf3
+#define TUNE_REQUEST_STATUS 0xf6
+
+void song_position_pointer(uint16_t position) {
+#ifdef DEBUG
+ if ((position & 0x0ff) > MAX_DATA_BYTE || (position >> 8) > MAX_DATA_BYTE) {
+ while(true);
+ }
+#endif
+
+ MIDI_send(SONG_POSITION_POINTER_STATUS, position & 0x00ff, position >> 8);
+
+}
+
+void song_select(uint8_t song) {
+#ifdef DEBUG
+ if (song > MAX_DATA_BYTE) {
+ while(true);
+ }
+#endif
+
+ MIDI_single_send(SONG_SELECT_STATUS, song);
+
+}
+
+void tune_request() {
+ MIDI_bare_send(TUNE_REQUEST_STATUS);
+}
+
+
+
+// System real time messages.
+// Not implemented:
+// - Timing Clock,
+// - Start,
+// - Continue,
+// - Stop,
+// - Active Sensing.
+
+#define TIMING_CLOCK_STATUS 0xf8
+// Undefined.
+#define START_STATUS 0xfa
+#define CONTINUE_STATUS 0xfb
+#define STOP_STATUS 0xfc
+// Undefined.
+#define ACTIVE_SENSING_STATUS 0xfe
+#define SYSTEM_RESET_STATUS 0xff
+void timing_clock() {
+ MIDI_bare_send(TIMING_CLOCK_STATUS);
+}
+
+void srt_start() {
+ MIDI_bare_send(START_STATUS);
+}
+
+void srt_continue() {
+ MIDI_bare_send(CONTINUE_STATUS);
+}
+
+void srt_stop() {
+ MIDI_bare_send(STOP_STATUS);
+}
+
+void active_sensing() {
+ MIDI_bare_send(ACTIVE_SENSING_STATUS);
+}
+
+void MIDI_reset() {
+ MIDI_bare_send(SYSTEM_RESET_STATUS);
+}
+
+
+// Universal system exclusive messages.
+
+
+// Only implement MIDI Tuning Standard universals and general information.
+
+
+#define TUNING_STANDARD_SUBID 0x08
+#define BULK_DUMP_REQUEST_SUBID 0x00
+void bulk_tuning_dump_request(uint8_t device_id, uint8_t program) {
+#ifdef DEBUG
+ if (program > MAX_DATA_BYTE || device_id > MAX_DATA_BYTE) {
+ while(true);
+ }
+#endif
+
+ uint8_t contents[] = {program};
+
+ universal_nonrealtime(device_id, (TUNING_STANDARD_SUBID << 8) | BULK_DUMP_REQUEST_SUBID, contents, 1);
+
+}
+
+
+#define BULK_DUMP_REPLY_SUBID 0x01
+void bulk_tuning_dump(uint8_t device_id, uint8_t program, char* name, uint8_t* tuning_data) {
+#ifdef DEBUG
+ if (device_id > MAX_DATA_BYTE || program > MAX_DATA_BYTE) {
+ while(true);
+ }
+#endif
+
+ uint8_t *contents = malloc(1 + TUNING_NAME_LENGTH + TUNING_LENGTH + 1);
+ contents[0] = program;
+ memcpy(contents + 1, name, TUNING_NAME_LENGTH);
+ memcpy(contents + 1 + TUNING_NAME_LENGTH, tuning_data, TUNING_LENGTH);
+
+ // I hate this freaking "standard"...what TF do I checksum???
+ uint8_t checksum = UNIVERSAL_NONREALTIME_SUBID ^ device_id ^ TUNING_STANDARD_SUBID ^ BULK_DUMP_REPLY_SUBID ^ program;
+ for (int i = 0; i < TUNING_LENGTH; i++) {
+ checksum ^= tuning_data[i];
+ }
+
+ contents[1 + TUNING_NAME_LENGTH + DEVICE_KEY_COUNT] = checksum;
+
+ universal_nonrealtime(device_id, (TUNING_STANDARD_SUBID << 8) | BULK_DUMP_REPLY_SUBID, contents,
+ 1 + TUNING_NAME_LENGTH + TUNING_LENGTH + 1);
+
+ free(contents);
+
+}
+
+#define NOTE_CHANGE_SUBID 0x02
+void single_note_tuning_change(uint8_t device_id, uint8_t program, uint8_t *note_tuning_data, uint8_t keys_changed) {
+#ifdef DEBUG
+ if (device_id > MAX_DATA_BYTE || program > MAX_DATA_BYTE || keys_changed > DEVICE_KEY_COUNT) {
+ while(true);
+ }
+#endif
+
+ uint8_t *contents = malloc(2 + NOTE_TUNING_BYTES_PER_KEY * keys_changed);
+ contents[0] = program;
+ contents[1] = keys_changed;
+ memcpy(contents + 2, note_tuning_data, NOTE_TUNING_BYTES_PER_KEY * keys_changed);
+
+ universal_realtime(device_id, (TUNING_STANDARD_SUBID << 8) | NOTE_CHANGE_SUBID, contents,
+ 2 + NOTE_TUNING_BYTES_PER_KEY * keys_changed);
+
+ free(contents);
+
+}
+
+// TODO: tuning banks.
+
+
+// Parsing.
+// Stream parsing.
+typedef struct {
+ uint8_t status;
+ bool first_byte;
+ uint8_t byte0;
+ bool send_eox;
+ uint8_t *stack;
+ size_t top;
+ size_t size;
+} ParserMemory;
+
+#define WAITING_FOR_STATUS 0x00
+ParserMemory memory = {.status = WAITING_FOR_STATUS,
+ .first_byte = false,
+ .byte0 = 0x00,
+ .send_eox = false,
+ .stack = NULL, // TODO: initialize from main.
+ .top = 0,
+ .size = 128};
+
+static inline void push_fn(uint8_t val) {
+ if (memory.top == memory.size - 1) {
+ memory.stack = realloc(memory.stack, memory.size + 128);
+ }
+
+ memory.stack[memory.top + 1] = val;
+ ++memory.top;
+}
+
+#define push() do { \
+ push_fn(byte); \
+ } while(0)
+
+static inline uint8_t pop() {
+ if (memory.size - memory.top > 128) {
+ memory.stack = realloc(memory.stack, memory.size - 128);
+ }
+
+ --memory.top;
+ return memory.stack[memory.top + 1];
+}
+
+static inline void reset() {
+ memory.stack = realloc(memory.stack, 128);
+ memory.size = 128;
+ memory.top = 0;
+ memory.status = WAITING_FOR_STATUS;
+}
+
+#define second_byte() do { \
+ memory.first_byte = false; \
+ memory.byte0 = byte; \
+ } while(0)
+
+
+#define channel (memory.status & 0x0f)
+
+
+// Memory should be large enough to handle the largest message that's expected to be processed automatically here.
+void parse_midi_stream(uint8_t byte) {
+ switch (byte) {
+ // Status byte.
+ case NOTE_OFF_MASK ... (PITCH_BEND_MASK + MAX_CHANNEL):
+ memory.status = byte;
+ break;
+
+ case MTC_QUARTER_FRAME_STATUS ... SONG_SELECT_STATUS:
+ memory.status = byte;
+ break;
+ case TUNE_REQUEST_STATUS:
+ pfns.tune_request_handler();
+ memory.status = WAITING_FOR_STATUS;
+ break;
+
+ case TIMING_CLOCK_STATUS:
+ pfns.timing_clock_handler();
+ break;
+ case START_STATUS:
+ pfns.start_handler();
+ break;
+ case CONTINUE_STATUS:
+ pfns.continue_handler();
+ break;
+ case STOP_STATUS:
+ pfns.stop_handler();
+ break;
+ case ACTIVE_SENSING_STATUS:
+ pfns.active_sensing_handler();
+ break;
+ case SYSTEM_RESET_STATUS:
+ pfns.system_reset_handler();
+ break;
+ case SYSEX_STATUS:
+ memory.status = byte;
+ break;
+ case EOX_STATUS:
+ if (memory.send_eox) {
+ pfns.end_of_sysex_handler();
+ memory.send_eox = false;
+ }
+ reset();
+ break;
+
+ // Data byte.
+ case 0x00 ... MAX_DATA_BYTE:
+ switch (memory.status) {
+ case WAITING_FOR_STATUS:
+ break;
+
+ case NOTE_OFF_MASK ... (NOTE_OFF_MASK + MAX_CHANNEL):
+ if (memory.first_byte)
+ second_byte();
+ else {
+ pfns.note_off_handler(channel, memory.byte0, byte);
+ memory.first_byte = true;
+ }
+ break;
+ case NOTE_ON_MASK ... (NOTE_ON_MASK + MAX_CHANNEL):
+ if (memory.first_byte)
+ second_byte();
+ else {
+ pfns.note_on_handler(channel, memory.byte0, byte);
+ memory.first_byte = true;
+ }
+ break;
+ case POLY_KEY_PRESSURE_MASK ... (POLY_KEY_PRESSURE_MASK + MAX_CHANNEL):
+ if (memory.first_byte)
+ second_byte();
+ else {
+ pfns.poly_key_handler(channel, memory.byte0, byte);
+ memory.first_byte = true;
+ }
+ break;
+ case CONTROL_CHANGE_MASK ... (CONTROL_CHANGE_MASK + MAX_CHANNEL):
+ if (memory.first_byte)
+ second_byte();
+ else {
+ switch (memory.byte0) {
+ case 0x00 ... MAX_CONTROLLER:
+ pfns.control_change_handler(channel, memory.byte0, byte);
+ break;
+ case SOUND_OFF_MODE:
+ pfns.all_sound_off_handler(channel);
+ break;
+ case RESET_ALL_MODE:
+ pfns.reset_all_controllers_handler(channel);
+ break;
+ case LOCAL_CONTROL_MODE:
+ pfns.local_control_handler(channel, byte);
+ break;
+ case NOTES_OFF_MODE:
+ pfns.all_notes_off_handler(channel);
+ break;
+ case OMNI_OFF_MODE:
+ pfns.omni_off_handler(channel);
+ break;
+ case OMNI_ON_MODE:
+ pfns.omni_on_handler(channel);
+ break;
+ case MONO_ON_MODE:
+ pfns.mono_on_handler(channel, byte);
+ break;
+ case POLY_ON_MODE:
+ pfns.poly_on_handler(channel);
+ break;
+ default:
+ while(true); // Should be unreachable.
+ }
+ memory.first_byte = true;
+ }
+ break;
+ case PROGRAM_CHANGE_MASK ... (PROGRAM_CHANGE_MASK + MAX_CHANNEL):
+ pfns.program_change_handler(channel, byte);
+ break;
+ case AFTERTOUCH_MASK ... (AFTERTOUCH_MASK + MAX_CHANNEL):
+ pfns.aftertouch_handler(channel, byte);
+ break;
+ case PITCH_BEND_MASK ... (PITCH_BEND_MASK + MAX_CHANNEL):
+ pfns.pitch_bend_change_handler(channel, byte);
+ break;
+
+
+ case MTC_QUARTER_FRAME_STATUS:
+ if (memory.first_byte)
+ second_byte();
+ else {
+ pfns.mtc_quarter_frame_handler(memory.byte0, byte);
+ memory.first_byte = true;
+ }
+ break;
+ case SONG_POSITION_POINTER_STATUS:
+ if (memory.first_byte)
+ second_byte();
+ else {
+ pfns.song_position_pointer_handler(memory.byte0, byte);
+ memory.first_byte = true;
+ }
+ break;
+ case SONG_SELECT_STATUS:
+ pfns.song_select_handler(byte);
+ break;
+
+ case SYSEX_STATUS:
+ if (memory.first_byte) {
+ reset(); // In case last sysex got interrupted by a status.
+ if (byte <= MAX_NON_UNIVERSAL_ID)
+ pfns.sysex_collector(byte);
+ second_byte();
+ } else
+ switch (memory.byte0) {
+ case UNIVERSAL_NONREALTIME_SUBID:
+ push();
+ if (memory.top > 2) {
+ uint8_t device_id = memory.stack[0];
+ uint8_t subid1 = memory.stack[1];
+ uint8_t subid2 = memory.stack[2];
+ switch (subid1) {
+ case TUNING_STANDARD_SUBID:
+ switch (subid2) {
+ case BULK_DUMP_REQUEST_SUBID:
+ pfns.bulk_tuning_dump_request_handler(device_id, byte);
+ reset();
+ break;
+ case BULK_DUMP_REPLY_SUBID:
+ if (memory.top == 4 + TUNING_NAME_LENGTH + TUNING_LENGTH - 1) {
+ uint8_t tuning_program = memory.stack[3];
+ uint8_t checksum = UNIVERSAL_NONREALTIME_SUBID ^ device_id ^ TUNING_STANDARD_SUBID \
+ ^ BULK_DUMP_REPLY_SUBID ^ tuning_program;
+ for (int i = 0; i < TUNING_LENGTH; i++) {
+ checksum ^= memory.stack[4 + TUNING_NAME_LENGTH - 1 + i];
+ }
+
+ if (memory.stack[memory.top] == checksum) {
+ pfns.bulk_tuning_dump_handler(memory.stack[0], memory.stack[3], (char *)(memory.stack + 4),
+ memory.stack + 4 + TUNING_NAME_LENGTH);
+ }
+
+ reset();
+ }
+ default:
+ while(true); // Unrecognized tuning standard subid2.
+ }
+ break;
+ default:
+ if (!pfns.unimplemented_universal_sysex_collector(byte)) {
+ reset();
+ } else {
+ push();
+ }
+ break;
+ }
+ }
+ break;
+
+ case UNIVERSAL_REALTIME_SUBID:
+ push();
+ if (memory.top > 2) {
+ uint8_t device_id = memory.stack[0];
+ uint8_t subid1 = memory.stack[1];
+ uint8_t subid2 = memory.stack[2];
+ switch (subid1) {
+ case TUNING_STANDARD_SUBID:
+ switch (subid2) {
+ case NOTE_CHANGE_SUBID:
+ uint8_t program_number = memory.stack[3];
+ uint8_t change_count = memory.stack[4];
+ if (memory.top >= 4 && memory.top == 4 + (size_t)change_count + 1 - 1) {
+ pfns.single_note_tuning_change_handler(device_id, program_number, change_count, memory.stack + 5);
+ reset();
+ }
+ break;
+ default:
+ while(true); // Unrecognized tuning standard message.
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ if (!pfns.sysex_collector(byte)) {
+ reset();
+ } else {
+ push();
+ }
+ break;
+ }
+ default:
+ while(true); // Unhandled status got set, somehow.
+ }
+ break;
+
+ default:
+ while(true); // Unrecognized status byte.
+ }
+}