Skip to content

Commit 1a7a893

Browse files
EleotleCramme-no-devlucasssvazP-R-O-C-H-Y
authored
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: Lucas Saavedra Vaz <[email protected]> * add skip files for C6+H2 * remove already patched workaroud for bug * move #define to top of file --------- Co-authored-by: Me No Dev <[email protected]> Co-authored-by: Lucas Saavedra Vaz <[email protected]> Co-authored-by: Jan Procházka <[email protected]> Co-authored-by: Lucas Saavedra Vaz <[email protected]>
1 parent 374280c commit 1a7a893

23 files changed

+555
-0
lines changed

Diff for: CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ set(LIBRARY_SRCS
115115
libraries/Update/src/Updater.cpp
116116
libraries/Update/src/HttpsOTAUpdate.cpp
117117
libraries/USB/src/USBHID.cpp
118+
libraries/USB/src/USBMIDI.cpp
118119
libraries/USB/src/USBHIDMouse.cpp
119120
libraries/USB/src/USBHIDKeyboard.cpp
120121
libraries/USB/src/USBHIDGamepad.cpp

Diff for: libraries/USB/examples/MIDI/MidiController/.skip.esp32

Whitespace-only changes.

Diff for: libraries/USB/examples/MIDI/MidiController/.skip.esp32c3

Whitespace-only changes.

Diff for: libraries/USB/examples/MIDI/MidiController/.skip.esp32c6

Whitespace-only changes.

Diff for: libraries/USB/examples/MIDI/MidiController/.skip.esp32h2

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
This is an example of a Simple MIDI Controller using an ESP32 with a native USB support stack (S2, S3,
3+
etc.).
4+
5+
For a hookup guide and more information on reading the ADC, please see:
6+
https://randomnerdtutorials.com/esp32-adc-analog-read-arduino-ide/ (Note: This sketch uses GPIO05)
7+
8+
For best results, it is recommended to add an extra offset resistor between VCC and the potentiometer.
9+
(For a standard 10kOhm potentiometer, 3kOhm - 4kOhm will do.)
10+
11+
View this sketch in action on YouTube: https://youtu.be/Y9TLXs_3w1M
12+
*/
13+
#if ARDUINO_USB_MODE
14+
#warning This sketch should be used when USB is in OTG mode
15+
void setup() {}
16+
void loop() {}
17+
#else
18+
19+
#include <math.h>
20+
21+
#include "USB.h"
22+
#include "USBMIDI.h"
23+
USBMIDI MIDI;
24+
25+
#define MIDI_NOTE_C4 64
26+
27+
#define MIDI_CC_CUTOFF 74
28+
29+
///// ADC & Controller Input Handling /////
30+
31+
#define CONTROLLER_PIN 5
32+
33+
// ESP32 ADC needs a ton of smoothing
34+
#define SMOOTHING_VALUE 1000
35+
static double controllerInputValue = 0;
36+
37+
void updateControllerInputValue() {
38+
controllerInputValue =
39+
(controllerInputValue * (SMOOTHING_VALUE - 1) + analogRead(CONTROLLER_PIN)) / SMOOTHING_VALUE;
40+
}
41+
42+
void primeControllerInputValue() {
43+
for (int i = 0; i < SMOOTHING_VALUE; i++) {
44+
updateControllerInputValue();
45+
}
46+
}
47+
48+
uint16_t readControllerValue() {
49+
// Lower ADC input amplitude to get a stable value
50+
return round(controllerInputValue / 12);
51+
}
52+
53+
///// Button Handling /////
54+
55+
#define BUTTON_PIN 0
56+
57+
// Simple button state transition function with debounce
58+
// (See also: https://tinyurl.com/simple-debounce)
59+
#define PRESSED 0xff00
60+
#define RELEASED 0xfe1f
61+
uint16_t getButtonEvent() {
62+
static uint16_t state = 0;
63+
state = (state << 1) | digitalRead(BUTTON_PIN) | 0xfe00;
64+
return state;
65+
}
66+
67+
///// Arduino Hooks /////
68+
69+
void setup() {
70+
Serial.begin(115200);
71+
MIDI.begin();
72+
USB.begin();
73+
74+
primeControllerInputValue();
75+
}
76+
77+
void loop() {
78+
uint16_t newControllerValue = readControllerValue();
79+
static uint16_t lastControllerValue = 0;
80+
81+
// Auto-calibrate the controller range
82+
static uint16_t maxControllerValue = 0;
83+
static uint16_t minControllerValue = 0xFFFF;
84+
85+
if (newControllerValue < minControllerValue) {
86+
minControllerValue = newControllerValue;
87+
}
88+
if (newControllerValue > maxControllerValue) {
89+
maxControllerValue = newControllerValue;
90+
}
91+
92+
// Send update if the controller value has changed
93+
if (lastControllerValue != newControllerValue) {
94+
lastControllerValue = newControllerValue;
95+
96+
// Can't map if the range is zero
97+
if (minControllerValue != maxControllerValue) {
98+
MIDI.controlChange(MIDI_CC_CUTOFF,
99+
map(newControllerValue, minControllerValue, maxControllerValue, 0, 127));
100+
}
101+
}
102+
103+
updateControllerInputValue();
104+
105+
// Hook Button0 to a MIDI note so that we can observe
106+
// the CC effect without the need for a MIDI keyboard.
107+
switch (getButtonEvent()) {
108+
case PRESSED:
109+
MIDI.noteOn(MIDI_NOTE_C4, 64);
110+
break;
111+
case RELEASED:
112+
MIDI.noteOff(MIDI_NOTE_C4, 0);
113+
break;
114+
default:
115+
break;
116+
}
117+
}
118+
#endif /* ARDUINO_USB_MODE */

Diff for: libraries/USB/examples/MIDI/MidiInterface/.skip.esp32

Whitespace-only changes.

Diff for: libraries/USB/examples/MIDI/MidiInterface/.skip.esp32c3

Whitespace-only changes.

Diff for: libraries/USB/examples/MIDI/MidiInterface/.skip.esp32c6

Whitespace-only changes.

Diff for: libraries/USB/examples/MIDI/MidiInterface/.skip.esp32h2

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
This is an example of using an ESP32 with a native USB support stack (S2, S3, etc.) as a Serial MIDI to
3+
USB MIDI bridge (AKA "A MIDI Interface").
4+
5+
View this sketch in action on YouTube: https://youtu.be/BXG5i55I9s0
6+
7+
Receiving and decoding USB MIDI 1.0 packets is a more advanced topic than sending MIDI over USB,
8+
please refer to the other examples in this library for a more basic example of sending MIDI over USB.
9+
10+
This example should still be self explanatory, please refer to the USB MIDI 1.0 specification (the spec)
11+
for a more in-depth explanation of the packet format.
12+
13+
For the spec please visit: https://www.midi.org/specifications-old/item/usb-midi-1-0-specification
14+
15+
Note: Because ESP32 works at VCC=3.3v normal schematics for Serial MIDI connections will not suffice,
16+
Please refer to the Updated MIDI 1.1 Electrical Specification [1] for information on how to hookup
17+
Serial MIDI for 3.3v devices.
18+
19+
[1] - https://www.midi.org/specifications/midi-transports-specifications/5-pin-din-electrical-specs
20+
*/
21+
#if ARDUINO_USB_MODE
22+
#warning This sketch should be used when USB is in OTG mode
23+
void setup() {}
24+
void loop() {}
25+
#else
26+
27+
#include "USB.h"
28+
#include "USBMIDI.h"
29+
USBMIDI MIDI;
30+
31+
#define MIDI_RX 39
32+
#define MIDI_TX 40
33+
34+
void setup() {
35+
// USBCDC Serial
36+
Serial.begin(115200);
37+
38+
// HW UART Serial
39+
Serial1.begin(31250, SERIAL_8N1, MIDI_RX, MIDI_TX);
40+
41+
MIDI.begin();
42+
USB.begin();
43+
}
44+
45+
void loop() {
46+
// MIDI Serial 1.0 to USB MIDI 1.0
47+
if (Serial1.available()) {
48+
byte data = Serial1.read();
49+
MIDI.write(data);
50+
}
51+
52+
// USB MIDI 1.0 to MIDI Serial 1.0
53+
midiEventPacket_t midi_packet_in = {0};
54+
// See Chapter 4: USB-MIDI Event Packets (page 16) of the spec.
55+
int8_t cin_to_midix_size[16] = {-1, -1, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1};
56+
57+
if (MIDI.readPacket(&midi_packet_in)) {
58+
midi_code_index_number_t code_index_num = MIDI_EP_HEADER_CIN_GET(midi_packet_in.header);
59+
int8_t midix_size = cin_to_midix_size[code_index_num];
60+
61+
// We skip Misc and Cable Events for simplicity
62+
if (code_index_num >= 0x2) {
63+
for (int i = 0; i < midix_size; i++) {
64+
Serial1.write(((uint8_t *)&midi_packet_in)[i + 1]);
65+
}
66+
Serial1.flush();
67+
}
68+
}
69+
}
70+
#endif /* ARDUINO_USB_MODE */

Diff for: libraries/USB/examples/MIDI/MidiMusicBox/.skip.esp32

Whitespace-only changes.

Diff for: libraries/USB/examples/MIDI/MidiMusicBox/.skip.esp32c3

Whitespace-only changes.

Diff for: libraries/USB/examples/MIDI/MidiMusicBox/.skip.esp32c6

Whitespace-only changes.

Diff for: libraries/USB/examples/MIDI/MidiMusicBox/.skip.esp32h2

Whitespace-only changes.
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
This is an example of a MIDI Music Box using an ESP32 with a native USB support stack (S2, S3, etc.).
3+
4+
Every time the button on the ESP32 board (attached to pin 0) is pressed the next note of a melody is
5+
played. It is up to the user to get the timing of the button presses right.
6+
7+
One simple way of running this sketch is to download the Pianoteq evaluation version, because upon
8+
application start it automatically listens to the first MIDI Input on Channel 1, which is the case,
9+
if the ESP32 is the only MIDI device attached.
10+
11+
View this sketch in action on YouTube: https://youtu.be/JFrc-wSmcus
12+
*/
13+
#if ARDUINO_USB_MODE
14+
#warning This sketch should be used when USB is in OTG mode
15+
void setup() {}
16+
void loop() {}
17+
#else
18+
19+
#include "USB.h"
20+
#include "USBMIDI.h"
21+
USBMIDI MIDI;
22+
23+
#define END_OF_SONG 255
24+
uint8_t notes[] = {END_OF_SONG, 71, 76, 79, 78, 76, 83, 81, 78, 76, 79, 78, 75, 77, 71};
25+
uint8_t noteIndex = 0; // From 0 to sizeof(notes)
26+
#define SONG_LENGTH (sizeof(notes) - 1) // END_OF_SONG does not attribute to the length.
27+
28+
#define BUTTON_PIN 0
29+
30+
// Simple button press check with debounce
31+
// (See also: https://tinyurl.com/simple-debounce)
32+
bool isButtonPressed() {
33+
static uint16_t state = 0;
34+
state = (state << 1) | digitalRead(BUTTON_PIN) | 0xfe00;
35+
return (state == 0xff00);
36+
}
37+
38+
void setup() {
39+
Serial.begin(115200);
40+
// Make the BUTTON_PIN an input:
41+
pinMode(BUTTON_PIN, INPUT_PULLUP);
42+
43+
MIDI.begin();
44+
USB.begin();
45+
}
46+
47+
void loop() {
48+
if (isButtonPressed()) {
49+
// Stop current note
50+
MIDI.noteOff(notes[noteIndex]);
51+
52+
// Play next note
53+
noteIndex = noteIndex < SONG_LENGTH ? noteIndex + 1 : 0;
54+
if (notes[noteIndex] != END_OF_SONG) {
55+
MIDI.noteOn(notes[noteIndex], 64);
56+
}
57+
}
58+
}
59+
#endif /* ARDUINO_USB_MODE */

Diff for: libraries/USB/examples/MIDI/ReceiveMidi/.skip.esp32

Whitespace-only changes.

Diff for: libraries/USB/examples/MIDI/ReceiveMidi/.skip.esp32c3

Whitespace-only changes.

Diff for: libraries/USB/examples/MIDI/ReceiveMidi/.skip.esp32c6

Whitespace-only changes.

Diff for: libraries/USB/examples/MIDI/ReceiveMidi/.skip.esp32h2

Whitespace-only changes.
+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
This is an example of receiving USB MIDI 1.0 messages using an ESP32 with a native USB support stack
3+
(S2, S3, etc.).
4+
5+
Receiving and decoding USB MIDI 1.0 packets is a more advanced topic than sending MIDI over USB,
6+
please refer to the other examples in this library for a more basic example of sending MIDI over USB.
7+
8+
This example should still be self explanatory, please refer to the USB MIDI 1.0 specification (the spec)
9+
for a more in-depth explanation of the packet format.
10+
11+
For the spec please visit: https://www.midi.org/specifications-old/item/usb-midi-1-0-specification
12+
*/
13+
#if ARDUINO_USB_MODE
14+
#warning This sketch should be used when USB is in OTG mode
15+
void setup() {}
16+
void loop() {}
17+
#else
18+
19+
#include "USB.h"
20+
#include "USBMIDI.h"
21+
USBMIDI MIDI;
22+
23+
void setup() {
24+
Serial.begin(115200);
25+
26+
MIDI.begin();
27+
USB.begin();
28+
}
29+
30+
void loop() {
31+
midiEventPacket_t midi_packet_in = {0};
32+
33+
if (MIDI.readPacket(&midi_packet_in)) {
34+
printDetails(midi_packet_in);
35+
}
36+
}
37+
38+
void printDetails(midiEventPacket_t &midi_packet_in) {
39+
// See Chapter 4: USB-MIDI Event Packets (page 16) of the spec.
40+
uint8_t cable_num = MIDI_EP_HEADER_CN_GET(midi_packet_in.header);
41+
midi_code_index_number_t code_index_num = MIDI_EP_HEADER_CIN_GET(midi_packet_in.header);
42+
43+
Serial.println("Received a USB MIDI packet:");
44+
Serial.println(".----.-----.--------.--------.--------.");
45+
Serial.println("| CN | CIN | STATUS | DATA 0 | DATA 1 |");
46+
Serial.println("+----+-----+--------+--------+--------+");
47+
Serial.printf("| %d | %X | %X | %X | %X |\n", cable_num, code_index_num,
48+
midi_packet_in.byte1, midi_packet_in.byte2, midi_packet_in.byte3);
49+
Serial.println("'----'-----'--------.--------'--------'\n");
50+
Serial.print("Description: ");
51+
52+
switch (code_index_num) {
53+
case MIDI_CIN_MISC:
54+
Serial.println("This a Miscellaneous event");
55+
break;
56+
case MIDI_CIN_CABLE_EVENT:
57+
Serial.println("This a Cable event");
58+
break;
59+
case MIDI_CIN_SYSCOM_2BYTE: // 2 byte system common message e.g MTC, SongSelect
60+
case MIDI_CIN_SYSCOM_3BYTE: // 3 byte system common message e.g SPP
61+
Serial.println("This a System Common (SysCom) event");
62+
break;
63+
case MIDI_CIN_SYSEX_START: // SysEx starts or continue
64+
case MIDI_CIN_SYSEX_END_1BYTE: // SysEx ends with 1 data, or 1 byte system common message
65+
case MIDI_CIN_SYSEX_END_2BYTE: // SysEx ends with 2 data
66+
case MIDI_CIN_SYSEX_END_3BYTE: // SysEx ends with 3 data
67+
Serial.println("This a system exclusive (SysEx) event");
68+
break;
69+
case MIDI_CIN_NOTE_ON:
70+
Serial.printf("This a Note-On event of Note %d with a Velocity of %d\n",
71+
midi_packet_in.byte2, midi_packet_in.byte3);
72+
break;
73+
case MIDI_CIN_NOTE_OFF:
74+
Serial.printf("This a Note-Off event of Note %d with a Velocity of %d\n",
75+
midi_packet_in.byte2, midi_packet_in.byte3);
76+
break;
77+
case MIDI_CIN_POLY_KEYPRESS:
78+
Serial.printf("This a Poly Aftertouch event for Note %d and Value %d\n",
79+
midi_packet_in.byte2, midi_packet_in.byte3);
80+
break;
81+
case MIDI_CIN_CONTROL_CHANGE:
82+
Serial.printf("This a Control Change/Continuous Controller (CC) event of Controller %d "
83+
"with a Value of %d\n",
84+
midi_packet_in.byte2, midi_packet_in.byte3);
85+
break;
86+
case MIDI_CIN_PROGRAM_CHANGE:
87+
Serial.printf("This a Program Change event with a Value of %d\n", midi_packet_in.byte2);
88+
break;
89+
case MIDI_CIN_CHANNEL_PRESSURE:
90+
Serial.printf("This a Channel Pressure event with a Value of %d\n", midi_packet_in.byte2);
91+
break;
92+
case MIDI_CIN_PITCH_BEND_CHANGE:
93+
Serial.printf("This a Pitch Bend Change event with a Value of %d\n",
94+
((uint16_t)midi_packet_in.byte2) << 7 | midi_packet_in.byte3);
95+
break;
96+
case MIDI_CIN_1BYTE_DATA:
97+
Serial.printf("This an embedded Serial MIDI event byte with Value %X\n",
98+
midi_packet_in.byte1);
99+
break;
100+
}
101+
102+
Serial.println();
103+
}
104+
#endif /* ARDUINO_USB_MODE */

0 commit comments

Comments
 (0)