WaveForm: the Background Sound Visualizer

by Arnov Sharma in Circuits > Audio

234 Views, 8 Favorites, 0 Comments

WaveForm: the Background Sound Visualizer

WaveFORM SOUND VISUALIZER
51.gif
39.gif
40.gif
IMG_1949.JPG
IMG_1929.JPG

WaveForm: The Background Sound Visualizer transforms one of the most common yet invisible elements of our environment—sound—into a dynamic visual experience.

Sound is all around us, all the time. We talk, type, walk, and listen to fans, ambient noise, traffic, and music, but the actual shape of those sounds is something we never see with our eyes. They exist as vibrations in the air, completely hidden from our visual senses.

WaveForm takes this unseen phenomenon and transforms it into an interactive visual wave displayed on a HUB75 matrix. As the sound grows louder, the wave expands and intensifies in real time.

We built a custom enclosure that houses the RGB matrix along with a custom PCB featuring the Raspberry Pi Pico 2 and the MAX9814 microphone amplifier, paired with a dedicated power and button board. This enclosure lets us mount the device on a wall inside our workspace. When the room is silent, WaveForm displays a calm, single center line. As soon as there’s activity—my 3D printer running, mechanical keyboard typing, or even general background music—the device picks up those sounds and transforms them into smooth, flowing waves. The result is a visually engaging ambient display that looks great in the background while revealing the hidden sound patterns of our environment.

This Instructables covers the complete build process of this project. Let's get started!

Supplies

These are the materials used in this project:

  1. Custom PCBs (provided by HQ NEXTPCB)
  2. Raspberry Pi PICO 2
  3. IP5306
  4. 10 uF Capacitor 1206 Package
  5. 10k Resistor 0805 Package
  6. 1uH Inductor
  7. RGB matrix HUB75 63x32
  8. MAX9814 Microphone module
  9. Indicator LED 0805 Package
  10. Push Buttons
  11. Type C Port
  12. 3D Printed Parts

UNDERSTANDING SOUND WAVES

wave-life.gif

Sound is all around us, but we never actually see it. At its core, sound is simply a vibration traveling through the air—tiny, rapid changes in air pressure created whenever something moves, taps, clicks, or speaks. These pressure variations form a waveform: a repeating pattern of peaks and valleys that represent how the air is compressing and expanding over time.

Our ears interpret these changes instantly, but the waveform itself remains completely invisible.

Our project, WaveForm, is built around the idea of revealing this hidden pattern.

Using a MAX9814 microphone amplifier, the Raspberry Pi Pico 2 continuously measures the incoming sound pressure and converts it into digital readings hundreds of times per second. In our code, each of these readings becomes part of a sequence of numbers—the raw shape of the sound wave.

Here’s the exact moment the waveform is captured: (I will be explaining more about code in later later section of the project.)


for (int i = 0; i < OVERSAMP; i++) {
int v = analogRead(MIC_PIN); // read sound level from mic
samples[i] = (float)v; // store it in the buffer
sum += v;
if (v < vmin) vmin = v;
if (v > vmax) vmax = v;
delayMicroseconds(150);
}

Each number represents a single instant of air pressure. By processing this stream of data in real time, we can calculate the sound’s amplitude (loudness), filter out background noise, and map every moment of the waveform to a corresponding point on the display.

These points are then drawn as a smooth, scrolling curve across the HUB75 RGB matrix. Quiet sounds create gentle ripples near the center of the screen, while louder sounds stretch the waveform outward and shift its color toward red. The result is a living visualization of something we normally experience only with our ears.

ELECTRONICS

25.gif
IMG_1859.JPG
Image4.jpg

For this project, our goal was to use a Raspberry Pi Pico 2 to control a HUB75 RGB 64×32 LED matrix, along with a power-management board capable of supplying a stable 5V 2A rail for both the LED matrix and the development board. Earlier, we built a similar project using the same Pico 2 configuration with an RGB matrix, so we took reference from that design and prepared a similar setup here as well.

For detecting sound, we went with the MAX9814 microphone module that offers several advantages over regular electret microphone modules with a simple op-amp stage. The MAX9814 includes an automatic gain control (AGC) system that continuously adjusts the amplification based on the loudness of the incoming audio. This means the microphone delivers a stable and consistent output even when sound levels vary drastically. It prevents clipping during loud sounds and boosts quieter audio automatically, which makes processing the signal far easier for the microcontroller.

Additionally, the MAX9814 has low noise, built-in biasing for electret microphones, and three selectable gain levels, making it much more reliable for real-time applications like visualizers or sound-reactive LED effects compared to a basic mic+op-amp circuit that requires manual tuning and can be very noisy.

3D DESIGN

untitled.398.png
untitled.395.png
Screenshot 2025-12-11 100132.jpg
Screenshot 2025-12-11 100044.jpg
Screenshot 2025-12-11 100150.jpg
Screenshot 2025-12-11 100301.jpg
Screenshot 2025-12-11 100336.jpg
untitled.396.png
untitled.397.png
Screenshot 2025-10-14 224819.jpg
Screenshot 2025-10-14 224356.jpg

Before diving into the PCB design process, we first prepared a complete 3D design of the device. This began by importing models of the LED matrix, the Pico 2, the MAX9814 microphone module, and the push buttons. We then rearranged them to form an enclosure layout: the matrix display sits in the center, and on the right side we created a custom compartment that holds three push buttons aligned in a row. Above the buttons, we placed the microphone module.

The idea was to keep all interactive elements—buttons, microphone, switch, and USB port—on one side. Because the internal space was quite limited, we positioned the Pico 2 driver board and the battery on the backside of the display.

The enclosure was split into two main parts: a left and a right side. The right side houses the switch, microphone, and power-management board. The left side exists mainly to support and stabilize the LED matrix. Finally, a back lid was designed to close the enclosure and secure all components together into a solid, unified body.

After assembling the full 3D model, we exported each part as a separate mesh and printed them in two colors to achieve a dual-tone aesthetic. The switch actuators, microphone grill, and back lid were printed in brown PLA, while both side enclosures were printed in white PLA.

PCB DESIGN

Combined_page-0001.jpg
MIC-SWITCH-POWER Board_page-0001.jpg
PICO DRIVER_page-0001.jpg
Screenshot 2025-12-11 150640.jpg
Screenshot 2025-12-11 150900.jpg
Screenshot 2025-12-11 150543.jpg
Screenshot 2025-12-11 150813.jpg

For this project, we designed two separate circuits. The first is a Switch–MIC–Power board, which includes three push buttons, the MAX9814 microphone module, and a dedicated power management IC. The second is the Pico Driver board, which pairs the Raspberry Pi Pico 2 with a HUB75 connector and is responsible for running the 64×32 LED matrix.

Let’s have a look at the Pico driver board first. To connect the Raspberry Pi Pico 2 to the matrix’s HUB75 interface, we designed a small driver PCB that uses a CON-16 connector.

We connected the matrix's HUB75 pins (CON 16) to the PICO's GPIO pins in the following order: A to GPIO19, B to GPIO16, C to GPIO18, D to GPIO20, E to GPIO22, CLK to GPIO11, LAT/STB to GPIO12, OE to GPIO13, R1 to GPIO2, G1 to GPIO3, B1 to GPIO4, R2 to GPIO5, G2 to GPIO8, and B2 to GPIO9.

We also added a CON2 port connected to the 5V and GND pins of the Pico. This CON2 connector is used to provide power to the Pico Driver Board. A CON4 connector is also added that breaks out GPIO0, GPIO1, GPIO26, and GPIO28. We will be using GPIO28 for linking the output of the MAX9814 with our Pico board.

Next, we have the Switch–MIC–Power board, which combines three main things all connected together. This board works as the power source for the whole project. A Li-Po cell is connected to the IP5306 power-management IC, which provides a stable 5V 2A output to power the matrix and the Pico driver. The board contains three buttons: one is the main power switch connected to the IP5306, and the other two are connected to GPIO0 and GPIO1. We also mounted the MAX9814 module on this board. It is powered through the 5V line from the IP5306, and its output is linked to GPIO28 of the Pico.

For both boards, we designed the PCBs using the PCB outline from our 3D CAD model. This helped us place the push buttons, USB Type-C port, microphone module, Pico footprint, mounting holes, and connectors correctly based on the enclosure dimensions.

NextPCB PCB SERVICE

01.gif
02.gif
IMG_0216.JPG

After completing the PCB design, Gerber data for both PCBs was sent to HQ NextPCB, and an order was placed for a green solder mask with white silkscreen.

After placing the order, the PCBs were received within a week, and the PCB quality was pretty great.

In addition, I have to bring in HQDFM to you, which helped me a lot through many projects. Huaqiu’s in-house engineers developed the free Design for Manufacturing software, HQDFM, revolutionizing how PCB designers visualize and verify their designs.

Take advantage of NextPCB's Accelerator campaign and get 2 free assembled RP2040-based PCBs for your innovative projects.

https://www.nextpcb.com/blog/rp2040-free-pcba-prototypes-nextpcb-accelerator

This offer covers all costs, including logistics, making it easier and more affordable to bring your ideas to life. SMT services can be expensive, but NextPCB is here to help you overcome that hurdle. Simply share your relevant project, and they'll take care of the rest. Don't miss out on this amazing opportunity to advance your tech creations!

HQDFM: Free Online Gerber Viewer and DFM Analysis Tool

Screenshot 2025-12-11 151158.jpg
Screenshot 2025-12-11 151242.jpg

Also, NextPCB has its own Gerber Viewer and DFM analysis software.

Your designs are improved by their HQDFM software (DFM) services. Since I find it annoying to have to wait around for DFM reports from manufacturers, HQDFM is the most efficient method for performing a pre-event self-check.

This is what I see in the online Gerber Viewer. It's decent for a quick look but not entirely clear. For full functionality—like detailed DFM analysis for PCBA—you’ll need to download the desktop software. The web version only offers a basic DFM report.

With comprehensive Design for Manufacture (DFM) analysis features, HQDFM is a free, sophisticated online PCB Gerber file viewer.

With over 15 years of industry experience, it offers valuable insights into advanced manufacturing processes. If you’re looking for reliable PCB services at a budget-friendly price, HQ NextPCB is definitely worth checking out.

PCB ASSEMBLY

03.gif
04.gif
05.gif
06.gif
07.gif
08.gif
09.gif
10.gif
11.gif
  1. We have two different circuits in this project. We start with the Switch–MIC–Power board assembly process. Using a solder paste dispensing needle, we apply solder paste (63/37 Sn/Pb) to each SMD component pad.
  2. Next, we use ESD tweezers to pick and place each SMD component onto the PCB.
  3. After placement, the PCB is moved onto the reflow hot plate. As the board heats up from below, the temperature gradually rises until it reaches the solder paste’s melting point. Once the PCB reaches around 200°C, the solder paste melts and all the SMD components are secured in place.
  4. We then add the push buttons, MAX9814 module, and Type-C port in their positions on the top side of the PCB. After that, the circuit is flipped over, and all the through-hole pads are soldered in place.
  5. Next, we begin the Pico Driver Board assembly. We install the CON8 male header pins and the CON20 female header pins in their positions, flip the board over, and solder both connectors securely in place.

POWER SOURCE

IMG_1852.JPG
12.gif
13.gif
14.gif

For the power source, we are using a 3.7V LiPo cell with a capacity of 10,000 mAh. Since this device will be mounted on a wall and the RGB matrix draws a significant amount of current, we chose a high-capacity pack to ensure long, stable operation. The battery terminals were soldered directly to the Battery + and Battery – pads on the Switch–MIC–Power board.

Pressing the power button once turns the device ON, and double-tapping it turns the device OFF.

To confirm that the power setup functions properly, we used a multimeter in voltage-measurement mode. After switching on the power board, we measured the voltage across the output pin and GND.

The reading showed a stable 5V, indicating that the entire power system is working correctly.

PICO DRIVER - MATRIX SETUP

15.gif
16.gif
17.gif
18A.gif
18B.gif
19.gif
20.gif
  1. We start by placing the Pico onto the header pins on the Pico Driver Board, ensuring that it is oriented correctly.
  2. Next, we connect the Switch–MIC–Power board to the Pico Driver by linking 5V to 5V, GND to GND, the MIC output to GPIO28, Button 1 to GPIO0, and Button 2 to GPIO1.
  3. After that, we connect the LED matrix power lines: the matrix VCC goes to the 5V output of the Switch–MIC–Power board, and the matrix GND goes to the common ground.
  4. Finally, we use the HUB75 wire harness to link the matrix to the Pico Driver GPIO pins. The cable is plugged into the matrix connector first, and then the other end is connected to the HUB75 socket on the Pico Driver board.

DISPLAY TEST- GAME OF LIFE

21.gif

For Test 1, we programmed the Pico using our previously converted Game of Life code, which was adapted from an example sketch in the FastLED library. This helped us verify whether the overall configuration was working correctly.

https://www.instructables.com/64x32-Matrix-Panel-Setup-With-PICO-2/

The remarkable cellular automaton known as the "Game of Life" was developed in 1970 by British mathematician John Horton Conway. Since it is a zero-player game, no extra input is required; rather, the game's progression is determined by its initial state.

Check out more about game of life from here—

https://playgameoflife.com/

MAIN CODE- WAVEFORM

22.gif

After testing the display, we uploaded the main sketch onto the Pico 2 and used a sound-generating device to verify the audio-reactive features. For this test, we used a small 555-timer–based synth that produces a high-pitched noise, which works perfectly for checking how the MAX9814 responds to sharp and continuous audio signals.

Below is the CODE, and it's a simple one.

#include <Adafruit_Protomatter.h>
#include <math.h>

#define WIDTH 64
#define HEIGHT 32

// HUB75 pinout (use your wiring)
#define R1 2
#define G1 3
#define B1 4
#define R2 5
#define G2 8
#define B2 9
#define A 10
#define B 16
#define C 18
#define D 20
#define CLK 11
#define LAT 12
#define OE 13

uint8_t rgbPins[] = { R1, G1, B1, R2, G2, B2 };
uint8_t addrPins[] = { A, B, C, D };
Adafruit_Protomatter matrix(
WIDTH, HEIGHT, 1,
rgbPins, 4, addrPins,
CLK, LAT, OE, false
);

// ---------- audio + waveform settings ----------

const int MIC_PIN = 28; // GP28 (your working mic pin)
const int FRAME_MS = 16; // ~60 FPS
const int OVERSAMP = 128; // samples per frame

// Horizontal spacing between points: 1 = packed, 2 = more spread, 3 = even more
const int X_STEP = 2;
const int NUM_POINTS = WIDTH / X_STEP; // how many points across the screen

// How much of the screen height the wave can use (leaves a small margin)
const float MAX_WAVE_FRACTION = 0.5f; // 0.5 -> up to half-height up and down

// Loudness tracking (visual auto-gain, not audio)
float rmsPeak = 200.0f; // starting guess, will adapt
float loudSmooth = 0.0f;

// Adaptive noise floor
bool noiseInit = false;
float noiseFloor = 100.0f; // will get overwritten quickly

// Gate: how strong the signal must be (0..1 after normalization) to draw
const float LOUD_GATE = 0.08f; // raise this if still too sensitive

// For drawing
float waveY[NUM_POINTS];

void setup() {
analogReadResolution(12); // 0..4095 on RP2040

matrix.begin();
matrix.fillScreen(0);
matrix.show();

// Initialise wave points to center
float mid = (HEIGHT - 1) * 0.5f;
for (int i = 0; i < NUM_POINTS; i++) {
waveY[i] = mid;
}
}

void loop() {
static unsigned long lastFrame = 0;
unsigned long now = millis();
if (now - lastFrame < (unsigned long)FRAME_MS) return;
lastFrame = now;

// --------- collect audio samples for this frame ---------
static float samples[OVERSAMP];

uint32_t sum = 0;
int vmin = 4095;
int vmax = 0;

for (int i = 0; i < OVERSAMP; i++) {
int v = analogRead(MIC_PIN);
samples[i] = (float)v;
sum += v;
if (v < vmin) vmin = v;
if (v > vmax) vmax = v;
// small delay between samples; tweak if needed
delayMicroseconds(150);
}

// DC offset = average of this frame
float dc = (float)sum / (float)OVERSAMP;

// Center samples and compute RMS + max absolute value
float rmsSum = 0.0f;
float maxAbs = 1.0f; // avoid divide by zero
for (int i = 0; i < OVERSAMP; i++) {
float centered = samples[i] - dc;
samples[i] = centered;
float a = fabsf(centered);
if (a > maxAbs) maxAbs = a;
rmsSum += centered * centered;
}
float rms = sqrtf(rmsSum / (float)OVERSAMP);

// --------- adaptive noise floor + effective RMS ---------

if (!noiseInit) {
noiseFloor = rms; // first frame sets starting noise level
noiseInit = true;
}

// If current rms is lower than floor, follow quickly (room got quieter)
if (rms < noiseFloor) {
noiseFloor = 0.95f * noiseFloor + 0.05f * rms;
} else {
// If louder, creep up very slowly (avoid calling constant background "noise")
noiseFloor = 0.999f * noiseFloor + 0.001f * rms;
}

// Effective signal above noise floor
float rmsEffective = rms - noiseFloor;
if (rmsEffective < 0.0f) rmsEffective = 0.0f;

// --------- auto visual gain from effective RMS ---------
// Track a peak RMS (above noise) that decays slowly so visuals adapt

if (rmsEffective > rmsPeak) {
rmsPeak = rmsEffective;
} else {
rmsPeak *= 0.995f; // decay, tweak for slower/faster adaptation
if (rmsPeak < 50.0f) rmsPeak = 50.0f; // floor
}

float loud = (rmsPeak > 0.0f) ? (rmsEffective / rmsPeak) : 0.0f; // 0..1-ish
if (loud > 1.5f) loud = 1.5f; // clamp a bit above 1
if (loud < 0.0f) loud = 0.0f;

// Apply gate: ignore very small signals
if (loud < LOUD_GATE) {
loud = 0.0f;
}

// Smooth loudness so it doesn't jitter
const float LOUD_ALPHA = 0.2f; // lower = smoother, higher = more responsive
loudSmooth = (1.0f - LOUD_ALPHA) * loudSmooth + LOUD_ALPHA * loud;

// If gated to zero, keep it exactly zero after smoothing when very small
if (loudSmooth < (LOUD_GATE * 0.5f)) {
loudSmooth = 0.0f;
}

// --------- build waveform points across the width ---------
float mid = (HEIGHT - 1) * 0.5f;
float maxAmp = (HEIGHT * MAX_WAVE_FRACTION); // how many pixels up/down

int stride = OVERSAMP / NUM_POINTS;
if (stride < 1) stride = 1;

for (int i = 0; i < NUM_POINTS; i++) {
int idx = i * stride;
if (idx >= OVERSAMP) idx = OVERSAMP - 1;
float s = samples[idx]; // centered sample

// Normalize this sample to -1..1 based on maxAbs in this frame
float sampleNorm = s / maxAbs;
if (sampleNorm > 1.0f) sampleNorm = 1.0f;
if (sampleNorm < -1.0f) sampleNorm = -1.0f;

// Scale by loudness (bigger wave when effective RMS is higher)
float amp = loudSmooth * maxAmp;

float y = mid - sampleNorm * amp; // minus so positive = up
if (y < 0.0f) y = 0.0f;
if (y > (float)(HEIGHT - 1)) y = (float)(HEIGHT - 1);

waveY[i] = y;
}

// --------- choose color based on loudness (no yellow) ---------
// loudSmooth = 0.0 → bright green
// loudSmooth ~ mid → dimmer green
// loudSmooth high → pure red

float loudNorm = loudSmooth;
if (loudNorm < 0.0f) loudNorm = 0.0f;
if (loudNorm > 1.0f) loudNorm = 1.0f;

const float RED_THRESHOLD = 0.6f; // when to switch from green → red

uint8_t r, g;

if (loudNorm < RED_THRESHOLD) {
// Green only, fade from 255 → ~100 as loudness grows
float t = loudNorm / RED_THRESHOLD; // 0..1
g = (uint8_t)(255.0f - 155.0f * t); // 255 → 100
r = 0;
} else {
// Red only, fade from 100 → 255 as loudness goes high
float t = (loudNorm - RED_THRESHOLD) / (1.0f - RED_THRESHOLD); // 0..1
r = (uint8_t)(100.0f + 155.0f * t); // 100 → 255
g = 0;
}

uint16_t color = matrix.color565(r, g, 0);

// --------- draw waveform ---------
matrix.fillScreen(0);

for (int i = 1; i < NUM_POINTS; i++) {
int x0 = (i - 1) * X_STEP;
int x1 = i * X_STEP;
int y0 = (int)roundf(waveY[i - 1]);
int y1 = (int)roundf(waveY[i]);
matrix.drawLine(x0, y0, x1, y1, color);
}

matrix.show();
}


I'm using the Adafruit_Protomatter library to drive the 64×32 HUB75 RGB LED matrix.

All RGB output pins, address lines (A–D), clock, latch, and OE are defined here along with the display dimensions.

#include <Adafruit_Protomatter.h>
#include <math.h>

#define WIDTH 64
#define HEIGHT 32

// RGB, Address, and Control Pins
#define R1 2
#define G1 3
#define B1 4
#define R2 5
#define G2 8
#define B2 9
#define A 10
#define B 16
#define C 18
#define D 20
#define CLK 11
#define LAT 12
#define OE 13

Pins are collected into arrays, which the Protomatter library uses to configure HUB75 timing:

uint8_t rgbPins[] = { R1, G1, B1, R2, G2, B2 };
uint8_t addrPins[] = { A, B, C, D };

Then the matrix object is created:

Adafruit_Protomatter matrix(
WIDTH, HEIGHT, 1,
rgbPins, 4, addrPins,
CLK, LAT, OE, false
);

Audio Input and Waveform Settings

This section defines which pin the microphone is connected to (MAX9814 output → GP28), how many samples to collect per frame, how many frames per second, and how wide the waveform should be.

const int MIC_PIN = 28;
const int FRAME_MS = 16; // ~60 FPS
const int OVERSAMP = 128; // samples per frame
const int X_STEP = 2; // horizontal spacing between waveform points
const int NUM_POINTS = WIDTH / X_STEP;

It also defines variables for noise tracking, adaptive gain, wave smoothing, and amplitude control.

Matrix Initialization

In setup(), the matrix is started and the waveform points are initialized to the vertical center of the display.

void setup() {
analogReadResolution(12);

matrix.begin();
matrix.fillScreen(0);
matrix.show();

float mid = (HEIGHT - 1) * 0.5f;
for (int i = 0; i < NUM_POINTS; i++) {
waveY[i] = mid;
}
}

Audio Sampling (Oversampling + DC Removal)

Every frame, the code collects 128 analog samples from the microphone at fixed intervals:

for (int i = 0; i < OVERSAMP; i++) {
int v = analogRead(MIC_PIN);
samples[i] = (float)v;
sum += v;
if (v < vmin) vmin = v;
if (v > vmax) vmax = v;
delayMicroseconds(150);
}

Adaptive Noise Floor

The code constantly adjusts to background noise:

if (rms < noiseFloor)
noiseFloor = 0.95 * noiseFloor + 0.05 * rms; // follow quickly
else
noiseFloor = 0.999 * noiseFloor + 0.001 * rms; // rise slowly

MAX9814 has AGC; background levels constantly change. This allows the system to ignore quiet room noise but still react to real sounds.

Visual Auto-Gain

This makes the waveform “stretch” visually even when sound is soft:

if (rmsEffective > rmsPeak) rmsPeak = rmsEffective;
else rmsPeak *= 0.995f;

Then normalize loudness:

float loud = rmsEffective / rmsPeak;

This ensures two things!

  1. Soft audio → wave still visible
  2. Loud audio → wave does not overflow display

Waveform Generation

The waveform is scaled into pixel positions:

float amp = loudSmooth * maxAmp;
waveY[i] = mid - sampleNorm * amp;
  1. sampleNorm = normalized audio sample (−1 to +1)
  2. amp = dynamic amplitude based on loudness
  3. mid = vertical center of screen

This produces a smooth oscilloscope-style line.

Color Selection Based on Loudness

The code intentionally avoids yellow.

Wave color transitions only between green and red:

if (loudNorm < 0.6)
g = 255 → 100 (green fades)
r = 0
else
r = 100 → 255 (red increases)
g = 0

Quiet = green

Loud = red

Medium = dark greenish

Drawing the Waveform

The screen is cleared each frame:


matrix.fillScreen(0);

Then every pair of points is drawn with a line:


matrix.drawLine(x0, y0, x1, y1, color);
matrix.show();

This creates a smooth, continuous, scrolling oscilloscope-style waveform.

SWITCH-MIC-POWER BOARD ENCLOSURE ASSEMBLY

24.gif
26.gif
27.gif
28.gif
29.gif
  1. We begin by placing the MIC Grill–Cover part over its mounting position on the right-side enclosure and securing it with a small amount of superglue.
  2. Next, we insert the switch actuators for each push button into their respective slots. After that, we position the Switch–MIC–Power circuit board in place and fasten it using four M2 screws to hold it firmly.

COMPLETE ASSEMBLY

30.gif
31.gif
32.gif
33.gif
34.gif
35.gif
  1. Next, we align the right-side enclosure with the backside of the matrix, using the mounting hole as a reference to position both parts accurately.
  2. The Pico Driver board is then placed on the back of the matrix and secured using hot glue to keep it firmly in place.
  3. After that, the left-side enclosure is installed on the opposite side, balancing the structure and completing the frame around the matrix.
  4. Finally, the back lid is positioned over the assembly. This lid holds the battery, circuit boards, and wiring securely inside. Once aligned, we fasten everything together using four M2.5 screws, completing the enclosure.

RESULT

WaveFORM SOUND VISUALIZER
49.gif
39.gif
40.gif
42.gif
38.gif

To test the final build, I hung the device on the wall inside my workspace and began trying out different sound sources. With music playing, the waveform reacted perfectly, smoothly visualizing every beat and variation.

Next, I placed it near my 3D printer, and it picked up the mechanical movements accurately. I even tested it beside a mechanical keyboard, and it responded to each keystroke without any issues.

The device is sensitive enough to track even small everyday sounds and translate them into flowing wave patterns on the matrix. Watching the waves move in real time is extremely satisfying and adds a beautiful ambient touch to the workspace.

CONCLUSION

46.gif
IMG_1948.JPG
IMG_1949.JPG
43.gif

Overall, this project resulted in a compact and visually engaging sound-reactive display that performs reliably in real-world conditions. By combining the Raspberry Pi Pico 2, the HUB75 RGB matrix, and the MAX9814 microphone module, we were able to create a device that not only looks great but also responds smoothly to a wide range of sound levels. The custom enclosure, power system, and optimized waveform algorithm all come together to form a polished, wall-mountable gadget that adds both functionality and aesthetic value to the workspace.

This build was a fun blend of electronics, coding, and 3D design—and the final result is a satisfying little ambient visualizer that makes everyday sounds come alive.

The unused buttons will be utilized in Revision 2 of this project. In the next version, I plan to add clock functionality and Wi-Fi capabilities by switching from the Pico 2 to a Pico W, along with a range of additional features to make the device even more versatile and interactive.

For now the project has been completed; leave a comment if you need any help regarding the project!

Special thanks to HQ NextPCB for providing components that I've used in this project; check them out for getting all sorts of PCB or PCBA-related services for less cost.

Peace