MIDI CLOCK performance problems with Teensy 3.2

Status
Not open for further replies.
Serial port / MIDI CLOCK problems with Teensy 3.2

Hi, I am working on a 32 step sequencer with various modes and tried to make it work on 16 channels simultanously.

The problem is, the BPM speed will decrease rapidly and become unstable, if I make more than 3 channels at the same time, or increase the clock divider with MIDI serial1 activated.
If I turn serial1 off and use only the USB MIDI the problem doesn't occur.
As the original code would be to big, I have broken it down to a test/performance code, which can be adjusted at int divider, int bpm and constant byte MAXTRACK. And off course deactivate the serial1.sendNoteOn for testing if USB works allone.
There will be some unnecessary parts be left in this code from the original like the pinouts or stp[], so don't note this.

I used an interrupt timer and I know you shouldn't use for examples arrays in it, but the problem doesn't occure with deactivated serial1.
To shorten the code for this inspection, I have left out all methods which fill out the arrays with own values and step information, chords and so on and made instead random values.
So there will be only the same chord per channel but on every channel another and it changes if you restart the device ;)
Am I overdoing it to the Teensy hardware or made I a concept failure or something else?

Code:
#include <MIDI.h>
#include <IntervalTimer.h>

USING_NAMESPACE_MIDI
MIDI_CREATE_INSTANCE(HardwareSerial ,Serial1, MIDI1);


const byte CLOCK = 0xF8; //248; 
const byte START = 0xFA; //250; 
const byte CONTINUE = 0xFB; //251; 
const byte STOP = 0xFC; //252;

const byte MAXTRACK = 4; //nr of max channels played simultanously

int bpm = 200;
int divider = 12; // 1/8 clock divider
//int divider = 6; // 1/16
//int divider = 3; // 1/32

byte seqValues[MAXTRACK][32] = {0};
byte seqControl[MAXTRACK][32] = {0};
float midIntervall = (1000.0/(float(bpm)/60.0))/24.0;

int tick[MAXTRACK] = {0}; //counter for ppq
int seqLength[MAXTRACK] = {0}; //length of step sequencer
int seqSpeed[MAXTRACK] = {0}; //clock divider
int stp[MAXTRACK] = {0}; //sequencer step position

IntervalTimer myTimer0;

void setup() {

  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, INPUT_PULLUP);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);

  for(int i = 0; i < MAXTRACK; i++){
    
    seqLength[i] = 16;
    seqSpeed[i] = divider;

    for(int j = 0; j < 32; j++){
      
      seqValues[i][j] = random(36,81); //filling random notes and velocities at start
      seqControl[i][j] = random(0,129);  
    }
  }
  MIDI1.begin(MIDI_CHANNEL_OMNI);
  myTimer0.begin(MidiClock, float(midIntervall) * 1000.0);
  myTimer0.priority(0);
}

void MidiClock() {

  usbMIDI.sendRealTime(CLOCK); //USB sync signal
  Serial1.write(CLOCK);        //DIN sync signal

  for (int i = 0; i < MAXTRACK; i++){
          
    RealTimeSystem(CLOCK,i);
  }
}

void RealTimeSystem(byte realtimebyte,byte curPattern) { // processes MIDI real time messages (MIDI system clock)

  boolean advance = false;

  if (realtimebyte == CLOCK) { // clock signal
    
    tick[curPattern]++;
    advance = (tick[curPattern] % seqSpeed[curPattern]) == 0;    
  }

  if (realtimebyte == START) {
    
    tick[curPattern] = 0;
  }

  if (realtimebyte == STOP) {
    
    StopLastInSequence(curPattern);
    ResetSequence();
  }

  if (advance) {

    tick[curPattern] = 0;
    SendNextInSequence(curPattern);    
  }
}

void ResetSequence() { //set all step sequencer position to 0

  for(int i = 0; i < MAXTRACK; i++){
    
    stp[i] = 0;
  }
}

void SendNextInSequence(byte curPattern) {
  
  byte seqChannel = curPattern + 1;

  StopLastInSequence(curPattern); //stops last played note before playing next

  usbMIDI.sendNoteOn(seqValues[curPattern][0],seqControl[curPattern][0],seqChannel);
  MIDI1.sendNoteOn(seqValues[curPattern][0],seqControl[curPattern][0],seqChannel);

  usbMIDI.sendNoteOn(seqValues[curPattern][1],seqControl[curPattern][1],seqChannel);
  MIDI1.sendNoteOn(seqValues[curPattern][1],seqControl[curPattern][1],seqChannel);

  usbMIDI.sendNoteOn(seqValues[curPattern][2],seqControl[curPattern][2],seqChannel);
  MIDI1.sendNoteOn(seqValues[curPattern][2],seqControl[curPattern][2],seqChannel);

  usbMIDI.sendNoteOn(seqValues[curPattern][3],seqControl[curPattern][3],seqChannel);
  MIDI1.sendNoteOn(seqValues[curPattern][3],seqControl[curPattern][3],seqChannel);
}

void StopLastInSequence(byte curPattern) {
  
  byte seqChannel = curPattern + 1;

  usbMIDI.sendNoteOn(seqValues[curPattern][0],0,seqChannel);
  MIDI1.sendNoteOn(seqValues[curPattern][0],0,seqChannel);

  usbMIDI.sendNoteOn(seqValues[curPattern][1],0,seqChannel);
  MIDI1.sendNoteOn(seqValues[curPattern][1],0,seqChannel);

  usbMIDI.sendNoteOn(seqValues[curPattern][2],0,seqChannel);
  MIDI1.sendNoteOn(seqValues[curPattern][2],0,seqChannel);

  usbMIDI.sendNoteOn(seqValues[curPattern][3],0,seqChannel);
  MIDI1.sendNoteOn(seqValues[curPattern][3],0,seqChannel);
}

void loop() {
  // put your main code here, to run repeatedly:

}
 
Last edited:
So I have made a workaround, until I found a better solution.

As I had thought, it seems to be a speed problem or a buffer overflow of the serial port.
I can leave it at 16 channel on usbMIDI and use only the first channel for sending both, USB and DIN at the same time.

But I hope, there will be information if I simply overload the serial port or if there is a solution that I didn't knew.

Somebody knows?
 
I have extended the TX buffer in serial1.c up to the maximum of 16k, and could see, that my program will run for 40 seconds stable, then the BPM speed will decrease and become unstable until it freezes my device completely.
I also have added Serial1.begin(32150) which I have increased to several higher baud rates like 500000. And I have changed the IntervalTimer.priority() to 80 because it has to be lower priority than the serial interrupt.
All without success.

Now I have read this article: https://www.codeproject.com/Articles/732646/Fast-digital-I-O-for-Arduino in which is written, that it may take 4 microseconds to change the logical level of an output pin.
Maybe my code is abusing the serial port, could this article help me by solving the problem or is it obsolete for Teensy 3.2?
 
I have found a way:

Code:
#include <MIDI.h>
#include <IntervalTimer.h>

USING_NAMESPACE_MIDI
MIDI_CREATE_INSTANCE(HardwareSerial ,Serial1, MIDI1);

const byte CLOCK = 0xF8; //248; 
const byte START = 0xFA; //250; 
const byte CONTINUE = 0xFB; //251; 
const byte STOP = 0xFC; //252;

const byte MAXTRACK = 16; //nr of max channels played simultanously //last working 1
const byte CLOCKBUFFER = 4;
const byte NBUFFER = 16;
const byte SBUFFER = 16;

int bpm = 400;
//int divider = 24; // 1/4
//int divider = 12; // 1/8 clock divider
//int divider = 6; // 1/16
int divider = 3; // 1/32

volatile byte seqValues[MAXTRACK][32] = {0};
volatile byte seqControl[MAXTRACK][32] = {0};
float midIntervall = 1000.0/(float(bpm)/60.0)/24.0;

volatile int tick[MAXTRACK] = {0}; //counter for ppq
int seqLength[MAXTRACK] = {0}; //length of step sequencer
int seqSpeed[MAXTRACK] = {0}; //clock divider
int stp[MAXTRACK] = {0}; //sequencer step position

IntervalTimer myTimer0;
IntervalTimer myTimer1;

void setup() {

  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, INPUT_PULLUP);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);

  for(int i = 0; i < MAXTRACK; i++){
    
    seqLength[i] = 32;
    seqSpeed[i] = divider;
  }
  RandomArrays();
  
  MIDI1.begin(MIDI_CHANNEL_OMNI);
  usbMIDI.begin();
  
  myTimer0.begin(MidiClock, midIntervall * 1000);
  myTimer0.priority(96);
  myTimer1.begin(NoteClock, midIntervall * 1000);
  myTimer1.priority(80);
}

void RandomArrays() {
  
  for(int i = 0; i < MAXTRACK; i++){
    for(int j = 0; j < 32; j++){
      
      seqValues[i][j] = random(36,81); //filling random notes and velocities at start
      seqControl[i][j] = random(64,128);  
    }
  }
}

void MidiClock() {
  
  if (Serial.availableForWrite() > CLOCKBUFFER) usbMIDI.sendRealTime(CLOCK); //USB sync signal
  if (Serial1.availableForWrite() > CLOCKBUFFER) Serial1.write(CLOCK);        //DIN sync signal  
}

void NoteClock() {

  for (int i = 0; i < MAXTRACK; i++){

    RealTimeSystem(CLOCK,i);
  }
}


void RealTimeSystem(byte realtimebyte,byte curPattern) { // processes MIDI real time messages (MIDI system clock)

  volatile boolean advance = false;

  if (realtimebyte == CLOCK) { // clock signal
    
    tick[curPattern]++;
    advance = (tick[curPattern] % seqSpeed[curPattern]) == 0;    
  }

  if (realtimebyte == START) {
    
    tick[curPattern] = 0;
  }

  if (realtimebyte == STOP) {
    
    StopLastInSequence(curPattern);
    ResetSequence();
  }

  if (advance) {

    tick[curPattern] = 0;
    SendNextInSequence(curPattern);    
  }
}

void ResetSequence() { //set all step sequencer position to 0

  for(int i = 0; i < MAXTRACK; i++){
    
    stp[i] = 0;
  }
}

void SendNextInSequence(byte curPattern) {
  
  volatile byte seqChannel = curPattern + 1;

  StopLastInSequence(curPattern); //stops last played note before playing next

    if (Serial.availableForWrite() > NBUFFER) usbMIDI.sendNoteOn(seqValues[curPattern][0],seqControl[curPattern][0],seqChannel);
    if (Serial1.availableForWrite() > NBUFFER) MIDI1.sendNoteOn(seqValues[curPattern][0],seqControl[curPattern][0],seqChannel);
  
    if (Serial.availableForWrite() > NBUFFER) usbMIDI.sendNoteOn(seqValues[curPattern][1],seqControl[curPattern][1],seqChannel);
    if (Serial1.availableForWrite() > NBUFFER) MIDI1.sendNoteOn(seqValues[curPattern][1],seqControl[curPattern][1],seqChannel);
 
    if (Serial.availableForWrite() > NBUFFER) usbMIDI.sendNoteOn(seqValues[curPattern][2],seqControl[curPattern][2],seqChannel);
    if (Serial1.availableForWrite() > NBUFFER) MIDI1.sendNoteOn(seqValues[curPattern][2],seqControl[curPattern][2],seqChannel);

    if (Serial.availableForWrite() > NBUFFER) usbMIDI.sendNoteOn(seqValues[curPattern][3],seqControl[curPattern][3],seqChannel);
    if (Serial1.availableForWrite() > NBUFFER) MIDI1.sendNoteOn(seqValues[curPattern][3],seqControl[curPattern][3],seqChannel);
    
  /*Serial.print(seqChannel);
  Serial.print(" ");  
  Serial.print(Serial.availableForWrite());
  Serial.print(" ");
  Serial.println(Serial1.availableForWrite());*/
}

void StopLastInSequence(byte curPattern) {
  
  volatile byte seqChannel = curPattern + 1;

    if (Serial.availableForWrite() > SBUFFER) usbMIDI.sendNoteOn(seqValues[curPattern][0],0,seqChannel);
   if (Serial1.availableForWrite() > SBUFFER) MIDI1.sendNoteOn(seqValues[curPattern][0],0,seqChannel);

    if (Serial.availableForWrite() > SBUFFER) usbMIDI.sendNoteOn(seqValues[curPattern][1],0,seqChannel);
    if (Serial1.availableForWrite() > SBUFFER) MIDI1.sendNoteOn(seqValues[curPattern][1],0,seqChannel);

    if (Serial.availableForWrite() > SBUFFER) usbMIDI.sendNoteOn(seqValues[curPattern][2],0,seqChannel); 
    if (Serial1.availableForWrite() > SBUFFER) MIDI1.sendNoteOn(seqValues[curPattern][2],0,seqChannel);

    if (Serial.availableForWrite() > SBUFFER) usbMIDI.sendNoteOn(seqValues[curPattern][3],0,seqChannel);
    if (Serial1.availableForWrite() > SBUFFER) MIDI1.sendNoteOn(seqValues[curPattern][3],0,seqChannel);

}

void loop() {
  // put your main code here, to run repeatedly:

}

My thought was to drop notes if the buffer is going full. But I have watched it in MIDI-OX and no note or note-off will be dropped, at 16 channels at once with 4-note-chord at 1/32 note with 400 BPM.
Now it seems to work fine, the buffer doesn't overflow and the speed keeps stable.
 
Last edited:
Running those timers (which do USB communication) at higher interrupt priority than the USB interrupt is just asking for all sorts of terrible trouble.

I just looked into the serial buffer issue. Indeed there is a problem where incoming data can starve other USB endpoints of memory.

That may or may not be related to your trouble here. At the very least, run this with the timers are default priority before you jump to the conclusion anything else is wrong.
 
Last edited:
Thank you!

I just wanted to set the timer interrupt priority lower than the serial interrupt without thinking of USB. XD

My impression is that writing to the serial without controlling if it is ready to write was the culprit.
I have watched it in MIDI-OX, no note or sync signal is dropped if I juse "Serial.availableForWrite()" or "Serial1.availableForWrite()" before writing.
 
For future reference, from other threads... the priorities are as follows:
All the interrupts default to priority 128
USB defaults to 112
The hardware serial ports default to 64
systick defaults to 0
 
Status
Not open for further replies.
Back
Top