Teensy 3.1/3.2 USB MIDI: huge latency on receiver side

Status
Not open for further replies.

amundsen

Well-known member
Hello,

I am running the code below to convert voltage from an expression controller into MIDI messages and send them over a USB connection.

Is there anything wrong with this code? It's compiled with the "Serial + MIDI" USB type option. Then, if I open the serial monitor in the Arduino IDE the dataflow reacts quickly when pushing the pedal. However with the MIDI, I have a latency of around 6 seconds (messages received in Max8 64bit/Windows 10)! I have the same issue on both a Teensy 3.1 and a 3.2.

Code:
int raw;
int input = A9;
int ctlNumber = 16;
const int channel = 14; // MIDI channel
int MSB, LSB;
#define LED_PIN 13
int older = -1;

void setup() {
 analogReadResolution(12);
 Serial.begin(115200);
 pinMode(LED_PIN, OUTPUT);
 digitalWrite(LED_PIN, HIGH);
 // reset all controllers
    usbMIDI.sendControlChange(ctlNumber, 0, channel); 
    usbMIDI.sendControlChange((ctlNumber+32), 0, channel);            
}

void loop() {
  // MIDI Controllers should discard incoming MIDI messages.
  while (usbMIDI.read()) {
    // read & ignore incoming messages
  }
  raw = analogRead(input);
  if (raw != older) {
    Serial.print("raw: ");
    Serial.print(raw);
    MSB = raw >> 7 ;
    LSB = raw % 128;
    Serial.print(" / MSB : ");
    Serial.print(MSB);
    Serial.print(" / LSB : ");
    Serial.println(LSB);
    usbMIDI.sendControlChange(ctlNumber, MSB, channel); 
    usbMIDI.sendControlChange((ctlNumber+32), LSB, channel); 
    older = raw;           
  }
}
 
As a quick experiment, try adding delay(10) in the loop.

Yeah, I know *adding* delay seems counter-intuitive. But please, give it a try anyway. It's so simple and easy to test.
 
You are trying to fire off two MIDI messages on every loop and you will eventually crash the event que on the receiving machine (at least if its Windows and I believe Mac too).

The serial calls you have are likely the only thing slowing the loop down enough to not completely crash the queue.

The main loop on a Teensy runs very quickly and you need to ensure you don't flood the queue with nearly identical MIDI events (and looks to me like the MSB would be identical nearly every time).

You're trying to read 14 bits of resolution from a 12 bit signal that is not going to be rock solid. At very least you'd need a very hard low-pass filter in the analog and/or digital domains so that the signal is remotely stable enough.

But even then I can't see not needing to finesse anything more than 11 bits out of it. Theoretically the much higher sample rate can mean you CAN gain resolution after filtering out non-signal noise (since you know the actual signal --- human motion -- is really SLOW).

An output signal then mimics the input at a much lower frequency based on a heavily low-passed value where a maximum rate of change can bu used; but you need to know what you're doing to achieve that (and I don't qualify there!).

I prefer to pick a maximum rate and reset a timer after sending any particular MIDI message before I'll send another where only the D2 value is different. But I've not been trying to get 14 bits out of a voltage divider either. You likely want to filter in both the analog domain (at a reasonably low cutoff) and then very hard in the digital - perhaps with something other than as simple feedback filter.

The only really viable option for the mere mortal hobbyist is to learn the finer points of the ResponsiveAnalogRead library and use it to produce a stable 'source' that you then time-limit as to how frequently (or after only some threshold change) the next update can occur.

(opps… i left my message in edit a while. Paul's suggestion seems to be attempting to confirm something like what I'm suggesting. It will almost certainly help but you will be getting something like 100 updates per second and they will almost all be nearly identical chatter around a central value.)
 
Yup, exactly my thoughts - just didn't have time to write a lengthy message.

Also, the ResponsiveAnalogRead library can work wonders for this sort of use.

Code that transmits MIDI messages too fast can overwhelm most PC-based software. You might think a little Teensy board couldn't overwhelm a powerful PC, but the PC software does a lot of computationally heavy things. This situation will become even more extreme on Teensy 4.0, which now as 480 Mbit/sec USB, and can run code quite fast.
 
Sorry for the delay to answer, I was busy on other projects.

As a quick experiment, try adding delay(10) in the loop.

Yeah, I know *adding* delay seems counter-intuitive. But please, give it a try anyway. It's so simple and easy to test.

Yes, it made the trick. Thank you.

I prefer to pick a maximum rate and reset a timer after sending any particular MIDI message before I'll send another where only the D2 value is different. But I've not been trying to get 14 bits out of a voltage divider either. You likely want to filter in both the analog domain (at a reasonably low cutoff) and then very hard in the digital - perhaps with something other than as simple feedback filter.
14-bit is only because with MIDI one has to go 7- or 14-bit encoding but I've never pretended all the bits were used.

What kind of filter would you use? On another project, I've had good results with a simple averaging filter on analog sliders actually, but the distance between the sliders and the Teensy was very short so the signals had less noise probably.

The only really viable option for the mere mortal hobbyist is to learn the finer points of the ResponsiveAnalogRead library and use it to produce a stable 'source' that you then time-limit as to how frequently (or after only some threshold change) the next update can occur.

Thanks for the suggestion. ResponsiveAnalogRead seems to be limited to a 10-bit input resolution but the output is very clean. I'll go with that solution as it appeared that the last bits in my signal were quite noisy indeed.
 
14-bit is only because with MIDI one has to go 7- or 14-bit encoding but I've never pretended all the bits were used.
That was the problem... your code was pretending that the bits were meaningful.

ResponsiveAnalogRead has settings for resolution and other control factors that will help you get as good a signal as possible.

Determine the bit level you feel is meaningful and then send the MIDI at that resolution.

I can't answer the filter question as I've never done the legwork to test exactly where the noise is but I'd guess it's mostly mains hum so you would want a very low cut-off.
Filtering digitally or in analog should give very similar results if the cut-off, type and order of the filter are the same.

But if it turns out it is mostly transients then any low-pass would help and even a capacity between the wiper and ground might 'solve' the worst of it.

But consider you may not find finer resolution on an expression pedal to be very useful as it's difficult to control the angle to increment each value without jumping... at speed the gestures you can make with your feet are pretty low-rez in nature especially as your range of travel is likely under 30 degrees --- that's four positions per degree.
 
Status
Not open for further replies.
Back
Top