From 18a2be0c24b68dbfb4667e08ab6e8912adf52e7b Mon Sep 17 00:00:00 2001 From: Duncan Wilkie Date: Tue, 18 Jul 2023 10:34:34 -0500 Subject: Refactored parsing, added header, all around based mode --- controller/Makefile | 2 +- controller/inc/midi.h | 207 +++++++++++ controller/libs/midi.c | 971 ++++++++++++++++++++++++++++++++++++------------- controller/src/main.c | 2 +- 4 files changed, 934 insertions(+), 248 deletions(-) create mode 100644 controller/inc/midi.h (limited to 'controller') diff --git a/controller/Makefile b/controller/Makefile index fef034d..00ed2da 100644 --- a/controller/Makefile +++ b/controller/Makefile @@ -22,7 +22,7 @@ IPATH = /home/dnw/Code/TivaC/libs # Flags. CFLAGS = -mthumb -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -ffunction-sections \ -fdata-sections -MD -std=c99 -Wall -pedantic -DPART_${MCU} -c -Os -Dgcc -ggdb -CFLAGS += ${patsubst %,-I%,${subst :, ,${IPATH}}} -Iinc +CFLAGS += ${patsubst %,-I%,${subst :, ,${IPATH}}} -iquote inc # Compiler/standard resource locations. LIBGCC := ${shell ${CC} ${CFLAGS} -print-libgcc-file-name} diff --git a/controller/inc/midi.h b/controller/inc/midi.h new file mode 100644 index 0000000..819db03 --- /dev/null +++ b/controller/inc/midi.h @@ -0,0 +1,207 @@ +#ifndef MIDI_H +#define MIDI_H + +#include +#include +#include + +// Configuration. + +#define DEBUG 0 // If defined, enable input safety checking (e.g. too large data bytes, bad ID numbers, etc). + // These checks have some performance downside. +#define RUNNING_STATUS 0 // If defined, the MIDI instrument stores a transmit status and uses running statuses whenever possible. +#define KEY_ON_VELOCITY 0 // If defined, the MIDI instrument sends velocities with Note On messages. +#undef RELEASE_VELOCITY // If defined, the MIDI instrument sends Note Off messages with velocities. + // Otherwise, sends Note On velocity 0 or Note Off velocity 0 according to ACTUAL_OFF_MESSAGE. +#undef ACTUAL_OFF_MESSAGE // If defined, the MIDI instrument sends true Note Off messages. + // Otherwise sends Note On velocity 0 (so as to exploit running status). +#undef EXPORT_CONTROLLERS // If defined, export constants useful for controller messages. + +// Consumers must specify +typedef struct { + void (*uart_write)(uint8_t); // Blocking, single-byte UART transmit function. + // Channel voice handlers. + void (*note_on_handler)(uint8_t, uint8_t, uint8_t); + void (*note_off_handler)(uint8_t, uint8_t, uint8_t); + void (*poly_key_handler)(uint8_t, uint8_t, uint8_t); + void (*control_change_handler)(uint8_t, uint8_t, uint8_t); + void (*program_change_handler)(uint8_t, uint8_t); + void (*aftertouch_handler)(uint8_t, uint8_t); + void (*pitch_bend_change_handler)(uint8_t, uint8_t); + // Channel mode handlers. + void (*all_sound_off_handler)(uint8_t); + void (*reset_all_controllers_handler)(uint8_t); + void (*local_control_handler)(uint8_t, uint8_t); + void (*all_notes_off_handler)(uint8_t); + void (*omni_on_handler)(uint8_t); + void (*omni_off_handler)(uint8_t); + void (*mono_on_handler)(uint8_t, uint8_t); + void (*poly_on_handler)(uint8_t); + // System common handlers. + void (*mtc_quarter_frame_handler)(uint8_t, uint8_t); + void (*song_position_pointer_handler)(uint8_t, uint8_t); + void (*song_select_handler)(uint8_t); + void (*tune_request_handler)(); + // System real time handlers. + void (*timing_clock_handler)(); + void (*start_handler)(); + void (*continue_handler)(); + void (*stop_handler)(); + void (*active_sensing_handler)(); + void (*system_reset_handler)(); + // System Exclusive handlers. + bool (*sysex_collector)(uint8_t); // Feeds most recent byte system exclusive byte (status and eox excluded, ID included) + // to client, returning false whenever the client determines sysex can be safely discarded. + // Client is expected to maintain parsing state externally, + // including resetting correctly after aborted parses. + // Client also should not abort the parse before the first post-manufacturer-ID byte. + void (*end_of_sysex_handler)(); // Sent only when client + // Universal System Exclusive handlers. + void (*bulk_tuning_dump_request_handler)(uint8_t, uint8_t); + void (*bulk_tuning_dump_handler)(uint8_t, uint8_t, char*, uint8_t*); + void (*single_note_tuning_change_handler)(uint8_t, uint8_t, uint8_t, uint8_t*); + // TODO: more. + bool (*unimplemented_universal_sysex_collector)(uint8_t); // Same as above. Parsing ends with end_of_sysex_handler. +} ConsumerBehavior; + +extern ConsumerBehavior pfns; // Initialize this globally in your main. + +// System exclusive sends. +void sysex(uint16_t manufacturer_id, bool long_id, uint8_t* contents, size_t contents_len); +void universal_nonrealtime(uint8_t device_id, uint16_t sub_id, uint8_t* contents, size_t contents_len); +void universal_realtime(uint8_t device_id, uint16_t sub_id, uint8_t* contents, size_t contents_len); + +// Channel voice sends. +#ifdef KEY_ON_VELOCITY +void note_on(uint8_t channel, uint8_t note, uint8_t velocity); +#else +void note_on(uint8_t channel, uint8_t note); +#endif +#ifdef RELEASE_VELOCITY +void note_off(uint8_t channel, uint8_t note, uint8_t velocity); +#else +void note_off(uint8_t channel, uint8_t note); +#endif +void control_change(uint8_t channel, uint8_t controller_number, uint8_t control_value); +void program_change(uint8_t channel, uint8_t program_number); +void aftertouch(uint8_t channel, uint8_t pressure_value); +void pitch_bend_change(uint8_t channel, uint16_t pressure_value); + +// Channel mode sends. +void all_sound_off(uint8_t channel); +void reset_all_controllers(uint8_t channel); +void local_control_on(uint8_t channel); +void local_control_off(uint8_t channel); +void all_notes_off(uint8_t channel); +void omni_on(uint8_t channel); +void omni_off(uint8_t channel); +void mono_on(uint8_t channel, uint8_t channel_count); +void poly_on(uint8_t channel); + + +// System Common sends. +// MTC Quarter Frame unimplemented. +void song_position_pointer(uint16_t position); +void song_select(uint8_t song); +void tune_request(); + +// System Real Time sends. +void timing_clock(); +void srt_start(); +void srt_continue(); +void srt_stop(); +void active_sensing(); +void MIDI_reset(); + +// Universal SysEx. +// Only implement MTS; consumers can do their own parsing via unimplemented_universal_sysex_collector. + +// MIDI Tuning Standard sends. +void bulk_tuning_dump_request(uint8_t device_id, uint8_t program); +void bulk_tuning_dump(uint8_t device_id, uint8_t program, char* name, uint8_t* tuning_data); +void single_note_tuning_change(uint8_t device_id, uint8_t program, uint8_t *note_tuning_data, uint8_t keys_changed); + + +// Feed bytes to this in the UART interrupt handler. +void parse_midi_stream(uint8_t byte); + + +// Public constants; magic numbers useful for consumers. +#define ALL_CALL_DEVICE_ID 0x7f +#define DEVICE_KEY_COUNT 128 +#define TUNING_LENGTH (3 * DEVICE_KEY_COUNT) +#define TUNING_NAME_LENGTH 16 +#define NOTE_TUNING_BYTES_PER_KEY 4 + + +#ifdef EXPORT_CONTROLLERS +// Controller constants. +typedef enum Controller { + BANK_SELECT = 0, + MODULATION_WHEEL = 1, + BREATH_CONTROLLER = 2, + // Undefined + FOOT_CONTROLLER = 4, + PORTAMENTO_TIME = 5, + DATA_ENTRY_MSB = 6, + CHANNEL_VOLUME = 7, + BALANCE = 8, + // Undefined + PAN = 10, + EXPRESSION = 11, + EFFECT1 = 12, + EFFECT2 = 13, + // Undefined + GP1 = 16, + GP2 = 17, + GP3 = 18, + GP4 = 19, + DAMPER_PEDAL = 64, + PORTAMENTO_TOGGLE = 65, + SOSTENUTO = 66, + SOFT_PEDAL = 67, + LEGATO_FOOTSWITCH = 68, + HOLD2 = 69, + SC1_SOUND_VARIATION = 70, + SC2_TIMBRE = 71, + SC3_RELEASE_TIME = 72, + SC4_ATTACK_TIME = 73, + SC5_BRIGHTNESS = 74, + SC6 = 75, + SC7 = 76, + SC8 = 77, + SC9 = 78, + SC10 = 79, + GP5 = 80, + GP6 = 81, + GP7 = 82, + GP8 = 83, + PORTAMENTO_CONTROL = 84, + // Undefined + EFFECTS_DEPTH1 = 91, + EFFECTS_DEPTH2 = 92, + EFFECTS_DEPTH3 = 93, + EFFECTS_DEPTH4 = 94, + EFFECTS_DEPTH5 = 95, + DATA_INCREMENT = 96, + DATA_DECREMENT = 97, + NONREGISTERED_LSB = 98, + NONREGISTERED_MSB = 99, + REGISTERED_LSB = 100, + REGISTERED_MSB = 101, + // undefined 102-119 + // reserved for channel mode 120-127 +}; + +typedef enum RegisteredParams { + PITCH_BEND_SENSITIVITY = 0, + FINE_TUNING = 1, + COARSE_TUNING = 2, + TUNING_PROGRAM_SELECT = 3, + TUNING_BANK_SELECT = 4 +}; + +#endif + + +#endif diff --git a/controller/libs/midi.c b/controller/libs/midi.c index 488b172..b718818 100644 --- a/controller/libs/midi.c +++ b/controller/libs/midi.c @@ -1,302 +1,420 @@ -// This file is written to be read side-along with the MIDI 1.0 spec. + // 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 -#include +#include "midi.h" -typedef struct { - uint8_t len; - uint8_t status; - uint8_t data1; - uint8_t data2; -} MIDIMessage; +#ifdef RUNNING_STATUS +uint8_t last_status = 0x00; +#endif -typedef struct { - uint8_t status; - uint8_t sub_id; - uint8_t* bytes; - uint8_t size; -} SysExMessage; +// 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); +} -// Channel voice messages. +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 KEY_ON_VELOCITY +#ifdef RUNNING_STATUS + if (status != last_status) { + pfns->uart_write(status); + last_status = status; + } +#else + pfns->uart_write(status); +#endif + pfns->uart_write(byte); +} -MIDIMessage note_on(uint8_t channel, uint8_t note, uint8_t velocity) { - if (channel > 0x0f || note > 127 || velocity > 127) { - while(1); +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 - return {.status = 0x90 & channel, - .data1 = note, - .data2 = velocity - .len = 3}; +#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); } -#ELSE -MIDIMessage note_on(uint8_t channel, uint8_t note) { - if (channel > 0x0f || note > 127) { - while(1); +#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. } - return {.status = 0x90 & channel, - .data1 = note, - .data2 = 0x40 - .len = 3}; + // Pretty inefficient to iterate contents twice, but prevents writing partial SysEx messages to the bus. + for (int 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 (int i = 0; i < message.contents_len; i++) { + pfns->uart_write(message.contents[i]); + } + + pfns->uart_write(EOX_STATUS); } +#define NON_REAL_TIME_ID 0x7e +#define REAL_TIME_ID 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); + } -#ENDIF + for (int i = 0; i < contents_len; i++) { + if (contents[i] > MAX_DATA_BYTE) { + while(true); + } + } +#endif -#IFDEF RELEASE_VELOCITY + pfns->uart_write(SYSEX_STATUS); + pfns->uart_write(REAL_TIME_ID); + pfns->uart_write(sub_id >> 8); + pfns->uart_write(sub_id & 0x00ff); -MIDIMessage note_off(uint8_t channel, uint8_t note, uint8_t velocity) { - if (channel > 0x0f || note > 127 || velocity > 127) { - while(1); + for (int i = 0; i < message.contents_len; i++) { + pfns->uart_write(message.contents[i]); } - return {.status = 0x80 & channel, - .data1 = note, - .data2 = velocity - .len = 3}; + pfns->uart_write(EOX_STATUS); } -#ELSE - -#IFDEF ACTUAL_OFF_MESSAGE - -MIDIMessage note_off(uint8_t channel, uint8_t note) { - if (channel > 0x0f || note > 127) { - while(1); +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 (int i = 0; i < contents_len; i++) { + if (contents[i] > MAX_DATA_BYTE) { + while(true); + } } - - return {.status = 0x80 & channel, - .data1 = note, - .data2 = 0x40 - .len = 3}; +#endif + + pfns->uart_write(SYSEX_STATUS); + pfns->uart_write(NON_REAL_TIME_ID); + pfns->uart_write(sub_id >> 8); + pfns->uart_write(sub_id & 0x00ff); + + for (int i = 0; i < message.contents_len; i++) { + pfns->uart_write(message.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 +} + +#else -MIDIMessage note_off(uint8_t channel, uint8_t note) { - if (channel > 0x0f || note > 127) { - while(1); +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); - return {.status = 0x90 & channel, - .data1 = note, - .data2 = 0 - .len = 3}; } -#ENDIF -#ENDIF -MIDIMessage control_change(uint8_t channel, uint8_t control_number, uint8_t control_value) { - if (channel > 0x0f || control_number > 119 || control_value > 127) { - while(1); +#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); - return {.status = 0xb0 & channel, - .data1 = control_number, - .data2 = control_value - .len = 3}; } -enum Controller { - bank_select = 0, - modulation_wheel = 1, - breath_controller = 2, - // undefined - foot_controller = 4, - portamento_time = 5, - data_entry_msb = 6, - channel_volume = 7, - balance = 8, - // undefined - pan = 10, - expression = 11, - effect1 = 12, - effect2 = 13, - // undefined - gp1 = 16, - gp2 = 17, - gp3 = 18, - gp4 = 19, - damper_pedal = 64, - portamento_toggle = 65, - sostenuto = 66, - soft_pedal = 67, - legato_footswitch = 68, - hold2 = 69, - sc1_sound_variation = 70, - sc2_timbre = 71, - sc3_release_time = 72, - sc4_attack_time = 73, - sc5_brightness = 74, - sc6 = 75, - sc7 = 76, - sc8 = 77, - sc9 = 78, - sc10 = 79, - gp5 = 80, - gp6 = 81, - gp7 = 82, - gp8 = 83, - portamento_control = 84, - // undefined - effects_depth1 = 91, - effects_depth2 = 92, - effects_depth3 = 93, - effects_depth4 = 94, - effects_depth5 = 95, - data_increment = 96, - data_decrement = 97, - nonregistered_lsb = 98, - nonregistered_msb = 99, - registered_lsb = 100, - regustered_msg = 101, - // undefined 102-119 - // channel mode 120-127 -}; - -enum RegisteredParams { - pitch_bend_sensitivity = 0, - fine_tuning = 1, - coarse_tuning = 2, - tuning_program_select = 3, - tuning_bank_select = 4 -}; - - -MIDIMessage program_change(uint8_t channel, uint8_t program_number) { - if (channel > 0x0f || program_number > 119) { - while(1); - } - - return {.status = 0xc0 & channel, - .data1 = program_number, - .len = 2}; -} - -MIDIMessage aftertouch(uint8_t channel, uint8_t pressure_value) { - if (channel > 0x0f || pressure_value > 127) { - while(1); - } - - return {.status = 0xd0 & channel, - .data1 = pressure_value, - .len = 2}; -} - -MIDIMessage pitch_bend_change(uint8_t channel, uint16_t change) { - if (channel > 0x0f || pressure_value < 0x40 || pressure_value > 0x7f7f) { - while(1); - } - - return {.status = 0xe0 & channel, - .data1 = pressure_value, - .len = 2}; +#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 + } -// Channel mode messages. +#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_NUMBER || control_value > MAX_DATA_BYTE) { + while(true); + } +#endif + MIDI_send(CONTROL_CHANGE_MASK | channel, controller_number, control_value); -MIDIMessage all_sound_off(uint8_t channel) { - if (channel > 0x0f) { - while(1); +} + +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); - return {.status = 0xb0 & channel, - .data1 = 120, - .data2 = 0}; } -MIDIMessage reset_all_controllers(uint8_t channel) { - if (channel > 0x0f) { - while(1); +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); - return {.status = 0xb0 & channel, - .data1 = 121, - .data2 = 0}; } -MIDIMessage local_control(uint8_t channel, bool status) { - if (channel > 0x0f) { - while(1); +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 - if (status) { - return {.status = 0xb0 & channel, - .data1 = 122, - .data2 = 127}; - } else { - return {.status = 0xb0 & channel, - .data1 = 122, - .data2 = 0}; + MIDI_single_send(PITCH_BEND_CHANGE_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); + } -MIDIMessage all_notes_off(uint8_t channel) { - if (channel > 0x0f) { - while(1); +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); - return {.status = 0xb0 & channel, - .data1 = 123, - .data2 = 0}; } -MIDIMessage omni_toggle(uint8_t channel, bool status) { - if (channel > 0x0f) { - while(1); +void local_control_on(uint8_t channel) { +#ifdef DEBUG + if (channel > MAX_CHANNEL) { + while(true); } +#endif - if (status) { - return {.status = 0xb0 & channel, - .data1 = 125, - .data2 = 0}; - } else { - return {.status = 0xb0 & channel, - .data1 = 124, - .data2 = 0}; + 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 -MIDIMessage mono_on(uint8_t channel, uint8_t channel_count) { - if (channel > 0x0f || channel_count > 127) { - while(1); +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); - return {.status = 0xb0 & channel, - .data1 = 126, - .data2 = channel_count}; } -MIDIMessage poly_on(uint8_t channel) { - if (channel > 0x0f) { - while(1); +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); - return {.status = 0xb0 & channel, - .data1 = 127, - .data2 = 0}; } // System common messages. // Not implemented: -// - MTC Quarter Frame, -// - Song Position Pointer, -// - Song Select, -// - Tune Request. +// - 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); +} -const MIDIMessage eox = {.status = 0xf7, .len = 1}; // System real time messages. @@ -305,43 +423,404 @@ const MIDIMessage eox = {.status = 0xf7, .len = 1}; // - Start, // - Continue, // - Stop, -// - Active Sensing, +// - 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. -MIDIMessage rst = {.status = 0xff, .len = 1}; +// Only implement MIDI Tuning Standard universals and general information. -// System exclusive messages. -SysExMessage sysex(uint8_t sub_id, uint8_t* contents, size_t size) { - if (sub_id > 127) { - while(1); +#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, BULK_DUMP_REQUEST_SUBID, contents, 1); - return {.status = 0xf0, - .sub_id = sub_id, - .data = contents, - .size = size}; } -// Only implement MIDI Tuning Standard universals -// and general information. -SysExMessage universal_sysex(uint8_t sub_id1, uint8_t sub_id2, uint8_t* contents, size_t size, bool real_time) { - if (sub_id1 > 127 || sub_id2 > 127) { - while(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* new = malloc(contents, size + 2); // Might forget to free this... - new[0] = sub_id1; - new[1] = subid2; - memcpy(new + 2, contents, size); + 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 = NON_REAL_TIME_ID ^ device_id ^ TUNING_STANDARD_SUBID ^ BULK_DUMP_REPLY_SUBID ^ program; + for (int i = 0; i < TUNING_LENGTH; i++) { + checksum ^= tuning_data[i]; + } - if (real_time) { - return sysex(0x7e, new, size + 2); - } else { - return sysex(0x7e, new, size + 2); + contents[1 + TUNING_NAME_LENGTH + DEVICE_KEY_COUNT] = checksum; + + universal_nonrealtime(device_id, TUNING_STANDARD_SUBID, 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, 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; + uint8_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 = malloc(128), + .top = 0, + .size = 128}; + +static inline void push(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; } -SysExMessage bulk_tuning_dump(uint8_t) +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; +} + +static inline void second_byte() { + memory->first_data_byte = false; + memory->byte0 = byte; +} + +#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_data_byte) + second_byte(); + else { + pfns->note_off_handler(channel, memory->byte0, byte); + memory->first_data_byte = true; + } + break; + case NOTE_ON_MASK ... (NOTE_ON_MASK + MAX_CHANNEL): + if (memory->first_data_byte) + second_byte(); + else { + pfns->note_on_handler(channel, memory->byte0, byte); + memory->first_data_byte = true; + } + break; + case POLY_KEY_PRESSURE_MASK ... (POLY_KEY_PRESSURE_MASK + MAX_CHANNEL): + if (memory->first_data_byte) + second_byte(); + else { + pfns->poly_key_handler(channel, memory->byte0, byte); + memory->first_data_byte = true; + } + break; + case CONTROL_CHANGE_MASK ... (CONTROL_CHANGE_MASK + MAX_CHANNEL): + if (memory->first_data_byte): + second_byte(); + else { + switch (memory->byte0) { + case 0x00 ... MAX_CONTROLLER: + pfns->control_change_handler(channel); + 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); + 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_data_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_data_byte) + second_byte(); + else { + pfns->mtc_quarter_frame_handler(memory->byte0, byte); + memory->first_data_byte = true; + } + break; + case SONG_POSITION_POINTER_STATUS: + if (memory->first_data_byte) + second_byte(); + else { + pfns->song_position_pointer_handler(memory->byte0, byte); + memory->first_data_byte = true; + } + break; + case SONG_SELECT_STATUS: + song_select_handler(byte); + break; + + case SYSEX_STATUS: + if (memory->first_data_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 = NON_REAL_TIME_ID ^ 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], 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 + 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. + } +} diff --git a/controller/src/main.c b/controller/src/main.c index 800f691..1650499 100644 --- a/controller/src/main.c +++ b/controller/src/main.c @@ -1,4 +1,4 @@ -#include "uart.h" +#include "midi.h" int main() { -- cgit v1.2.3