Project tutorial
Audio Spectrum Visualiser with Colour Selection

Audio Spectrum Visualiser with Colour Selection © GPL3+

Choose the colours of the LED strip using Hue/Saturation/Value parameters, flick the switch and watch your music come to life through FFT.

  • 753 views
  • 0 comments
  • 3 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)

Apps and online services

About this project

I was intrigued by the possibilities of addressable “Neopixel” RGB LEDs. I wanted to learn how to control them and to use them in a challenging project. I had read a few projects that use LED arrays and matrices to represent the audio spectrum, much like the illuminated displays on graphic equalizers.

I thought of something a bit different. How about a vertical string of LEDs where the low frequencies would be at the bottom, the high frequencies would be at the top, and the amplitude of each frequency band would appear as the brightness of each LED? The music would then “pulse” along this string. I am certainly not the first to think of this or execute it, but I had not yet seen this particular application.

If the brightness of the LEDs was controlled by the music, could the colour of the LEDs be set by the device's user?

How It Works

The circuit and the code for this device operate in two modes. These modes are selected with the mode switch at the right of the front panel. When the mode switch is in the left-hand position, the Colour Set mode is activated.

The Colour Set mode allows the user to set the colour range of the LED strip using the 5 potentiometers. Specifically, from left to right, the potentiometers adjust:

1. Value - the overall brightness of the LED strip

2. Saturation - this setting gradually shifts the Hues between vivid colours and pure white

3. Top Hue - specifies which Hue (colour) is at the top of the LED strip

4. Bottom Hue - specifies which Hue (colour) is at the bottom of the LED strip

When the LED strip contains more than one Hue, the colour sequence always follows the sequence of the colour spectrum: red, orange, yellow, green, blue, violet.

To select a single Hue for the entire LED strip, simply place the Top Hue and Bottom Hue potentiometers in the same position.

5. Spread - controls how widely the Hues are spread along the LED strip, from concentrated in the middle to “bunched up” at both ends.

I used the Hue, Saturation and Value (HSV) colour model to control the LEDs as opposed to RGB parameters, because HSV directly produces the effects I wanted. This article explains and compares the HSV and RGB colour models. Luckily, the Adafruit Neopixel library used in the code accommodates both RGB and HSV.

When the mode switch is in the right-hand position, the FFT mode is activated. In this mode, the potentiometers are deactivated.

The FFT (Fast Fourier Transform) mode accepts input from a music player's headphone connection. Using FFT, it calculates the Value (brightness) part of the HSV settings for each frequency band. It then combines this Value parameter with the colours chosen by the user and sends them to the appropriate LEDs. This article gives an easy-to-follow explanation of Fourier transforms. The actual calculations are over my head but once again, the generous Arduino community comes to the rescue in the form of the ArduinoFFT library.

The Circuit

Circuit Diagram

In this diagram, the device labelled as Arduino Nano (Rev3.0) is in fact an Arduino Nano Every.

The device labelled Voltage Regulator 5V is in fact a 5V buck converter. The 9VDC wall adapter supplies power to the Nano through its Vin pin and supplies power to the buck convertor. The buck convertor's function is to supply 5V power to the LED strip. The Nano communicates to the LED strip through pin D3.

I used the audio input circuit (at the top left of the diagram) detailed in this project:

https://create.arduino.cc/projecthub/shajeeb/32-band-audio-spectrum-visualizer-analyzer-902f51.

This circuit uses the Nano's REF and 3.3V pins, and feeds into the A0 analog input.

The five potentiometers are used to set the colours and colour range of the LED strip. The five settings are described in the Code section. They are read by the Nano's analog inputs A1 to A5.

The mode switch is used to select the Colour Set or FFT mode. The switch position is read at the Nano's D2 pin.

The Fritzing file is available for download in the Schematics section below.

Breadboard Assembly

I included this diagram for those who would like to explore and modify the circuit as I did. Breadboarding is also the least frustrating way to test the code and all the components. The Fritzing file is available for download in the Schematics section below.

Here again, the device that looks like a voltage regulator is actually a 5V buck converter. The controller represented as an Arduino Nano (Rev3.0) is in fact an Arduino Nano Every. All red wires carry 5V, orange wires carry 9V and black wires go to ground.

Prototype Board and Box Assembly

This diagram is available for download in the Schematics section below. The board illustrated is larger than the one I used, but it allowed me to illustrate the layout more clearly.

By standing up the resistors and doubling some connections in perforations, I was able to squeeze the components onto a 7cm x 3cm board. It fits nicely, with plenty of room to spare, in the enclosure I chose.

The Nano is positioned with its USB connector slightly overhanging the edge of the board. I cut an access hole through the enclosure through which I could connect a USB cable. This allows me to reprogram the Nano without removing it.

I used rubber cement to initially position the Nano directly on the board, until the solder joints secured it in place.

I decided to install JST connectors between the board and the panel-mounted components. This might seem overzealous for a prototype, but having time to spare, I simply wanted to get familiar with these connectors. I also suspect that I will eventually want to rearrange things a bit.

I included my construction drawings in the Custom Parts and Enclosures section. These could save you time in measuring, positioning and mounting the components. You could also use them to print the symbols for the controls.

This photo shows the base of the LED column assembly. This project requires the LED strip to contain 29 LEDs. The back of the LED strip is self-adhesive, so I tacked it onto a piece of 1/2”x1/8” aluminum bar. I wanted to diffuse the light of the LEDs, and I thought of neoprene tubing. It often has a translucent white colour. I found such tubing with 12mm internal diameter which fit perfectly over the bar and LED strip. The light is not yet diffused to my liking, and I will be experimenting with other materials. My eldest daughter, an artist, suggested mylar film.

The Code

I added comments to almost every line of the code in order to explain it to myself as I learned. So here, I will describe it more generally.

Communicating With the LED Strip

The code uses the Adafruit NeoPixel Library to communicate with the LED strip. Here are links to the library and the library user guide.

Structure of the Main Loop

The main loop is divided into two loops; the Colour Set loop and the FFT loop. The position of the mode switch determines which loop is active. The loops are controlled by a “while” statement that reads the switch position.

void loop() 
while (digitalRead(modeSwitchPin)==LOW){
//Colour Setting code goes here because the mode switch is in the LOW (Colour Set) position.
}
//FFT code goes here because the mode switch is in the HIGH (FFT) position, outside “while” loop.
//If the mode switch is moved to LOW (Colour Set), reset Nano to re-initialize variables and memory.
if (digitalRead(modeSwitchPin)==LOW){
resetFunc();
}
}

While the mode switch is in the Colour Set position (LOW), the Colour Set code will keep looping.

As soon as the mode switch is flicked to the FFT position (HIGH), the “while” loop breaks and the FFT code starts looping.

When the mode switch is again flicked to the Colour Set position, a soft Reset Function is activated, which re-initializes the Nano and allows the Colour Setting code to start looping again.

I included the soft reset function because the Colour Setting code refuses to run after the FFT code has run. I don't know why this happens, but a soft reset worked.

All instructions sent to the LED strip are in the form strip.setPixelColor() using the RGB standard. However, I chose to set colours using the HSV parameters, then convert them to RGB.

Colour Setting Mode: The Spread Adjustment

This feature needs some explanation. I added this adjustment because I wanted to alter the distribution of hues along the LED strip, from concentrated in the middle to “bunched up” at both ends. I applied a sigmoid curve (also called an s-curve) formula to the range of hues.

k=analogRead(kPin); //read k pot, 0-1023

This line reads the position of the Spread potentiometer.

hueSig[i] = hueMax/(1+(pow(2.718,(0-(k/4000000))*(hue-(hueMax/2))))); //apply s-curve

In this line, the value of the Spread potentiometer (k) is used to adjust the slope of the sigmoid curve, which alters the hue of each LED. The hue of each LED then becomes hueSig[i]. By the way, I arrived at the value 4000000 by experimentation.

This article explains sigmoid curves. I used the basic equation at the very start of the article.

FFT Mode

I certainly did not invent anything here. Thanks to Chris Parker for sharing his design and his code. I also drew from the examples in the Arduino FFT Library documentation.

Code

Arduino SketchArduino
#include "Adafruit_NeoPixel.h"
#include <arduinoFFT.h>

//LED STRIP VARIABLES
int leds=29; //number of LEDs in strip
int brightness=64; //overall brightness level of entire strip, 0 (off) to 255
int sat=255; //saturation from 0(grayscale) to 255(max sat.)
int val=64; //value (individual brightness) from 0(off) to 255(max)
int valMax=255; //max value of individual led brightness
uint32_t rgbColour; //used to convert HSV colour to RGB colour
long hueBottom; //hue value for top LED, selected with pot at hueBottomPin
long hueBottomMapped; //mapped to hueMin, hueMax range
long hueTop; //hue value for bottom LED, selected with pot at hueBottomPin
long hueTopMapped; //mapped to hueMin, hueMax range
long hueStep; //hue range (mapped) per led
long hue=43691; //calculated final hue value, from 65536(violet) to 0(red)
float k; //steepness of s-curve, read from k Pot as 0-1023
long hueSig[29]; //array to store hue (with s-curve applied) for each LED
long hueMin=0; //min possible hue: red
long hueMax=65536; //max possible hue: violet
int ledPin=3; //digital output pin to LED strip
int modeSwitchPin=2; //digital input pin for FFT/Colour Set switch
int audioPin=A0; //audio input pin
int satPin=A2; //saturation potentiometer pin
int brightnessPin=A1; //overall brightness potentiometer pin
int hueTopPin=A3; //top hue potentiometer pin
int hueBottomPin=A4; //bottom hue potentiometer pin
int kPin=A5; //s-curve steepness (k) potentiometer pin

Adafruit_NeoPixel strip = Adafruit_NeoPixel(leds, ledPin, NEO_GRB + NEO_KHZ800);

//FFT VARIABLES
const int bands=32;   //total number of frequency bands, must be <= SAMPLES/2
const int SAMPLES=64; //2x the number of freq bands. Must be a power of 2
const int audioIn=A0; //audio input is analog pin A0
double vReal[SAMPLES];
double vImag[SAMPLES];
arduinoFFT FFT = arduinoFFT(); //creates an FFT object

//This function causes the Nano to reset. It is necessary to initiate the Colour Set mode.
void(* resetFunc) (void) = 0;

void setup() {
  pinMode(modeSwitchPin,INPUT); //set modeSwitchPin as input
  delay(3000); //power-up safety delay
  strip.begin(); //initialize LED strip
  strip.show();  //initialize all pixels
}
 
void loop() {
  
  //While mode switch is low, set colours. Otherwise, perform FFT.
  while (digitalRead(modeSwitchPin)==LOW){
    
    //analogRead gives 0-1023 & brightness requires 1-255, so divide reading by 4
    brightness=analogRead(brightnessPin)/4; //read brightness pot
    strip.setBrightness(brightness); // set overall brightness
    //analogRead gives 0-1023 & sat requires 1-255, so divide reading by 4
    sat=analogRead(satPin)/4; //read brightness pot
    //read hueTop & hueBottom pots. analogRead gives 0-1023
    hueBottom=analogRead(hueBottomPin); //read hueBottom pot, 0-1023
    hueBottomMapped=map(hueBottom,0,1023,hueMin,hueMax);
    hueTop=analogRead(hueTopPin); //read hueTop pot, 0-1023
    hueTopMapped=map(hueTop,0,1023,hueMin,hueMax);
    hueStep=(hueTopMapped-hueBottomMapped)/leds;
    k=analogRead(kPin); //read k pot, 0-1023

    for (int i=0;i<leds;i++) { 
      hue = hueBottomMapped+(i*hueStep); //calculate hue for each of the LEDs
      Serial.print ("hue: ");
      Serial.print (hue);
      hueSig[i] = hueMax/(1+(pow(2.718,(0-(k/4000000))*(hue-(hueMax/2))))); //apply s-curve
      Serial.print ("hueSig: ");
      Serial.println (hueSig[i]);
      rgbColour = strip.ColorHSV(hueSig[i],sat,val); //calculate colour setting from HSV values
      strip.setPixelColor(i,rgbColour); //output RGB colour setting to LED
      strip.show(); //show the colour
      delay(10);
    }
  }

   //FFT sampling
  for(int i = 0; i < SAMPLES; i++){
    vReal[i] = analogRead(audioIn);
    Serial.println(vReal[i]);
    vImag[i] = 0;
  }

    //FFT computation
    FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
    FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
    FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);

    //re-arrange FFT result to match with number of LEDs in strip
    int step = (SAMPLES/2)/bands; 
    for(int i=0; i<(SAMPLES/2); i+=step)  
    {
    vReal[i] = constrain(vReal[i],0,2047); //set max value for vReal
    vReal[i] = map(vReal[i], 0, 2047, 0, valMax); //map vReal values to brightness value range
    }

    //send to LED strip according to desired values
    for(int i=0; i<leds; i++)
    {
      val=vReal[i+3]; //ignore the bottom 3 freq. bands 0, 1 and 2 (they're always on)
      //Therefore, 29 LEDs will be addressed. This is why leds is set to 29, not 32.
      rgbColour = strip.ColorHSV(hueSig[i],sat,val); //calculate colour setting from HSV values
      strip.setPixelColor(i,rgbColour); //output RGB colour setting to LED
      strip.show(); //show the colour
     }

  //if mode switch is moved to Colour Set mode, reset Nano to re-initialize colour parameters
  if (digitalRead(modeSwitchPin)==LOW){
    resetFunc();
  }
}

Custom parts and enclosures

Enclosure Layout - Open Office Draw file
audspecvis_enclosurelayout_ZJfSXpqTCy.odg
Enclosure Layout - PDF file

Schematics

Breadboard Layout
audspecvis_breadboard_hK4Aho3kAo.fzz
Circuit Diagram
audspecvis_schematic_pa43Lzp3q5.fzz
Pre-perforated Board Diagram
audspecvis_perfboard_ElqY19FDi7.fzz

Comments

Similar projects you might like

32-Band Audio Spectrum Visualizer Analyzer

Project showcase by shajeeb

  • 130,145 views
  • 169 comments
  • 271 respects

RGB 32-Band Audio Spectrum Visualizer

Project tutorial by janux

  • 10,610 views
  • 5 comments
  • 9 respects

FHT Audio Spectrum Visualizer

Project tutorial by janux

  • 7,722 views
  • 26 comments
  • 11 respects

DIY Audio Signal Spectrum Analyzer

Project tutorial by Mirko Pavleski

  • 5,912 views
  • 1 comment
  • 10 respects

DIY FFT Audio Spectrum Analyzer

Project tutorial by Mirko Pavleski

  • 22,135 views
  • 2 comments
  • 23 respects

2 x 16-Band Audio Spectrum Analyzer with LCD

by shajeeb and ThomAce

  • 12,128 views
  • 4 comments
  • 12 respects
Add projectSign up / Login