summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDuncan Wilkie <antigravityd@gmail.com>2023-07-18 10:34:34 -0500
committerDuncan Wilkie <antigravityd@gmail.com>2023-07-18 10:34:34 -0500
commit18a2be0c24b68dbfb4667e08ab6e8912adf52e7b (patch)
tree2b600e0e45a313c6477e09400fc443ba9ea832cd
parent1fd366764d17874b8211ef63d41313995833e36d (diff)
Refactored parsing, added header, all around based mode
-rw-r--r--controller/Makefile2
-rw-r--r--controller/inc/midi.h207
-rw-r--r--controller/libs/midi.c971
-rw-r--r--controller/src/main.c2
-rw-r--r--specs/periph_driver_lib.pdfbin0 -> 4830798 bytes
5 files changed, 934 insertions, 248 deletions
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 <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+// 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 <stdint.h>
-#include <stdbool.h>
+#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() {
diff --git a/specs/periph_driver_lib.pdf b/specs/periph_driver_lib.pdf
new file mode 100644
index 0000000..711ab77
--- /dev/null
+++ b/specs/periph_driver_lib.pdf
Binary files differ