Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add USB MIDI support to libraries/USB #8166

Merged
merged 14 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Empty file.
Empty file.
118 changes: 118 additions & 0 deletions libraries/USB/examples/MIDI/MidiController/MidiController.ino
Original file line number Diff line number Diff line change
@@ -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 <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 */
Empty file.
Empty file.
70 changes: 70 additions & 0 deletions libraries/USB/examples/MIDI/MidiInterface/MidiInterface.ino
Original file line number Diff line number Diff line change
@@ -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 */
Empty file.
Empty file.
59 changes: 59 additions & 0 deletions libraries/USB/examples/MIDI/MidiMusicBox/MidiMusicBox.ino
Original file line number Diff line number Diff line change
@@ -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 */
Empty file.
Empty file.
104 changes: 104 additions & 0 deletions libraries/USB/examples/MIDI/ReceiveMidi/ReceiveMidi.ino
Original file line number Diff line number Diff line change
@@ -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 */
Loading