Timing accuracy using Audio library

Status
Not open for further replies.
Hello Teensy and audio enthusiasts,

I am loving the Teensy and the audio library. I have an issue with the timing accuracy though. Let me first describe the issue and then give you details like source code.
I want to play a sound at a regular interval, say 600 ms. It's a very short sound. The issue is that the intervals are not exactly 600 ms. A curious phenomenon appears where the interval is sometimes slightly too long and then slightly too short. Specifically, the interval is sometimes 600.6 ms, and other times 597.7 ms. The average interval is pretty much spot on at 600.01 ms. Here you see a histogram (yes, actually a histogram!) of the actually produced intervals:
intervals.png

So there appears to be a magic 2.90 ms "quantisation" of the intervals.

So my question is: am I doing something wrong? Is this behaviour somehow expected from the audio library? Is there something that can be done to fix this? What I want is to be able to play at pretty much exactly 600 ms intervals.

Any help greatly appreciated! And thanks for all the developers of Teensy & the Audio library, it's really fantastic.


Implementation details

See below for the Teensy source code. I connected the audio jack of the Teensy Audio board to my PC sound card and recorded the Teensy output as a wave file (about 25 minutes of recording of the Teensy generating the sounds). Then I took the original WAV file that Teensy is supposed to play (included in the source code here below), and I used a Python script to determine the exact onsets of that wave file in the Teensy audio output (using a cross correlation technique). I can give details of the latter procedure as well, but I am very confident nothing is wrong there because a friend had exactly the same analysis using a completely different recording, hardware setup, and onset detection technique.

Also, the above issue does not just hold for 600 ms intervals. When trying to produce other interval durations, I get the same effect.

The actually produced intervals:
overall intervals:
mean 600.010253 SD 1.157552 ms

The two bars in the plot above correspond to:
mean 597.684822 SD 0.006784 ms
mean 600.586433 SD 0.007828 ms

As you can see, each bar is actually reproduced remarkably precise (with only several microseconds jitter)! But why are there these two modes? And is there a way to Teensy output a single mode, centered at exactly 600 ms?

In case this is useful, here is the actual recording from the Teensy: http://132.206.106.46:4404/simplified_metronome_25min_07April2018.wav




This is the source code for the Teensy (with audio board):


/*
* Play a metronome sound
*
*/

#include <Audio.h>


// Converted from metronome.wav, using 44100 Hz, u-law encoding
const unsigned int AudioSampleMetronome[353] = {
0x0100052C,0x04010101,0x06830004,0x3C322719,0x5F554D44,0x57656765,0xF4F0E4C0,0xE7EEF2F4,
0xDDE0E0E2,0x42AFCDD7,0x77726759,0x6D737779,0xF0E48A61,0x6150D6ED,0xCA505D61,0xF8F7F4E9,
0xF1F4F6F7,0x6750D8EB,0x7875716E,0x76757678,0x6C737576,0xF6EED259,0xF5F5F7F8,0xF5F5F6F6,
0xDDECF3F5,0x60615706,0x746D625D,0x75757777,0x73757676,0x08596770,0xF4F0E8D9,0xE3EDF3F5,
0xA99FC3D6,0xEEE9E1D0,0xD8E5EBEE,0x756E5E80,0x6E747777,0xC2145364,0xE7D5C3C3,0xF7F7F6F2,
0xF4F4F5F6,0x54CEE9F2,0x72727068,0x74757271,0xC2526570,0x30D1E0DE,0xC13C4D4D,0xEBEAE7DD,
0xDBE6EAEB,0x66604CBD,0x964A5E66,0x685A34B5,0x676F7170,0xC0B33E5B,0xDFD7CEC4,0xDCE1E2E2,
0xCCCCCFD4,0x534594C5,0x565B5B58,0x4441424D,0x50423E42,0xA1515A58,0xE7E8E4D7,0xD0D5DDE3,
0xA7BAC5CB,0xC3AC8D8F,0x40C3D2D0,0x5D616159,0x494B5056,0x4C4D4C4A,0x2B343E46,0xD8D0C094,
0xB4D1D9DB,0x1839443A,0x3C311C8B,0xB8344443,0xDADBD9D1,0xE6E2DCD9,0xD3E1E6E8,0x62605290,
0x66626161,0x6A6A6A69,0x5A646869,0x18072D48,0xBC12312C,0xDEDBD7D0,0xDADDE0E0,0xD1D2D6D8,
0xEBE6DED4,0xD9E3E9EC,0x625D49C0,0x414D5760,0x48312031,0x65646157,0x51596063,0xCBC5A540,
0x31B3C3C9,0x1C4E544D,0xDBD9D6CB,0xE1E2E1DF,0xCBD6DCE0,0x393F36AA,0x4A454037,0x64615951,
0x434F5A62,0x49494642,0x4D474546,0x47515452,0xB78D2237,0xE6E2DACD,0xE7EAEAE9,0xDFDBDEE3,
0xE1E2E3E2,0xC0D3DCE0,0x5B564B2E,0x4E4E545A,0x51535351,0x544F4D4F,0x5E61605B,0x41485057,
0xD4CCB629,0xE0E1DFDA,0xD4D5D7DB,0xAAB7C5D0,0xA70A83A0,0x33BAC5BF,0x5D5D5A51,0x434C5459,
0x44464442,0x4B494442,0x2F3D4449,0xB4B1A803,0xCEC9C2B9,0xC5C9CDD0,0x8891AEBD,0xB9B6B2A4,
0xD0CFC8C0,0x2592BCCA,0xA9AD9B1E,0x42403305,0x463D393F,0x983F4B4C,0xC7CCCBC2,0xCDC8C2C2,
0xCACBCCCE,0x3024B0C4,0x1EAEAC12,0x2D414640,0x41351E06,0x51525048,0x504C494C,0x41454A50,
0xB2A32338,0xC2C1BCB7,0x3620ADBE,0xB419363C,0xD1D0CDC5,0xC9D0D2D2,0x311CA8BF,0x17263336,
0x514B3F28,0x40484F52,0xB9A11D33,0xD5D5D1C7,0xC0C5CDD2,0xC4C4C1BE,0x238AB0BE,0x3F39342F,
0x9E313F41,0xABB9BEB8,0x4C463A1A,0x4C4A4B4D,0x494D4E4D,0x4A464445,0x2A414A4D,0x2702A29A,
0x1B2A2F2F,0xBBB7B19B,0xD2CEC7C1,0xD7D8D7D5,0xCCD0D3D5,0x17A8BBC5,0x31373831,0x453A302B,
0x4A4E4F4C,0x832D3B44,0xC4C2BAAE,0x0CA8BAC2,0xA9901A21,0xC2C0BCB5,0xB9C1C2C2,0x343422A6,
0xA7A20128,0x3D2F07A2,0x41444543,0x8A2C373E,0xB5BBBBB2,0xBBB7B1B0,0xB1B7BBBC,0x413A2B95,
0x45454442,0x2A353C42,0xB4B5AC02,0xABA8A7AE,0xA9B0B0AF,0x0109859C,0xBCB6AB99,0xC3C3C2C0,
0xB9BABDC1,0xA2B1B7B9,0x211E1B04,0x2C2D2E28,0x31373530,0xB6AA951E,0xC1C3C2BF,0xA9B6BCC0,
0x43403417,0x3C414445,0xAC9B1F32,0xBEB6B1B0,0xC4C2C2C1,0xBBBFC2C4,0x9BB0B7BA,0x3F39311D,
0x49464240,0x504E4C4A,0x4B4E5051,0x30384146,0xBDB3A01C,0xCFCDC8C3,0xC7CACDCF,0xB4BBC1C4,
0x8394A1AB,0x840B110B,0x1F009291,0x3A39352E,0x3B3B3B3B,0x4042413E,0x93162C37,0xACA8A5A1,
0xA9AEB0B0,0x0192A0A5,0xA2A39B86,0xA7A29C9D,0xB9B7B3AD,0xBEBDBAB9,0xB9BBBCBD,0x2290AAB5,
0x44423C32,0x42434444,0x39404243,0x1A242B32,0x0F0C0B10,0xA49A8709,0xAFAEACA9,0xADA9AAAD,
0xBBB6B3B1,0xBCC0C1C0,0x87A1B0B7,0x24221910,0x2B262323,0x2E30302F,0x06162128,0x929A988F,
0x27241905,0x97011A25,0xA5A3A2A0,0xA5A7A9A7,0x989B9FA2,0x9E9F9D99,0x9997989B,0xA1A3A39F,
0x0983929B,0x8C87020A,0x0A05838A,0x0C080809,0x04050A0C,0x10110E07,0x09060309,0x91850208,
0x9E9E9D99,0xB0AFA9A3,0xAAADAFB0,0x128198A4,0x22211D18,0x1F212223,0x010C141A,0x96908C86,
0xA5A4A29E,0x9DA3A6A5,0x8F949597,0x0F060185,0x070B1212,0x93880407,0x00829095,0x82868783,
0x8A888482,0x9693908B,0x90909396,0x0D06868E,0x1E171110,0x23222221,0x21232423,0x0A151A1E,
0x95959389,0x97979795,0x95979796,0x878C9092,0x82838283,0x83000100,0x928E8885,0x98989896,
0x92959898,0x8A8C8B8C,0x04030085,0x09040202,0x1110100D,0x02060D10,0x05050402,0x81020405,
0x8F8C8884,0x91908E8F,0x8F909191,0x87898B8D,0x03018184,0x090A0907,0x06070708,0x02040405,
0x01020203,0x00000001,0x00000080,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,
};

/*
Setting up infrastructure for capturing taps (from a connected FSR)
*/

boolean prev_active = false; // Whether we were active on the previous loop iteration


int metronome_interval = 600; // Time between metronome clicks

unsigned long next_metronome_t = 0; // the time at which we should play the next metronome beat
unsigned long current_t = 0; // the current time (in ms)
unsigned long prev_t = 0; // the time stamp at the previous iteration (used to ensure correct loop time)
unsigned long missed_frames = 0;



/*
Setting up the audio
*/

float sound_volume = .5; // the volume

// Create the Audio components.
// We create two sound memories so that we can play two sounds simultaneously
AudioPlayMemory sound0;
AudioPlayMemory sound1;
AudioMixer4 mix1; // one four-channel mixer (we'll only use two channels)
AudioOutputI2S headphones;

// Create Audio connections between the components
AudioConnection c1(sound0, 0, mix1, 0);
AudioConnection c2(sound1, 0, mix1, 1);
AudioConnection c3(mix1, 0, headphones, 0);
AudioConnection c4(mix1, 0, headphones, 1); // We connect mix1 to headphones twice so that the sound goes to both ears

// Create an object to control the audio shield.
AudioControlSGTL5000 audioShield;





void setup(void) {
/* This function will be executed once when we power up the Teensy */

Serial.begin(9600); // Initiate serial communication
Serial.print("TeensyTap starting...\n");

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

// turn on the output
audioShield.enable();
audioShield.volume(sound_volume);

// reduce the gain on mixer channels, so more than 1
// sound can play simultaneously without clipping
mix1.gain(0, 0.5);
mix1.gain(1, 0.5);

Serial.print("TeensyTap ready.\n");
}



void do_activity() {
/* This is the usual activity loop */

/* If this is our first loop ever, initialise the time points at which we should start taking action */
if (prev_t == 0) { prev_t = current_t; } // To prevent seeming "lost frames"
if (next_metronome_t == 0) { next_metronome_t = current_t+metronome_interval; }

if (current_t > prev_t) {
// Main loop tick (one ms has passed)


if ((prev_active) && (current_t-prev_t > 1)) {
// We missed a frame (or more)
missed_frames += (current_t-prev_t);
}


/*
* Deal with the metronome
*/
// Is this a time to play a metronome click?
if (current_t >= next_metronome_t) {

// Play metronome click
sound1.play(AudioSampleMetronome);

// And schedule the next upcoming metronome click
next_metronome_t += metronome_interval;

}

// Update the "previous" state of variables
prev_t = current_t;
}

}


void loop(void) {
/* This is the main loop function which will be executed ad infinitum */

current_t = millis(); // get current time (in ms)
do_activity();

}
 
Your example code doesn't compile because of blank in hex constants in AudioSampleMetronome. Perhaps an artifact of your cut and paste? You can use the CODE tags (#) to preserve indentation of your sketch.

Removing the blanks and printing millis() on each sound1.play(), I get exactly 600 ms each time.
Code:
...
225213
225813
226413
227013
227613
228213
228813
229413
230013
230613
...

I'll have to setup scope to look at actual audio output interval. (2.9 ms is the about the time for 128 byte sample at 44.1khz)

EDIT: here is scope shot of audio pulse with digital pin toggle on channel 2, and it looks like pulse is on 600 ms boundary.
metro.png

maybe if one zooms in, one could see the small variation ?? Or i need a better way to measure pulse gap
 
Last edited:
Hi Manitou,
Thanks so much for sending your insights! Yes, you're right, something went wrong when copying the source code.
Let me try again here below.

As for your comment about using
Code:
millis()
instead of playing the sound - yes, I can confirm I get the same thing, namely Teensy according to its own clock says it's exactly 600 ms. However, the issue is that the sound that physically plays does not seem to have exactly that interval of 600 ms. Maybe with the source code below you can take a look? Or feel free to analyse the wave file that I sent, that is a recording I made from the Teensy running the script below.

Again, many thanks for your help!

Code:
/* 
 *  Play a metronome sound
 *  
 */

#include <Audio.h>


// Converted from metronome.wav, using 44100 Hz, u-law encoding
const unsigned int AudioSampleMetronome[353] = {
0x0100052C,0x04010101,0x06830004,0x3C322719,0x5F554D44,0x57656765,0xF4F0E4C0,0xE7EEF2F4,
0xDDE0E0E2,0x42AFCDD7,0x77726759,0x6D737779,0xF0E48A61,0x6150D6ED,0xCA505D61,0xF8F7F4E9,
0xF1F4F6F7,0x6750D8EB,0x7875716E,0x76757678,0x6C737576,0xF6EED259,0xF5F5F7F8,0xF5F5F6F6,
0xDDECF3F5,0x60615706,0x746D625D,0x75757777,0x73757676,0x08596770,0xF4F0E8D9,0xE3EDF3F5,
0xA99FC3D6,0xEEE9E1D0,0xD8E5EBEE,0x756E5E80,0x6E747777,0xC2145364,0xE7D5C3C3,0xF7F7F6F2,
0xF4F4F5F6,0x54CEE9F2,0x72727068,0x74757271,0xC2526570,0x30D1E0DE,0xC13C4D4D,0xEBEAE7DD,
0xDBE6EAEB,0x66604CBD,0x964A5E66,0x685A34B5,0x676F7170,0xC0B33E5B,0xDFD7CEC4,0xDCE1E2E2,
0xCCCCCFD4,0x534594C5,0x565B5B58,0x4441424D,0x50423E42,0xA1515A58,0xE7E8E4D7,0xD0D5DDE3,
0xA7BAC5CB,0xC3AC8D8F,0x40C3D2D0,0x5D616159,0x494B5056,0x4C4D4C4A,0x2B343E46,0xD8D0C094,
0xB4D1D9DB,0x1839443A,0x3C311C8B,0xB8344443,0xDADBD9D1,0xE6E2DCD9,0xD3E1E6E8,0x62605290,
0x66626161,0x6A6A6A69,0x5A646869,0x18072D48,0xBC12312C,0xDEDBD7D0,0xDADDE0E0,0xD1D2D6D8,
0xEBE6DED4,0xD9E3E9EC,0x625D49C0,0x414D5760,0x48312031,0x65646157,0x51596063,0xCBC5A540,
0x31B3C3C9,0x1C4E544D,0xDBD9D6CB,0xE1E2E1DF,0xCBD6DCE0,0x393F36AA,0x4A454037,0x64615951,
0x434F5A62,0x49494642,0x4D474546,0x47515452,0xB78D2237,0xE6E2DACD,0xE7EAEAE9,0xDFDBDEE3,
0xE1E2E3E2,0xC0D3DCE0,0x5B564B2E,0x4E4E545A,0x51535351,0x544F4D4F,0x5E61605B,0x41485057,
0xD4CCB629,0xE0E1DFDA,0xD4D5D7DB,0xAAB7C5D0,0xA70A83A0,0x33BAC5BF,0x5D5D5A51,0x434C5459,
0x44464442,0x4B494442,0x2F3D4449,0xB4B1A803,0xCEC9C2B9,0xC5C9CDD0,0x8891AEBD,0xB9B6B2A4,
0xD0CFC8C0,0x2592BCCA,0xA9AD9B1E,0x42403305,0x463D393F,0x983F4B4C,0xC7CCCBC2,0xCDC8C2C2,
0xCACBCCCE,0x3024B0C4,0x1EAEAC12,0x2D414640,0x41351E06,0x51525048,0x504C494C,0x41454A50,
0xB2A32338,0xC2C1BCB7,0x3620ADBE,0xB419363C,0xD1D0CDC5,0xC9D0D2D2,0x311CA8BF,0x17263336,
0x514B3F28,0x40484F52,0xB9A11D33,0xD5D5D1C7,0xC0C5CDD2,0xC4C4C1BE,0x238AB0BE,0x3F39342F,
0x9E313F41,0xABB9BEB8,0x4C463A1A,0x4C4A4B4D,0x494D4E4D,0x4A464445,0x2A414A4D,0x2702A29A,
0x1B2A2F2F,0xBBB7B19B,0xD2CEC7C1,0xD7D8D7D5,0xCCD0D3D5,0x17A8BBC5,0x31373831,0x453A302B,
0x4A4E4F4C,0x832D3B44,0xC4C2BAAE,0x0CA8BAC2,0xA9901A21,0xC2C0BCB5,0xB9C1C2C2,0x343422A6,
0xA7A20128,0x3D2F07A2,0x41444543,0x8A2C373E,0xB5BBBBB2,0xBBB7B1B0,0xB1B7BBBC,0x413A2B95,
0x45454442,0x2A353C42,0xB4B5AC02,0xABA8A7AE,0xA9B0B0AF,0x0109859C,0xBCB6AB99,0xC3C3C2C0,
0xB9BABDC1,0xA2B1B7B9,0x211E1B04,0x2C2D2E28,0x31373530,0xB6AA951E,0xC1C3C2BF,0xA9B6BCC0,
0x43403417,0x3C414445,0xAC9B1F32,0xBEB6B1B0,0xC4C2C2C1,0xBBBFC2C4,0x9BB0B7BA,0x3F39311D,
0x49464240,0x504E4C4A,0x4B4E5051,0x30384146,0xBDB3A01C,0xCFCDC8C3,0xC7CACDCF,0xB4BBC1C4,
0x8394A1AB,0x840B110B,0x1F009291,0x3A39352E,0x3B3B3B3B,0x4042413E,0x93162C37,0xACA8A5A1,
0xA9AEB0B0,0x0192A0A5,0xA2A39B86,0xA7A29C9D,0xB9B7B3AD,0xBEBDBAB9,0xB9BBBCBD,0x2290AAB5,
0x44423C32,0x42434444,0x39404243,0x1A242B32,0x0F0C0B10,0xA49A8709,0xAFAEACA9,0xADA9AAAD,
0xBBB6B3B1,0xBCC0C1C0,0x87A1B0B7,0x24221910,0x2B262323,0x2E30302F,0x06162128,0x929A988F,
0x27241905,0x97011A25,0xA5A3A2A0,0xA5A7A9A7,0x989B9FA2,0x9E9F9D99,0x9997989B,0xA1A3A39F,
0x0983929B,0x8C87020A,0x0A05838A,0x0C080809,0x04050A0C,0x10110E07,0x09060309,0x91850208,
0x9E9E9D99,0xB0AFA9A3,0xAAADAFB0,0x128198A4,0x22211D18,0x1F212223,0x010C141A,0x96908C86,
0xA5A4A29E,0x9DA3A6A5,0x8F949597,0x0F060185,0x070B1212,0x93880407,0x00829095,0x82868783,
0x8A888482,0x9693908B,0x90909396,0x0D06868E,0x1E171110,0x23222221,0x21232423,0x0A151A1E,
0x95959389,0x97979795,0x95979796,0x878C9092,0x82838283,0x83000100,0x928E8885,0x98989896,
0x92959898,0x8A8C8B8C,0x04030085,0x09040202,0x1110100D,0x02060D10,0x05050402,0x81020405,
0x8F8C8884,0x91908E8F,0x8F909191,0x87898B8D,0x03018184,0x090A0907,0x06070708,0x02040405,
0x01020203,0x00000001,0x00000080,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,
};

/*
  Setting up infrastructure for capturing taps (from a connected FSR)
*/

boolean prev_active = false; // Whether we were active on the previous loop iteration


int metronome_interval = 600; // Time between metronome clicks

unsigned long next_metronome_t            = 0; // the time at which we should play the next metronome beat
unsigned long current_t            = 0; // the current time (in ms)
unsigned long prev_t               = 0; // the time stamp at the previous iteration (used to ensure correct loop time)
unsigned long missed_frames = 0;



/*
  Setting up the audio
*/

float sound_volume = .5; // the volume

// Create the Audio components.
// We create two sound memories so that we can play two sounds simultaneously
AudioPlayMemory    sound0;
AudioPlayMemory    sound1;
AudioMixer4        mix1;   // one four-channel mixer (we'll only use two channels)
AudioOutputI2S     headphones;

// Create Audio connections between the components
AudioConnection c1(sound0, 0, mix1, 0);
AudioConnection c2(sound1, 0, mix1, 1);
AudioConnection c3(mix1, 0, headphones, 0);
AudioConnection c4(mix1, 0, headphones, 1); // We connect mix1 to headphones twice so that the sound goes to both ears

// Create an object to control the audio shield.
AudioControlSGTL5000 audioShield;





void setup(void) {
  /* This function will be executed once when we power up the Teensy */
  
  Serial.begin(9600);  // Initiate serial communication
  Serial.print("TeensyTap starting...\n");

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

  // turn on the output
  audioShield.enable();
  audioShield.volume(sound_volume);

  // reduce the gain on mixer channels, so more than 1
  // sound can play simultaneously without clipping
  mix1.gain(0, 0.5);
  mix1.gain(1, 0.5);

  Serial.print("TeensyTap ready.\n");
}









void do_activity() {
  /* This is the usual activity loop */
  
  /* If this is our first loop ever, initialise the time points at which we should start taking action */
  if (prev_t == 0)           { prev_t = current_t; } // To prevent seeming "lost frames"
  if (next_metronome_t == 0) { next_metronome_t = current_t+metronome_interval; }
  
  if (current_t > prev_t) {
    // Main loop tick (one ms has passed)
    
    
    if ((prev_active) && (current_t-prev_t > 1)) {
      // We missed a frame (or more)
      missed_frames += (current_t-prev_t);
    }
    
   
    /* 
     * Deal with the metronome
    */
    // Is this a time to play a metronome click?
      if (current_t >= next_metronome_t) {

  	// Play metronome click
  	sound1.play(AudioSampleMetronome);
	
  	// And schedule the next upcoming metronome click
  	next_metronome_t += metronome_interval;
	
      }
    
    // Update the "previous" state of variables
    prev_t = current_t;
  }

}


void loop(void) {
  /* This is the main loop function which will be executed ad infinitum */

  current_t = millis(); // get current time (in ms)
      do_activity();

}
 
If i trigger scope on digital pin toggle before sound1.play(), I do see a variation/progression in when the audio signal starts from 3.7 to 6.3 ms after the toggle. Variations in delay 4.4, 4.6, 5.1, 5.7 ms. I'm not familiar enough with workings of audio lib to comment on cause.
 
If i trigger scope on digital pin toggle before sound1.play(), I do see a variation/progression in when the audio signal starts from 3.7 to 6.3 ms after the toggle. Variations in delay 4.4, 4.6, 5.1, 5.7 ms. I'm not familiar enough with workings of audio lib to comment on cause.

The audio is processed in packets, each 2.9 ms long. The pin toggle can happen any time, you don't know which sample is currently played. It can be at the beginning, the end, middle of a packet..whereever. Your program does not know this, but the audio-library can only write to the *next* packet. This explains the variation. In addition, there may be a delay which depends on the number of connections ("patch cords") between your input and output.
 
Last edited:
The audio is processed in packets, each 2.9 ms long. The pin toggle can happen any time, you don't know which sample is currently played. It can be at the beginning, the end, middle of a packet..whereever. Your program does not know this, but the audio-library can only write to the *next* packet. This explains the variation. In addition, there may be a delay which depends on the number of connections ("patch cords") between your input and output.

I don't think your arguments about the pin toggle apply in this case. (edit: actually, see below, I understand your argument now) The audio data in the sketch only takes about 30 ms (consists of 11 samples). Then after 600 ms, the audio is re-started with sound1.play(AudioSampleMetronome);. I inserted a pin toggle right before the sound1.play. The scope image above shows the short audio event (yellow, channel 1) every 600 ms. My last post explained that on closer examination, one could see that the start time for the audio event progressively varies after the toggle. I can understand the sample delay (2.9ms), but not the variation...

EDIT: here is plot of duration of play() with isPlaying() using micros() and playing the audio every 60 ms.
nice.png
The difference in duration times is from the variation in when the audio actually starts. max difference is 2881 us.

So this matches Frank B's observation that the sample packet clock is "running", and your play() request won't be scheduled til the next packet slot.
 
Last edited:
Thanks to you both for these comments that help me understand the problem! My question now is if there is a way to circumvent this. I'd like to play this audio sample with reliable timing, and 2.9 ms uncertainty is quite a lot for the type of experiments that I intend to use it for.
Any suggestions much appreciated!
 
The simple way is to launch your play requests synchronized with the packet clock, choosing entire multiples of 2.9ms as trigger intervals.
The more complicated way would be not using the audio library with its block processing but handle audio generation sample wise or with very small bocks, almost in real time. That’s the approach which I took for my current project, generating the samples on the fly in batches of 5 and outputting these at a 192kHz sample rate with the help of the internal DAC ring buffer set to a depth of 10, so that one half plays while the other half is reloaded, which induces a delay of only 26us.
A compromise would be keeping the audio library but forking and modifying it for smaller blocks, so that the “uncertainty” is shortened to an acceptable value, i.e. 0.7256ms with blocks of 32 samples.

Don’t misunderstand me, the audio library is a great tool! But one has to see that it is streaming oriented, optimized for optimal mass handling of a more ore less constant data flow. But generating a metronome sound from an array or wavetable, and launched at arbitrary moments, is not a streaming thing.
 
Last edited:
If your samples vary in a consistent pattern, like the graph in post#6, you could just pad the beginning of the sample with silence. Have 4 separate samples trimmed with silence to offset the variation in delay, and cycle through them to match the repetition of the delays?
 
Status
Not open for further replies.
Back
Top