Wireless Audio Visualizer – Part II

This post is about the wireless module that receives data from the base station described in Wireless Audio Visualizer – Part I and visualizes it on two VU-meters and a TFT display.

Since the AI-Thinker AudioKit base station from Part I does most of the heavy lifting, all that’s left for the wireless module is a rather simple visualization of the received data. That’s why the code for my setup (below) probably needs no further explanation, even if you decide to use different components.

My own choice of components for this module (see picture) were entirely determined by what I had lying on the shelf, and by the size of my lunch box, that I was prepared to sacrifice for the good cause. Otherwise, using an 52 M5Stack Core 2 would be a very expensive overkill (and I don’t even use its entire display). Even the cheapest ESP32 and a small display can do the job. Besides, these panel meters are also available with an LED backlight, what would make the DC converter in my setup obsolete. That said, my lunch box and the M5Stack seemed to be just made for each other and I definitely prefer the warm glow of incandescent bulbs over LEDs.



An even cheaper solution is emulating the VU-meters on two displays, for instance TTGO T-Display boards, that I used for my first YouTube video.

So it all depends on how much money you want to spend and what you want your module to show (and how). Note that using a different M5Stack core module could be a problem. Both my Gray and Fire have their speaker hard-wired to one of the DAC pins, so you would have to physically remove it. Which is probably a good idea anyway, as at least the Gray will click with every call to analogRead(). Nice looks, poor electronic design.

Wiring of analog panel meters

All analog panel VU-meters that I have come across so far have similar specs, but I’m far from an electronics expert, so if you use my wiring, make sure that their resistance is about 650Ω and their current limit 0.5 mA max.

As my code drives these meters from the ESP32’s 3.3V DACs (GPIO 25 & 26), we need to add current limiting resistors in series. For meters with the above specs, their value R follows from 3.3 = 0.0005 * (R + 650), which means that we would need two 6K resistors. For my module, however, I used the wiring scheme from an earlier (currently archived) post, using a 4K7 resistor and a flyback diode for each meter, and it works great.

My code has a variable maxDac that takes its value (in the [50, 255] range) from a potentiometer reading:

For testing whether your resistor’s value is safe for your type of meter, you can temporarily hard-code the value of maxDac, starting with a low value, or play with the range of the map command.

If you use a display without touch and want one of the spectrum variants to be displayed instead of the default logo / battery indicator, just set the value of variable spectrum in the code to 1 for a leaking peak spectrum and to 2 for the 3-color version.

Finally, you’ll need to upload 6 small jpg files to SPIFFS. They can be found here.

Here is the complete code for an M5Stack Core 2, with analog panel meters connected to pins 25 & 26 and a 10K potentiometer connected to pin 35 (‘sensitivity’ control).

Wireless Audio Visualizer – Part I

My last YouTube video, showing a wireless VU-meter module with a small audio spectrum analyzer, was surprisingly well received. Soon after asking me for the code, some sneaky YouTuber now tries to make money out my idea and code, without even crediting me! Another reason for me to stop sharing code links via YouTube.

Here’s Part I of the project. Part II (the wireless module) is covered in the next post.

Audio Visualizer


The idea was to let an ESP32 ‘base station’ convert analog stereo output from my amplifier to digital data, make it calculate VU levels and FFT frequency magnitudes and broadcast their values via ESP-NOW. Any ESP-NOW ‘receiver’ within range could then use them for audio visualizion on a display. The base station can be placed out of sight and be powered by a 5V adapter plugged into an AC outlet on the back of my amplifier.

Part I – Base station

Any ESP32 can perform the tasks outlined above, but conversion of the AC audio signal would normally require some rectifier circuit or two voltage dividers, as an ESP32 can only handle DC voltages (in the 0-3.3V range). Although that totally works, I was lucky to come across the AI-Thinker AudioKit V2.2 (and some great libraries).

Powered by an ESP32-A1S chip with built-in es8388 codec, this board allows you to plug an analog stereo source directly into its LINEIN input. You can program it with Espressif’s own Audio Development Framework, but I prefer the Arduino IDE, allowing me to use Phil Schatzmann’s arduino-audio-tools library, truly a Swiss Army Knife for audio projects.

I first tried the above library’s streams-audiokit-audiokit example. It loops back the analog input via a digital (I2S) stream to the board’s earphone plug, but unfortunately it produced a terrible distortion. My guess that the used es8388 library caused the trouble seemed right when I used a different es8388 library instead. Its streams-i2s-i2s example produced crystal clear sound, and since that example further uses the arduino-audio-tools library, all of that library’s extensive functionality remains available.

Now that digital audio data flowed through the ESP32, I only had to find out how to tap it programmatically. Few libraries on Github will have better documentation and support than the arduino-audio-tools library. Built on the concept of Arduino streams, it allows you to control an audio stream from source to destination (sink). With a little help from Phil (issue #161), I figured out two tapping mechanisms that I could use for my base station.

1. For calculating VU levels, I defined the following subclass of the library’s I2S class:

The data array holds a stream chunk of 4608 uint8_t values, casted to 2304 int16_t values, corresponding with 1152 left-right sample value pairs. After the for loop, global variables left and right will hold the maximum peak-to-peak values of the analyzed sample for both stereo channels. A global boolean sampled is set to true, which will trigger a freeRTOS task to perform an ESP-NOW broadcast. An object of this customized MyI2S class can now be used as a stream destination. In the code it’s named i2s.

2. For the Fast Fourier Transformation, I used the AudioRealFFT library. It lets you create an object of the AudioRealFFT class that can also be used as a stream destination. I made its callback function fill a global array of 32 frequency magnitudes that will be broadcast via ESP-NOW. In the code it’s named fft.

Having two different destinations for the digital audio stream is no problem, thanks to the library’s MultiOutput class. An object of this class acts as a kind of ‘destination envelope’ that can hold multiple destination objects. In the code it’s named multi and holds the previously mentioned i2s and fft objects.

After specifying input and ouput of the es8388 codec

we have defined our audio stream from source (line input) to sink (headphone), including taps for calculating both VU levels and FFT magnitudes.

The complete code that I used for the base station is at the bottom of this post . Note that compilation requires version 2.0.2 or higher of the arduino-esp32 package. To avoid conflicts with any other installed es8388 libraries, I put the es8388.h and es8388.cpp files from thaaraak’s library in the sketch folder. In the Arduino IDE , I selected the ESP32 Dev Module with PSRAM enabled and the Huge App partition scheme (probably not necessary).

After uploading the sketch, an AudioKit V2.2 with es8388 codec (connected to an audio source) will broadcast the following struct:

You can check if it works by uploading the following sketch to an ESP32 and view the results (VU values) in the Serial Plotter.



Complete code for the base station: