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:
Buffer Transfer Object Included as "output_copybuffer.h"
output_copybuffer.cpp:
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; }
}
}