Skip to content

Instantly share code, notes, and snippets.

@cowboy
Last active December 22, 2023 21:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cowboy/23e9df2897a4f63aca8f5cb9d99db814 to your computer and use it in GitHub Desktop.
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
// 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
};
// ===============================================================
// 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);
}
}
@cowboy
Copy link
Author

cowboy commented Aug 18, 2022

WIP photos

2022-08-17 23 52 25

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment