Unverified Commit 1a7a8934 authored by Marcel Toele's avatar Marcel Toele Committed by GitHub

Add USB MIDI support to libraries/USB (#8166)

* Added USBMIDI support to libraries/USB

* Added MIDI examples to libraries/USB

* Added missing newline at end of file to MidiController.ino

* Added USBMIDI.cpp to CMake file

* Fix narrowing conversion warning in USBMIDI.cpp

* Fix incomplete initializers warning in USBMIDI.cpp

* Apply suggestions from code review
Co-authored-by: default avatarLucas Saavedra Vaz <lucassvaz@yahoo.com.br>

* add skip files for C6+H2

* remove already patched workaroud for bug

* move #define to top of file

---------
Co-authored-by: default avatarMe No Dev <me-no-dev@users.noreply.github.com>
Co-authored-by: default avatarLucas Saavedra Vaz <lucas.vaz@espressif.com>
Co-authored-by: default avatarJan Procházka <90197375+P-R-O-C-H-Y@users.noreply.github.com>
Co-authored-by: default avatarLucas Saavedra Vaz <lucassvaz@yahoo.com.br>
parent 374280cc
......@@ -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
......
/*
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 <math.h>
#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 */
/*
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 */
/*
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 */
/*
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 */
#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<uint8_t>(((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<uint8_t>(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<uint16_t>(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<int16_t>(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 */
#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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment