Non-realtime audio debugging

h4yn0nnym0u5e

Well-known member
Hi folks. Here's a sketch I'd been meaning to circle back to at some time ... and the time is now.

An oscilloscope is massively useful for debugging some audio issues, but not everyone will have access to one. Here's a sketch which does grossly nasty things to the audio engine in the name of being able to use the Arduino IDE's serial plotter as a 'scope substitute. It works by waiting for an audio engine cycle to execute, then stopping it while the results are plotted, then re-starting it. Obviously this is zero use for debugging issues which are dependent on execution in real time (and I freely admit that's quite a lot of them!), but for exploring the behaviour of internal processes, such as wave-shaping, envelopes, filtering etc., I've found it can be pretty useful.

Here's a sample design and a screenshot of the sketch's output plot:
2022-03-26 10_35_38-Audio System Design Tool for Teensy Audio Library.png 2022-03-26 10_34_40-AudioNonRealtimeDemo _ Arduino 1.8.15.png

And here's the code:
C++:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioSynthWaveform       waveform1;      //xy=302,260
AudioSynthWaveform       waveform2; //xy=304,309
AudioMixer4              mixer4;       //xy=519,305
AudioEffectWaveshaper    waveshape1;     //xy=535,426
AudioRecordQueue         queue1;         //xy=585,93
AudioRecordQueue         queue2; //xy=585,144
AudioRecordQueue         queue3; //xy=697,198
AudioRecordQueue         queue4; //xy=709,424
AudioOutputSPDIF2        spdif2;       //xy=754,316
AudioConnection          patchCord1(waveform1, 0, mixer4, 0);
AudioConnection          patchCord2(waveform1, queue1);
AudioConnection          patchCord3(waveform2, 0, mixer4, 1);
AudioConnection          patchCord4(waveform2, waveshape1);
AudioConnection          patchCord5(waveform2, queue2);
AudioConnection          patchCord6(mixer4, queue3);
AudioConnection          patchCord7(mixer4, 0, spdif2, 0);
AudioConnection          patchCord8(waveshape1, queue4);
// GUItool: end automatically generated code


#define COUNT_OF(x) ((int32_t)(sizeof x / sizeof x[0]))
AudioRecordQueue* queues[] = {&queue1,&queue2,&queue3,&queue4};
int16_t* dptrs[COUNT_OF(queues)];
float shape[] = {-1.0f,-0.8f,0.0f,0.8f,1.0f};

bool outputEnabled = true;
void processQueues(void)
{
  for (int i=0;i<COUNT_OF(queues);i++)
    dptrs[i] = queues[i]->readBuffer(); 

  // actual processing:
  if (outputEnabled)
  {
    for (int j=0;j<AUDIO_BLOCK_SAMPLES;j++)
    {
      for (int i=0;i<COUNT_OF(queues);i++)
        Serial.printf("%d ",dptrs[i][j]);
      Serial.println();
      delay(10); // slow graph down a bit
    }
  }
 
  for (int i=0;i<COUNT_OF(queues);i++)
    queues[i]->freeBuffer(); 
}


void setup()
{
  //AudioNoInterrupts(); // freeze the audio system
 
  // wait for serial connection to be established
  Serial.begin(115200);
  while (!Serial)
    ;
   
  // set up the audio system:
  AudioMemory(10);

  waveform1.begin(0.5,770,WAVEFORM_SINE);
  waveform2.begin(1.0,220,WAVEFORM_TRIANGLE);

  waveshape1.shape(shape,COUNT_OF(shape));
 
  for (int i=0;i<COUNT_OF(queues);i++)
    queues[i]->begin();

  // output graph legend
  Serial.println("wave1 wave2 mixed shaped");
}


int next;
void loop()
{
  if (queue1.available()) // assume if there's data for queue1, there's data for all
  {
    AudioNoInterrupts();
    processQueues();
    next = 250;
  }
  else
    AudioInterrupts();

  if (Serial.available())
  {
    while (Serial.available())
      Serial.read();

    outputEnabled = !outputEnabled;
  }

  // output dots if something's not working
  delay(1);
  if (--next < 0)
  {
    next = 250;
    Serial.print('.');   
  }
}

I've tried to keep everything as simple as possible, so it's "obvious" how it's working and you can extend it to suit your needs. Here are a few tips:
  • for every signal you plot, you'll need an AudioOutputQueue: think of these as 'scope probes
  • every signal plot queue needs a pointer to it in the queues[] array
  • change the Serial.println() in setup() to get a helpful legend
  • you can stop plotting by sending something to the serial port, and re-start by sending something, but...
  • ...if you want to do this, don't use a "Teensy port", use a normal Arduino serial port*
  • you do need an audio object with "update responsibility" in your design...
  • ...and it's helpful to pick one with nothing real connected to it - I've used SPDIF2 on my Teensy 4.1
* see https://forum.pjrc.com/threads/68019-Minor-issue-sending-to-Teensy-from-Serial-plotter

Hope this helps someone. I don't particularly propose to maintain or update it, unless there's a surprising amount of demand - I see it as a platform / quick demo that others can extend for their use cases.
 
Last edited:
Back
Top