UNDERRUN dropout

jsalsburg

Member
To allow for stereo operation, the TEENSY 4.0 Buffer size must be a minimum of 16 for each channel or a total of 32 so reducing the buffer size to 32 is as far as it goes resulting in a latency of 2 ms.
I found the code that controls latency in the TEENSY Audio Stream. It is in the Audiostream.h file. When I discovered how to reduce audio latency from input to output, I did not consider if the TEENSY was dropping portions of the audio stream, which sounds like clicking or buzzing in the output. Dropout or what is called UNDRRUN is caused by the audio buffer reaching its end before it is refreshed with new samples from the input. By reducing the #define AUDIO_BLOCK_SAMPLES to 32 from its default of 128, the TEENSY was dropping the output stream periodically.
So there is a direct relationship between UNDERRUN and System clock speed.
The default system clock is 600 MHz.
The TEENSY has a feature that allows it to be Overclocked. I increased the system clock frequency to the default 800 MHz resulting in no UNDERRUN dropout of the output stream with the #define AUDIO_BLOCK_SAMPLES size of 32.
I do anticipate the the TEENSY will have UNDERRUN problems when I start programming complex operations.
I was using the PassThroughStereo.ino example to test latency. With AUDIO_BLOCK_SAMPLES set to 128, the latency was about 6.8 milliseconds. I added a delay to adjust the latency to exact millisecond values. Even with zero delay with AUDIO_BLOCK_SAMPLES set to 16 or 32 caused UNDERRUN dropout at 600 MHz System Clock. Overclocking prevented UNDERRUN dropout. My objective is stereo delay of precise durations.

After preliminary testing for UNDERRUN errors, I have determined that there is a direct/indirect relationship between:
1. #define AUDIO_BLOCK_SAMPLES size in the AudioStream.h Library.
2. CPU speed.
3. AudioMemory(XXX); size in the sketch.
4. delay1.delay(0, XXX); size in the sketch
5. the amount or number and complexity of the effects in the sketch

Modified PassThroughStereo.ino with no UNDERRUN errors: AudioMemory(200); delay1.delay(0, 100); (#define AUDIO_BLOCK_SAMPLES 32)(CPU speed 600 MHz)

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code 
AudioInputI2S            i2s1;        
AudioEffectDelay         delay1;   
AudioOutputI2S           i2s2;       
AudioConnection          patchCord1(i2s1, 1, delay1, 0);
AudioConnection          patchCord2(delay1, 0, i2s2, 1);
AudioControlSGTL5000     sgtl5000_1;    
// GUItool: end automatically generated code
 
const int myInput = AUDIO_INPUT_LINEIN;

void setup() {
  // Audio connections require memory to work.  For more
  // detailed information, see the MemoryAndCpuUsage example
  AudioMemory(200);

  // Enable the audio shield, select input, and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.5);

  delay1.delay(0, 100);
}

elapsedMillis volmsec=0;

void loop() {
  // every 50 ms, adjust the volume
  if (volmsec > 50) {
    float vol = analogRead(15);
    vol = vol / 1023.0;
    //audioShield.volume(vol); // <-- uncomment if you have the optional
    volmsec = 0;               //     volume pot on your audio shield
  }
}
 
The delay effect is one of the many special cases in the Audio library, which needs careful reading of the documentation. In this case, it uses audio blocks for its delay memory so you need to allocate “enough” at compile time to avoid running out. This is slightly compounded by the effect using only what it needs at the time, so if you change from a 50ms delay to 100ms, you suddenly need twice as many.

Your 100ms delay needs 44100 samples, and thus 138 of your revised 32-sample audio blocks. I’d expect 150 to be enough, but your choice of 200 gives a sensible margin.

Offhand I don’t recall how many other effects consume multiple blocks. In general the audio flow is optimised by laying out you flow in the Design Tool in a logical fashion, and blocks are then reused during each update for a very efficient system. There are functions to track usage, so if it’s an issue just allocate plenty, monitor usage, and size the final allocation accordingly.
 
By adding the Serial.print lines to the PassThroughStereo.ino sketch I found it possible to monitor the percentage of Audio Processor usage and Audio Memory usage in real time. The Audio Processor usage is expressed in decimal and the Audio Memory usage, in direct effect of delay1.delay(0, XXX);
Increasing/Decreasing delay time will show that if AudioMemoryUsage() size goes over the AudioMemory(XXX); size, UNDERRUN DROPOUT will occur.

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code 
AudioInputI2S            i2s1;        
AudioEffectDelay         delay1;   
AudioOutputI2S           i2s2;       
AudioConnection          patchCord1(i2s1, 1, delay1, 0);
AudioConnection          patchCord2(delay1, 0, i2s2, 1);
AudioControlSGTL5000     sgtl5000_1;    
// GUItool: end automatically generated code
 
const int myInput = AUDIO_INPUT_LINEIN;

void setup() {
  AudioMemory(200);

  // Enable the audio shield, select input, and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.5);

  delay1.delay(0, 100);
}

elapsedMillis volmsec=0;

void loop() {
  // every 50 ms, adjust the volume
  if (volmsec > 50) {
    float vol = analogRead(15);
    vol = vol / 1023.0;
    //audioShield.volume(vol); // <-- uncomment if you have the optional
    volmsec = 0;               //     volume pot on your audio shield
  }
  Serial.print(AudioProcessorUsage());
  Serial.print(",");
  Serial.print("Memory: ");
  Serial.print(AudioMemoryUsage());
  Serial.println();
}
 
Last edited:
Yes, that is the intention of these functions.... however you can't really rely on AudioProcessorUsage() because there are some things that are not taken into account.
But for a rough estimation it is good enough (in most cases, at least...)
 
Last edited:
Using LCD to report TEENSY Status. Three significant parameters separated by a dash.
Left to right: 19% Audio Processor Usage, 74 Audio Memory blocks used, 63.80 Degrees Celsius CPU Temperature.
PXL_20230809_075017332.jpg

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
//#include <SD.h>
#include <SerialFlash.h>
#include <Wire.h>
#include <hd44780.h>                       // main hd44780 header
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header
hd44780_I2Cexp lcd; // declare lcd object: auto locate & auto config expander chip

const int LCD_COLS = 20;
const int LCD_ROWS = 4;
int count=1;

/ GUItool: begin automatically generated code
AudioInputI2S            i2s1;           //xy=249.3333282470703,241.3333282470703
AudioEffectDelay         delay1;         //xy=385.3333435058594,248.0000114440918
AudioOutputI2S           i2s2;           //xy=523.333381652832,194.3333511352539
AudioConnection          patchCord1(i2s1, 1, delay1, 0);
AudioConnection          patchCord2(delay1, 0, i2s2, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=255.3333511352539,299.99999618530273
// GUItool: end automatically generated code

const int myInput = AUDIO_INPUT_LINEIN;
//const int myInput = AUDIO_INPUT_MIC;

extern float tempmonGetTemp(void);

void setup() {
   // Audio connections require memory to work.  For more
  // detailed information, see the MemoryAndCpuUsage example
  AudioMemory(200);

  // Enable the audio shield, select input, and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.5);

  delay1.delay(0, 50);

  int status;
	status = lcd.begin(LCD_COLS, LCD_ROWS);
	if(status) // non zero status means it was unsuccesful
	{
			hd44780::fatalError(status); // does not return
	}
	  lcd.setCursor(0, 0);
    lcd.print("                    ");
    lcd.setCursor(0, 1);
    lcd.print("                    ");
    lcd.setCursor(0, 2);
    lcd.print("                    ");
    lcd.setCursor(0, 3);
    lcd.print("                    ");
}

elapsedMillis volmsec=0;

void loop() {
  // every 50 ms, adjust the volume
  if (volmsec > 50) {
    float vol = analogRead(15);
    vol = vol / 1023.0;
    //audioShield.volume(vol); // <-- uncomment if you have the optional
    volmsec = 0;               //     volume pot on your audio shield
  }
   lcd.setCursor(0, 0);
     lcd.print(AudioProcessorUsage());
     lcd.print("-");
     lcd.print(AudioMemoryUsage());
     lcd.print("-");
     lcd.print(tempmonGetTemp());

    lcd.setCursor(0, 2);
    lcd.print("-------");

  delay(50); //Increase to retard LCD
 
Last edited:
A few days ago I purchased ten 360 rotary encoders from Amazon in anticipation of applying them to the TEENSY in hopes of being able to vary different parameters in my audio experiment.
I successfully applied one of the encoders and was able to display its value on the LCD. Then I was able to apply this value to the Delay time parameter in the sketch.
The encoder library interprets each click on the encoder as a count of 4; dividing by 4 produces single integers <plus and minus> direction. At first the values were jumping around, The TEENSY is so fast it processes the encoder's switch bounces as input so I quieted the jumping with capacitors on the switch outputs of the encoder.
No attempt has been made in the Code below to detect or limit UNDERRUN Errors.
PXL_20230810_041618721.jpg

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SerialFlash.h>
#include <hd44780.h>                       // main hd44780 header
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header
#include <Encoder.h>
hd44780_I2Cexp lcd; // declare lcd object: auto locate & auto config expander chip
Encoder myEnc(16, 17);
const int LCD_COLS = 20;
const int LCD_ROWS = 4;

AudioInputI2S            i2s1;           //xy=249.3333282470703,241.3333282470703
AudioEffectDelay         delay1;         //xy=385.3333435058594,248.0000114440918
AudioOutputI2S           i2s2;           //xy=523.333381652832,194.3333511352539
AudioConnection          patchCord1(i2s1, 1, delay1, 0);
AudioConnection          patchCord2(delay1, 0, i2s2, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=255.3333511352539,299.99999618530273

const int myInput = AUDIO_INPUT_LINEIN;
//const int myInput = AUDIO_INPUT_MIC;

extern float tempmonGetTemp(void);

void setup() {
   // Audio connections require memory to work.  For more
  // detailed information, see the MemoryAndCpuUsage example
  AudioMemory(200);

  // Enable the audio shield, select input, and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.5);

  delay1.delay(0, 0);

  int status;
	status = lcd.begin(LCD_COLS, LCD_ROWS);
	if(status) // non zero status means it was unsuccesful
	{
			hd44780::fatalError(status); // does not return
	}
	  lcd.setCursor(0, 0);
    lcd.print("                    ");
    lcd.setCursor(0, 1);
    lcd.print("                    ");
    lcd.setCursor(0, 2);
    lcd.print("                    ");
    lcd.setCursor(0, 3);
    lcd.print("                    ");
}

//long oldPosition  = -999;
long oldPosition  = 0;
elapsedMillis volmsec=0;

void loop() {
  long newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    oldPosition = newPosition;
    //Serial.println(newPosition);
    
    lcd.setCursor(0, 1);
    lcd.print(abs(newPosition/4));
    lcd.print("                    ");
    delay1.delay(0, newPosition/4);
  }

  // every 50 ms, adjust the volume
  if (volmsec > 50) {
    float vol = analogRead(15);
    vol = vol / 1023.0;
    //audioShield.volume(vol); // <-- uncomment if you have the optional
    volmsec = 0;               //     volume pot on your audio shield
  }
   lcd.setCursor(0, 0);
     lcd.print(AudioProcessorUsage());
     lcd.print("-");
     lcd.print(AudioMemoryUsage());
     lcd.print("-");
     lcd.print(tempmonGetTemp());

    lcd.setCursor(0, 2);
    lcd.print("-------");

  delay(10);
}
 
Can confirm, fraction of 1% is about right for just I2S input & output and delay using internal RAM. These are all quite efficient.
 
It is entirely possible I make mistakes in my observations and code. This forum can bring up these errors and discuss them. I am fortunate to have access to it.
 
Succeeded in adding FLANGER Effect after the DELAY Effect routine. Extending FLANGE delay too much underruns its buffer. Knowing flanging and other effects like Reverb can be added, I need to learn how to save/load presets on the SD Card for obvious reasons. I believe this can be done with the CASE instruction. Presets will make the project very agile. Since SD Cards are massive, learning to save/write, load/read, record, play, and delete files and programs through a user interface will help in other ways not yet realized.
 
By isolating a Powered Speaker with the Transformer from AMAZON listed below, driven by the TEENSY Audio OUTPUT, the unwanted ground loop noise in the speaker is eliminated.
BOJACK EI-14 High Efficiency Audio Isolation Transformers 1:1 600:600 Ohm(Pack of 10 Pieces)
 
Back
Top