Bug report: Teensy 4.0 and AudioPlayQueue

When AudioPlayQueue.getBuffer() and AudioPlayQueue.playBuffer() are used in an audio rate ISR, they slowly consume AudioMemory buffers and T4.0 crashes when all buffers are used (default 80 for IMXRT1062). The same code works flawlessly on Teensy 3.6 for hours, on 4.0 it cracks up in ~570s. I tried everything I could to bypass this issue, but no luck so far.

Here's a code to test/reproduce this issue. I would be thankful if someone could run this on T4.0 and report back. If you reduce to AudioMemory(16) in Setup, it will freeze in ~100s.
Code:
#include <Audio.h>
#include <SerialFlash.h>

AudioPlayQueue           audio;
AudioPlayQueue           audio2;
AudioOutputI2S           i2s1;
AudioConnection          patchCord1(audio, 0, i2s1, 0);
AudioConnection          patchCord2(audio2, 0, i2s1, 1);

uint8_t cycle=0;
elapsedMillis tme;

void audioISR()    //dummy ISR routine to test getBuffer()/playBuffer()
{
  if((cycle % 128) == 127){     //for 128 int16 array
    int16_t *p1 = audio.getBuffer();
    p1 = p1; // to avoid unused variable compiler warning
    audio.playBuffer();
  }
  cycle++;
}

void setup()
{
  AudioNoInterrupts();
  noInterrupts();
  AudioMemory(512);
  Serial.begin(9600);
  IntervalTimer *t1 = new IntervalTimer();
  t1->begin(audioISR, 1000000.0f / 44100.0f);
  interrupts();
  AudioInterrupts();
}

void loop()
{
  if (tme>10000)
  {
    tme=0;
    Serial.printf("Run time: %i s \n", (millis()/1000));
    Serial.printf("CPU: %.2f (%.2f),  MEM: %i (%i)\n",
    AudioProcessorUsage(), AudioProcessorUsageMax(),
    AudioMemoryUsage(), AudioMemoryUsageMax());
    AudioProcessorUsageMaxReset();
    AudioMemoryUsageMaxReset();
  }
}

Reports from Serial Monitor:

Teensy 4.0, no AudioPlayBuffer

Run time: 10s
CPU: 0.51 (0.54), MEM: 2 (2)

Run time: 200s
CPU: 0.51 (0.54), MEM: 2 (2)

Run time: 2340s
CPU: 0.51 (0.54), MEM: 2 (2)

Teensy 4.0, one AudioPlayBuffer

Run time: 10s
CPU: 0.52 (0.57), MEM: 3 (4)

Run time: 20s
CPU: 0.52 (0.56), MEM: 4 (5)

Run time: 30s
CPU: 0.52 (0.57), MEM: 6 (7)

Run time: 200s
CPU: 0.52 (0.57), MEM: 29 (30)

Run time: 560s
CPU: 0.52 (0.57), MEM: 79 (80)

Run time: 570s
CPU: 0.52 (0.57), MEM: 80 (81)

======= CRASH =======

Teensy 4.0, two AudioPlayBuffers

Run time: 10s
CPU: 0.52 (0.55), MEM: 4 (6)

Run time: 20s
CPU: 0.52 (0.55), MEM: 6 (8)

Run time: 30s
CPU: 0.52 (0.55), MEM: 10 (12)

Run time: 200s
CPU: 0.52 (0.55), MEM: 56 (58)

Run time: 560s
CPU: 0.52 (0.56), MEM: 156 (158)

Run time: 570s
CPU: 0.52 (0.56), MEM: 158 (160)

======= CRASH =======

Teensy 3.6, two AudioPlayBuffers

Run time: 10s
CPU: 0.89 (0.96), MEM: 4 (4)

Run time: 200s
CPU: 0.89 (0.97), MEM: 2 (4)

Run time: 2340s
CPU: 0.89 (0.95), MEM: 2 (4)

Thanks in advance for your kind answers!
 
It is not safe to use getBuffer or playBuffer in an interrupt routine. The getBuffer function can call yield() and playBuffer has a while loop which won't terminate unless something external (in an interrupt) happens.
I don't know why it doesn't crash in a T3.6, perhaps you need to let it run much longer.

Pete
 
Like I said, it was tested on T3.6 for hours and it works very well. I'm aware it's potentially unsafe, but:
a) It doesn't crash just like that, I specified exactly what's going on. I know why it happens and when it happens, I just have no idea why or how to prevent/solve it
b) It's AFAIK the only way to communicate data from Arduino sketch to the AudioLibrary. That's a bummer
c) How else are you supposed to use getBuffer and playBuffer with just 128 words of data? It gets exhausted in 0.003s, certainly calling it in Void Loop won't do the trick :(

If anybody knows a way to use Teensy 4 I2S/TDM without audio library, suggestions are welcome.
 
certainly calling it in Void Loop won't do the trick
Why not? The T4 chugs along at 600MHz. It can do a lot in 3 milliseconds.

Try this:
Code:
#include <Audio.h>
#include <SerialFlash.h>

AudioPlayQueue           audio;
AudioPlayQueue           audio2;
AudioOutputI2S           i2s1;
AudioConnection          patchCord1(audio, 0, i2s1, 0);
AudioConnection          patchCord2(audio2, 0, i2s1, 1);

uint8_t cycle=0;
elapsedMillis tme;

// set by the ISR
volatile uint8_t int_flag = 0;

void audioISR()    //dummy ISR routine to test getBuffer()/playBuffer()
{
  int_flag = 1;
}

void setup()
{
  AudioNoInterrupts();
  noInterrupts();
  AudioMemory(512);
  Serial.begin(9600);
  IntervalTimer *t1 = new IntervalTimer();
  t1->begin(audioISR, 1000000.0f / 44100.0f);
  interrupts();
  AudioInterrupts();
}

void loop()
{
  if( int_flag) {
    int_flag = 0;
    if((cycle % 128) == 127) {    //for 128 int16 array
      int16_t *p1 = audio.getBuffer();
      p1 = p1; // to avoid unused variable compiler warning
      audio.playBuffer();
    }
    cycle++;
  }
  if (tme>10000) {
    tme=0;
    Serial.printf("Run time: %i s \n", (millis()/1000));
    Serial.printf("CPU: %.2f (%.2f),  MEM: %i (%i)\n",
          AudioProcessorUsage(), AudioProcessorUsageMax(),
          AudioMemoryUsage(), AudioMemoryUsageMax());
    AudioProcessorUsageMaxReset();
    AudioMemoryUsageMaxReset();
  }
}

Pete
 
I am a little puzzled why you use TWO interrupt routines, the queue in the audio lib and an additional one!? But maybe I am misunderstanding your approach.
I use the record and the play queue with great success with both, the T3.6 and the T4.0 without problems.
But I do NOT use an IntervalTimer, but I only use the audio lib with its built-in interrupt.
Maybe also you have to use FreeBuffer in order to release audio blocks?
 
Why not? The T4 chugs along at 600MHz. It can do a lot in 3 milliseconds.

Well, because I'm doing some other not-so-timing-sensitive things in Void Loop :D
Reading inputs, calculating huge amounts of side data and drawing a meaningful representation of all that to SPI LCD takes exactly 7ms per one loop (and I had to write my own specialized routine to drive the LCD, otherwise with the generic library it took 36ms :rolleyes:). You can imagine what it sounds like with 7ms delay...

Try this:
// set by the ISR
volatile uint8_t int_flag = 0;

void audioISR()
{
int_flag = 1;
}

Thanks for your sugestion Pete, that's exactly where I was heading! In fact I'm trying to do just that right now, even called the variable int_flag (but declared volatile bool instead of uint8) :eek: I'll try to prioritize playBuffer() routine and use the remaining time for everything else, hopefully that will not screw up the rest of things going on in the loop.
 
I am a little puzzled why you use TWO interrupt routines, the queue in the audio lib and an additional one!? But maybe I am misunderstanding your approach.

I'm actually using three interrupt routines in my code, not counting the Audio Lib (which I don't have any control over) :D One fast at audio rate (44.100Hz) to produce the complex waveforms, one medium at 8.000Hz to crunch and prepare the math calculations for the filters and one slow at 1.000Hz to process the inputs :eek:

I use the record and the play queue with great success with both, the T3.6 and the T4.0 without problems. But I do NOT use an IntervalTimer, but I only use the audio lib with its built-in interrupt.

That's exactly the problem: Audio Lib just doesn't do what I'm trying to accomplish, and the methods to communicate between Arduino sketch and the library are severely limited. So I'm doing everything that's needed in my own routines, and then just serve them to the Audio Lib playBuffer() for output.

Maybe also you have to use FreeBuffer in order to release audio blocks?

I was under impression that's done automatically by the Audio Lib when all audio blocks are used? At least it works that way on T3.6, beats me why the things would be any different on T4.0. But I'll certainly try if nothing else works, thanks for your suggestion.
 
Warning, I more or less never use Audio library... Not because anything is wrong with it, but more because most Audio I typically need is a series of Beeps...

But if it were me and I needed (or really wanted to control this with ISR doing this.)

The things I would experiment with include:

a) Don't call getBuffer from ISR as this can call yield... Instead call available and bail if it returns false. Or better yet remember it failed and try again on your next ISR.

b) See if one could add a new member function which tries to queue the buffer, but fails if queue is full, and again remember this and try to queue it on the next ISR.
This function would be simple... Not sure what @PaulStoffregen would think of it worked and you did a PR... But something like:
Code:
bool AudioPlayQueue::queueBufferNoWait(void)
{
	uint32_t h;

	if (!userblock) return false;
	h = head + 1;
	if (h >= max_buffers) h = 0;
	if (tail == h) return false;
	queue[h] = userblock;
	head = h;
	userblock = NULL;
	return true;
}

But again I don't know if this will fix your issue or not. Hard to know exactly where you are, what crashed...
 
Warning, I more or less never use Audio library... Not because anything is wrong with it, but more because most Audio I typically need is a series of Beeps...

It's OK, we all have our thing and I really want to hear all sugestions and/or feedback :D

But if it were me and I needed (or really wanted to control this with ISR doing this.)

The things I would experiment with include:

a) Don't call getBuffer from ISR as this can call yield... Instead call available and bail if it returns false. Or better yet remember it failed and try again on your next ISR.

Ugh. I've tried to send a flag from ISR and use getBuffer() and playBuffer() in the loop() - been testing this for a few days - but it's extremely unstable. I have to put all blocking stuff in the loop, so there's a 7ms delay between cycles... Even if I comment out all blocking routines, the results are unpredictable - ISRs suck the most of available cycles and it produces all kinds of noises and bumps. All this must be pretty much confusing for the guys who use Teensy to do simpler stuff, but I'm seriously thinking about installing a cooler and bumping it up to 1GHz :rolleyes:
TL/DR: loop() is not usable.

b) See if one could add a new member function which tries to queue the buffer, but fails if queue is full, and again remember this and try to queue it on the next ISR.
This function would be simple... Not sure what @PaulStoffregen would think of it worked and you did a PR...

Thanks for your suggestion Kurt, I'll try this for sure!

But again I don't know if this will fix your issue or not. Hard to know exactly where you are, what crashed...

We'll see, I've been testing this for a long time. Lowering the proc speed doesn't help, if the ISR is slowed down to 22.050Hz everything works, if I skip every second cycle everything works, if I use T3.6 everything works, so obviously the ISR on T4.0 is interrupting the Audio Lib before it can release memory. I've described the problem in details, and would really like to hear from @PaulStoffregen about this :eek:
 
Hello,

this thread is a bit older, but the problem is still actual.
I tried a simple code without any extras on a Teensy 4.1.
At the defined max of 80 blocks the loop stucks.

I'm using Arduino 1.8.5 and Teensyduino 1.53.

Code:
#include <Audio.h>

AudioPlayQueue  playQueue;

int16_t   sampleBuffer[AUDIO_BLOCK_SAMPLES];
int16_t   *queueTransmitBuffer;


void PlayQueue() {
    queueTransmitBuffer = playQueue.getBuffer();
    if (queueTransmitBuffer) {
      memcpy(queueTransmitBuffer, sampleBuffer, AUDIO_BLOCK_SAMPLES * 2);
      Serial.print("Audio Mem Used: ");  
      Serial.println(AudioMemoryUsage());
      playQueue.playBuffer();
    }
    else { digitalWriteFast(13, HIGH); }
}

void setup() {
    Serial.begin(115200);
    delay(250);
    AudioMemory(100);
    pinMode(13, OUTPUT);
}

void loop() {
    PlayQueue();
    delay(10);
}

The AudioPlayQueue.getBuffer() function always returns a valid buffer address. The pointer is never NULL.

Ok i don't understand how AudioPlayQueue.getBuffer() should return a NULL.
Because the function stays in a while-loop until a userblock is allocated:

Code:
int16_t * AudioPlayQueue::getBuffer(void)
{
	if (userblock) return userblock->data;
	while (1) {
		userblock = allocate();
		if (userblock) return userblock->data;
		yield();
	}
}

Seems to be a bigger problem :confused:
 
At the defined max of 80 blocks the loop stucks.


Yeah, I figured out loop() cycle is a very low priority process and it gets intetrupted very often by a bunch of other things. Lesson learned - don't use loop() with GetBuffer/PlayBuffer, or in fact anything that needs to be executed in <3ms.
I eventually transferred all my code to custom AudioLibrary objects and now everything works pretty smooth. But it took me A LOT of time.

The AudioPlayQueue.getBuffer() function always returns a valid buffer address. The pointer is never NULL.

Ok i don't understand how AudioPlayQueue.getBuffer() should return a NULL.


Why should a pointer to AudioPlayQueue.getBuffer() be NULL? You mean you never run out of buffers to play and audio still stutters?
One 128 words cycle gets emptied in ~2.9ms, 80 buffers can hold approx. ~23ms of audio. It should be more than enough, especially for crazy fast T4 - the problem isn't there :)
Btw. this
Code:
AudioMemory(100);
is too much for sure. Check AudioMemoryUsage(), it probably reports under 10 buffers.
 
To start loop() cycles some millions of times each second on a t_4.x - slowed by added code or any delay()'s. The main code is loop() - _isr()'s and background tasks will take time. If core processing takes time in loop() of course then that has to be factored in.
 
Yeah, I figured out loop() cycle is a very low priority process and it gets intetrupted very often by a bunch of other things. Lesson learned - don't use loop() with GetBuffer/PlayBuffer, or in fact anything that needs to be executed in <3ms.
I eventually transferred all my code to custom AudioLibrary objects and now everything works pretty smooth. But it took me A LOT of time.

Why should a pointer to AudioPlayQueue.getBuffer() be NULL? You mean you never run out of buffers to play and audio still stutters?
One 128 words cycle gets emptied in ~2.9ms, 80 buffers can hold approx. ~23ms of audio. It should be more than enough, especially for crazy fast T4 - the problem isn't there :)
Btw. this
Code:
AudioMemory(100);
is too much for sure. Check AudioMemoryUsage(), it probably reports under 10 buffers.

Yes you are right. It takes 2.9mS for a block of 128 samples. Thats why i put a delay of 10 mS in the loop.
There should be more than enough time to release all used blocks.
But the library allocates a new one each call and doesn't release the old ones.
Just run the code :)
 
The bug is in your code. The Audio library expects that when you play a buffer, you have allocated an audio object that will receive and process that buffer. All you are doing is allocating buffers without providing the library with a way to use them.
Add these statements after AudioPlayQueue
Code:
AudioOutputI2S   audioOutput;
AudioControlSGTL5000 audioShield;
AudioConnection c1(playQueue, 0, audioOutput, 0);
That will allow the library to pass a buffer from playQueue to audioOutput.
Your code will now always print:
Code:
Audio Mem Used: 1

Pete
 
Thank you Pete!!! In my actual project on a Teensy 3.6 i was using AudioOutputUSB and the dummy to use it was AudioOutputAnalog.
After compiling the code and running on a Teensy 4.1 i had these memory problems. Yes there is no DAC on Teensy 4.1, so it will not work.
A test with AudioOutputMQS as dummy looks good. In the end i have the same problem like Subfanatic.
I have to feed a large sample buffer block by block in the right moment into the queue. Unfortunately there is no way to check the actual playing position
of AudioPlayQueue.playBuffer(). So have to feed it "blind". When you feed it too slow it crackles, when you feed it too fast the
used audio memory is growing. When you reach the memory limit, the consumed processing time by interrupts is also growing.
But you helped me a lot to understand how the library works. Thanks :)
 
Thank you Pete!!! In my actual project on a Teensy 3.6 i was using AudioOutputUSB and the dummy to use it was AudioOutputAnalog.
After compiling the code and running on a Teensy 4.1 i had these memory problems. Yes there is no DAC on Teensy 4.1, so it will not work.
A test with AudioOutputMQS as dummy looks good. In the end i have the same problem like Subfanatic.
I have to feed a large sample buffer block by block in the right moment into the queue. Unfortunately there is no way to check the actual playing position
of AudioPlayQueue.playBuffer(). So have to feed it "blind". When you feed it too slow it crackles, when you feed it too fast the
used audio memory is growing. When you reach the memory limit, the consumed processing time by interrupts is also growing.
But you helped me a lot to understand how the library works. Thanks :)

Not sure if I understand what the problem is. I use Teensy audio lib in many projects only as a queue in - queue out helper.

Maybe you would like to have a look at an example which works:

https://github.com/DD4WH/Uniformly_...ar_cabinet_impulse/guitar_cabinet_impulse.ino

There are some things missing in your sketch. I assume you would like to do the following:

* let the audio lib do the interrupt and DMA handling
* deliver one (or as many as you desire) block(s) of audio
* then your code does some DSP on this block
* then you push the processed audio into the queue
* the audio lib plays that block at exactly the right time

To achieve that you have to do the following (Pseudocode):

Code:
AudioRecordQueue QUEUE_IN;

AudioPlayQueue QUEUE_OUT;

int16_t *Audio;

//in setup:

QUEUE_IN.begin();

//in loop:

if(QUEUE_IN.available() > NO_OF_DESIRED_BLOCKS)
{ 
Audio = QUEUE_IN.readBuffer();
QUEUE_IN.freeBuffer();

// do some DSP magic with your Audio block
Audio = QUEUE_OUT.getBuffer();
Audio = Whatever_your_DSP_has_done;

QUEUE_OUT.playBuffer();

}
else
{// wait for the audio blocks}

I have used the input and output queues of the audio lib in many sketches on the Teensy 3.6, T4.0 and T4.1 without any problems.
 
Hello DD4WH,

thank you for your answer and your recommendations.
I already took a look at your example. But in my case there is no need for realtime processing.
I just want to record audio into a ram buffer and play it back in an endless loop.

Recording audio with AudioRecordQueue is easy. But playing audio with AudioPlayQueue is not.
One audio block holds approximately 2.9mS of audio. To play it in an endless loop without any
clicks or crackle it is important to feed the queue fast enough.
The AudioPlayQueue does not give a feedback when it's time to transmit the next block.
So you have to feed it in an interval below 2.9mS.
But this leads me always in a fast increasing amount of the used AudioMemory and also in a
massive load of the processor.

I wrote a little program to explain you my problem. It transmits audio blocks in a loop, shows the AudioMemory usage
and also the counted loops per second. The blinking led is just a dummy load.


Code:
/* Teensy 4.1, Arduino 1.8.5, Teensyduino 1.53 */

#include <Audio.h>

AudioPlayQueue    playQueue;
AudioOutputSPDIF3 spdif3Output;

AudioConnection patchCord1(playQueue, 0, spdif3Output, 0);

uint32_t  blinkLedTicks = 0;
uint32_t  serialTerminalOutMillis = 0;
uint32_t  serialTerminalOutRuns = 0;
uint32_t  serialTerminalRowCnt = 0;
uint32_t  transmitAudioBlockIntervalMicros = 0;

int16_t   sampleBuffer[AUDIO_BLOCK_SAMPLES];
int16_t   *queueTransmitBuffer;
uint32_t  audioMemUsed;
uint32_t  maxAudioMemUsed;
uint32_t  audioCpuUsed;
uint32_t  maxAudioCpuUsed;

uint32_t  playQueueCalls = 0;
uint32_t  loopCnt = 0;
uint32_t  avgLoopsPerSecond = 0;
uint32_t  testState = 0;

#define   AUDIO_MEMORY_BLOCKS             100
#define   AUDIO_BLOCK_TRANSMIT_INTERVAL   2900      // In uS

#define   SERIAL_TERMINAL_OUT_INTERVAL    500       // In mS
#define   MAX_SERIAL_TERMINAL_OUT_RUNS    20        // Max rows for the test. If it's Zero the test runs until Loops/S drops.

void setup() {
    Serial.begin(115200);
    delay(250);
    AudioMemory(AUDIO_MEMORY_BLOCKS);
    Serial.printf("\n*AudioPlayQueue Test*    AudioBlockSamples: %d    AudioMemory: %d    AudioBlock Tx Interval: %d uS \n\n", AUDIO_BLOCK_SAMPLES, AUDIO_MEMORY_BLOCKS, AUDIO_BLOCK_TRANSMIT_INTERVAL);
    pinMode(13, OUTPUT);
}

void loop() {
    loopCnt++;
    PlayQueue();
    SerialTerminalOut();
    BlinkLed();
}

void PlayQueue() {
    if (micros() - transmitAudioBlockIntervalMicros >= AUDIO_BLOCK_TRANSMIT_INTERVAL) {
      transmitAudioBlockIntervalMicros = micros();
      queueTransmitBuffer = playQueue.getBuffer();
      memcpy(queueTransmitBuffer, sampleBuffer, AUDIO_BLOCK_SAMPLES * 2);
      playQueue.playBuffer();
      audioMemUsed = AudioMemoryUsage();
      audioCpuUsed = AudioProcessorUsage();
      if (audioMemUsed > maxAudioMemUsed) { maxAudioMemUsed = audioMemUsed; }
      if (audioCpuUsed > maxAudioCpuUsed) { maxAudioCpuUsed = audioCpuUsed; }
      playQueueCalls++;
    }
}

void SerialTerminalOut() {
    if (MAX_SERIAL_TERMINAL_OUT_RUNS == 0) { serialTerminalOutRuns = 0; }
    if (testState < 3) {
      if (millis() - serialTerminalOutMillis > SERIAL_TERMINAL_OUT_INTERVAL) {
        uint32_t loopspersecond = float(loopCnt * (1000.0f / (millis() - serialTerminalOutMillis)));
        serialTerminalOutMillis = millis();
        avgLoopsPerSecond = (avgLoopsPerSecond + loopspersecond) / 2;
        loopCnt = 0;
        Serial.printf("%3d    PlayQueue Calls: %5d    AudioMemory Usage: %2d    Loops/S: %7d\n", ++serialTerminalRowCnt, playQueueCalls, audioMemUsed, loopspersecond);
        if (playQueueCalls > 1 && loopspersecond < avgLoopsPerSecond / 4) { testState++; }            // Loops/S droped? Stop the test.
        if (serialTerminalOutRuns++ +1 == MAX_SERIAL_TERMINAL_OUT_RUNS) {  testState = 3; }           // Max runs reached? Stop the test.
      }
    }
    if (testState == 3) {
      Serial.printf("\nMax AudioProcessor Usage: %2d\n", maxAudioCpuUsed);
      testState++;
    }
}

void BlinkLed() {
  blinkLedTicks++;
  if (blinkLedTicks > 100) {
    blinkLedTicks = 0;
    digitalWriteFast(13, !digitalReadFast(13));
  }
}

With an interval of 2900 microseconds between the audio blocks everything seems to be fine.
But the audio will crackle because of the gap between the blocks (the interval between the rows is 500 milliseconds):

Code:
*AudioPlayQueue Test*    AudioBlockSamples: 128    AudioMemory: 100    AudioBlock Tx Interval: 2900 uS 

  1    PlayQueue Calls:     1    AudioMemory Usage:  1    Loops/S:       1
  2    PlayQueue Calls:   173    AudioMemory Usage:  2    Loops/S: 5828221
  3    PlayQueue Calls:   346    AudioMemory Usage:  2    Loops/S: 5833714
  4    PlayQueue Calls:   518    AudioMemory Usage:  2    Loops/S: 5833928
  5    PlayQueue Calls:   691    AudioMemory Usage:  2    Loops/S: 5833716
  6    PlayQueue Calls:   864    AudioMemory Usage:  2    Loops/S: 5833716
  7    PlayQueue Calls:  1036    AudioMemory Usage:  2    Loops/S: 5833928
  8    PlayQueue Calls:  1209    AudioMemory Usage:  2    Loops/S: 5833712
  9    PlayQueue Calls:  1381    AudioMemory Usage:  2    Loops/S: 5833934
 10    PlayQueue Calls:  1554    AudioMemory Usage:  2    Loops/S: 5833710
 11    PlayQueue Calls:  1727    AudioMemory Usage:  2    Loops/S: 5833701
 12    PlayQueue Calls:  1899    AudioMemory Usage:  2    Loops/S: 5833916
 13    PlayQueue Calls:  2072    AudioMemory Usage:  2    Loops/S: 5833705
 14    PlayQueue Calls:  2244    AudioMemory Usage:  2    Loops/S: 5833918
 15    PlayQueue Calls:  2417    AudioMemory Usage:  2    Loops/S: 5833746
 16    PlayQueue Calls:  2590    AudioMemory Usage:  2    Loops/S: 5833701
 17    PlayQueue Calls:  2762    AudioMemory Usage:  2    Loops/S: 5833918
 18    PlayQueue Calls:  2935    AudioMemory Usage:  2    Loops/S: 5833703
 19    PlayQueue Calls:  3108    AudioMemory Usage:  2    Loops/S: 5833703
 20    PlayQueue Calls:  3280    AudioMemory Usage:  2    Loops/S: 5833916

Max AudioProcessor Usage:  0

With an interval of 2800 microseconds audio is fine, but first grows the used AudioMemory and
then rises the processor load very fast:

Code:
*AudioPlayQueue Test*    AudioBlockSamples: 128    AudioMemory: 100    AudioBlock Tx Interval: 2800 uS 

  1    PlayQueue Calls:     1    AudioMemory Usage:  1    Loops/S:       1
  2    PlayQueue Calls:   179    AudioMemory Usage:  9    Loops/S: 5715750
  3    PlayQueue Calls:   358    AudioMemory Usage: 15    Loops/S: 5721120
  4    PlayQueue Calls:   537    AudioMemory Usage: 21    Loops/S: 5721325
  5    PlayQueue Calls:   716    AudioMemory Usage: 28    Loops/S: 5721120
  6    PlayQueue Calls:   895    AudioMemory Usage: 34    Loops/S: 5721116
  7    PlayQueue Calls:  1074    AudioMemory Usage: 40    Loops/S: 5721331
  8    PlayQueue Calls:  1253    AudioMemory Usage: 46    Loops/S: 5721132
  9    PlayQueue Calls:  1432    AudioMemory Usage: 53    Loops/S: 5721315
 10    PlayQueue Calls:  1611    AudioMemory Usage: 59    Loops/S: 5721118
 11    PlayQueue Calls:  1790    AudioMemory Usage: 65    Loops/S: 5721130
 12    PlayQueue Calls:  1969    AudioMemory Usage: 72    Loops/S: 5721333
 13    PlayQueue Calls:  2147    AudioMemory Usage: 78    Loops/S: 5721112
 14    PlayQueue Calls:  2323    AudioMemory Usage: 80    Loops/S: 2664213
 15    PlayQueue Calls:  2496    AudioMemory Usage: 80    Loops/S:     344
 16    PlayQueue Calls:  2669    AudioMemory Usage: 80    Loops/S:     344
 17    PlayQueue Calls:  2842    AudioMemory Usage: 80    Loops/S:     344

Max AudioProcessor Usage:  0

And with an interval of 1000 microseconds:
Code:
*AudioPlayQueue Test*    AudioBlockSamples: 128    AudioMemory: 100    AudioBlock Tx Interval: 1000 uS 

  1    PlayQueue Calls:     1    AudioMemory Usage:  1    Loops/S:       1
  2    PlayQueue Calls:   252    AudioMemory Usage: 80    Loops/S: 1367332
  3    PlayQueue Calls:   425    AudioMemory Usage: 80    Loops/S:     344
  4    PlayQueue Calls:   598    AudioMemory Usage: 80    Loops/S:     344
  5    PlayQueue Calls:   771    AudioMemory Usage: 80    Loops/S:     344

Max AudioProcessor Usage:  0

That's my problem. May be it is not a good idea to use the AudioPlayQueue for looped playback...
May be i'm totally wrong and i have to use the AudioPlayQueue in an other way.

:confused::confused::confused:
 
Ok i guess i'm a bit closer to the issue.

I changed the cpu speed to the minimum of 24MHz.
That's the result with an interval of 2700 microseconds:

Code:
*AudioPlayQueue Test*

AudioBlockSamples: 128    AudioMemory: 100    AudioBlock Tx Interval: 2700 uS    F_CPU: 24 MHz

  1    PlayQueue Calls:     1    AudioMemory Usage:  1    Loops/S:       1
  2    PlayQueue Calls:   173    AudioMemory Usage:  2    Loops/S:  194754
  3    PlayQueue Calls:   346    AudioMemory Usage:  2    Loops/S:  194950
  4    PlayQueue Calls:   518    AudioMemory Usage:  2    Loops/S:  195141
  5    PlayQueue Calls:   691    AudioMemory Usage:  2    Loops/S:  194958
  6    PlayQueue Calls:   864    AudioMemory Usage:  2    Loops/S:  195039
  7    PlayQueue Calls:  1036    AudioMemory Usage:  2    Loops/S:  195223
  8    PlayQueue Calls:  1209    AudioMemory Usage:  2    Loops/S:  194948
  9    PlayQueue Calls:  1382    AudioMemory Usage:  2    Loops/S:  195077
 10    PlayQueue Calls:  1554    AudioMemory Usage:  2    Loops/S:  195017
 11    PlayQueue Calls:  1727    AudioMemory Usage:  2    Loops/S:  195023
 12    PlayQueue Calls:  1899    AudioMemory Usage:  2    Loops/S:  195131
 13    PlayQueue Calls:  2072    AudioMemory Usage:  2    Loops/S:  195019
 14    PlayQueue Calls:  2244    AudioMemory Usage:  2    Loops/S:  195002
 15    PlayQueue Calls:  2417    AudioMemory Usage:  2    Loops/S:  195081
 16    PlayQueue Calls:  2590    AudioMemory Usage:  2    Loops/S:  194942
 17    PlayQueue Calls:  2762    AudioMemory Usage:  2    Loops/S:  195209
 18    PlayQueue Calls:  2935    AudioMemory Usage:  2    Loops/S:  195021
 19    PlayQueue Calls:  3108    AudioMemory Usage:  2    Loops/S:  195025
 20    PlayQueue Calls:  3280    AudioMemory Usage:  2    Loops/S:  195135

Max AudioProcessor Usage:  0

Everything seems to be fine. But i should check the audio output for a meaningful test in a next step.

Now the same for the next possible cpu speed at 150MHz:

Code:
*AudioPlayQueue Test*

AudioBlockSamples: 128    AudioMemory: 100    AudioBlock Tx Interval: 2700 uS    F_CPU: 150 MHz

  1    PlayQueue Calls:     1    AudioMemory Usage:  1    Loops/S:       1
  2    PlayQueue Calls:   186    AudioMemory Usage: 15    Loops/S: 1401570
  3    PlayQueue Calls:   371    AudioMemory Usage: 28    Loops/S: 1402838
  4    PlayQueue Calls:   557    AudioMemory Usage: 41    Loops/S: 1402950
  5    PlayQueue Calls:   742    AudioMemory Usage: 54    Loops/S: 1402918
  6    PlayQueue Calls:   928    AudioMemory Usage: 67    Loops/S: 1402832
  7    PlayQueue Calls:  1113    AudioMemory Usage: 79    Loops/S: 1403028
  8    PlayQueue Calls:  1288    AudioMemory Usage: 80    Loops/S:  165173
  9    PlayQueue Calls:  1461    AudioMemory Usage: 80    Loops/S:     344
 10    PlayQueue Calls:  1634    AudioMemory Usage: 80    Loops/S:     344

Max AudioProcessor Usage:  0

So to me this looks like an internal problem of the library or am i wrong?
 
To be sure that audio is working correctly with the transmit interval of 2700 microseconds and 24MHz cpu clock i have added usb outs
and generated a sine wave in the sample buffer:

Code:
/* Teensy 4.1, Arduino 1.8.5, Teensyduino 1.53 */
#include <Audio.h>

AudioPlayQueue    playQueue;
AudioOutputSPDIF3 spdif3Output;
AudioOutputUSB    usbOutput;

AudioConnection patchCord1(playQueue, 0, spdif3Output, 0);
AudioConnection patchCord2(playQueue, 0, usbOutput, 0);
AudioConnection patchCord3(playQueue, 0, usbOutput, 1);

uint32_t  blinkLedTicks = 0;
uint32_t  serialTerminalOutMillis = 0;
uint32_t  serialTerminalOutRuns = 0;
uint32_t  serialTerminalRowCnt = 0;
uint32_t  transmitAudioBlockIntervalMicros = 0;

const int32_t  samples = AUDIO_BLOCK_SAMPLES * 64;
int16_t   sampleBuffer[samples];
uint32_t  bufferPosition = 0;
int16_t   *queueTransmitBuffer;
uint32_t  audioMemUsed;
uint32_t  maxAudioMemUsed;
uint32_t  audioCpuUsed;
uint32_t  maxAudioCpuUsed;

uint32_t  playQueueCalls = 0;
uint32_t  loopCnt = 0;
uint32_t  avgLoopsPerSecond = 0;
uint32_t  testState = 0;

#define   AUDIO_MEMORY_BLOCKS             100
#define   AUDIO_BLOCK_TRANSMIT_INTERVAL   2700      // In uS

#define   SERIAL_TERMINAL_OUT_INTERVAL    500       // In mS
#define   MAX_SERIAL_TERMINAL_OUT_RUNS    10        // Max rows for the test. If it's Zero the test runs until Loops/S drops.


void setup() {
    Serial.begin(115200);
    delay(500);
    AudioMemory(AUDIO_MEMORY_BLOCKS);
    Serial.printf("\n*AudioPlayQueue Test*\n\n");
    Serial.printf("AudioBlockSamples: %d    AudioMemory: %d    SampleTime: %d mS    AudioBlock Tx Interval: %d uS    F_CPU: %d MHz\n\n", AUDIO_BLOCK_SAMPLES, AUDIO_MEMORY_BLOCKS, samples * 1000 / 44100, AUDIO_BLOCK_TRANSMIT_INTERVAL, F_CPU/1000000);
    pinMode(13, OUTPUT);

    /* Build a sine wave */
    uint32_t cycles = (((samples-1) * 1000) / 44100);
    for (uint32_t i = 0; i < samples; i++) {
      sampleBuffer[i] = int16_t((7000 * (sin((i * (2 * PI * cycles)) / samples))) / 2);
    }
}

void loop() {
    loopCnt++;
    PlayQueue();
    SerialTerminalOut();
    BlinkLed();
}

void PlayQueue() {
    if (micros() - transmitAudioBlockIntervalMicros >= AUDIO_BLOCK_TRANSMIT_INTERVAL) {
      transmitAudioBlockIntervalMicros = micros();
      queueTransmitBuffer = playQueue.getBuffer();
      memcpy(queueTransmitBuffer, sampleBuffer + bufferPosition, AUDIO_BLOCK_SAMPLES * 2);
      playQueue.playBuffer();
      audioMemUsed = AudioMemoryUsage();
      audioCpuUsed = AudioProcessorUsage();
      if (audioMemUsed > maxAudioMemUsed) { maxAudioMemUsed = audioMemUsed; }
      if (audioCpuUsed > maxAudioCpuUsed) { maxAudioCpuUsed = audioCpuUsed; }
      playQueueCalls++;
      bufferPosition = bufferPosition + AUDIO_BLOCK_SAMPLES;
      if (bufferPosition == samples) { bufferPosition = 0; }
    }
}

void SerialTerminalOut() {
    if (MAX_SERIAL_TERMINAL_OUT_RUNS == 0) { serialTerminalOutRuns = 0; }
    if (testState < 3) {
      if (millis() - serialTerminalOutMillis > SERIAL_TERMINAL_OUT_INTERVAL) {
        uint32_t loopspersecond = float(loopCnt * (1000.0f / (millis() - serialTerminalOutMillis)));
        serialTerminalOutMillis = millis();
        avgLoopsPerSecond = (avgLoopsPerSecond + loopspersecond) / 2;
        loopCnt = 0;
        Serial.printf("%3d    PlayQueue Calls: %5d    AudioMemory Usage: %2d    Loops/S: %7d\n", ++serialTerminalRowCnt, playQueueCalls, audioMemUsed, loopspersecond);
        if (playQueueCalls > 1 && loopspersecond < avgLoopsPerSecond / 4) { testState++; }            // Loops/S droped? Stop the test.
        if (serialTerminalOutRuns++ +1 == MAX_SERIAL_TERMINAL_OUT_RUNS) {  testState = 3; }           // Max runs reached? Stop the test.
      }
    }
    if (testState == 3) {
      Serial.printf("\nMax AudioProcessor Usage: %2d\n", maxAudioCpuUsed);
      testState++;
    }
}

void BlinkLed() {
  blinkLedTicks++;
  if (blinkLedTicks > 100) {
    blinkLedTicks = 0;
    digitalWriteFast(13, !digitalReadFast(13));
  }
}

The result looks like in the previous test:

Code:
*AudioPlayQueue Test*

AudioBlockSamples: 128    AudioMemory: 100    SampleTime: 185 mS    AudioBlock Tx Interval: 2700 uS    F_CPU: 24 MHz

  1    PlayQueue Calls:     1    AudioMemory Usage:  1    Loops/S:       1
  2    PlayQueue Calls:   173    AudioMemory Usage:  2    Loops/S:  192824
  3    PlayQueue Calls:   346    AudioMemory Usage:  2    Loops/S:  192888
  4    PlayQueue Calls:   519    AudioMemory Usage:  2    Loops/S:  192952
  5    PlayQueue Calls:   691    AudioMemory Usage:  2    Loops/S:  193033
  6    PlayQueue Calls:   864    AudioMemory Usage:  2    Loops/S:  192886
  7    PlayQueue Calls:  1036    AudioMemory Usage:  2    Loops/S:  193089
  8    PlayQueue Calls:  1209    AudioMemory Usage:  2    Loops/S:  192914
  9    PlayQueue Calls:  1382    AudioMemory Usage:  2    Loops/S:  192882
 10    PlayQueue Calls:  1554    AudioMemory Usage:  2    Loops/S:  193081

Max AudioProcessor Usage:  1

And the audio signal sounds absolutely clean. I use this little tool to route the Teensy usb outs to my soundcard:

AudioRepeater.PNG

For a cpu speed of 24MHz i got the following transmit interval values:
2475 microseconds is the shortest transmit interval that still works. 2474 microseconds leads to the described problem.
2898 microseconds is the longest transmit interval before some crackle starts.

For a cpu speed of 600MHz the shortest usable interval is 2885 microseconds. The longest usable interval is 2902 microseconds.
That means there is a tolerance range of 8 microseconds when i use an interval of 2894 microseconds.
That's not much. In a "normal" application it will fail.
So what can i do? Using an interval timer like Subfanatic did??
 
So what can i do? Using an interval timer like Subfanatic did??

And here comes the answer. I have tried different types of timers with no success. All kinds of calling the AudioPlayQueue from an interrupt are leading to problems.
So i decided to link the PlayQueue to a RecordQueue to get the right timing for the audio library. Every time the RecordQueue receives a new audio packet it triggers the PlayQueue.
And this looks very good to me.

Sample played from EXTMEM:

Code:
*AudioPlayQueue Test*

AudioBlockSamples: 128    AudioMem: 10    BufSize: 32768 Bytes    SampleT: 371 mS    FCPU: 600 MHz    EXTMEM    TestInterval: 500 mS

  1    PlayQueueCalls   0    LastTxIntervals   Min    0   Avg    0   Max     0 uS    AudioMemUsed  0    Loops/S       1
  2    PlayQueueCalls 173    LastTxIntervals   Min    0   Avg    0   Max     0 uS    AudioMemUsed  2    Loops/S 6813184
  3    PlayQueueCalls 172    LastTxIntervals   Min 2896   Avg 2901   Max  2907 uS    AudioMemUsed  2    Loops/S 6832277
  4    PlayQueueCalls 173    LastTxIntervals   Min 2898   Avg 2902   Max  2908 uS    AudioMemUsed  2    Loops/S 6832345
  5    PlayQueueCalls 173    LastTxIntervals   Min 2895   Avg 2902   Max  2907 uS    AudioMemUsed  2    Loops/S 6832297
  6    PlayQueueCalls 172    LastTxIntervals   Min 2896   Avg 2901   Max  2910 uS    AudioMemUsed  2    Loops/S 6832990
  7    PlayQueueCalls 173    LastTxIntervals   Min 2897   Avg 2901   Max  2907 uS    AudioMemUsed  2    Loops/S 6832443
  8    PlayQueueCalls 172    LastTxIntervals   Min 2894   Avg 2901   Max  2909 uS    AudioMemUsed  2    Loops/S 6832742
  9    PlayQueueCalls 173    LastTxIntervals   Min 2895   Avg 2902   Max  2908 uS    AudioMemUsed  2    Loops/S 6832591
 10    PlayQueueCalls 173    LastTxIntervals   Min 2895   Avg 2901   Max  2909 uS    AudioMemUsed  2    Loops/S 6832639

MaxAudioCpu Used:  0    ElapsedTime: 5 Seconds    TxIntervals   Min: 2894   Avg: 2901   Max: 2910 uS

Sample played from DMAMEM:

Code:
*AudioPlayQueue Test*

AudioBlockSamples: 128    AudioMem: 10    BufSize: 32768 Bytes    SampleT: 371 mS    FCPU: 600 MHz    DMAMEM    TestInterval: 500 mS

  1    PlayQueueCalls   0    LastTxIntervals   Min    0   Avg    0   Max     0 uS    AudioMemUsed  0    Loops/S       1
  2    PlayQueueCalls 173    LastTxIntervals   Min    0   Avg    0   Max     0 uS    AudioMemUsed  2    Loops/S 6828643
  3    PlayQueueCalls 172    LastTxIntervals   Min 2900   Avg 2901   Max  2903 uS    AudioMemUsed  2    Loops/S 6834204
  4    PlayQueueCalls 173    LastTxIntervals   Min 2901   Avg 2902   Max  2903 uS    AudioMemUsed  2    Loops/S 6833840
  5    PlayQueueCalls 173    LastTxIntervals   Min 2901   Avg 2902   Max  2903 uS    AudioMemUsed  2    Loops/S 6833998
  6    PlayQueueCalls 172    LastTxIntervals   Min 2901   Avg 2902   Max  2903 uS    AudioMemUsed  2    Loops/S 6834184
  7    PlayQueueCalls 173    LastTxIntervals   Min 2901   Avg 2902   Max  2903 uS    AudioMemUsed  2    Loops/S 6834008
  8    PlayQueueCalls 172    LastTxIntervals   Min 2901   Avg 2902   Max  2903 uS    AudioMemUsed  2    Loops/S 6834233
  9    PlayQueueCalls 173    LastTxIntervals   Min 2900   Avg 2901   Max  2903 uS    AudioMemUsed  2    Loops/S 6834066
 10    PlayQueueCalls 173    LastTxIntervals   Min 2900   Avg 2901   Max  2903 uS    AudioMemUsed  2    Loops/S 6834120

MaxAudioCpu Used:  0    ElapsedTime: 5 Seconds    TxIntervals   Min: 2900   Avg: 2901   Max: 2903 uS

No crackle, no problems :D


Code:
/* Teensy 4.1, Arduino 1.8.5, Teensyduino 1.53 */
#include <Audio.h>

AudioSynthWaveformDc  dcSynth;
AudioRecordQueue      recordQueue;
AudioPlayQueue        playQueue;
AudioOutputSPDIF3     spdif3Output;
AudioOutputUSB        usbOutput;

AudioConnection patchCord1(dcSynth, 0, recordQueue, 0);
AudioConnection patchCord2(playQueue, 0, spdif3Output, 0);
AudioConnection patchCord3(playQueue, 0, usbOutput, 0);
AudioConnection patchCord4(playQueue, 0, usbOutput, 1);

uint32_t  blinkLedTicks = 0;
uint32_t  serialTerminalOutMillis = 0;
uint32_t  serialTerminalOutRuns = 0;
uint32_t  serialTerminalRowCnt = 0;
uint32_t  transmitAudioBlockIntervalMicros = 0;
uint32_t  elapsedTime = 0;

#define MEM_TO_USE    2

const int32_t  SAMPLES = AUDIO_BLOCK_SAMPLES * 128;
#if MEM_TO_USE == 1
EXTMEM int16_t   sampleBuffer[SAMPLES];
char* memText = "EXTMEM";
#elif MEM_TO_USE == 2
DMAMEM int16_t   sampleBuffer[SAMPLES];
char* memText = "DMAMEM";
#elif MEM_TO_USE == 3
int16_t   sampleBuffer[SAMPLES];
char* memText = "RAM";
#endif

uint32_t  bufferPosition = 0;
int16_t   *queueTransmitBuffer;
uint32_t  audioMemUsed;
uint32_t  maxAudioMemUsed;
uint32_t  audioCpuUsed;
uint32_t  maxAudioCpuUsed;

uint32_t  triggerPlayQueueCnt = 0;
uint32_t  playQueueCalls = 0;
uint32_t  playQueueCallsSum = 0;
uint32_t  loopCnt = 0;
uint32_t  avgLoopsPerSecond = 0;
uint32_t  testState = 0;

bool      buildNewSineWave = true;
uint32_t  sineWaveFrequency = 1000;
uint32_t  sineWaveFrequencyDirection = 0;
#define   SINE_WAVE_MIN_FREQUENCY         100
#define   SINE_WAVE_MAX_FREQUENCY         1000
#define   SINE_WAVE_FREQUENCY_CHANGE      100

#define   AUDIO_MEMORY_BLOCKS             10

#define   SERIAL_TERMINAL_OUT_INTERVAL    500             // In mS
#define   MAX_SERIAL_TERMINAL_OUT_RUNS    10              // Max rows for the test. If it's Zero the test runs until Loops/S drops.


const uint32_t  CLOCK_CYCLES_PER_MICROSECOND = F_CPU / 1000000;
uint32_t  lastTxAudioBlockIntervalCycCnt = 0;
uint32_t  txAudioBlockIntervalCycleCountMicros = 0;
uint32_t  txAudioBlockIntervalCycleCountMicrosSum = 0;
uint32_t  txAudioBlockIntervalCycleCountMicrosSumSum = 0;
uint32_t  lastMinTxAudioBlockIntervalCycleCountMicros = 0;
uint32_t  lastMaxTxAudioBlockIntervalCycleCountMicros = 0;
uint32_t  overallMinTxAudioBlockIntervalCycleCountMicros = 9999;
uint32_t  overallMaxTxAudioBlockIntervalCycleCountMicros = 0;


void setup() {
    /* Enable Clock Cycle Counter */
    ARM_DEMCR |= ARM_DEMCR_TRCENA;
    ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
  
    Serial.begin(115200);
    delay(500);
    AudioMemory(AUDIO_MEMORY_BLOCKS);
    Serial.printf("\n*AudioPlayQueue Test*\n\n");
    Serial.printf("AudioBlockSamples: %d    AudioMem: %d    BufSize: %d Bytes    SampleT: %d mS    FCPU: %d MHz    %s    TestInterval: %d mS\n\n", AUDIO_BLOCK_SAMPLES, AUDIO_MEMORY_BLOCKS, SAMPLES * 2, SAMPLES * 1000 / 44100, F_CPU/1000000, memText, SERIAL_TERMINAL_OUT_INTERVAL);
    pinMode(13, OUTPUT);
    BuildSineWave();
    recordQueue.begin();
    elapsedTime = millis();
}

void loop() {
    loopCnt++;
    CheckRecordQueue();
    PlayQueue();
    //BuildSineWave();              // Generate a new sine wave each buffer end
    SerialTerminalOut();
    BlinkLed();
}

void CheckRecordQueue() {
    if (recordQueue.available()) {
      recordQueue.readBuffer();
      recordQueue.freeBuffer();
      triggerPlayQueueCnt++;
    }
}

void PlayQueue() {
    if (triggerPlayQueueCnt > 0) {
      triggerPlayQueueCnt--;
      queueTransmitBuffer = playQueue.getBuffer();
      memcpy(queueTransmitBuffer, sampleBuffer + bufferPosition, AUDIO_BLOCK_SAMPLES * 2);
      playQueue.playBuffer();
      if (lastTxAudioBlockIntervalCycCnt) txAudioBlockIntervalCycleCountMicros = (ARM_DWT_CYCCNT - lastTxAudioBlockIntervalCycCnt) / CLOCK_CYCLES_PER_MICROSECOND;
      lastTxAudioBlockIntervalCycCnt = ARM_DWT_CYCCNT;
      if (serialTerminalRowCnt > 1) {
        txAudioBlockIntervalCycleCountMicrosSum = txAudioBlockIntervalCycleCountMicrosSum + txAudioBlockIntervalCycleCountMicros;
        txAudioBlockIntervalCycleCountMicrosSumSum = txAudioBlockIntervalCycleCountMicrosSumSum + txAudioBlockIntervalCycleCountMicros;
        if (txAudioBlockIntervalCycleCountMicros < lastMinTxAudioBlockIntervalCycleCountMicros || lastMinTxAudioBlockIntervalCycleCountMicros == 0) lastMinTxAudioBlockIntervalCycleCountMicros = txAudioBlockIntervalCycleCountMicros;
        if (txAudioBlockIntervalCycleCountMicros > lastMaxTxAudioBlockIntervalCycleCountMicros) lastMaxTxAudioBlockIntervalCycleCountMicros = txAudioBlockIntervalCycleCountMicros;
        playQueueCallsSum++;
      }
      playQueueCalls++;
      bufferPosition = bufferPosition + AUDIO_BLOCK_SAMPLES;
      if (bufferPosition == SAMPLES) {
        bufferPosition = 0;
        buildNewSineWave = true;
      }
    }
}

void SerialTerminalOut() {
    if (MAX_SERIAL_TERMINAL_OUT_RUNS == 0) { serialTerminalOutRuns = 0; }
    if (testState < 3) {
      if (millis() - serialTerminalOutMillis > SERIAL_TERMINAL_OUT_INTERVAL) {
        uint32_t loopspersecond = float(loopCnt * (1000.0f / (millis() - serialTerminalOutMillis)));
        serialTerminalOutMillis = millis();
        avgLoopsPerSecond = (avgLoopsPerSecond + loopspersecond) / 2;
        loopCnt = 0;
        audioMemUsed = AudioMemoryUsage();
        audioCpuUsed = AudioProcessorUsage();
        if (audioMemUsed > maxAudioMemUsed) maxAudioMemUsed = audioMemUsed;
        if (audioCpuUsed > maxAudioCpuUsed) maxAudioCpuUsed = audioCpuUsed;
        Serial.printf("%3d    PlayQueueCalls %3d    LastTxIntervals   Min%5d   Avg%5d   Max%6d uS    AudioMemUsed %2d    Loops/S %7d\n", ++serialTerminalRowCnt, playQueueCalls, lastMinTxAudioBlockIntervalCycleCountMicros, txAudioBlockIntervalCycleCountMicrosSum / playQueueCalls, lastMaxTxAudioBlockIntervalCycleCountMicros, audioMemUsed, loopspersecond);
        if (serialTerminalRowCnt > 1) {
          if (lastMinTxAudioBlockIntervalCycleCountMicros < overallMinTxAudioBlockIntervalCycleCountMicros || overallMinTxAudioBlockIntervalCycleCountMicros == 0) overallMinTxAudioBlockIntervalCycleCountMicros = lastMinTxAudioBlockIntervalCycleCountMicros;
          if (lastMaxTxAudioBlockIntervalCycleCountMicros > overallMaxTxAudioBlockIntervalCycleCountMicros) overallMaxTxAudioBlockIntervalCycleCountMicros = lastMaxTxAudioBlockIntervalCycleCountMicros;
          lastMinTxAudioBlockIntervalCycleCountMicros = 9999;
          lastMaxTxAudioBlockIntervalCycleCountMicros = 0;
          txAudioBlockIntervalCycleCountMicrosSum = 0;
        }
        playQueueCalls = 0;
        if (playQueueCallsSum > 1 && loopspersecond < avgLoopsPerSecond / 4) { testState++; }         // Loops/S droped? Stop the test.
        if (serialTerminalOutRuns++ +1 == MAX_SERIAL_TERMINAL_OUT_RUNS) {  testState = 3; }           // Max runs reached? Stop the test.

      }
    }
    if (testState == 3) {
      Serial.printf("\nMaxAudioCpu Used: %2d    ElapsedTime: %d Seconds    TxIntervals   Min: %4d   Avg: %4d   Max: %4d uS\n", maxAudioCpuUsed, (millis() - elapsedTime + SERIAL_TERMINAL_OUT_INTERVAL) / 1000, overallMinTxAudioBlockIntervalCycleCountMicros, txAudioBlockIntervalCycleCountMicrosSumSum / playQueueCallsSum, overallMaxTxAudioBlockIntervalCycleCountMicros);
      testState++;
    }
}

void BlinkLed() {
  blinkLedTicks++;
  if (blinkLedTicks > 100) {
    blinkLedTicks = 0;
    digitalWriteFast(13, !digitalReadFast(13));
  }
}

void BuildSineWave() {
    if (buildNewSineWave == true) {
      buildNewSineWave = false;
      if (sineWaveFrequencyDirection == 0) {
        sineWaveFrequency = sineWaveFrequency - SINE_WAVE_FREQUENCY_CHANGE;
        if (sineWaveFrequency <= SINE_WAVE_MIN_FREQUENCY) {
          sineWaveFrequencyDirection = 1;
        }
      }
      else if (sineWaveFrequencyDirection == 1) {
        sineWaveFrequency = sineWaveFrequency + SINE_WAVE_FREQUENCY_CHANGE;
        if (sineWaveFrequency >= SINE_WAVE_MAX_FREQUENCY) {
          sineWaveFrequencyDirection = 0;
        }
      }
      uint32_t cycles = (((SAMPLES-1) * sineWaveFrequency) / 44100);
      for (uint32_t i = 0; i < SAMPLES; i++) {
        sampleBuffer[i] = int16_t((7000 * (sin((i * (2 * PI * cycles)) / SAMPLES))) / 2);
      }
    }
}
 
Last edited:
Back
Top