diff --git a/CMakeLists.txt b/CMakeLists.txt index 2517f490010..0834ae1ec34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,7 @@ set(LIBRARY_SRCS libraries/Update/src/Updater.cpp libraries/Update/src/HttpsOTAUpdate.cpp libraries/USB/src/USBHID.cpp + libraries/USB/src/USBMIDI.cpp libraries/USB/src/USBHIDMouse.cpp libraries/USB/src/USBHIDKeyboard.cpp libraries/USB/src/USBHIDGamepad.cpp diff --git a/libraries/USB/examples/MIDI/MidiController/.skip.esp32 b/libraries/USB/examples/MIDI/MidiController/.skip.esp32 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/MidiController/.skip.esp32c3 b/libraries/USB/examples/MIDI/MidiController/.skip.esp32c3 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/MidiController/.skip.esp32c6 b/libraries/USB/examples/MIDI/MidiController/.skip.esp32c6 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/MidiController/.skip.esp32h2 b/libraries/USB/examples/MIDI/MidiController/.skip.esp32h2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/MidiController/MidiController.ino b/libraries/USB/examples/MIDI/MidiController/MidiController.ino new file mode 100644 index 00000000000..a140f3831a7 --- /dev/null +++ b/libraries/USB/examples/MIDI/MidiController/MidiController.ino @@ -0,0 +1,118 @@ +/* +This is an example of a Simple MIDI Controller using an ESP32 with a native USB support stack (S2, S3, +etc.). + +For a hookup guide and more information on reading the ADC, please see: +https://randomnerdtutorials.com/esp32-adc-analog-read-arduino-ide/ (Note: This sketch uses GPIO05) + +For best results, it is recommended to add an extra offset resistor between VCC and the potentiometer. +(For a standard 10kOhm potentiometer, 3kOhm - 4kOhm will do.) + +View this sketch in action on YouTube: https://youtu.be/Y9TLXs_3w1M +*/ +#if ARDUINO_USB_MODE +#warning This sketch should be used when USB is in OTG mode +void setup() {} +void loop() {} +#else + +#include + +#include "USB.h" +#include "USBMIDI.h" +USBMIDI MIDI; + +#define MIDI_NOTE_C4 64 + +#define MIDI_CC_CUTOFF 74 + +///// ADC & Controller Input Handling ///// + +#define CONTROLLER_PIN 5 + +// ESP32 ADC needs a ton of smoothing +#define SMOOTHING_VALUE 1000 +static double controllerInputValue = 0; + +void updateControllerInputValue() { + controllerInputValue = + (controllerInputValue * (SMOOTHING_VALUE - 1) + analogRead(CONTROLLER_PIN)) / SMOOTHING_VALUE; +} + +void primeControllerInputValue() { + for (int i = 0; i < SMOOTHING_VALUE; i++) { + updateControllerInputValue(); + } +} + +uint16_t readControllerValue() { + // Lower ADC input amplitude to get a stable value + return round(controllerInputValue / 12); +} + +///// Button Handling ///// + +#define BUTTON_PIN 0 + +// Simple button state transition function with debounce +// (See also: https://tinyurl.com/simple-debounce) +#define PRESSED 0xff00 +#define RELEASED 0xfe1f +uint16_t getButtonEvent() { + static uint16_t state = 0; + state = (state << 1) | digitalRead(BUTTON_PIN) | 0xfe00; + return state; +} + +///// Arduino Hooks ///// + +void setup() { + Serial.begin(115200); + MIDI.begin(); + USB.begin(); + + primeControllerInputValue(); +} + +void loop() { + uint16_t newControllerValue = readControllerValue(); + static uint16_t lastControllerValue = 0; + + // Auto-calibrate the controller range + static uint16_t maxControllerValue = 0; + static uint16_t minControllerValue = 0xFFFF; + + if (newControllerValue < minControllerValue) { + minControllerValue = newControllerValue; + } + if (newControllerValue > maxControllerValue) { + maxControllerValue = newControllerValue; + } + + // Send update if the controller value has changed + if (lastControllerValue != newControllerValue) { + lastControllerValue = newControllerValue; + + // Can't map if the range is zero + if (minControllerValue != maxControllerValue) { + MIDI.controlChange(MIDI_CC_CUTOFF, + map(newControllerValue, minControllerValue, maxControllerValue, 0, 127)); + } + } + + updateControllerInputValue(); + + // Hook Button0 to a MIDI note so that we can observe + // the CC effect without the need for a MIDI keyboard. + switch (getButtonEvent()) { + case PRESSED: + MIDI.noteOn(MIDI_NOTE_C4, 64); + break; + case RELEASED: + MIDI.noteOff(MIDI_NOTE_C4, 0); + break; + default: + break; + } +} +#endif /* ARDUINO_USB_MODE */ diff --git a/libraries/USB/examples/MIDI/MidiInterface/.skip.esp32 b/libraries/USB/examples/MIDI/MidiInterface/.skip.esp32 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/MidiInterface/.skip.esp32c3 b/libraries/USB/examples/MIDI/MidiInterface/.skip.esp32c3 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/MidiInterface/.skip.esp32c6 b/libraries/USB/examples/MIDI/MidiInterface/.skip.esp32c6 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/MidiInterface/.skip.esp32h2 b/libraries/USB/examples/MIDI/MidiInterface/.skip.esp32h2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/MidiInterface/MidiInterface.ino b/libraries/USB/examples/MIDI/MidiInterface/MidiInterface.ino new file mode 100644 index 00000000000..6fb99d8ab95 --- /dev/null +++ b/libraries/USB/examples/MIDI/MidiInterface/MidiInterface.ino @@ -0,0 +1,70 @@ +/* +This is an example of using an ESP32 with a native USB support stack (S2, S3, etc.) as a Serial MIDI to +USB MIDI bridge (AKA "A MIDI Interface"). + +View this sketch in action on YouTube: https://youtu.be/BXG5i55I9s0 + +Receiving and decoding USB MIDI 1.0 packets is a more advanced topic than sending MIDI over USB, +please refer to the other examples in this library for a more basic example of sending MIDI over USB. + +This example should still be self explanatory, please refer to the USB MIDI 1.0 specification (the spec) +for a more in-depth explanation of the packet format. + +For the spec please visit: https://www.midi.org/specifications-old/item/usb-midi-1-0-specification + +Note: Because ESP32 works at VCC=3.3v normal schematics for Serial MIDI connections will not suffice, + Please refer to the Updated MIDI 1.1 Electrical Specification [1] for information on how to hookup + Serial MIDI for 3.3v devices. + +[1] - https://www.midi.org/specifications/midi-transports-specifications/5-pin-din-electrical-specs +*/ +#if ARDUINO_USB_MODE +#warning This sketch should be used when USB is in OTG mode +void setup() {} +void loop() {} +#else + +#include "USB.h" +#include "USBMIDI.h" +USBMIDI MIDI; + +#define MIDI_RX 39 +#define MIDI_TX 40 + +void setup() { + // USBCDC Serial + Serial.begin(115200); + + // HW UART Serial + Serial1.begin(31250, SERIAL_8N1, MIDI_RX, MIDI_TX); + + MIDI.begin(); + USB.begin(); +} + +void loop() { + // MIDI Serial 1.0 to USB MIDI 1.0 + if (Serial1.available()) { + byte data = Serial1.read(); + MIDI.write(data); + } + + // USB MIDI 1.0 to MIDI Serial 1.0 + midiEventPacket_t midi_packet_in = {0}; + // See Chapter 4: USB-MIDI Event Packets (page 16) of the spec. + int8_t cin_to_midix_size[16] = {-1, -1, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1}; + + if (MIDI.readPacket(&midi_packet_in)) { + midi_code_index_number_t code_index_num = MIDI_EP_HEADER_CIN_GET(midi_packet_in.header); + int8_t midix_size = cin_to_midix_size[code_index_num]; + + // We skip Misc and Cable Events for simplicity + if (code_index_num >= 0x2) { + for (int i = 0; i < midix_size; i++) { + Serial1.write(((uint8_t *)&midi_packet_in)[i + 1]); + } + Serial1.flush(); + } + } +} +#endif /* ARDUINO_USB_MODE */ diff --git a/libraries/USB/examples/MIDI/MidiMusicBox/.skip.esp32 b/libraries/USB/examples/MIDI/MidiMusicBox/.skip.esp32 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/MidiMusicBox/.skip.esp32c3 b/libraries/USB/examples/MIDI/MidiMusicBox/.skip.esp32c3 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/MidiMusicBox/.skip.esp32c6 b/libraries/USB/examples/MIDI/MidiMusicBox/.skip.esp32c6 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/MidiMusicBox/.skip.esp32h2 b/libraries/USB/examples/MIDI/MidiMusicBox/.skip.esp32h2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/MidiMusicBox/MidiMusicBox.ino b/libraries/USB/examples/MIDI/MidiMusicBox/MidiMusicBox.ino new file mode 100644 index 00000000000..1d540324943 --- /dev/null +++ b/libraries/USB/examples/MIDI/MidiMusicBox/MidiMusicBox.ino @@ -0,0 +1,59 @@ +/* +This is an example of a MIDI Music Box using an ESP32 with a native USB support stack (S2, S3, etc.). + +Every time the button on the ESP32 board (attached to pin 0) is pressed the next note of a melody is +played. It is up to the user to get the timing of the button presses right. + +One simple way of running this sketch is to download the Pianoteq evaluation version, because upon +application start it automatically listens to the first MIDI Input on Channel 1, which is the case, +if the ESP32 is the only MIDI device attached. + +View this sketch in action on YouTube: https://youtu.be/JFrc-wSmcus +*/ +#if ARDUINO_USB_MODE +#warning This sketch should be used when USB is in OTG mode +void setup() {} +void loop() {} +#else + +#include "USB.h" +#include "USBMIDI.h" +USBMIDI MIDI; + +#define END_OF_SONG 255 +uint8_t notes[] = {END_OF_SONG, 71, 76, 79, 78, 76, 83, 81, 78, 76, 79, 78, 75, 77, 71}; +uint8_t noteIndex = 0; // From 0 to sizeof(notes) +#define SONG_LENGTH (sizeof(notes) - 1) // END_OF_SONG does not attribute to the length. + +#define BUTTON_PIN 0 + +// Simple button press check with debounce +// (See also: https://tinyurl.com/simple-debounce) +bool isButtonPressed() { + static uint16_t state = 0; + state = (state << 1) | digitalRead(BUTTON_PIN) | 0xfe00; + return (state == 0xff00); +} + +void setup() { + Serial.begin(115200); + // Make the BUTTON_PIN an input: + pinMode(BUTTON_PIN, INPUT_PULLUP); + + MIDI.begin(); + USB.begin(); +} + +void loop() { + if (isButtonPressed()) { + // Stop current note + MIDI.noteOff(notes[noteIndex]); + + // Play next note + noteIndex = noteIndex < SONG_LENGTH ? noteIndex + 1 : 0; + if (notes[noteIndex] != END_OF_SONG) { + MIDI.noteOn(notes[noteIndex], 64); + } + } +} +#endif /* ARDUINO_USB_MODE */ diff --git a/libraries/USB/examples/MIDI/ReceiveMidi/.skip.esp32 b/libraries/USB/examples/MIDI/ReceiveMidi/.skip.esp32 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/ReceiveMidi/.skip.esp32c3 b/libraries/USB/examples/MIDI/ReceiveMidi/.skip.esp32c3 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/ReceiveMidi/.skip.esp32c6 b/libraries/USB/examples/MIDI/ReceiveMidi/.skip.esp32c6 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/ReceiveMidi/.skip.esp32h2 b/libraries/USB/examples/MIDI/ReceiveMidi/.skip.esp32h2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/USB/examples/MIDI/ReceiveMidi/ReceiveMidi.ino b/libraries/USB/examples/MIDI/ReceiveMidi/ReceiveMidi.ino new file mode 100644 index 00000000000..4f1bf4273e2 --- /dev/null +++ b/libraries/USB/examples/MIDI/ReceiveMidi/ReceiveMidi.ino @@ -0,0 +1,104 @@ +/* +This is an example of receiving USB MIDI 1.0 messages using an ESP32 with a native USB support stack +(S2, S3, etc.). + +Receiving and decoding USB MIDI 1.0 packets is a more advanced topic than sending MIDI over USB, +please refer to the other examples in this library for a more basic example of sending MIDI over USB. + +This example should still be self explanatory, please refer to the USB MIDI 1.0 specification (the spec) +for a more in-depth explanation of the packet format. + +For the spec please visit: https://www.midi.org/specifications-old/item/usb-midi-1-0-specification +*/ +#if ARDUINO_USB_MODE +#warning This sketch should be used when USB is in OTG mode +void setup() {} +void loop() {} +#else + +#include "USB.h" +#include "USBMIDI.h" +USBMIDI MIDI; + +void setup() { + Serial.begin(115200); + + MIDI.begin(); + USB.begin(); +} + +void loop() { + midiEventPacket_t midi_packet_in = {0}; + + if (MIDI.readPacket(&midi_packet_in)) { + printDetails(midi_packet_in); + } +} + +void printDetails(midiEventPacket_t &midi_packet_in) { + // See Chapter 4: USB-MIDI Event Packets (page 16) of the spec. + uint8_t cable_num = MIDI_EP_HEADER_CN_GET(midi_packet_in.header); + midi_code_index_number_t code_index_num = MIDI_EP_HEADER_CIN_GET(midi_packet_in.header); + + Serial.println("Received a USB MIDI packet:"); + Serial.println(".----.-----.--------.--------.--------."); + Serial.println("| CN | CIN | STATUS | DATA 0 | DATA 1 |"); + Serial.println("+----+-----+--------+--------+--------+"); + Serial.printf("| %d | %X | %X | %X | %X |\n", cable_num, code_index_num, + midi_packet_in.byte1, midi_packet_in.byte2, midi_packet_in.byte3); + Serial.println("'----'-----'--------.--------'--------'\n"); + Serial.print("Description: "); + + switch (code_index_num) { + case MIDI_CIN_MISC: + Serial.println("This a Miscellaneous event"); + break; + case MIDI_CIN_CABLE_EVENT: + Serial.println("This a Cable event"); + break; + case MIDI_CIN_SYSCOM_2BYTE: // 2 byte system common message e.g MTC, SongSelect + case MIDI_CIN_SYSCOM_3BYTE: // 3 byte system common message e.g SPP + Serial.println("This a System Common (SysCom) event"); + break; + case MIDI_CIN_SYSEX_START: // SysEx starts or continue + case MIDI_CIN_SYSEX_END_1BYTE: // SysEx ends with 1 data, or 1 byte system common message + case MIDI_CIN_SYSEX_END_2BYTE: // SysEx ends with 2 data + case MIDI_CIN_SYSEX_END_3BYTE: // SysEx ends with 3 data + Serial.println("This a system exclusive (SysEx) event"); + break; + case MIDI_CIN_NOTE_ON: + Serial.printf("This a Note-On event of Note %d with a Velocity of %d\n", + midi_packet_in.byte2, midi_packet_in.byte3); + break; + case MIDI_CIN_NOTE_OFF: + Serial.printf("This a Note-Off event of Note %d with a Velocity of %d\n", + midi_packet_in.byte2, midi_packet_in.byte3); + break; + case MIDI_CIN_POLY_KEYPRESS: + Serial.printf("This a Poly Aftertouch event for Note %d and Value %d\n", + midi_packet_in.byte2, midi_packet_in.byte3); + break; + case MIDI_CIN_CONTROL_CHANGE: + Serial.printf("This a Control Change/Continuous Controller (CC) event of Controller %d " + "with a Value of %d\n", + midi_packet_in.byte2, midi_packet_in.byte3); + break; + case MIDI_CIN_PROGRAM_CHANGE: + Serial.printf("This a Program Change event with a Value of %d\n", midi_packet_in.byte2); + break; + case MIDI_CIN_CHANNEL_PRESSURE: + Serial.printf("This a Channel Pressure event with a Value of %d\n", midi_packet_in.byte2); + break; + case MIDI_CIN_PITCH_BEND_CHANGE: + Serial.printf("This a Pitch Bend Change event with a Value of %d\n", + ((uint16_t)midi_packet_in.byte2) << 7 | midi_packet_in.byte3); + break; + case MIDI_CIN_1BYTE_DATA: + Serial.printf("This an embedded Serial MIDI event byte with Value %X\n", + midi_packet_in.byte1); + break; + } + + Serial.println(); +} +#endif /* ARDUINO_USB_MODE */ diff --git a/libraries/USB/src/USBMIDI.cpp b/libraries/USB/src/USBMIDI.cpp new file mode 100644 index 00000000000..b29b6fefe5d --- /dev/null +++ b/libraries/USB/src/USBMIDI.cpp @@ -0,0 +1,142 @@ +#include "USBMIDI.h" +#if SOC_USB_OTG_SUPPORTED + +#if CONFIG_TINYUSB_MIDI_ENABLED + +#include "Arduino.h" +#include "esp32-hal-tinyusb.h" + +// Default Cable Number (for simplified APIs that do not expose this) +#define DEFAULT_CN 0 + +static bool tinyusb_midi_descriptor_loaded = false; +static bool tinyusb_midi_interface_enabled = false; + +extern "C" uint16_t tusb_midi_load_descriptor(uint8_t *dst, uint8_t *itf) { + if (tinyusb_midi_descriptor_loaded) { + return 0; + } + tinyusb_midi_descriptor_loaded = true; + + uint8_t str_index = tinyusb_add_string_descriptor("TinyUSB MIDI"); + uint8_t ep_in = tinyusb_get_free_in_endpoint(); + TU_VERIFY(ep_in != 0); + uint8_t ep_out = tinyusb_get_free_out_endpoint(); + TU_VERIFY(ep_out != 0); + uint8_t descriptor[TUD_MIDI_DESC_LEN] = { + TUD_MIDI_DESCRIPTOR(*itf, str_index, ep_out, (uint8_t)(0x80 | ep_in), 64), + }; + *itf += 2; + memcpy(dst, descriptor, TUD_MIDI_DESC_LEN); + + return TUD_MIDI_DESC_LEN; +} + +USBMIDI::USBMIDI() { + if (!tinyusb_midi_interface_enabled) { + tinyusb_midi_interface_enabled = true; + tinyusb_enable_interface(USB_INTERFACE_MIDI, TUD_MIDI_DESC_LEN, tusb_midi_load_descriptor); + } else { + log_e("USBMIDI: Multiple instances of USBMIDI not supported!"); + } +} + +void USBMIDI::begin() {} +void USBMIDI::end() {} + +// uint compatible version of constrain +#define uconstrain(amt, low, high) ((amt) <= (low) ? (low) : ((amt) > (high) ? (high) : (amt))) + +#define STATUS(CIN, CHANNEL) \ + static_cast(((CIN & 0x7F) << 4) | (uconstrain(CHANNEL - 1, 0, 15) & 0x7F)) + +// Note: All the user-level API calls do extensive input constraining to prevent easy to make mistakes. +// (You can thank me later.) +#define _(x) static_cast(uconstrain(x, 0, 127)) + +// Note On +void USBMIDI::noteOn(uint8_t note, uint8_t velocity, uint8_t channel) { + midiEventPacket_t event = {MIDI_CIN_NOTE_ON, STATUS(MIDI_CIN_NOTE_ON, channel), _(note), + _(velocity)}; + writePacket(&event); +} + +// Note Off +void USBMIDI::noteOff(uint8_t note, uint8_t velocity, uint8_t channel) { + midiEventPacket_t event = {MIDI_CIN_NOTE_OFF, STATUS(MIDI_CIN_NOTE_OFF, channel), _(note), + _(velocity)}; + writePacket(&event); +} + +// Program Change +void USBMIDI::programChange(uint8_t program, uint8_t channel) { + midiEventPacket_t event = {MIDI_CIN_PROGRAM_CHANGE, STATUS(MIDI_CIN_PROGRAM_CHANGE, channel), + _(program), 0x0}; + writePacket(&event); +} + +// Control Change (Continuous Controller) +void USBMIDI::controlChange(uint8_t control, uint8_t value, uint8_t channel) { + midiEventPacket_t event = {MIDI_CIN_CONTROL_CHANGE, STATUS(MIDI_CIN_CONTROL_CHANGE, channel), + _(control), _(value)}; + writePacket(&event); +} + +// Polyphonic Key Pressure (Aftertouch) +void USBMIDI::polyPressure(uint8_t note, uint8_t pressure, uint8_t channel) { + midiEventPacket_t event = {MIDI_CIN_POLY_KEYPRESS, STATUS(MIDI_CIN_POLY_KEYPRESS, channel), _(note), + _(pressure)}; + writePacket(&event); +} + +// Channel Pressure (Aftertouch) +void USBMIDI::channelPressure(uint8_t pressure, uint8_t channel) { + midiEventPacket_t event = {MIDI_CIN_CHANNEL_PRESSURE, STATUS(MIDI_CIN_CHANNEL_PRESSURE, channel), + _(pressure), 0x0}; + writePacket(&event); +} + +// Pitch Bend Change [-8192,0,8191] +void USBMIDI::pitchBend(int16_t value, uint8_t channel) { + uint16_t pitchBendValue = constrain(value, -8192, 8191) + 8192; + pitchBend(pitchBendValue); +} + +// Pitch Bend Change [0,8192,16383] +void USBMIDI::pitchBend(uint16_t value, uint8_t channel) { + uint16_t pitchBendValue = static_cast(uconstrain(value, 0, 16383)); + // Split the 14-bit integer into two 7-bit values + uint8_t lsb = pitchBendValue & 0x7F; // Lower 7 bits + uint8_t msb = (pitchBendValue >> 7) & 0x7F; // Upper 7 bits + + midiEventPacket_t event = {MIDI_CIN_PITCH_BEND_CHANGE, STATUS(MIDI_CIN_PITCH_BEND_CHANGE, channel), + lsb, msb}; + writePacket(&event); +} + +// Pitch Bend Change [-1.0,0,1.0] +void USBMIDI::pitchBend(double value, uint8_t channel) { + // Multiply by 8191 and round to nearest integer + int16_t pitchBendValue = static_cast(round(constrain(value, -1.0, 1.0) * 8191.0)); + + pitchBend(pitchBendValue, channel); +} + +bool USBMIDI::readPacket(midiEventPacket_t *packet) { + return tud_midi_packet_read((uint8_t *)packet); +} + +bool USBMIDI::writePacket(midiEventPacket_t *packet) { + return tud_midi_packet_write((uint8_t *)packet); +} + +size_t USBMIDI::write(uint8_t c) { + // MIDI_CIN_1BYTE_DATA => Verbatim MIDI byte-stream copy + // (See also Table 4-1 of USB MIDI spec 1.0) + midiEventPacket_t packet = {DEFAULT_CN | MIDI_CIN_1BYTE_DATA, c, 0, 0}; + + return tud_midi_packet_write((uint8_t *)&packet); +} + +#endif /* CONFIG_TINYUSB_MIDI_ENABLED */ +#endif /* SOC_USB_OTG_SUPPORTED */ diff --git a/libraries/USB/src/USBMIDI.h b/libraries/USB/src/USBMIDI.h new file mode 100644 index 00000000000..44253fda51d --- /dev/null +++ b/libraries/USB/src/USBMIDI.h @@ -0,0 +1,61 @@ +#include "soc/soc_caps.h" +#if SOC_USB_OTG_SUPPORTED +#include "esp32-hal-tinyusb.h" +#include "sdkconfig.h" + +#if CONFIG_TINYUSB_MIDI_ENABLED + +#pragma once + +#define MIDI_EP_HEADER_CN_GET(x) (x >> 4) +#define MIDI_EP_HEADER_CIN_GET(x) ((midi_code_index_number_t)((x)&0xF)) + +typedef struct { + uint8_t header; + uint8_t byte1; + uint8_t byte2; + uint8_t byte3; +} midiEventPacket_t; + +class USBMIDI { +public: + USBMIDI(void); + void begin(void); + void end(void); + + /* User-level API */ + + // Note On + void noteOn(uint8_t note, uint8_t velocity = 0, uint8_t channel = 1); + // Note Off + void noteOff(uint8_t note, uint8_t velocity = 0, uint8_t channel = 1); + // Program Change + void programChange(uint8_t inProgramNumber, uint8_t channel = 1); + // Control Change (Continuous Controller) + void controlChange(uint8_t inControlNumber, uint8_t inControlValue = 0, uint8_t channel = 1); + // Polyphonic Key Pressure (Aftertouch) + void polyPressure(uint8_t note, uint8_t pressure, uint8_t channel = 1); + // Channel Pressure (Aftertouch) + void channelPressure(uint8_t pressure, uint8_t channel = 1); + // Pitch Bend Change [-8192,0,8191] + void pitchBend(int16_t pitchBendValue, uint8_t channel = 1); + // Pitch Bend Change [0,8192,16383] + void pitchBend(uint16_t pitchBendValue, uint8_t channel = 1); + // Pitch Bend Change [-1.0,0,1.0] + void pitchBend(double pitchBendValue, uint8_t channel = 1); + + /* USB MIDI 1.0 interface */ + + // Attempt to read a USB MIDI packet from the USB Bus + bool readPacket(midiEventPacket_t *packet); + // Attempt to write a USB MIDI packet to the USB Bus + bool writePacket(midiEventPacket_t *packet); + + /* Serial MIDI 1.0 interface */ + + // Write a Serial MIDI byte (status or data) to the USB Bus + size_t write(uint8_t c); +}; + +#endif /* CONFIG_TINYUSB_MIDI_ENABLED */ +#endif /* SOC_USB_OTG_SUPPORTED */