Introduction: Arduino Audio Spectrum Shield WS2812 LED Display

My first Instructable!

This is a really simple build made up of essentially 3 components and some fun coding with nested loops to translate audio amplitude data into colorful displays;

  1. An Arduino (I used an Uno but any Arduino with at least 2 analog inputs should work)
  2. Arduino Audio Spectrum Shield, 2x MSGEQ7, 3.5mm Jacks + 3-pin In
  3. Some number of WS2812 individually addressable LED strips. I used 7 strips, 1 meter long each, 30 LEDs each

The Audio Spectrum shield has a 3.5mm jack for stereo input and another to pass the audio through to the output. It has 2 chips (one for each channel) that split the audio signal into 7 frequency bands.

Code in the Arduino "strobes" a digital pin to cycle through the 7 channels and reads the left and right analog value using 2 analog inputs on the Arduino and stores them in a "left" and "right" array.

There's a good tutorial at SparkFun on the board with some good example code which doesn't need the LED strips; https://learn.sparkfun.com/tutorials/spectrum-shield-hookup-guide-v2

Within the Arduino "loop" you call the function to read those values and do some interesting processing to put values into an "leds" array and then use the FastLED library to "show" the LEDs. This happens about 100 times per second unless you insert some delay.

The LED strips have 3 wires. Ground, 5V, and the signal. On mine, white is ground, red is 5V, and green is the signal.

The LED strips are wired from the bottom left to the top right. So the bottom left LED is 0, the bottom right is 29. One row up on the left is 30, and on the right end is 59. The top right is 209 since I have 7 strips of 30 that's 210 LEDs numbered 0 - 209.

Supplies

  1. Arduino - I had mine for a while and don't recall where it came from.
    Here's what looks to be compatible https://www.adafruit.com/product/2488 for about $17.50 USD.
    Here's another a bit cheaper https://www.ebay.com/itm/UNO-ATmega328-5V-CH340-US... but beware there's a note "Might not be compatible with Mac/Apple OS"
  2. Audio Spectrum Shield available here https://learn.sparkfun.com/tutorials/spectrum-shi... or here https://microcontrollershop.com/product_info.php?p...
  3. You'll have to solder on some header pins. Available on the sparkfun link above or here https://www.ebay.com/itm/5pk-Arduino-R3-Uno-Stack...
  4. LED strips like https://www.adafruit.com/product/1460?length=1 Some are sold by the meter. Some are sold in 1 meter lengths with connectors on both ends to daisy-chain them together. Mine had the connectors but I cut them off anyway to run the signal wire from one side to the other. Some strips have 30 LEDs per meter and some have 60 per meter. Either one would work with a little adjustment to the code. Some LED strips are in a clear rubber cover to make them waterproof and others aren't. Your choice.
  5. I had some 1/2" thick foam board I used with double-sided tape to stick the strips to it. And then some thin black foam board that I cut into strips and glued between the LED strips. But be creative. You could use plywood, stick them across a window, suspend them in space, etc.
  6. A bit of wire. You'll need 3 wires to connect the Arduino to the LED strips. My wires are several feet long so I can have the Arduino connected to my laptop and the audio input from my stereo or laptop while the board is a few feet away. But you could also mount the Arduino right on the display board and use longer USB and audio cables. Up to you.

Step 1: Solder Your Headers

I don't have any pictures of me soldering. I didn't have the stackable headers I wanted to use and I was anxious to try this out so I used some regular header pins on the bottom of the shield so it would plug into the Arduino.

The analog pins on the shield line up with the analog pins on the Arduino so as long as you don't change which pins are used in the code you're fine.

left[band] = analogRead(0); // store left band reading
right[band] = analogRead(1); // and the right

Same with the digital pins for strobing the audio shield through the 7 bands. These line up when you stack the shield on the Arduino.

int strobe = 4; // strobe pins on digital 4
int res = 5; // reset pins on digital 5

Step 2: Test Audio Input

The easiest thing here is probably to just use the SparkFun tutorial code and run it. The Serial Plotter in the Arduino IDE shows the lines for each band so that's pretty cool.

When I hook the audio input up to my laptop headphone jack, the laptop volume control will affect the level that the audio shield measures. But if you connect to something like the "Tape Out" RCA jacks on the back of a stereo they should be a constant level even as you turn your volume up and down.

Step 3: Layout Your LEDs

There are many ways you could lay the LED strips out. I did them horizontally but you could also use them vertically. In either case you can make your display go either way through code.

Lesson learned: Since my LED strips had connectors on each end, I was initially lazy and folded the strips back and forth across my board. Something like this;

60, 61,...87, 88, 89

59, 58.... 32, 31, 30

0, 1, 2.....27, 28, 29

While I got some initial code to work well, it was much more complicated than keeping them all going left to right. So after a few days of tinkering, I unsoldered the strips and flipped every other one around so they all went the same direction. I created a spreadsheet as shown in the picture to make it easier to visualize some of the affects.

Step 4: Converting Left and Right Data Into LED Patterns

If you completed the SparkFun tutorial you could see the you have a "left[ ]" array of 7 values in the range of 0 to 1024 and a "right[ ] " array of values. My first visualization was coded like this;

    for (band = 0; band < 7; band++) {
      for(int i = 14; i > 14 - int(left[band]/divisor); i--) {
        leds[(band*30) + i] = rainbow[band];
      }
      for(int i = 15; i < 15 + int(right[band]/divisor); i++) {
        leds[(band*30) + i] = rainbow[band];
      }
    } 

We know the left and right arrays each hold the value of 7 frequency bands and their range is from 0 to 1024. I want to display the amplitude of the first band (the lowest frequency) on the bottom row of my LEDs starting from the center (or one to the left of center) and light up a proportional number of LEDs to the left of that based on the value in the left[0] element of the array. Since my strip is 30 LEDs wide, I want the maximum amplitude of 1024 to light all 15 LEDs so I need to divide left[band] by 1024/15, or about 68. Maybe my audio signal is low, so I'll use a variable I called "divisor" that I can tweak in the code or maybe even have it dynamically adjust to the peak level. As an example, if the amplitude of the left channel was mid volume, of 512, I would have 512/68 which is about 7.5 which would be about half of my 15 LEDs for each side.

So I have the outer "for" loop with band going from 0 to 6.

The inner for loop decides which LEDs in this 15 LED half should be lit. In the example above I would light 7 LEDs. The rest of those 15 LEDs I'm not changing. Oh, that means I need code somewhere that turns off all the LEDs ever time through the loop before I start setting these colors. In this case I'm going to do that by setting the brightness of all the LEDs to 0.

// blank everything first<br>    for( int i = 0; i < NUM_LEDS; i++) {
        leds[i] = ColorFromPalette( currentPalette, colorIndex, 0, currentBlending);
    }

Back to the previous code block, we do the same thing for the right channel except we start at 15 and increment our counter instead of decrement it.

To make the next row light up based on the next band, we use;

leds[(band*30) + i]

We're setting these LEDs to "rainbow[band]" which is an array of colors which starts at red and ends with purple;

CRGB rainbow[] = {red, orange, yellow, green, aqua, blue, purple};

Wow! It looks pretty cool with some music playing. And I have it behind me during Zoom calls so people can see their voice making it light up based on their voice!

But we can do more!

Step 5: Show the Peak Value Longer

My coworker over Zoom suggests I show the peak values like some other displays do. Sure, why not.

We need another set of 2 arrays to hold the copy the current values of the bands of the left and right channel. We'll show it in red. Each time through the loop, if the current values are greater than the saved values we increase the saved values. Else, we reduce the saved values by some amount so they fade back towards the center. In this case I'm reducing it by 4 out of a max of 1024, but remember this runs about 100 times per second so the peaks should fade back to the center in about 2 seconds.

// this is for the red peak indicators for mode 1 and 2<br>    for (band = 0; band < 7; band++) {
      if (left[band] >= lHold[band]) {
        lHold[band] = left[band];
      } else {
        lHold[band] = lHold[band] - 4;
      }
      if (right[band] >= rHold[band]) {
        rHold[band] = right[band];
      } else {
        rHold[band] = rHold[band] - 4;
      }
    }

https://youtu.be/yBa1LhfGmKo

Step 6: Vertical Vue Meter

Vue meters I've seen in the past were always vertical. I might as well make a different "mode" I can switch to with vertical bars in the same rainbow colors. Since my display is 30 LEDs wide, I can make each band 2 LEDs wide, leave a gap in the middle between left and right channels and it will all fit.

My divisor here is about 1024/7 (I tweaked it a bit).

    for (int band = 0; band < 7; band++){
      for (int i = 0; i < 7; i++) {
        if (left[band] > int(1100/7)*i ) {
          leds[(i * 30) + band*2] = rainbow[band];
          leds[(i * 30) + 1 + band*2] = rainbow[band];
        }
        if (right[band] > int(1100/7)*i ) {
          leds[(i * 30) + band*2 + 15] = rainbow[band];
          leds[(i * 30) + 1 + band*2 + 15] = rainbow[band];
        }
      }
    }

Step 7: Time Scrolling View

My nested visualization takes a different approach to show patterns over a short timespan by scrolling the values right to left across the display. It's the top one in this Instructable.

The pattern is, start at the second column, which is column 1 since arrays in C are zero-based. And copy that column over to the column to the left.

After copying everything left (losing the original first column), write the last column using only the value and color of the band with the maximum value.

// this is going to start with a single pixel in the far right column showing the amplitude of band with max value(in it's rainbox color)<br>    // then each pass shift everything left
    // so start with copying column 1 into column 0, up to column 28, then populate column 29 with fresh data
    for (int c = 1; c < 30; c++) {
      for (int r = 0; r < 7; r++) {
        leds[(c-1) + (r*30)] = leds[c + (r*30)];
      }
    }


    for( int i = 0; i < 7; i++) {
      leds[29  + (i*30)] = ColorFromPalette( currentPalette, colorIndex, 0, currentBlending);
    }<br><br>    int maxBand = 0;
    int maxVal = 0;<br>    // increase left[0] because we need more red in the display
    left[0] = left[0]*2;
    for (int band = 0; band < 7; band++) {<br>      // we're averaging left and right channels
      if ((left[band]+right[band])/2 > maxVal) {
        maxBand = band;
        maxVal = (left[band]+right[band])/2;
      }
    }
    if (maxVal > 10) { // if it's quiet let's go blank
     leds[29  + ((int(8*maxVal/1024)-1)*30)] = rainbow[maxBand];
    }<br>    delay(5);

Step 8: Wrap Up

In the attached code SpectrumAnalyzer3.ino I used a "mode" variable to select from the different visualizations I came up with so far. You can change the mode in the setup() method or you can open the Serial Monitor and type a number and hit Enter.

Soon I'll add a push button so that each push switches to the next visualization and then back to the start.