Hi, I am working on a Teensy 3.6 audio project where I am controlling an array of shift register outputs via a USB audio stream from Max/MSP. I've written a custom object to transfer the data from audio frames to the shift registers via SPI, which is working great, except that I am having an issue with dropped audio frames. I've done a lot of debugging on this front and determined it is not a hardware issue, but that the frame dropping is happening somewhere between the point when audio leaves Max/MSP and the audio data reaches my SPI writing function.
To test this I hooked up a pullup input on the Teensy to be pulled low via a transistor when a certain shift register pin is high. I then write this shift register pin high using my audio stream method (by sending a constant value, corresponding to that pin). If I ever read the pullup pin as high, I know there has been a dropout somewhere in my signal chain. I then added some debugging statements to the process that catches these dropout events to determine the length and frequency of the dropouts.
What I have found by doing this is that the dropout is always the exact length of the sample frame size set in my Max Audio status window, and that the dropouts occur at a fairly regular interval which scales proportionally to frame size (small frame size creates shorter dropouts more often, higher frame size creates longer dropouts less often). Dropout down time can be calculated by the formula:
DropInMs = (1000 / 44117.64706) * FrameSizeInSamples
Dropout rate is roughly 50-70 times the FrameSize, with the ratio growing slightly larger as frame size increases, and always at a regular interval within 1-2 seconds of variation.
I also test the values I am writing to the SPI buffer, which show up as zero during the dropouts, confirming that this is not a hardware issue, but that I'm actually getting an incorrect zero value in software.
These patterns seem to suggest a very specific issue, but I'm not sure where to look next in terms of debugging. Can anyone think of what might be causing this?
Here is the Teensy Sketch I am using to test and debug:
Here the class I am using to transfer audio buffers to the SPI output:
output_copybuffer.h:
output_copybuffer.cpp
Here are the results of my debug print statements at different buffer sizes:
To test this I hooked up a pullup input on the Teensy to be pulled low via a transistor when a certain shift register pin is high. I then write this shift register pin high using my audio stream method (by sending a constant value, corresponding to that pin). If I ever read the pullup pin as high, I know there has been a dropout somewhere in my signal chain. I then added some debugging statements to the process that catches these dropout events to determine the length and frequency of the dropouts.
What I have found by doing this is that the dropout is always the exact length of the sample frame size set in my Max Audio status window, and that the dropouts occur at a fairly regular interval which scales proportionally to frame size (small frame size creates shorter dropouts more often, higher frame size creates longer dropouts less often). Dropout down time can be calculated by the formula:
DropInMs = (1000 / 44117.64706) * FrameSizeInSamples
Dropout rate is roughly 50-70 times the FrameSize, with the ratio growing slightly larger as frame size increases, and always at a regular interval within 1-2 seconds of variation.
I also test the values I am writing to the SPI buffer, which show up as zero during the dropouts, confirming that this is not a hardware issue, but that I'm actually getting an incorrect zero value in software.
These patterns seem to suggest a very specific issue, but I'm not sure where to look next in terms of debugging. Can anyone think of what might be causing this?
Here is the Teensy Sketch I am using to test and debug:
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
AudioInputUSB usb1;
AudioOutputAnalog dac; //just to trigger audio library update
AudioOutputCopyBuffer copyBuffer;
AudioConnection patchCord1(usb1, 0, dac, 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 33
int16_t out[2];
void doOutput(){
__disable_irq();
digitalWriteFast(SS_PIN, HIGH);
copyBuffer.readFromBuffers(&out[0], &out[1]);
digitalWriteFast(SS_PIN, LOW);
SPI.transfer16(out[1]);
SPI.transfer16(out[0]);
__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, 22.666666666062222);
copyBuffer.begin();
}
unsigned long blip = 0;
unsigned long lastBlip = 0;
unsigned long blipInterval = 0;
int16_t checkOut[2] = {};
void loop() {
if(copyBuffer.checkOverrunAndClear()){
Serial.println("Error! Overrun!");
Serial.println("");
delay(100);
}
if(digitalRead(PULLUP_PIN)){
blip = micros();
blipInterval = micros() - lastBlip;
lastBlip = micros();
checkOut[0] = out[0];
checkOut[1] = out[1];
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("");
}
}
Here the class I am using to transfer audio buffers to the SPI output:
output_copybuffer.h:
Code:
#ifndef output_copybuffer_h_
#define output_copybuffer_h_
#define COPY_BUFFER_COUNT 16
#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 = error;
error = false;
return check;
};
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;
audio_block_t *inputQueueArray[2];
static bool update_responsibility;
};
#endif
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){ error = true; }
__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;
}
}
}else{
error = true;
}
}
Here are the results of my debug print statements at different buffer sizes:
Code:
128 Frame Size:
Error! Blip Detected!
Down Time: 2.900000 ms
Blip Interval: 7189.503906 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 2.901000 ms
Blip Interval: 8640.170898 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 2.900000 ms
Blip Interval: 7148.884766 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 2.901000 ms
Blip Interval: 7642.111816 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 2.900000 ms
Blip Interval: 7917.738770 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
256 Frame Size:
Error! Blip Detected!
Down Time: 5.802000 ms
Blip Interval: 15231.999023 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 5.802000 ms
Blip Interval: 15707.820313 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 5.802000 ms
Blip Interval: 15319.039063 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 5.803000 ms
Blip Interval: 15673.002930 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
512 Frame Size:
Error! Blip Detected!
Down Time: 11.604000 ms
Blip Interval: 30788.949219 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 11.604000 ms
Blip Interval: 30649.683594 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 11.604000 ms
Blip Interval: 30928.212891 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
1024 Frame Size:
Error! Blip Detected!
Down Time: 23.209000 ms
Blip Interval: 61856.429687 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 23.211000 ms
Blip Interval: 62158.164062 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 23.209999 ms
Blip Interval: 61902.847656 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 23.209000 ms
Blip Interval: 60626.261719 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
2048 Frame Size:
Error! Blip Detected!
Down Time: 46.419998 ms
Blip Interval: 123109.375000 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 46.419998 ms
Blip Interval: 124734.117188 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 46.419998 ms
Blip Interval: 124177.062500 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 46.419998 ms
Blip Interval: 123202.218750 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0
Error! Blip Detected!
Down Time: 46.421001 ms
Blip Interval: 124594.859375 ms
Out Reg 0 Val: 0
Out Reg 1 Val: 0