Closer consecutive Midi in readings

Status
Not open for further replies.

Sandro

Well-known member
Hi all, my code for an audio sampler (complete code is here: https://github.com/SandroGrassia/Lilla-audio-sampler) reads MIDI every 2.9 ms; I use an object Trigger.h which doesn't send any sound, only receive an update() call every 2.9ms and send an update() call to an object Midi_Reader.h which reads midi-in.
Actually the behave is very good, but I'd like to exeperiment closer consecutive readings, for example every 1.45ms or less but... how could I make an inner "metronome" faster than 2.9ms? Any idea?
 
Hi Sandro, that looks like an interesting project. update is getting called every 128 samples (128@44.1k=2.9ms) you can change the number of samples in a block from 128 to say 16 or 32 or 64 and the time between calls will reduce. edit AUDIO_BLOCK_SAMPLES in ..../cores/teensy4/AudioStream.h

That is an unusual approach (using the audio objects to call midi) - your code is complex so just had a quick look but is there a reason for calling midi updates that way?

cheers Paul
 
Hi Paul, thank you for your answer! I know that AUDIO_BLOCK_SAMPLES can be changed, but I would avoid it, because than I should go hunting for constants in my code connected to 128 samples... don't know all the implications. About your question, the reason is that if the midi-in read function Midi.read() was call in the main code, than the time lapse between consecutive midi readings would be randomic, and unacceptable the delay between pressed key and sound. Actually, constant 2.9ms beteen readings is very good, using both a keyboard and a sequencer, but I would check the behave with a faster response.
 
How long does your main loop take? I have a main loop consisting of over 250 if statements (and actions to perform) and it takes a whopping 15uS for my main loop to run, 20uS worse case. Have you checked yours? 100uS jitter is pretty inconsequential for a midi channel in this context.
 
Hi boxxofrobots, thank you for your answer; I had a problem with display (128x160 SPI display, with Adafruit library Adafruit_ST7735.h) operations, which require long time to be executed.
In order to compare the values you sent me, I used a previous version (Teensy 3.6 @256MHz) of my sampler, whith the previous software version, where MIDI.read is inside loop() and the display is updated immediately after receiving a command.
In a case like this:
Code:
if(Read_encoder_volume)
{
update_some_data;
display_volume_value; // cancel the previous value (2 digits), writes the new value
}

the loop() execution time is:
no operations: 138 us
moving volume encoder: 500-700us

And this can be still acceptable for midi in; everything changes with this code:

Code:
if(Read_encoder_volume)
{
update_some_data;
display_volume_value; // cancel previous value (2 digits), writes the new value
display_waveform; // cancel the waveform area, draws the waveform (made up with 128 points and 127 lines) and some numbers in the waveform area
}

here the loop() execution time rises to 60.000us, which cause an unacceptable delay for midi reading...
 
If you want shorter loop times when you are using displays then you have to buffer the entire display and write the buffer to the display. Since you are using the Adafruit-GFX-Library you don’t have to change or add much code to switch to using a buffer instead. They have GFXcanvas objects that accept all the same functions as the displays do and instead of outputting to a display it saves it into a buffer. Then you can draw the canvas object to your display all at one time which is much faster than doing all of the drawing straight to the display. This also lets you easily cap the FPS of the display so that it’s not updating with every loop and causing any kind of slow downs from SPI.
 
Hi vjmuzik, great info, I didn't know about this canvass... I'll try soon, thank you!!!
 
Hi Paul, thank you for your answer! I know that AUDIO_BLOCK_SAMPLES can be changed, but I would avoid it, because than I should go hunting for constants in my code connected to 128 samples...

You should do this anyway I think, since any such constants should have been defined in terms of AUDIO_BLOCK_SAMPLES
from the get-go?
 
They have GFXcanvas objects that accept all the same functions as the displays do and instead of outputting to a display it saves it into a buffer. Then you can draw the canvas object to your display all at one time which is much faster than doing all of the drawing straight to the display.
Wow that is great advice - thank you (I am facing similar kinds of issue).

Sandro, What I've done is to split a lot of my UI stuff out to a separate micro too. So I will have dual joystick, buttons, 8 encoders and so on being serviced by an AVR micro (also loads of IO pins so need for multiplexing) then just have that report "UI updates" by UART to the Teensy. It should remove "noise" and CPU overhead from the Audio part of things. Might be worth exploring?
 
You should do this anyway I think, since any such constants should have been defined in terms of AUDIO_BLOCK_SAMPLES
from the get-go?
Thank you Mark, I completely agree; maybe everything in code is alreadly correctly calculated using AUDIO_BLOCK_SAMPLES, but I should check. Very important to prevent bugs.
 
You should do this anyway I think, since any such constants should have been defined in terms of AUDIO_BLOCK_SAMPLES
from the get-go?
Thank you Mark, I completely agree; maybe everything in code is alreadly correctly calculated using AUDIO_BLOCK_SAMPLES, but I should check. Very important in order to prevent bugs.
 
Wow that is great advice - thank you (I am facing similar kinds of issue).

Sandro, What I've done is to split a lot of my UI stuff out to a separate micro too. So I will have dual joystick, buttons, 8 encoders and so on being serviced by an AVR micro (also loads of IO pins so need for multiplexing) then just have that report "UI updates" by UART to the Teensy. It should remove "noise" and CPU overhead from the Audio part of things. Might be worth exploring?

Hi wrightflyer, in my project 33 user interfaces have to be monitored (15 encoders and 18 pushbuttons), while display must be kept updated, midi has to listen for messages and code decide what to do, 16 voices have to read audio samples from different sources and make a lot of interopolations, and so on. My strategy is use one micro and use priority: first audio (of course!) than midi, than commads, than display. Audio Library gives a dramatic help because audio interrupts always guarantees that audio jobs are done with "maximum" priority; for human interfaces my main code looks for an event, than pulls up a flag, and waits for an "external object" (a "fake" audio object) to update data; than, when the external object has finished (maximum time should be 2.9ms), main code is allowed to go on, updating the display if needed, than hunting for next event. The behave is good in my application.
 
They have GFXcanvas objects that accept all the same functions as the displays do and instead of outputting to a display it saves it into a buffer. Then you can draw the canvas object to your display all at one time which is much faster than doing all of the drawing straight to the display. This also lets you easily cap the FPS of the display so that it’s not updating with every loop and causing any kind of slow downs from SPI.
Hi vjmuzik, by the way do you know if there is somewhere an example/application of GFXcanvas? i cannot find a good documentation, and cannot get the example inside Arduino IDE working with my display...
 
Yeah there isn't really any documentation for it that I remember, I figured out how to use it myself. The gist of it is that you can replace any command that you've made with the tft such as tft.fillScreen with the canvas and it stores all the pixel data in the canvas. Then when you want to write the canvas to the display you just have to send it like you do any other bitmap. Here's an example I've used in a different thread for 6 displays that just uses the fillScreen command to swap between colors. You can easily modify this for your display, if it's still not fast enough I can show you the minor modifications I made to the libraries that sped it up even more.
Code:
//Will be slow when updating all displays without unofficial modifications to Adafruit_GFX and SPI
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>

//Change these if needed
uint8_t displayNumber = 6;  //Number of displays to update, only 6 are setup below
uint8_t targetFPS = 24;     //FPS to target

//Don't change
elapsedMillis oneSecond;
elapsedMillis internalRefresh;
uint32_t looped = 0, internal = 0, fps = 0;
elapsedMicros screenRefresh;
uint8_t displayToUpdate = 0;

uint16_t solidColor = 0;

Adafruit_ST7735 display0 = Adafruit_ST7735(&SPI, 35, 34, 33);
Adafruit_ST7735 display1 = Adafruit_ST7735(&SPI, 36, 34, -1);
Adafruit_ST7735 display2 = Adafruit_ST7735(&SPI, 37, 34, -1);
Adafruit_ST7735 display3 = Adafruit_ST7735(&SPI, 44, 34, -1);
Adafruit_ST7735 display4 = Adafruit_ST7735(&SPI, 45, 34, -1);
Adafruit_ST7735 display5 = Adafruit_ST7735(&SPI, 46, 34, -1);
Adafruit_SPITFT *displays[6] = {
  &display0,
  &display1,
  &display2,
  &display3,
  &display4,
  &display5,
};

const uint16_t canvasWidth = 80;
const uint16_t canvasHeight = 160;
GFXcanvas16 Canvas0 = GFXcanvas16(canvasHeight, canvasWidth);
GFXcanvas16 Canvas1 = GFXcanvas16(canvasHeight, canvasWidth);
GFXcanvas16 Canvas2 = GFXcanvas16(canvasHeight, canvasWidth);
GFXcanvas16 Canvas3 = GFXcanvas16(canvasHeight, canvasWidth);
GFXcanvas16 Canvas4 = GFXcanvas16(canvasHeight, canvasWidth);
GFXcanvas16 Canvas5 = GFXcanvas16(canvasHeight, canvasWidth);
GFXcanvas16 *Canvas[6] = {
  &Canvas0,
  &Canvas1,
  &Canvas2,
  &Canvas3,
  &Canvas4,
  &Canvas5,
};

void setup() {
  // put your setup code here, to run once:
  delay(1000);
  Serial.begin(115200);

  for(uint8_t i = 0; i < displayNumber; i++){
    Adafruit_ST7735* disp = (Adafruit_ST7735*)displays[i];
//    disp->initS(); //Unofficial modification for ST7735S displays
    disp->initR(INITR_MINI160x80);
    disp->setSPISpeed(79999999);
    Canvas[i]->setRotation(1);
    Canvas[i]->setTextWrap(0);
  }
  
  fps = 0;
  looped = 0;
  internal = 0;
}

void loop() {
  // put your main code here, to run repeatedly:
  while(true){
    screenUpdate();
  
    if(internalRefresh >= 500){ //Update or read peripherals here, lower refresh rate if needed
      internalRefresh -= 500;
      internal++;

      //Screen color is being changed here
      static uint8_t toggle = false;
      toggle++;
      if(toggle == 3){
        solidColor = display0.color565(0,0,0xFF);
        toggle = 0;
      }
      else if(toggle == 2){
        solidColor = display0.color565(0,0xFF,0);
      }
      else if(toggle == 1){
        solidColor = display0.color565(0xFF,0,0);
      }
    }
    
    looped++;
    if(oneSecond >= 1000){
      oneSecond -= 1000;
      Serial.print("FPS: ");
      Serial.print(fps);
      Serial.print("  Looped: ");
      Serial.print(looped);
      Serial.print("  Internal: ");
      Serial.println(internal);
      fps = 0;
      looped = 0;
      internal = 0;
    }
  }
}

void screenUpdate(){  //Displays are refreshed here
  if(Serial.available()){
    targetFPS = (uint8_t)Serial.parseInt();
    Serial.print("Setting FPS to ");
    Serial.println(targetFPS);
  }
  if(screenRefresh >= (uint32_t)round((1000000/targetFPS/displayNumber))){  //Display refresh interval based on the number of displays and desired FPS
//    screenRefresh -= (uint32_t)round((1000000/targetFPS/displayNumber));
    screenRefresh = 0;

    drawDisplay(displays[displayToUpdate], Canvas[displayToUpdate]);
    
    displayToUpdate++;
    if(displayToUpdate == displayNumber){
      displayToUpdate = 0;
      fps++;
    }
  }
}

void drawDisplay(Adafruit_SPITFT *display, GFXcanvas16* canvas){  //Displays are drawn here (only make changes to the canvas here before drawing to your display for increased efficiency)
  //Use varibles to keep track of what to draw to the canvas then make the updates here accordingly
  canvas->fillScreen(solidColor);  //Change canvas color
  display->drawRGBBitmap(0,0,canvas->getBuffer(), canvas->height(), canvas->width());  //Draw to display once canvas is ready
}
 
Hi vjmuzik, thank you so much for your code! I tried with this simplified version, for Teensy 3.6 and one 128x160 display:
Code:
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>

#define Sclk 32
#define Mosi 21
#define Cs   31
#define Dc   29
#define Rst  30

Adafruit_ST7735 display0 = Adafruit_ST7735(Cs, Dc, Mosi, Sclk, Rst);
Adafruit_SPITFT *_display = &display0;
const uint16_t canvasWidth = 128;
const uint16_t canvasHeight = 160;
GFXcanvas16 canvas0 = GFXcanvas16(canvasHeight, canvasWidth);
GFXcanvas16 *_canvas = &canvas0;

void setup()
{
  Adafruit_ST7735 *_disp = (Adafruit_ST7735*) _display;
  // disp->initS(); //Unofficial modification for ST7735S displays
   _disp->initR(INITR_BLACKTAB); // I'm using a 1.8" TFT 128x160 displays
  // _disp->setSPISpeed(79999999);
  _canvas->setRotation(0);
  _canvas->setTextWrap(0);
}

void loop()
{
  _canvas->fillScreen(ST7735_RED);
  _canvas->drawFastVLine(10, 10, 50, ST7735_WHITE);
  _display->drawRGBBitmap(0, 0, _canvas->getBuffer(), _canvas->height(), _canvas->width());
  delay(1000);

  _canvas->fillScreen(ST7735_GREEN);
  _display->drawRGBBitmap(0, 0, _canvas->getBuffer(), _canvas->height(), _canvas->width());
  delay(1000);
}

I decide to comment _disp->setSPISpeed(79999999) just because I cannot find a working value... The result is that fillscreen function works, while drawFastVLine draws 4 vertical dotted lines. I'd be grateful if you had time to have a look at this code..
 
Here this should work, I had the height and width swapped in my code that I copy and pasted because I had my displays vertical that's why the setRotation was in there.
Code:
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>

#define Sclk 32
#define Mosi 21
#define Cs   31
#define Dc   29
#define Rst  30

Adafruit_ST7735 display0 = Adafruit_ST7735(Cs, Dc, Mosi, Sclk, Rst);
Adafruit_SPITFT *_display = &display0;
const uint16_t canvasWidth = 160;
const uint16_t canvasHeight = 128;
GFXcanvas16 canvas0 = GFXcanvas16(canvasWidth, canvasHeight);
GFXcanvas16 *_canvas = &canvas0;

void setup()
{
  Adafruit_ST7735 *_disp = (Adafruit_ST7735*) _display;
//   _disp->initS(); //Unofficial modification for ST7735S displays
   _disp->initR(INITR_BLACKTAB); // I'm using a 1.8" TFT 128x160 displays
  // _disp->setSPISpeed(79999999);
  _canvas->setRotation(0);
  _canvas->setTextWrap(0);
}

void loop()
{
  _canvas->fillScreen(ST7735_RED);
  _canvas->drawFastVLine(10, 10, 50, ST7735_WHITE);
  _display->drawRGBBitmap(0, 0, _canvas->getBuffer(), _canvas->width(), _canvas->height());
  delay(1000);

  _canvas->fillScreen(ST7735_GREEN);
  _display->drawRGBBitmap(0, 0, _canvas->getBuffer(), _canvas->width(), _canvas->height());
  delay(1000);
}
 
Here this should work, I had the height and width swapped in my code that I copy and pasted because I had my displays vertical that's why the setRotation was in there.

Great! It works perfectly! Thank you vjmuzik, also for your super quick answers :)
Sandro
 
Last edited:
Status
Not open for further replies.
Back
Top