Audio Sequencer: IntervalTimer + ILI9341 + AudioPlaySdWav

Status
Not open for further replies.
Hi to everyone, I am new to this forum and also to the world of microcontrollers, I need a suggestion on a project that I started to develop: a small sequencer that uses the audio library to play some samples in conjunction with a TFT display to show some graphics.

I am trying to understand what is the right approach, so far I tried to set up an IntervalTimer that will be my clock, which will check the state of a pattern and in case it will play the samples from an sd card. In the meantime the screen should show some knob values.

this is my timer function

Code:
IntervalTimer timer;

void setup() {
//various config are excluded
timer.priority(250);
timer.begin(tick, 50000);
}


volatile unsigned long patternLength = 1;
volatile unsigned long patternDivision = 16;
volatile unsigned long tickPosition = 0;

int pattern0[] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};

int pattern1[] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};

void tick() {
  if(pattern0[tickPosition]==1){  
    playSdWav1.play("KICK.WAV");
  }

  if(pattern1[tickPosition]==1){
    playSdWav1.play("SNARE.WAV");
  }
  tickPosition ++;
  if(tickPosition == patternLength * patternDivision){
    tickPosition=0;
  }
}

This works fine, but I don't know if it is the right method.

Anyway, when in my loop I also try to call a function to draw the tft, everything freeze:

Code:
void loop() {
  drawScreen();
}

//code copied from the audio tutorial
void drawScreen(){
    int leftNumber = map(analogRead(A3),0,1023,0,240);
    int rightNumber = map(analogRead(A2),0,1023,0,240);
    // draw the verticle bars
      int height = leftNumber ;
      tft.fillRect(60, 280 - height, 40, height, ILI9341_GREEN);
      tft.fillRect(60, 280 - 240, 40, 240 - height, ILI9341_BLACK);
      height = rightNumber ;
      tft.fillRect(140, 280 - height, 40, height, ILI9341_GREEN);
      tft.fillRect(140, 280 - 240, 40, 240 - height, ILI9341_BLACK);
      delayMicroseconds(300);
}


I'd like to know what I am doing wrong, what I should consider and/or reconsider from a design perspective.

The screen works perfectly when I don't initialise the timer, and vice versa.

As I will also integrate other functions (recording steps, reading sensor, etc..) I'd like to know what is the proper way to approach this design.

Thanks!
 
Look into using an elapsedMilli variable to remove the delay in drawScreen()

Also - how fast should the screen update? 1000000/300 is a big number for screen refresh rate.

Drawing both bars in full is going to cause flicker and slowness. Track the left/right numbers between calls and only redraw the difference area. Here is a sample I did do that for a full screen of bars for a different display.

Initiating the playSdWav1 in the interrupt code isn't good. Just set an 'index' into a global variable and have loop do the playSdWav1 when the index is changed.
 
Hi defragster, thanks for your reply!

Ok, I modified the tft function as suggested. About the playSdWav1, I tried to use a flag in the timer and to use it in the loop but it isn't steady, it kind of fluctuate in time, and other functions and processes also influence this time fluctuation(for example a knob twist). I thought there would be a way to give absolute priority to the clock and to the sample start, and to leave pushbuttons, knobs, the screen and all the rest in the loop.

When I play the wavs in the IntervalTimer the timing is rock solid.

Here the code with the flag:

Code:
volatile boolean playSample1 = false;
volatile boolean playSample2 = false;

void tick() {
  if(pattern0[tickPosition]==1){  
     playSample1 = true;   
  }
  if(pattern1[tickPosition]==1){
     playSample2 = true;   
  }
  tickPosition ++;
  if(tickPosition == patternLength * patternDivision){
    tickPosition=0;
  }
}

void loop() {
  if(playSample1 == true){
    playSample1 = false;
    playSdWav1.play("KICK.WAV");
  }
  if(playSample2 == true){
    playSample2 = false;
    playSdWav2.play("SNARE.WAV");
  }
  drawScreen();
}


Thanks for your help!
 
Without the second if() being an else if() - both sounds are trying to start at the same time? I don't have the process for that memorized - It was that way before - does one sound starting stop the other sound? Perhaps check if a sound is playing and wait for it to stop before starting the next sound?

Would be interested in seeing your resultant display code. Also taking out the delay will make everything else you add later run more smoothly. And learning the use of the elapsedMicros would be fitting here and helpful in the future.
 
The 2 sound sources are independent and they do not interfere with each other. I also tried to start and stop the sounds before playing, but the result is still the same.

About the display, I just integrated the millis, like so:

Code:
elapsedMillis msecs;

void drawScreen(){
  
  if (msecs > 50) {
    msecs = 0;
   //code to display
  }
}

I still have to work on the display code, but right now I would like to reach a steady timing with the sound sources. So far msec at 50 react smoothly when I move a knob.

I feel like there should be a way to use a timer to play the sounds, but I haven't found a proper solution..
 
The same code for the display would work for the sounds without the timer.

Code:
elapsedMillis msecsSight;
elapsedMillis msecsSound;

void loop() {

  if ( msecsSight > 50) {
    msecsSight = 0;
    drawScreen(); //code to display
  }
  if (msecsSound > 500) {
    msecsSound = 0;
    //decide to play sounds
    if (pattern0[tickPosition] == 1) {
      playSdWav1.play("KICK.WAV");
    }

    if (pattern1[tickPosition] == 1) {
      playSdWav1.play("SNARE.WAV");
    }
    tickPosition ++;
    if (tickPosition == patternLength * patternDivision) {
      tickPosition = 0;
    }
  }

}
 
Your solution is indeed a big step forward, now both the screen and the sounds are playing nicely and the time is pretty stable, but not as stable as the IntervalTimer.

Should I maybe try to save the wav from the sd to the memory? Maybe with a buffer? I'd like to avoid using wave2sketch because I am interested in saving various sounds on the sd card and loading 2 selected ones on the fly.
 
I'd expect it to work about the same - the timing may not be as exact but unless there is a hitch as you have it coded it shouldn't be off too much - and general usability as you add more code should be better. If you post your current code maybe something else will show, or I could try it myself (indicate the hardware you have).

The hitch is likely the time in the drawing code - the IntervalTimer will interrupt that - but the code above will make it wait until the drawing completes. Taking out the delay() and also only drawing the added height to the L/R bars or erasing the excess (a few pixel rows) from the last value will speed that dramatically and it will return hundreds of times faster when no or minimal change to the values is seen.

I'm not sure if you looked at the link to the ILI9163 (I had that working the same on the ILI9341) gave you the tips you need to edit your code? [that code had me confused a bit] You'll need a static Left/Right initialized to a value (0 or -1 or 4096 or ?) that will force you to draw both colors completely as you did at above the first time. Then on exit update those with the last seen values. On entry if either is the same (after doing the map()) - don't draw that one. If either is off 'x' pixels then either draw/GREEN only the "added/>" 'x' pixels above or erase/BLACK the 'x' pixels "below/<=" the old value for each Left/Right.
 
I haven't tried your tft code suggestion as I need to work on it with a clearer mind (here is night already), tomorrow I will make new tests.

Thank you very much for your help! I'll post the updated code as soon as possible.
 
Status
Not open for further replies.
Back
Top