// 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 "base_midi.h" #include #include // TODO: startup function to initialize ParseMemory buffer. #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 { #ifdef DEBUG if (manufacturer_id > MAX_NON_UNIVERSAL_ID) { while(true); // Used universal sysex ID. } #endif 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. } }