Audio Workshop 13
Audio Analysis – Peak Detection
We played, created and processed sound in the prior tutorials, which required only simple control from the program to start playing or configure settings. In this section, we’ll explore more sophisticated ways for Arduino code to interact with the audio system. We’ll also look at the limitation of processing audio on a single chip microcontroller.
For some projects, like sound reactive costumes and DJ stage lights, your program needs information about the sound, rather than just allowing the audio system to process it. The analysis objects fill this role.
We’ll start with peak, the simplest analysis object. As its name implies, it tracks the peak amplitude of the signal such as you might see when using a VU meter.
Design the Audio System
In this system, the playSdWave1 is sent to both the Audio Adapter (i2s1) and the peak1 (left channel) and peak2 (right channel) objects.
A subtle issue is the need for the i2s1 object. All audio systems require at least one ‘physical’ input or output, due to a technical requirement of the Teensy Audio Library.
Draw the audio system below in the Design Tool.
Turn the Design into Code and Use it in a Program
Copy and paste the code below into the IDE window. Compared to the original Part_3_01_Peak_Detection example in the IDE, we are initializing the LCD since we will be playing music from the SD card.
// Advanced Microcontroller-based Audio Workshop // // http://www.pjrc.com/store/audio_tutorial_kit.html // https://hackaday.io/project/8292-microcontroller-audio-workshop-had-supercon-2015 // // Part 3-1: Peak Detection // // ProtoSupplies.com Changes and Additions // Initialize, but don't use LCD in this example #include <ILI9341_t3.h> #include <font_Arial.h> #include <XPT2046_Touchscreen.h> /////////////////////////////////// // copy the Design Tool code here /////////////////////////////////// // Use these with the Teensy Audio Shield #define SDCARD_CS_PIN 10 #define SDCARD_MOSI_PIN 7 #define SDCARD_SCK_PIN 14 // Use these with the Teensy 4.1 SD card //#define SDCARD_CS_PIN BUILTIN_SDCARD //#define SDCARD_MOSI_PIN 11 // not actually used //#define SDCARD_SCK_PIN 13 // not actually used // LCD control pins defined by baseboard #define TFT_CS 40 #define TFT_DC 9 // Use main SPI bus MOSI=11, MISO=12, SCK=13 with different control pins ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC); // Touch screen control pins defined by baseboard // TIRQ interrupt if used is on pin 2 #define TS_CS 41 //#define TIRQ_PIN 2 XPT2046_Touchscreen ts(TS_CS); // Param 2 = NULL - No interrupts //=============================================================================== // Initialization //=============================================================================== void setup() { // Setup LCD screen tft.begin(); tft.setRotation(3); ts.begin(); ts.setRotation(1); tft.fillScreen(ILI9341_BLUE); Serial.begin(9600); AudioMemory(10); sgtl5000_1.enable(); sgtl5000_1.volume(0.5); SPI.setMOSI(SDCARD_MOSI_PIN); SPI.setSCK(SDCARD_SCK_PIN); if (!(SD.begin(SDCARD_CS_PIN))) { while (1) { Serial.println("Unable to access the SD card"); delay(500); } } delay(1000); } // for best effect make your terminal/monitor a minimum of 62 chars wide and as high as you can. elapsedMillis msecs; //=============================================================================== // Main //============================================================================== void loop() { if (playSdWav1.isPlaying() == false) { Serial.println("Start playing"); //playSdWav1.play("SDTEST1.WAV"); playSdWav1.play("SDTEST2.WAV"); //playSdWav1.play("SDTEST3.WAV"); //playSdWav1.play("SDTEST4.WAV"); delay(10); // wait for library to parse WAV info } if (msecs > 40) { if (peak1.available() && peak2.available()) { msecs = 0; float leftNumber = peak1.read(); float rightNumber = peak2.read(); int leftPeak = leftNumber * 30.0; int rightPeak = rightNumber * 30.0; int count; for (count=0; count < 30-leftPeak; count++) { Serial.print(" "); } while (count++ < 30) { Serial.print("<"); } Serial.print("||"); for (count=0; count < rightPeak; count++) { Serial.print(">"); } while (count++ < 30) { Serial.print(" "); } Serial.print(leftNumber); Serial.print(", "); Serial.print(rightNumber); Serial.println(); } } }
Now Export and copy and paste the Design Tool code into the program to complete it and verify and upload the program to the Teensy.
Looking at the Peak Output Data
When you run the program, you should see this display in the Serial Monitor window.
The reported peak numbers are shown on the right side, while ‘<‘ for left channel and ‘>’ for right channel are used to give a ‘graphical’ output that simulates a bar LED VU meter.
The peak objects give numbers between 0 to 1.0, representing the largest amplitude peak of audio signal during the time elapsed since the previous reading.
To see the ASCII graphic as it might appear in horizontal LEDs on a stereo display, move another window on top of the Serial Monitor to block all but the last line.
Closer Look at Peak Object Functions
Looking at the code, you can see the peak objects have functions very similar to other Arduino libraries. The available() function tells you when you can read more data and the read() functions give you the data. These read() give floating point numbers, rather than data bytes.
if (msecs > 40) { if (peak1.available() && peak2.available()) { msecs = 0; float leftNumber = peak1.read(); float rightNumber = peak2.read();
These familiar-looking functions do have subtle but important differences from Arduino’s normal usage. With typical incoming Serial data, available() and read() access a queue, where available() tells you how many items are currently in the queue, and read() removes 1 item from the queue for you. They are designed to prevent data loss. You want to read the oldest data first, even if it actually arrived some time ago.
Audio analysis objects are designed to provide only recent information. The available() returns only true or false. When new information is available, only the most recent data can be read. Audio analysis is designed to lose old information. Some analysis, like peak, gives you the cumulative effect since your last read(). Information about smaller peaks is automatically lost as larger peaks occur. Other types of analysis give you information about a recent window in time, where results from previous windows are automatically discarded. Details are in each objects documentation in the Design Tool.
Gating the Output Speed
This example has code to lower the printing speed. it uses an elapsedMillis variable, which is a convenient Teensyduino feature. elapsedMillis are integers which automatically increment 1000 times per second.
elapsedMillis msecs; //=============================================================================== // Main //============================================================================== if (msecs > 40) { if (peak1.available() && peak2.available()) { msecs = 0;
The example only prints if msecs has incremented past 40. Try reducing this number. If you use 0, it will print every time a new peak analysis is available. Though very fast, this speed is 1 update for every 128 audio samples, or about 344Hz.