Hi there,
I spent the last couple of weeks narrowing down an issue I had with a rather complex USBHost MIDI Setup. (Arduino 2.10 Teensy 1.58.1)
Basically I have 5 USB-Midi Devices, A DIN Midi Device and regular USB-Midi all on a Teensy 4.1
one of the usb-midi devices that is connected sends out midi, all others (including DIN and USB-MIDI client) receive midi. this was all working as expected but I got a lot of hanging notes and some very sluggish performance every now and then. I rewrote the code a couple of times and inserted Serial.println() commands in all sort of places but I could not find the problem.
Finally today I realised that the problem always appears after I sent out a Program Change. The way I have set it up, my "master midi controller" sends out a program change to all synths that are connected so I can change the whole scene on all synths simultaneously. Everytime I sent a ProgramChange to all those synths, I got hanging notes and sluggish behaviour.
My code was adopted from the 16x16 example and is quite basic. I simply copy all incoming MIDI messages from one device to all others based on channels. This gave me the hanging/sluggish behaviour as soon as I issued a ProgramChange. I have now (in the attached code) separated all ProgramChange messages from the stream and treat them separately. At first I just issued the ProgramChanges like this:
which immediately got me hanging notes and sluggish behaviour again. if I only issued one ProgramChange though, like this:
everything kept running fine. so I concluded that maybe something is wrong in the USBHost implementation of ProgramChange Messages, and sending more then one on different channels immediately after each other (or at the "same" time) caused the hiccups. and indeed....
changing my code to:
fixed all issues. a simple delay(1); between messages is enough. so it seems the USBHost code does somehow get corrupted messages when sending multiple program changes at the same time. it would be great if this could be fixed. for now I am VERY happy I figured this out and found a workaround.
finally here is the full code for reference:
I spent the last couple of weeks narrowing down an issue I had with a rather complex USBHost MIDI Setup. (Arduino 2.10 Teensy 1.58.1)
Basically I have 5 USB-Midi Devices, A DIN Midi Device and regular USB-Midi all on a Teensy 4.1
one of the usb-midi devices that is connected sends out midi, all others (including DIN and USB-MIDI client) receive midi. this was all working as expected but I got a lot of hanging notes and some very sluggish performance every now and then. I rewrote the code a couple of times and inserted Serial.println() commands in all sort of places but I could not find the problem.
Finally today I realised that the problem always appears after I sent out a Program Change. The way I have set it up, my "master midi controller" sends out a program change to all synths that are connected so I can change the whole scene on all synths simultaneously. Everytime I sent a ProgramChange to all those synths, I got hanging notes and sluggish behaviour.
My code was adopted from the 16x16 example and is quite basic. I simply copy all incoming MIDI messages from one device to all others based on channels. This gave me the hanging/sluggish behaviour as soon as I issued a ProgramChange. I have now (in the attached code) separated all ProgramChange messages from the stream and treat them separately. At first I just issued the ProgramChanges like this:
Code:
else if (type == 0xC0) {
if (pulse2 >= 0) midilist[pulse2]->sendProgramChange(data1,6);
if (axodub >= 0 ) midilist[axodub]->sendProgramChange(data1,1);
if (minitaur >= 0) midilist[minitaur]->sendProgramChange(data1 + 1,8);
//send to dreadbox typhon...
MIDI1.sendProgramChange(data1,9);
usbMIDI.sendProgramChange(data1,1);
}
which immediately got me hanging notes and sluggish behaviour again. if I only issued one ProgramChange though, like this:
Code:
else if (type == 0xC0) {
if (pulse2 >= 0) midilist[pulse2]->sendProgramChange(data1,6);
}
everything kept running fine. so I concluded that maybe something is wrong in the USBHost implementation of ProgramChange Messages, and sending more then one on different channels immediately after each other (or at the "same" time) caused the hiccups. and indeed....
changing my code to:
Code:
else if (type == 0xC0) {
if (pulse2 >= 0) midilist[pulse2]->sendProgramChange(data1,6);
delay(1);
if (axodub >= 0 ) midilist[axodub]->sendProgramChange(data1,1);
delay(1);
if (minitaur >= 0) midilist[minitaur]->sendProgramChange(data1 + 1,8);
//send to dreadbox typhon...
MIDI1.sendProgramChange(data1,9);
usbMIDI.sendProgramChange(data1,1);
}
fixed all issues. a simple delay(1); between messages is enough. so it seems the USBHost code does somehow get corrupted messages when sending multiple program changes at the same time. it would be great if this could be fixed. for now I am VERY happy I figured this out and found a workaround.
finally here is the full code for reference:
Code:
//cpu speed 600 mhz and fast instead of fastest increases stability?
#include <MIDI.h> // access to serial (5 pin DIN) MIDI
#include <USBHost_t36.h> // access to USB MIDI devices (plugged into 2nd USB port)
//includes for OLEDs
//#include <i2c_driver_wire.h> //Library for I2C interface
#include <U8x8lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#include <Wire.h>
U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE, /* clock=*/ 19, /* data=*/ 18);
#define DEBUG
// Create the Serial MIDI ports
MIDI_CREATE_INSTANCE(HardwareSerial, Serial8, MIDI1);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial3, MIDI2);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial4, MIDI3);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial5, MIDI4);
//midi::MidiInterface &SerialMidiList[6] = {MIDI1, MIDI2, MIDI3, MIDI4, MIDI5, MIDI6};
//usb synth devices
int pulse2 = -1;
int keysaxo = -1;
int minitaur = -1;
int axodub = -1;
//usb midibass controller
int midibass = -1;
//handle on screen boot messages...
elapsedMillis boottime;
int state = 0;
// 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_BigBuffer midi01(myusb);
MIDIDevice_BigBuffer midi02(myusb);
MIDIDevice_BigBuffer midi03(myusb);
MIDIDevice_BigBuffer midi04(myusb);
MIDIDevice_BigBuffer midi05(myusb);
MIDIDevice_BigBuffer midi06(myusb);
MIDIDevice_BigBuffer midi07(myusb);
MIDIDevice_BigBuffer * midilist[7] = {
&midi01, &midi02, &midi03, &midi04, &midi05, &midi06, &midi07
};
// A variable to know how long the LED has been turned on
//elapsedMillis ledOnMillis;
void setup() {
Wire.setSDA(18);
Wire.setSCL(19);
u8x8.begin();
u8x8.setPowerSave(0);
u8x8.setFont(u8x8_font_5x8_f);
while (!Serial && millis() < 1000) {
u8x8.drawString(0,4,"Booting...");
};
u8x8.clear();
for (int i=0; i<13; i++) {
pinMode(i,INPUT_DISABLE);
}
for (int i=24; i<34; i++) {
pinMode(i,INPUT_DISABLE);
}
for (int i=36; i<55; i++) {
pinMode(i,INPUT_DISABLE);
}
pinMode(22,INPUT_DISABLE);
pinMode(23,INPUT_DISABLE);
#ifdef DEBUG
Serial.begin(115200);
#endif
pinMode(13, OUTPUT); // LED pin
digitalWrite(13, LOW);
MIDI1.begin(MIDI_CHANNEL_OMNI);
MIDI2.begin(MIDI_CHANNEL_OMNI);
MIDI3.begin(MIDI_CHANNEL_OMNI);
MIDI4.begin(MIDI_CHANNEL_OMNI);
// Wait 3.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.
#ifdef DEBUG
Serial.println("Booting...");
#endif
boottime = 0;
while (boottime < 1000) {
if (boottime < 300) {
if (!state) u8x8.drawString(0,4,"W");
state = 1;
} else if (boottime < 600) {
if (state == 1) u8x8.drawString(0,4,"WA");
state = 2;
} else if (boottime < 900) {
if (state == 2) u8x8.drawString(0,4,"WAI");
state = 3;
}
}
// u8x8.drawString(0,0,"Booting");
// delay(3500);
//
//usbMIDI.begin();
myusb.begin();
delay(100);
boottime = 0;
//wait another 10 seconds before enumerating the devices, hopefully by now they have settled.
while (boottime < 3000) {
if (boottime < 300) {
if (state == 3) u8x8.drawString(0,4,"WAIT");
state = 4;
} else if (boottime < 600) {
if (state == 4) u8x8.drawString(0,4,"WAITI");
state = 5;
} else if (boottime < 900) {
if (state == 5) u8x8.drawString(0,4,"WAITIN");
state = 6;
} else if (boottime < 1200) {
if (state == 6) u8x8.drawString(0,4,"WAITING");
state = 7;
} else if (boottime < 1500) {
if (state == 7) u8x8.drawString(0,4,"WAITING.");
state = 8;
} else if (boottime < 1800) {
if (state == 8)u8x8.drawString(0,4,"WAITING..");
state = 9;
} else if (boottime < 2100) {
if (state == 9)u8x8.drawString(0,4,"WAITING...");
state = 10;
} else if (boottime < 2400) {
if (state == 10) u8x8.drawString(0,4,"WAITING....");
state = 11;
} else if (boottime < 2700) {
if (state == 11) u8x8.drawString(0,4,"WAITING.....");
state = 12;
} else if (boottime < 3000){
if (state == 12) u8x8.drawString(0,4,"WAITING......");
state = 13;
}
}
u8x8.clear();
u8x8.drawString(0,0," USB DEVICES ");
u8x8.drawString(0,1,"---------------");
u8x8.drawString(0,7,"---------------");
delay(500);
//delay(10000);
Serial.println("");
Serial.println("------------NEW START--------------");
for (int i=0; i<7; i++) {
if (midilist[i]->manufacturer() != NULL && midilist[i]->serialNumber() != NULL) {
#ifdef DEBUG
Serial.println("-----------------------------------");
Serial.print("Serial:");
Serial.println((char*)midilist[i]->serialNumber());
Serial.print("ProductID:");
Serial.println(midilist[i]->idProduct());
Serial.println("-----------------------------------");
#endif
if (!strcmp(reinterpret_cast<const char *>(midilist[i]->serialNumber()),"003600433435511733353932")) {//Axoloti for qunexus/vocoder serial
#ifdef DEBUG
Serial.println("here is axovocoder");
#endif
u8x8.drawString(0,i+2,"AxoVox");
u8x8.drawString(10,i+2,String(i).c_str());
keysaxo = i;
}
if (midilist[i]->idProduct() == 22) {
#ifdef DEBUG
Serial.println("here is Pulse2");
#endif
u8x8.drawString(0,i+2,"Pulse2");
u8x8.drawString(10,i+2,String(i).c_str());
pulse2 = i;
}
/* if (!strcmp(reinterpret_cast<const char *>(midilist[i]->serialNumber()),"325B346C3030")) {//typhon dreadbox
#ifdef DEBUG
Serial.println("here is typhon dreadbox");
#endif
dreadbox = i;
} */
/* if (!strcmp(reinterpret_cast<const char *>(midilist[i]->serialNumber()),"6633190")) {//midibass
#ifdef DEBUG
Serial.println("here is MIDIBASS!!");
#endif
midibass = i;
} */
if (!strcmp(reinterpret_cast<const char *>(midilist[i]->serialNumber()),"12209010")) {//midibass
#ifdef DEBUG
Serial.println("here is MIDIBASS!!");
#endif
u8x8.drawString(0,i+2,"MIDIBASS");
u8x8.drawString(10,i+2,String(i).c_str());
midibass = i;
}
if (!strcmp(reinterpret_cast<const char *>(midilist[i]->serialNumber()),"003500323532470532323631")) {//Axoloti for qunexus/vocoder serial
#ifdef DEBUG
Serial.println("here is axodub");
#endif
u8x8.drawString(0,i+2,"AxoDub");
u8x8.drawString(10,i+2,String(i).c_str());
axodub = i;
}
if (!strcmp(reinterpret_cast<const char *>(midilist[i]->serialNumber()),"MTc537ef")) {//moog minitaur
#ifdef DEBUG
Serial.println("here is the minitaur");
#endif
u8x8.drawString(0,i+2,"Minitaur");
u8x8.drawString(10,i+2,String(i).c_str());
minitaur = i;
}
}
delay(300);
}
delay(100);
Serial.println("------------Finished------------");
if (pulse2 >= 0) midilist[pulse2]->sendProgramChange(0,6);
delay(1);
if (axodub >= 0 ) midilist[axodub]->sendProgramChange(0,1);
delay(1);
if (minitaur >= 0) midilist[minitaur]->sendProgramChange(1,8);
//send to dreadbox typhon...
MIDI1.sendProgramChange(0,9);
usbMIDI.sendProgramChange(0,1);
}
void loop() {
if(usbMIDI.read()) {
uint8_t type = usbMIDI.getType();
uint8_t data1 = usbMIDI.getData1();
uint8_t data2 = usbMIDI.getData2();
uint8_t channel = usbMIDI.getChannel();
uint8_t cable = usbMIDI.getCable();
midilist[midibass]->send(type, data1, data2, channel, cable);
}
myusb.Task();
if (midibass >= 0){
if (midilist[midibass]->read()) {
uint8_t type = midilist[midibass]->getType();
uint8_t data1 = midilist[midibass]->getData1();
uint8_t data2 = midilist[midibass]->getData2();
uint8_t channel = midilist[midibass]->getChannel();
uint8_t cable = midilist[midibass]->getCable();
// const uint8_t *sys = midilist[midibass]->getSysExArray();
midi::MidiType mtype = (midi::MidiType)type;
if (type != 0xC0) {
if (!cable) {
if (channel == 1) {
sendToMacMini(type, data1, data2, channel, 0);
if (axodub >= 0) midilist[axodub]->send(type, data1, data2, channel);
}
if (channel == 6) {
if (pulse2 >= 0) midilist[pulse2]->send(type, data1, data2, channel);
}
if (channel == 7) {
if (axodub >= 0) midilist[axodub]->send(type, data1, data2, channel);
}
if (channel == 8) {
if (minitaur >= 0) midilist[minitaur]->send(type, data1, data2, channel);
}
if (channel == 9) {
//if (dreadbox < 11) midilist[dreadbox]->send(type, data1, data2, channel);
MIDI1.send(mtype, data1, data2, channel);
}
if (((channel > 1) && (channel < 6)) || (channel > 9)) sendToMacMini(type, data1, data2, channel, 0); // channel 2-5 and 10-16
// case 9: if (keysaxo >= 0) midilist[keysaxo]->send(type, data1, data2, channel); break;
// case 9: MIDI1.send(mtype, data1, data2, channel); break;
}
else if (cable == 1) {
sendToMacMini(type, data1, data2, channel - 5, 1); //send all accordion data to the macmini on port 1
if ((channel > 12) && (keysaxo >= 0)) midilist[keysaxo]->send(type, data1, data2, channel);
}
} else if (type == 0xC0) {
if (pulse2 >= 0) midilist[pulse2]->sendProgramChange(data1,6);
delay(1);
if (axodub >= 0 ) midilist[axodub]->sendProgramChange(data1,1);
delay(1);
if (minitaur >= 0) midilist[minitaur]->sendProgramChange(data1 + 1,8);
//send to dreadbox typhon...
MIDI1.sendProgramChange(data1,9);
usbMIDI.sendProgramChange(data1,1);
}
// activity = true;
}
}
}
void sendToMacMini(byte type, byte data1, byte data2, byte channel, byte cable)
{
if (type != midi::SystemExclusive) {
usbMIDI.send(type, data1, data2, channel, cable);
} else {
// unsigned int SysExLength = data1 + data2 * 256;
// usbMIDI.sendSysEx(SysExLength, sysexarray, true, cable);
}
}