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:

And here's the code:
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:
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.
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:


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
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: