USB Audio Frame Sync on Teensy 4.0

grinch

Well-known member
Hi, I am working on an audio project using the Teensy 4.0 as a USB audio device. In this project I use a usb audio stream from Max/MSP to control a large array of binary shift register outputs, 64 in total. I have this working well with 32 outputs, by mapping the bits of the two 16-bit audio channels to different outputs. I also had this working well with 64 outputs on the Teensy 3.6, by creating a max patch that switches between two audio streams every sample, effectively encoding 4 16-bit channels at a sample rate of 22050. I switched to the Teensy 4.0 to solve a separate issue with this project, outlined in detail here: POST

Switching to the 4.0 has solved my other issue, but now I am experiencing a problem where the order of received samples in a frame appears not to be constant, or not synced to the beginning and end of a frame. My test patch which worked on the Teensy 3.6 (save for the unrelated issue linked above), now does not work, so I feel certain this is a hardware issue rather than an issue with my Max Patch. The samples within frames go out of sync and start controlling the wrong output, and first stuttering back and forth, and then switching entirely.

I am hoping someone with in depth knowledge of the audio library can shed some light on how frame sync works with Teensy 4.0 usb audio. The behavior I'm getting seems to suggest that the USB audio library is either throwing out or arbitrarily interpolating samples at some point, which doesn't seem right to me. After browsing the source code here: teensy4/usb_audio.cpp I'm also interested in the function of the variable "usb_high_speed". It appears to change some parameters of how USB audio gets synced, and I'm wondering if there is a compile setting whereby I can change this, since that seems worth trying.

This has been a pretty ambitious project for me and I'm extremely close to it working perfectly, so any help navigating this complex issue is much appreciate. Thanks!

Here is my source code:

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

AudioInputUSB            usb1;           
AudioOutputMQS           mqs;           //just to trigger audio library update 
AudioOutputCopyBuffer    copyBuffer;
AudioConnection          patchCord1(usb1, 0, mqs, 0);
AudioConnection          patchCord2(usb1, 0, copyBuffer, 0);
AudioConnection          patchCord3(usb1, 1, copyBuffer, 1);

SPISettings spiSettings(25000000, MSBFIRST, SPI_MODE0); 
IntervalTimer myTimer;

#define SS_PIN 10
#define PULLUP_PIN 14

volatile int16_t out[2];

bool alt = false;

void doOutput(){
  __disable_irq();
  if(alt){
    digitalWriteFast(SS_PIN, HIGH);
  }else{
    copyBuffer.readFromBuffers(&out[0], &out[1]);
    digitalWriteFast(SS_PIN, LOW);
    SPI.transfer16(out[1]); 
    SPI.transfer16(out[0]);  
  }
  alt = !alt;
  __enable_irq();
}

void setup() {                
  AudioMemory(100);
  Serial.begin(115200);
  pinMode(PULLUP_PIN, INPUT_PULLUP);
  pinMode(SS_PIN, OUTPUT);
  digitalWriteFast(SS_PIN, LOW);
  SPI.begin();
  SPI.beginTransaction(spiSettings);
  myTimer.priority(0);
  myTimer.begin(doOutput, 11.4); //11.337868480725624 //22.675736961451247 //22.666666666062222
  copyBuffer.begin();
}

unsigned long blip = 0;
unsigned long lastBlip = 0;
unsigned long blipInterval = 0;
int16_t checkOut[2] = {};
unsigned long counter = 0;

unsigned long getOverrun;

void loop() {

  if(millis() - counter > 1000){
    if(copyBuffer.checkOverrunAndClear()){
      Serial.println("Error! Overrun!");
      Serial.println("");
    }
    counter = millis();
  }

  if(digitalRead(PULLUP_PIN)){
    blip = micros();
    blipInterval = micros() - lastBlip;
    lastBlip = micros();
    AudioNoInterrupts();
    checkOut[0] = out[0];
    checkOut[1] = out[1];
    AudioInterrupts();
    while(digitalRead(PULLUP_PIN)){ /*wait*/ }
    blip = micros() - blip;
    Serial.println("Error! Blip Detected!");
    Serial.print("Down Time: ");
    Serial.print(float(blip) / 1000.0f, 6);
    Serial.println(" ms");
    Serial.print("Blip Interval: ");
    Serial.print(float(blipInterval) / 1000.0f, 6);
    Serial.println(" ms");
    Serial.print("Out Reg 0 Val: ");
    Serial.println(checkOut[0]);
    Serial.print("Out Reg 1 Val: ");
    Serial.println(checkOut[1]);
    Serial.println("");
  }
}

Buffer Transfer Object Included as "output_copybuffer.h"
Code:
#ifndef output_copybuffer_h_
#define output_copybuffer_h_

#define COPY_BUFFER_COUNT 128

#include "Arduino.h"
#include "AudioStream.h"
#include "DMAChannel.h"

class AudioOutputCopyBuffer : public AudioStream
{
public:
	AudioOutputCopyBuffer(void) : AudioStream(2, inputQueueArray) { begin(); }
	virtual void update(void);
	void begin(void);
	void readFromBuffers(int16_t *p1, int16_t *p2);
	void readFromBuffersAlternating(int16_t *p1, int16_t *p2, int16_t *p3, int16_t *p4);
	bool checkOverrunAndClear(){
		bool check = overrun;
		overrun = false;
		return check;
	};
  bool checkUnderrunAndClear(){
    bool check = underrun;
    underrun = false;
    return check;
  };
  unsigned long getOverrunInterval(){
    if(!overrun){ return 0; }
    else{
      unsigned long count = overrunTime;
      overrunTime = 0;
      overrun = false;
      return count;
    }
  }
private:
	int16_t leftBuffer[COPY_BUFFER_COUNT][AUDIO_BLOCK_SAMPLES];
	int16_t rightBuffer[COPY_BUFFER_COUNT][AUDIO_BLOCK_SAMPLES];
	int16_t write, read;
	int16_t index = 0;
	bool error = false;
  bool underrun = false;
  bool underrunInternal = false;
  bool overrun = false;
  unsigned long overrunCounter = 0;
  unsigned long overrunTime = 0;
	audio_block_t *inputQueueArray[2];
	static bool update_responsibility;
};

output_copybuffer.cpp:
Code:
#include <Arduino.h>
#include "output_copybuffer.h"
#include "utility/pdb.h"

bool AudioOutputCopyBuffer::update_responsibility = false;

void AudioOutputCopyBuffer::begin(void)
{
	write = 0;
	read = 0;
	error = false;
	index = 0;
}


void AudioOutputCopyBuffer::update(void)
{
	audio_block_t *b1;
	audio_block_t *b2;
	b1 = receiveReadOnly(0); // input 0
	b2 = receiveReadOnly(1); // input 1
	if (!b1 || !b2) {
		return;
	}

	__disable_irq();
	memcpy(leftBuffer[write], b1->data, AUDIO_BLOCK_SAMPLES * 2);
	memcpy(rightBuffer[write], b2->data, AUDIO_BLOCK_SAMPLES * 2);
	write++;
	if(write >= COPY_BUFFER_COUNT){ write = 0;}
	if(write == read){ underrun = underrunInternal = true; }
  else{ underrunInternal = false; }
	__enable_irq();
	release(b1);
	release(b2);
}


void AudioOutputCopyBuffer::readFromBuffers(int16_t *p1, int16_t *p2)
{
	if(write != read){
		*p1 = leftBuffer[read][index];
		*p2 = rightBuffer[read][index];
		index++;
		if(index >= AUDIO_BLOCK_SAMPLES){
			index = 0;
			read++;
			if(read >= COPY_BUFFER_COUNT){
				read = 0;
			}
		}
    overrunCounter++;
	}else{
		overrun = true;
    overrunTime = overrunCounter;
    overrunCounter = 0;
	}
}

void AudioOutputCopyBuffer::readFromBuffersAlternating(int16_t *p1, int16_t *p2, int16_t *p3, int16_t *p4)
{
	if(write != read){
		*p1 = leftBuffer[read][index];
		*p2 = rightBuffer[read][index];
		index++;
		*p3 = leftBuffer[read][index];
		*p4 = rightBuffer[read][index];
		index++;
		if(index >= AUDIO_BLOCK_SAMPLES){
			index = 0;
			read++;
			if(read >= COPY_BUFFER_COUNT){
				read = 0;
			}
		}
	}else{
		if(!underrun && !underrunInternal){ overrun = true; }
	}
}
 
It seems to me that the problems of your 3 different threads posted here all stem from the same source --- attempting to use USB audio. Is that a requirement? Can Max/MSP (whatever THAT is) provide S/PDIF output? I can think of a scheme to get perfect sample-accurate synchronization on a T3.6 by using this board to do S/PDIF --> I2S conversion: https://www.amazon.com/gp/product/B07HHPJLWC/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1

-Max/MSP is a visual programming environment that's well set up for audio synthesis. The point here isn't to use Max though, but rather to get audio from any programming environment on a laptop. This could be Chuck, Reaktor, CSound, Ableton, Touch Designer, PureData, Processing, etc... Just anything that streamlines the composition process and makes it so that I don't have to do my composition via slow, messy embedded c uploads.

-Using usb audio is attractive to me because it would be a really slick plug and play setup if I can get it to work properly. Adding a bunch of weird hardware requirements starts to feel pretty inelegant.

-To get S/PDIF from a laptop I'd have to get a specialized audio interface, on top of the S/PDIF to I2S converter. This is pretty unattractive both in terms of complexity and expense, so I'd probably just go with using 2 Teensy 4.0s controlling 32 channels each before going that route. I'm pretty close to getting one controlling 64 channels however, and part of the point of this project is to expand my understanding of audio protocols, so my original idea seems like it's worth seeing through.
 
I'm hoping someone can answer my original question. The behavior I'm getting seems to suggest that USB audio is discarding samples at some point, which seems weird. I'm wondering how this happens and why it's set up that way. Can anyone shed some light on what is going on there?
 
Also hoping someone can tell me the function of "usb_high_speed" and how that variable gets set
 
The behavior I'm getting seems to suggest that USB audio is discarding samples at some point, which seems weird. I'm wondering how this happens and why it's set up that way. Can anyone shed some light on what is going on there?

I can't say exactly what's happening in your case. I don't have it set up here to investigate. But I can answer your questions about the general design.

USB audio has 3 ways to deal with small differences in clock speed between the host and device. Teensy is using the asynchronous method, where it sends feedback to the host and the host is supposed to adjust its transmit rate to match the rate Teensy needs. You can learn more about this the USB audio class specification and the USB 2.0 spec. In the USB 2.0 spec (PDF link), read pages 65-76.

Normally Teensy should not need to discard incoming data. But if the USB host sends too quickly, the incoming buffer overflows. Normally this shouldn't happen. But if the USB host ignored the rate feedback, or we mistakenly sent feedback asking to bite off more data than we could chew, or something went wrong that stalled the audio library so we didn't use up the data on schedule, then the incoming buffer suddenly has too much data. The buffer is large enough to tolerate some disruption. The rate feedback uses a simple PID (but no D factor) approach, so when the buffer is more than half full we do start asking the USB host to slow down. Usually that works well.

Anyway, the fundamental concept is Teensy has to deal with whatever the USB host actually sends. We calculate asynchronous rate feedback and hope the USB host will respond by sending at the rate we are consuming. Teensy uses a small buffer to allow some mismatch in speed. But buffering adds latency and ultimately does no good if something is going wrong if the average rate the USB host transmits is different than the rate we consume.

The buffer can overflow (host sent too much data) or underflow (host didn't send enough audio data) when things go wrong. We transmit the rate feedback to the host and hope we get the right amount of data. But we have no control over how much actually arrives! The code has to deal with those unlikely scenarios somehow....


Also hoping someone can tell me the function of "usb_high_speed" and how that variable gets set

This variable gets written during USB enumeration. If the USB connection is detected as 12 MBit/sec, it's set to zero. If the hardware is using 480 MBit, this variable is set to one.

This matters for USB audio's asynchronous rate feedback. On page 74 of the USB 2.0 spec (in the last 2 paragraphs of that long page), you'll see it specifies a 3 byte format is to be transmitted to the USB host at full speed (12 Mbit/sec) and a different 4 byte format is to be sent when using high speed (480 Mbit/sec) communication.
 
Thank you for hopping on the thread Paul, was hoping I could get your input on this ;). That helps clear up a lot of my understanding.

After making a test patch to check the overrun and underrun count variables it appears that they always increment at least slightly over the course of usb audio running. This happens even when usb audio is passed directly to one of the audio library output objects:

Code:
#include <Audio.h>

AudioInputUSB            usb1;           
AudioOutputMQS           mqs;  
AudioConnection          patchCord1(usb1, 0, mqs, 0);

void setup() {                
  AudioMemory(100);
  Serial.begin(115200);
}

extern volatile uint32_t usb_audio_underrun_count;
extern volatile uint32_t usb_audio_overrun_count;

unsigned long counter = 0;

void loop() {

  if(millis() - counter > 1000){
    Serial.println("");
    Serial.print("USB Overrun Count: ");
    Serial.println(usb_audio_overrun_count);
    Serial.print("USB Underrun Count: ");
    Serial.println(usb_audio_underrun_count);
    Serial.println("");
    counter = millis();
  }
  
}

This doesn't seem to be an issue a lot of the time in terms of the audible result, but it does definitely presents a problem in terms of how I'm trying to encode things. This seems like it might make the usb audio option untenable for 64 binary outputs, but before giving up there are just a few other options I'd like to explore.

1. How difficult would it be to modify the AudioInputUSB object to receive 4 channel audio instead of 2? There are a lot of 4 channel or greater usb audio interfaces, and given the speed of the Teensy 4.0 it seems like this would be possible. In a past project I successfully modified AudioPlaySdWav object to work for 8 channel audio, so I have a proof of concept for this kind of thing working.

As far as the audio library portion AudioInputUSB class goes, this seems like a simple matter of changing the number of input and transmit buffers from 2 to 4 and doing related updates to the code that copies them.

Elsewhere, there must be something that defines how many channels usb audio sends and receives. Where can I find this code and how would I modify it?


2. Would it be feasible to somehow track the sample offset created by overruns and underruns in the usb receive buffer when those occur and update my output code based on that? Seems like I'd just need to know whether this created an even or odd number sample offset and I could adjust my output index accordingly.


Appreciate your assistance. Just thinking things through here so any suggestions are much appreciated.
 
To directly answer your questions,

1: Somewhere between "fairly hard" to "almost impossible" depending on your level of skill and whether you have hardware or software to reliably view the USB communication. You can find other threads on this forum where people have tried and (as far as I know) nobody has done so successfully, which might be a good indication of the difficulty...

2: Should is feasible if you edit the code the right way.

Just to be clear, let me say very clearly that I'm not going to get involved in changing the USB audio code right now.

But I will ask you one quick question. Which operating system are you using? Please be specific about the version. If it's Linux, be specific about the distro and which audio system is in use (Jack, PulseAudio, etc). As I explained earlier, the overall behavior depends heavily on how the USB host responds to the asynchronous rate feedback. Reporting a problem with USB audio needs to have very specific info about the host side.
 
To directly answer your questions,

1: Somewhere between "fairly hard" to "almost impossible" depending on your level of skill and whether you have hardware or software to reliably view the USB communication. You can find other threads on this forum where people have tried and (as far as I know) nobody has done so successfully, which might be a good indication of the difficulty...

2: Should is feasible if you edit the code the right way.

Just to be clear, let me say very clearly that I'm not going to get involved in changing the USB audio code right now.

But I will ask you one quick question. Which operating system are you using? Please be specific about the version. If it's Linux, be specific about the distro and which audio system is in use (Jack, PulseAudio, etc). As I explained earlier, the overall behavior depends heavily on how the USB host responds to the asynchronous rate feedback. Reporting a problem with USB audio needs to have very specific info about the host side.

1. Gotcha, I'll go with other options in that case.

2. That's fine, appreciate you just giving advice. The sample offset tracking thing is very case specific so I wasn't expecting to have anyone else do that for me.

In answer to your OS question, I'm using OSX Mojave Version 10.14.6, on a MacBook Pro (15-inch, 2019). The specific program I'm using is Max/MSP with the CoreAudio driver.
 
I case it matters, I also have to use one of those stupid USB C to regular USB converter dongles since Apple decided to start eliminating ports. I've tried a couple of these out to see if it makes a difference and I get the same behavior with several different dongles.
 
Tested this on another computer with built-in USB ports running OSX 10.12. I get the same overrun / underrun problem as before.

One thing that would be helpful to know: is this a longstanding issue with the USB audio library, or is this something that hasn't been brought up before and is perhaps related to using the USB audio library with the OSX operating system as opposed to Windows or Linux?
 
If I had only 15 minutes to try to reproduce this problem, what should I try? The code on msg #8, right? I have a Macbook Air with 10.15.5. It does *not* have Max/MSP software. Will just playing a MP3 file with Apple's default software on 10.15.5 be enough?

Imagine I will make only 1 attempt and spend at most 15 minutes. Please help me to try the thing which has best likelihood of reproducing the problem. If I spend all 15 minutes without seeing the error, imagine I will not spend any more time...
 
Here is the code I'm using to test:

Code:
#include <Audio.h>

AudioInputUSB            usb1;           
AudioOutputMQS           mqs;           //just to trigger audio library update 
AudioConnection          patchCord1(usb1, 0, mqs, 0);

void setup() {                
  AudioMemory(100);
  Serial.begin(115200);
}

extern volatile uint32_t usb_audio_underrun_count;
extern volatile uint32_t usb_audio_overrun_count;

unsigned long counter = 0;

void loop() {

  if(millis() - counter > 1000){
    Serial.println("");
    Serial.print("USB Overrun Count: ");
    Serial.println(usb_audio_overrun_count);
    Serial.print("USB Underrun Count: ");
    Serial.println(usb_audio_underrun_count);
    Serial.println("");
    counter = millis();
  }
  
}

This issue occurs with any audio output. Just selecting "Teensy MIDI_Audio" as your output device in system preferences should be fine for testing. You can play an mp3 with QuickTime, VLC or iTunes if you want, but I don't think it matters that audio is actually playing since this is more of a sync issue, which will still occur with empty frames.

Steps to reproduce:

1. Upload this code to a Teensy 4.0 with USB Type: "Serial + MIDI + Audio" selected.

2. When you first upload the code you'll see the USB Underrun Count increment rapidly in the Serial port, until "Teensy MIDI_Audio" is selected as your output device. Select "Teensy MIDI_Audio" as your output device in system preferences. At this point Underrun Count will stick at whatever number it has reached.

3. The Overrun Count will sit stably at 0 for a while, and the Underrun Count will sit at whatever number it reached before the output device was selected.

4. After about 5-10 minutes, both Overrun and Underrun counts will start incrementing upwards. This is accompanied by small glitches in audio output whenever overrun / underrun occurs.

Should be pretty easy to reproduce. Let me know if you need any more information or if there is any way I can help. I assume from your response this is a new issue?
 
After doing some more research I have another thought. Seems like this would be a great application for the new S/PDIF input object that was added to the audio library for the Teensy 4.0. I know some suggested an SPDIF to I2S converter, which seemed really inelegant, but using SPDIF directly seems acceptable. Wondering why they didn't just suggest that... 🤔

Since SPDIF sets the sample rate for the audio library, seems like I could just wait for SPDIF PLL lock in my startup function, then set my output interrupt frequency based on the value returned by the sampleRate() function once PLL lock is achieved. Also eliminates the need for an extraneous output object for update responsibility, which is nice.

Does this seem like a workable idea? Just something I'm thinking through, so I appreciate anyone bringing up suggestions and/or potential issues before I go down the rabbit hole with that one. Also, if anyone has an example schematic for using SPDIF input with Teensy 4.0 that would be a big help.
 
Confirmed overrun and underrun of your code after a few minutes on a macbook pro, OSX 10.13.6.
I'm also having problems with highly audible clicking at regular intervals running any USB audio on the 4.0, on four different audio monitoring programs (Audacity, AudioMonitor, AudioHijack, Ableton Live 10). Seems to be related to teensy USB audio buffer overruns, reproducible cross-OS. Somehow these glitches don't seem to be widespread enough to be noticed by most users (?)

There was some success here at the bottom of this thread two years ago where several users were able to remove the glitches on T3.x, in part by limiting the teensy's output sample rate to just under 44100 (he set 44099.7). They did this using a timing library that is not compatible with the 4.0, unfortunately.

overall behavior depends heavily on how the USB host responds to the asynchronous rate feedback
I haven't been able to find information on how people have handled this from the host side --- are there any pointers?
 
Last edited:
Wondering why they didn't just suggest that... ��
Because I've never used S/PDIF on a T4.0. Or, in fact used T4.0. at all. Therefore, I won't make a recommendation based on it. I only recommend solutions that I'm 100% sure will work. I've used the S/PDIF to I2S converter before. I know how it works. I know how I2S on a T3.x works. My projects all work and I'm pretty confident I know how to make yours work, but only on that platform. I prefer an "inelegant" solution that works to an "elegant" one that doesn't.
 
Last edited:
Got the parts in today to try the built-in SPDIF method. It's working great. I'm getting very clean signal with no dropouts. Additionally by running the SPDIF output at 48kHz I eliminate an buffer overruns in my output transfer process (I believe this is because the 48kHz timing syncs well with the resolution of the interval timer interrupt).

Here is the code I am using to test this:
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include "output_copybuffer.h"

AudioInputSPDIF3 spdifIn;
AudioAnalyzePeak peak1;
AudioAnalyzePeak peak2;
AudioOutputCopyBuffer    copyBuffer;

AudioConnection          patchCord1(spdifIn, 0, peak1, 0);
AudioConnection          patchCord2(spdifIn, 1, peak2, 0);
AudioConnection          patchCord3(spdifIn, 0, copyBuffer, 0);
AudioConnection          patchCord4(spdifIn, 1, copyBuffer, 1);

SPISettings spiSettings(25000000, MSBFIRST, SPI_MODE0); 
IntervalTimer myTimer;

#define SS_PIN 10
#define PULLUP_PIN 14

volatile int16_t out[4];

bool alt = false;

void doOutput(){
  __disable_irq();
  if(alt){
    digitalWriteFast(SS_PIN, HIGH);
  }else{
    copyBuffer.readFromBuffersAlternating(&out[0], &out[1], &out[2], &out[3]);
    digitalWriteFast(SS_PIN, LOW);
    SPI.transfer16(out[3]); 
    SPI.transfer16(out[2]);
    SPI.transfer16(out[1]); 
    SPI.transfer16(out[0]);  
  }
  alt = !alt;
  __enable_irq();
}

double outputRate = 0;

void setup() {
  // put your setup code here, to run once:
  AudioMemory(100);
  Serial.begin(115200);
//  pinMode(LED_BUILTIN, OUTPUT);
//  digitalWrite(LED_BUILTIN, HIGH);
//  while (!Serial);
  delay(1000);
  
  while(!spdifIn.pllLocked()){
    Serial.println("PLL Error");
    delay(1000);
  }

  delay(1000);

  pinMode(PULLUP_PIN, INPUT_PULLUP);
  pinMode(SS_PIN, OUTPUT);
  digitalWriteFast(SS_PIN, LOW);

  outputRate = (1000000.0f / ((float)(spdifIn.sampleRate()))) + 0.01; 

  Serial.println(""); 
  Serial.print("Sample Rate: ");
  Serial.println(spdifIn.sampleRate());
  Serial.print("Output Rate (us): ");
  Serial.println(outputRate, 8);

  SPI.begin();
  SPI.beginTransaction(spiSettings);
  myTimer.priority(0);
  myTimer.begin(doOutput, outputRate); //11.337868480725624 //22.675736961451247 //22.666666666062222
  copyBuffer.begin();
}

unsigned long blip = 0;
unsigned long lastBlip = 0;
unsigned long blipInterval = 0;
int16_t checkOut[4] = {};
unsigned long counter = 0;

unsigned long getOverrun;

void loop() {

  if(millis() - counter > 1000){
    if(!spdifIn.pllLocked()){
      Serial.println("PLL Error");
    }
    if(copyBuffer.checkOverrunAndClear()){
        Serial.println("Error! Overrun!");
        Serial.println("");
    }
    if(copyBuffer.checkUnderrunAndClear()){
        Serial.println("Error! Underrun!");
        Serial.println("");
    }
    counter = millis();
  }

  if(digitalRead(PULLUP_PIN)){
    blip = micros();
    blipInterval = micros() - lastBlip;
    lastBlip = micros();
    AudioNoInterrupts();
    checkOut[0] = out[0];
    checkOut[1] = out[1];
    checkOut[2] = out[2];
    checkOut[3] = out[3];
    AudioInterrupts();
    while(digitalRead(PULLUP_PIN)){ /*wait*/ }
    blip = micros() - blip;
    Serial.println("Error! Blip Detected!");
    Serial.print("Down Time: ");
    Serial.print(float(blip) / 1000.0f, 6);
    Serial.println(" ms");
    Serial.print("Blip Interval: ");
    Serial.print(float(blipInterval) / 1000.0f, 6);
    Serial.println(" ms");
    Serial.print("Out Reg 0 Val: ");
    Serial.println(checkOut[0]);
    Serial.print("Out Reg 1 Val: ");
    Serial.println(checkOut[1]);
    Serial.print("Out Reg 2 Val: ");
    Serial.println(checkOut[2]);
    Serial.print("Out Reg 3 Val: ");
    Serial.println(checkOut[3]);
    Serial.println("");
  }
}
 
Mostly posting here out of a sense of completion. Further topics of interest would be higher resolution interrupts for clean buffer transfer at 44.1kHz sample rate and a fix for the apparent bug in the USB audio library, but those seem like matters for another time / post.

Appreciate everyone who gave advice!
 
I usually see the USB audio issue in #14 right away. And some might not notice. But it sounds fine when I set the USB mode to "Audio" only.

> SPDIF sets the sample rate for the audio library

An important point - evidently if you provide a SPDIF or toslink input to the teensy, the teensy will use this as the master clock for outgoing digital audio.
 
I looked into this more and occasional glitches are occurring even in Audio only mode. Linux Mint 19.1 with Pulseaudio, T4, even while music is playing. So probably best to use toslink in and out.

Code:
#include <Audio.h>

AudioOutputSPDIF3        spdif3;         //xy=502,202    // other inputs and outputs are possible
AudioInputUSB            usb1;           //xy=143,207
AudioConnection          patchCord1(usb1, 0, spdif3, 0);
AudioConnection          patchCord2(usb1, 1, spdif3, 1);

const int ledPin = 13;
unsigned ledRate = 1000;      // how fast to flash - faster with an error

void loop() {

  static unsigned next_time = 0;

  if (millis() >= next_time)   // normally every second
  {
    digitalToggle(ledPin);    // blink LED

    extern volatile uint32_t usb_audio_underrun_count;
    extern volatile uint32_t usb_audio_overrun_count;
    static uint32_t prev_count;

    // flash fast when errors occur
    if (usb_audio_underrun_count + usb_audio_overrun_count != prev_count)
      ledRate = 250;
    else
      ledRate = 1000;

    prev_count = usb_audio_underrun_count + usb_audio_overrun_count;

    next_time += ledRate;
  }

}

/////////////////////////////////////////////////////////

void setup() {           
  pinMode(ledPin, OUTPUT);
  AudioMemory(50);
} // setup()
 
More info. When there is no audio playing on the PC, under-runs keep occurring, but this isn't a problem and they should be ignored.

Yet in usb_audio.cpp, these under-runs cause feedback_accumulator to become way too high and it takes a long time to bring it back down to a correct value.

It converges to the right value somewhat more quickly with this line commented out:

if (f) feedback_accumulator += 3500;

With Linux, it's a good idea to disable pulseaudio's default "suspend on idle". Do this and feedback_accumulator will be correct a couple minutes after a teensy reload. It will then remain correct as you turn on/off audio on the PC.

Not clear what the best solution is. Perhaps the teensy code can detect the suspend state and not update feedback_accumulator during this period.
 
In addition to removing the "+= 3500" line, I had to change to this to completely get rid of over/under runs. This has measurably better performance (faster convergence, less deviation in buffer level). Can someone else try it in usb_audio.cpp?

Code:
                int diff = AUDIO_BLOCK_SAMPLES/2 - (int)c;

                // while feedback appears continuous, there are actually only 3 states
                if (diff > 0)
                   feedback_accumulator = 45 * (1<<24);         // fast
                else if (diff < 0)
                   feedback_accumulator = 44 * (1<<24);         // slow
                else
                   feedback_accumulator = 44.1 * (1<<24);       // nominal

                // feedback_accumulator += diff;
 
Even a bit better, good enough control of buffer level that the amount of buffering could be reduced for less latency.

Code:
                if (diff > 0)
                   feedback_accumulator = 44.2 * (1<<24);       // fast
                else if (diff < 0)
                   feedback_accumulator = 44.0 * (1<<24);       // slow
                else
                   feedback_accumulator = 44.1 * (1<<24);       // nominal

But the question is how does this perform on PCs and Macs.
 
Confirmed on Teensyduino 1.54 Beta #4. Some overruns/glitches with the stock code, none with the above changes. Using Linux Mint 19.1 with pulseaudio and a usb card that is about 200 ppm off from the teensy audio clock.
 
Back
Top