Last active
December 22, 2023 21:51
-
-
Save cowboy/23e9df2897a4f63aca8f5cb9d99db814 to your computer and use it in GitHub Desktop.
USB MIDI "proxy" that converts Note On + Sustain + Note Off => Note On + "Sustained" Note Off for devices that don't handle Sustain (CC 64) well
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// To give your project a unique name, this code must be | |
// placed into a .c file (its own tab). It can not be in | |
// a .cpp file or your main sketch (the .ino file). | |
#include "usb_names.h" | |
// Edit these lines to create your own name. The length must | |
// match the number of characters in your custom name. | |
#define MIDI_NAME {'C', 'o', 'w', 'b', 'o', 'y', ' ', 'F', 'i', 'l', 't', 'e', 'r'} | |
#define MIDI_NAME_LEN 13 | |
// Do not change this part. This exact format is required by USB. | |
struct usb_string_descriptor_struct usb_string_product_name = { | |
2 + MIDI_NAME_LEN * 2, | |
3, | |
MIDI_NAME | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// =============================================================== | |
// MIDI Sustain Filter (USB MIDI) - for Teensy 4.1 | |
// "Cowboy" Ben Alman, 2022 | |
// https://gist.github.com/cowboy/23e9df2897a4f63aca8f5cb9d99db814 | |
// =============================================================== | |
// Based on Arduino Teensy examples: | |
// File > Examples > Teensy > USB_MIDI | |
// File > Examples > USBHost_t36 > MIDI > Interface_16x16 | |
// | |
// Settings: | |
// Tools > Board = "Teensy 4.1" | |
// Tools > USB Type to "MIDIx4" | |
#include <MIDI.h> // access to serial (5 pin DIN) MIDI | |
#include <USBHost_t36.h> // access to USB MIDI devices (plugged into 2nd USB port) | |
// Create the ports for USB devices plugged into Teensy's 2nd USB port (via hubs) | |
USBHost myusb; | |
USBHub hub1(myusb); | |
USBHub hub2(myusb); | |
USBHub hub3(myusb); | |
USBHub hub4(myusb); | |
MIDIDevice midi01(myusb); | |
MIDIDevice midi02(myusb); | |
MIDIDevice midi03(myusb); | |
MIDIDevice midi04(myusb); | |
MIDIDevice * midilist[4] = { | |
&midi01, &midi02, &midi03, &midi04 | |
}; | |
// A variable to know how long the LED has been turned on | |
elapsedMillis ledOnMillis; | |
const int SUSTAIN_CC_NUMBER = 64; | |
bool isSustaining[4][16] = {}; | |
bool isNoteOff[4][16][127] = {}; | |
void initArrays() { | |
for (int cable = 0; cable < 4; cable++) { | |
for (int channel = 0; channel < 16; channel++) { | |
isSustaining[cable][channel] = false; | |
for (int note = 0; note < 127; note++) { | |
isNoteOff[cable][channel][note] = false; | |
} | |
} | |
} | |
} | |
const int LED_PIN = LED_BUILTIN; | |
void blink(int count) { | |
int delay_ms = 100; | |
for (int i = 0; i < count; i++) { | |
digitalWrite(LED_PIN, HIGH); | |
delay(delay_ms); | |
digitalWrite(LED_PIN, LOW); | |
delay(delay_ms); | |
} | |
} | |
void setup() { | |
Serial.begin(115200); | |
// Setup pins | |
pinMode(LED_PIN, OUTPUT); | |
blink(3); | |
initArrays(); | |
// Wait 1.5 seconds before turning on USB Host. If connected USB devices | |
// use too much power, Teensy at least completes USB enumeration, which | |
// makes isolating the power issue easier. | |
delay(1500); | |
myusb.begin(); | |
} | |
void loop() { | |
bool activity = false; | |
// Read messages arriving from the (up to) 4 USB devices plugged into the USB Host port | |
for (int port = 0; port < 4; port++) { | |
if (midilist[port]->read()) { | |
uint8_t type = midilist[port]->getType(); | |
uint8_t data1 = midilist[port]->getData1(); | |
uint8_t data2 = midilist[port]->getData2(); | |
uint8_t channel = midilist[port]->getChannel(); | |
const uint8_t *sys = midilist[port]->getSysExArray(); | |
int cable = port; | |
sendToUpstreamHost(type, data1, data2, channel, sys, cable); | |
activity = true; | |
} | |
} | |
// Read messages the PC (upstream host) sends and forward them to devices | |
if (usbMIDI.read()) { | |
byte type = usbMIDI.getType(); | |
byte data1 = usbMIDI.getData1(); | |
byte data2 = usbMIDI.getData2(); | |
byte channel = usbMIDI.getChannel(); | |
const uint8_t *sys = usbMIDI.getSysExArray(); | |
byte cable = usbMIDI.getCable(); | |
sendToDownstreamDevice(type, data1, data2, channel, sys, cable); | |
activity = true; | |
} | |
// blink the LED when any activity has happened | |
if (activity) { | |
digitalWriteFast(LED_PIN, HIGH); // LED on | |
ledOnMillis = 0; | |
} | |
if (ledOnMillis > 15) { | |
digitalWriteFast(LED_PIN, LOW); // LED off | |
} | |
} | |
// Send data from the downstream MIDI device (eg. controller) to the upstream host (eg. computer) | |
void sendToUpstreamHost(byte type, byte data1, byte data2, byte channel, const uint8_t *sysexarray, byte cable) { | |
if (type != midi::SystemExclusive) { | |
// ====================================================================== | |
// Convert Note On + Sustain + Note Off => Note On + "Sustained" Note Off | |
// ====================================================================== | |
if (type == usbMIDI.ControlChange && data1 == SUSTAIN_CC_NUMBER) { | |
isSustaining[cable][channel] = data2 != 0; | |
// Sustain released, send all pending Note Off messages | |
if (!isSustaining[cable][channel]) { | |
for (int note = 0; note < 127; note++) { | |
if (isNoteOff[cable][channel][note]) { | |
usbMIDI.send(usbMIDI.NoteOff, note, 0, channel, cable); | |
} | |
isNoteOff[cable][channel][note] = false; | |
} | |
} | |
// Don't send Sustain messages | |
return; | |
} | |
if (isSustaining[cable][channel]) { | |
if (type == usbMIDI.NoteOn) { | |
// Send a Note Off immediately so that multiple sequntial Note On messages aren't sent | |
if (isNoteOff[cable][channel][data1]) { | |
usbMIDI.send(usbMIDI.NoteOff, data1, 0, channel, cable); | |
} | |
// Cancel any pending Note Off | |
isNoteOff[cable][channel][data1] = false; | |
} | |
else if (type == usbMIDI.NoteOff) { | |
// Store Note Off for later | |
isNoteOff[cable][channel][data1] = true; | |
// Don't send the Note Off now | |
return; | |
} | |
} | |
// ================================================== | |
// Minilab Mk2 Pad 1 (Note 127) => Channel Aftertouch | |
// ================================================== | |
if ((type == usbMIDI.AfterTouchPoly || type == usbMIDI.NoteOn || type == usbMIDI.NoteOff) && data1 == 127) { | |
if (type == usbMIDI.AfterTouchPoly) { | |
// Convert Polyphonic Aftertouch to Channel Aftertouch | |
type = usbMIDI.AfterTouchChannel; | |
data1 = data2; | |
data2 = 0; | |
} | |
else if (type == usbMIDI.NoteOff) { | |
// Releasing the pad should reset Channel Aftertouch to 0 | |
type = usbMIDI.AfterTouchChannel; | |
data1 = 0; | |
data2 = 0; | |
} | |
else { | |
// Ignore Note On | |
return; | |
} | |
} | |
usbMIDI.send(type, data1, data2, channel, cable); | |
} | |
else { | |
unsigned int SysExLength = data1 + data2 * 256; | |
usbMIDI.sendSysEx(SysExLength, sysexarray, true, cable); | |
} | |
} | |
// Send data from the upstream host (eg. computer) to the downstream MIDI device (eg. controller) | |
void sendToDownstreamDevice(byte type, byte data1, byte data2, byte channel, const uint8_t *sysexarray, byte cable) { | |
if (type != usbMIDI.SystemExclusive) { | |
midilist[cable]->send(type, data1, data2, channel); | |
} | |
else { | |
unsigned int SysExLength = data1 + data2 * 256; | |
midilist[cable]->sendSysEx(SysExLength, sysexarray, true); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
WIP photos