|
| 1 | +/* |
| 2 | + * SPDX-FileCopyrightText: 2019 Ha Thach (tinyusb.org) |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: MIT |
| 5 | + * |
| 6 | + * SPDX-FileContributor: 2022-2023 Espressif Systems (Shanghai) CO LTD |
| 7 | + * |
| 8 | + * SPDX-FileContributor: 2023 [email protected] |
| 9 | + */ |
| 10 | + |
| 11 | +// This program is based on an ESP-IDF USB MIDI TinyUSB example with minimal |
| 12 | +// changes so it works on Arduino-esp32. |
| 13 | + |
| 14 | +#if ARDUINO_USB_MODE |
| 15 | +#warning This sketch must be used when USB is in OTG mode |
| 16 | +void setup() {} |
| 17 | +void loop() {} |
| 18 | +#else |
| 19 | +#include "USB.h" |
| 20 | + |
| 21 | +#include "esp32-hal-tinyusb.h" |
| 22 | + |
| 23 | +static const char *TAG = "usbdmidi"; |
| 24 | + |
| 25 | +/** TinyUSB descriptors **/ |
| 26 | + |
| 27 | +extern "C" uint16_t tusb_midi_load_descriptor(uint8_t *dst, uint8_t *itf) { |
| 28 | + uint8_t str_index = tinyusb_add_string_descriptor("TinyUSB MIDI"); |
| 29 | + uint8_t ep_num = tinyusb_get_free_duplex_endpoint(); |
| 30 | + TU_VERIFY(ep_num != 0); |
| 31 | + uint8_t descriptor[TUD_MIDI_DESC_LEN] = { |
| 32 | + // Interface number, string index, EP Out & EP In address, EP size |
| 33 | + TUD_MIDI_DESCRIPTOR(*itf, str_index, ep_num, (uint8_t)(0x80 | ep_num), |
| 34 | + 64)}; |
| 35 | + *itf += 1; |
| 36 | + memcpy(dst, descriptor, TUD_MIDI_DESC_LEN); |
| 37 | + return TUD_MIDI_DESC_LEN; |
| 38 | +} |
| 39 | + |
| 40 | +// From usb.org MIDI 1.0 specification. This 4 byte structure is the unit |
| 41 | +// of transfer for MIDI data over USB. |
| 42 | +typedef struct __attribute__((__packed__)) { |
| 43 | + uint8_t code_index_number : 4; |
| 44 | + uint8_t cable_number : 4; |
| 45 | + uint8_t MIDI_0; |
| 46 | + uint8_t MIDI_1; |
| 47 | + uint8_t MIDI_2; |
| 48 | +} USB_MIDI_t; |
| 49 | + |
| 50 | +static void midi_task_read_example(void *arg) { |
| 51 | + // The MIDI interface always creates input and output port/jack descriptors |
| 52 | + // regardless of these being used or not. Therefore incoming traffic should be |
| 53 | + // read (possibly just discarded) to avoid the sender blocking in IO |
| 54 | + uint8_t packet[4]; |
| 55 | + bool read = false; |
| 56 | + for (;;) { |
| 57 | + delay(1); |
| 58 | + while (tud_midi_available()) { |
| 59 | + read = tud_midi_packet_read(packet); |
| 60 | + if (read) { |
| 61 | + ESP_LOGI(TAG, |
| 62 | + "Read - Time (ms since boot): %lld, Data: %02hhX %02hhX " |
| 63 | + "%02hhX %02hhX", |
| 64 | + esp_timer_get_time(), packet[0], packet[1], packet[2], |
| 65 | + packet[3]); |
| 66 | + USB_MIDI_t *m = (USB_MIDI_t *)packet; |
| 67 | + Serial.printf( |
| 68 | + "%lld: Cable: %d Code: %01hhX, Data: %02hhX %02hhX %02hhX\n", |
| 69 | + esp_timer_get_time(), m->cable_number, m->code_index_number, |
| 70 | + m->MIDI_0, m->MIDI_1, m->MIDI_2); |
| 71 | + } |
| 72 | + } |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +// Basic MIDI Messages |
| 77 | +#define NOTE_OFF 0x80 |
| 78 | +#define NOTE_ON 0x90 |
| 79 | + |
| 80 | +static void periodic_midi_write_example_cb(void *arg) { |
| 81 | + // Example melody stored as an array of note values |
| 82 | + uint8_t const note_sequence[] = { |
| 83 | + 74, 78, 81, 86, 90, 93, 98, 102, 57, 61, 66, 69, 73, 78, 81, 85, |
| 84 | + 88, 92, 97, 100, 97, 92, 88, 85, 81, 78, 74, 69, 66, 62, 57, 62, |
| 85 | + 66, 69, 74, 78, 81, 86, 90, 93, 97, 102, 97, 93, 90, 85, 81, 78, |
| 86 | + 73, 68, 64, 61, 56, 61, 64, 68, 74, 78, 81, 86, 90, 93, 98, 102}; |
| 87 | + |
| 88 | + static uint8_t const cable_num = 0; // MIDI jack associated with USB endpoint |
| 89 | + static uint8_t const channel = 0; // 0 for channel 1 |
| 90 | + static uint32_t note_pos = 0; |
| 91 | + |
| 92 | + // Previous positions in the note sequence. |
| 93 | + int previous = note_pos - 1; |
| 94 | + |
| 95 | + // If we currently are at position 0, set the |
| 96 | + // previous position to the last note in the sequence. |
| 97 | + if (previous < 0) { |
| 98 | + previous = sizeof(note_sequence) - 1; |
| 99 | + } |
| 100 | + |
| 101 | + // Send Note On for current position at full velocity (127) on channel 1. |
| 102 | + ESP_LOGI(TAG, "Writing MIDI data %d", note_sequence[note_pos]); |
| 103 | + |
| 104 | + if (tud_midi_mounted()) { |
| 105 | + uint8_t note_on[3] = {NOTE_ON | channel, note_sequence[note_pos], 127}; |
| 106 | + tud_midi_stream_write(cable_num, note_on, 3); |
| 107 | + |
| 108 | + // Send Note Off for previous note. |
| 109 | + uint8_t note_off[3] = {NOTE_OFF | channel, note_sequence[previous], 0}; |
| 110 | + tud_midi_stream_write(cable_num, note_off, 3); |
| 111 | + } |
| 112 | + |
| 113 | + // Increment position |
| 114 | + note_pos++; |
| 115 | + |
| 116 | + // If we are at the end of the sequence, start over. |
| 117 | + if (note_pos >= sizeof(note_sequence)) { |
| 118 | + note_pos = 0; |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +void app_main(void) { |
| 123 | + // Periodically send MIDI packets |
| 124 | + int const tempo = 286; |
| 125 | + const esp_timer_create_args_t periodic_midi_args = { |
| 126 | + .callback = &periodic_midi_write_example_cb, |
| 127 | + /* name is optional, but may help identify the timer when debugging */ |
| 128 | + .name = "periodic_midi"}; |
| 129 | + |
| 130 | + ESP_LOGI(TAG, "MIDI write task init"); |
| 131 | + esp_timer_handle_t periodic_midi_timer; |
| 132 | + ESP_ERROR_CHECK(esp_timer_create(&periodic_midi_args, &periodic_midi_timer)); |
| 133 | + ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_midi_timer, tempo * 1000)); |
| 134 | + |
| 135 | + // Read recieved MIDI packets |
| 136 | + ESP_LOGI(TAG, "MIDI read task init"); |
| 137 | + xTaskCreate(midi_task_read_example, "midi_task_read_example", 2 * 1024, NULL, |
| 138 | + 5, NULL); |
| 139 | +} |
| 140 | + |
| 141 | +static void usbEventCallback(void *arg, esp_event_base_t event_base, |
| 142 | + int32_t event_id, void *event_data) { |
| 143 | + if (event_base == ARDUINO_USB_EVENTS) { |
| 144 | + arduino_usb_event_data_t *data = (arduino_usb_event_data_t *)event_data; |
| 145 | + switch (event_id) { |
| 146 | + case ARDUINO_USB_STARTED_EVENT: |
| 147 | + Serial.println("USB PLUGGED"); |
| 148 | + break; |
| 149 | + case ARDUINO_USB_STOPPED_EVENT: |
| 150 | + Serial.println("USB UNPLUGGED"); |
| 151 | + break; |
| 152 | + case ARDUINO_USB_SUSPEND_EVENT: |
| 153 | + Serial.printf("USB SUSPENDED: remote_wakeup_en: %u\n", |
| 154 | + data->suspend.remote_wakeup_en); |
| 155 | + break; |
| 156 | + case ARDUINO_USB_RESUME_EVENT: |
| 157 | + Serial.println("USB RESUMED"); |
| 158 | + break; |
| 159 | + |
| 160 | + default: |
| 161 | + break; |
| 162 | + } |
| 163 | + } |
| 164 | +} |
| 165 | + |
| 166 | +void setup() { |
| 167 | + Serial.begin(115200); |
| 168 | + |
| 169 | + USB.onEvent(usbEventCallback); |
| 170 | + tinyusb_enable_interface(USB_INTERFACE_MIDI, TUD_MIDI_DESC_LEN, |
| 171 | + tusb_midi_load_descriptor); |
| 172 | + USB.begin(); |
| 173 | + while (!Serial && millis() < 5000) |
| 174 | + delay(10); |
| 175 | + app_main(); |
| 176 | +} |
| 177 | + |
| 178 | +void loop() {} |
| 179 | +#endif /* ARDUINO_USB_MODE */ |
0 commit comments