Latency in Receiving Midi CC Messages via Serial

I've been scouring the internet, arduino forum, and various midi library documentation over the last few days looking for answers and have come up blank. I apologize if I'm missing something obvious, but maybe this post can help others as well.

Goal:
Receive midi cc messages sent from an external midi controller in 'reali-ish' time from multiple Control Channel #'s.

Problem:
I AM able to successfully read the midi data from the external midi controller's faders, but there is a pretty substantial latency when I move a fader up and down quickly or move multiple faders at once. It looks like individual CC messages come in very quickly, but as moving a faders sends a CC message for every value between 0-127, it looks like a ‘queue’ forms in the serial port and it takes about .5 seconds (depending on how many times I quickly move the fader up and down) for the ESP32 to ‘catch up’ with all of the messages.

Does anyone know of any strategies to receive midi CC messages more quickly and/or to avoid that ‘queue’ of cc messages?

Hardware:

  • ESP32 Dev Board (receiving midi via RX2)
  • 16n Faderbank (teensy, sending midi via TRS jack)

CODE:

#include <MIDI.h>

//ESP32 RX Pin RX2 Used:
MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, MIDI);

const i nt XRES = 320;
const int YRES = 240;
const int numfaders = 16;
int faderValue[numfaders];
int x[numfaders];
int y[numfaders];

// declare midi CC numbers for the 16n faders
const byte fader1 = 32;
const byte fader2 = 33;
const byte fader3 = 34;
const byte fader4 = 35;
const byte fader5 = 36;
const byte fader6 = 37;
const byte fader7 = 38;
const byte fader8 = 39;
const byte fader9 = 40;
const byte fader10 = 41;
const byte fader11 = 42;
const byte fader12 = 43;
const byte fader13 = 44;
const byte fader14 = 45;
const byte fader15 = 46;
const byte fader16 = 47;


// callback function when a control change message is received
void OnControlChange(byte channel, byte number, byte value)
{
  if(number > 119 || value > 127) return; //exit function if received bad data
  
  Serial.println(String("Control Change, ch=") + channel + ", control #= " + number + ", value= " + value);  

  switch(number){
    case fader1:
      y[0] = map(value, 0, 127, YRES-10, 0); // yres-5 as yres seems to be off screen
      break;
    case fader2:
      y[1] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader3:
      y[2] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader4:
      y[3] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader5:
      y[4] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader6:
      y[5] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader7:
      y[6] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader8:
      y[7] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader9:
      y[8] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader10:
      y[9] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader11:
      y[10] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader12:
      y[11] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader13:
      y[12] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader14:
      y[13] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader15:
      y[14] = map(value, 0, 127, YRES-10, 0);
      break;
    case fader16:
      y[15] = map(value, 0, 127, YRES-10, 0);
      break;
  }
  
}


void setup() {
  MIDI.begin(MIDI_CHANNEL_OMNI);   //Listens for messages on all midi channels. Adjust if necessary.
  MIDI.setHandleControlChange(OnControlChange);
   Serial.begin(115200);             //NOTE: changed from default midi 57600 to 115200
  Serial.println("MIDI Input Test");
}
 
void loop() {
  MIDI.read();

}

EXAMPLE SERIAL MONITOR OUTPUT:

Control Change, ch=1, control #= 46, value= 0
Control Change, ch=1, control #= 46, value= 1
Control Change, ch=1, control #= 46, value= 2
Control Change, ch=1, control #= 46, value= 3
Control Change, ch=1, control #= 46, value= 4
Control Change, ch=1, control #= 46, value= 5
Control Change, ch=1, control #= 46, value= 6
Control Change, ch=1, control #= 46, value= 7
Control Change, ch=1, control #= 46, value= 8
Control Change, ch=1, control #= 46, value= 9
Control Change, ch=1, control #= 46, value= 10
Control Change, ch=1, control #= 46, value= 11
Control Change, ch=1, control #= 46, value= 12
Control Change, ch=1, control #= 46, value= 13
Control Change, ch=1, control #= 46, value= 14
Control Change, ch=1, control #= 46, value= 15
Control Change, ch=1, control #= 46, value= 16
Control Change, ch=1, control #= 46, value= 17
Control Change, ch=1, control #= 46, value= 18
Control Change, ch=1, control #= 46, value= 19
Control Change, ch=1, control #= 46, value= 20
Control Change, ch=1, control #= 46, value= 21
Control Change, ch=1, control #= 46, value= 22
Control Change, ch=1, control #= 46, value= 23
Control Change, ch=1, control #= 46, value= 24
Control Change, ch=1, control #= 46, value= 25
Control Change, ch=1, control #= 46, value= 26
Control Change, ch=1, control #= 46, value= 27
Control Change, ch=1, control #= 46, value= 28
Control Change, ch=1, control #= 46, value= 29
Control Change, ch=1, control #= 46, value= 30
Control Change, ch=1, control #= 46, value= 31

etc, etc. (sequential between 0 and 127 when moving fader up and down)

In the example serial output shown above, I feel like I could save time if I did something like "ignoring" odd values if the fader is moving quickly (the current fader position is more important than where the fader was 1s ago) and receive all values if moving slowly. Not sure if filtering incoming midi messages is something I can do 'out the box' with the midi library?

The 16n midi controller does send midi cc messages very quickly when I connect it to synths and such that receive midi cc messages.

Thanks in advance for any guidance!

Whelp, with the help from Dave at https://www.partsnotincluded.com/, I learned that the latency was coming from the content I Serial.printing, not the actual arduino <--> midi communication.

Changing the original:

Serial.println(String("Control Change, ch=") + channel + ", control #= " + number + ", value= " + value);

to the following:

Serial.print(number);Serial.print(" ");Serial.print(value);
Serial.println();

removed most of the latency :slight_smile: