Arduino FFT lightorgan with fading lights

  • Post author:
  • Post last modified:1. August 2021

some quick information

Cost: 5-20€

Buildtime: one day

Required Knowledge: 2/5

What is it about?

A lightorgan isn't just a must have for RGB-Freaksm it also is also provides an introduction into the amazing world of electronics and is a great project for getting to know the Arduino.

This Tutorial makes use of just simple components, which can be found at any makers home. The detection of the frequencies utilizes a Fourier transform algorithm, thus an additional equalizer IC isn't needed.  

light organ complete

The basic idea

The concept in detail

lightorgan arduino schematic diagramm

The heart of this project is an Arduino Nano. With a price of a few Euro it is relatively cheap and at this place meets our requirements.

The Arduino will detect the loudness of each frequency band (bass, mids and treble) with one analogpin and three digitalpins will control the LEDs

So far, so good. But why do we transistors at this place and don't control the LEDs directly? The digitalpins of an Arduino are only able to deliver a maximum current of 40mA. If a connected device requires more current we use transistors as kind of switch (more about in my article about transistors). 

But since transistors can't be switched with an unlimited amount of current, the 1kΩ resistor prevents the transistor from beeing used as fog machine.

We live in the 21th century and music is mostly available in stereo and thus, the Arduino would have to scan the two audio signals. Our project is supposed to be as simple as possible, but we can't just connect the two pins together. As my schematic diagram depicts I used two 10kΩ resistors to prevent a short - Not ideal, but it works. 

But if there is no audiosignal applied the Arduino will very vague values and correspondingly, the results on the LEDs will look like s****. The 100kΩ Resistor acts as pulldown resistor and if no signal is connected, will wire it to ground. If connected the resistance is so small, that it can be neglected fisiks.

But I haven't mentioned the 5.1kΩ connecting the AREF-Pin and the 3.3V.

Measuring the voltage with an analogpin the Arduino compares the applied voltage with another voltage (On an Arduino Nano typically 5V, otherwise look up here ). The resulting values is between 0 and 1023 (0V 0, 2,5V ~ 512 and 5V 1023). Most of the Arduinos have resolution of 10bit - more then enough.

A different comparison voltage can be applied at the AREF Pin. This will be important if you want to measure a voltage lower then 5V. The lower comparison voltage will increase the precision of the result. But the new voltage needs to be between 0V and 5V. 

An example: The audio level of your signal reaches a maximum of 3V, thus, you use the 3.3V from the Arduino microchip. The resistor prevents a high current. Consequently, at a voltage of 1V the Arduino would measure a value of 310. (Usual audio levels only reach 1V)

Last but not least: POWER

My LED strips require 12V and I had an appropiate power supply laying around. 

This supply should also power my arduino. Altough the Atmega-328 of the Arduino Nano has its own linear voltage regulator, this one has a maximum voltage rating of 12V. The required 20mA already heats it up quite nicely. An additional 7805 linear voltage regulator limits the voltage to 5V and dissipates the heat over their relatively big heatspreader. 

The capacitor are supposed to stabilize the voltage, but they didn't made it on my board because the demands from the 7805 aren't that high. 

The first tests can be done with just three LEDs and without the transistors (don't forget the 470Ω series resistor to prevent the LEDs from high current!). The Arduino can also be powered through the USB port. A breadboard and, if you want to play it safe, a regulated powersupply aren't a bad idea.

So, search your components, solder and start programming!

But what is fast fourier transform (FFT)?

Befor we have closer look on the program, lets firstly discuss the basics fast fourier transform within an Arduino.

A short Audiosignal can be seen as the sum of different oscillations. Each oscillation has its own frequency (=pitch) and amplitude (=level).

fourier-transform-fft-arduino

The algorithm of a fourier transform can be used to backtrack the different frequencies and amplitudes of a short audiosignal. (more about this topic here). The fast fourier transform is an improved version of the discrete fourier transform. Both alogrithms use matrix multiplication to calculate the frequency bands from a given audiosignal.

Thankfully, there are plenty of Arduino libraries which implement it very well. I used the arduinFFT library from Enrique Condes (Github). We use the librarymanager to install it. In the Arduino IDE got to Sketch > Include Library > Manage Libraries... Search for arduinoFFT and click "install" on the right library.

install_arduino_fft_library

The program

Don't forget to change the LED pins in your code at the LED[] array and the analogpin at AUDIO. I have set them the LED pins to 9, 10 and 11 and the audioinput to A0.

#include <arduinoFFT.h>

#define SAMPLES 64  // must be a power of 2
#define AUDIO A0



// defines how much brigthness the LEDs will loose each loop
// 1024 is the maximum --> the organ will be very responsive
// lower will smootly the the leds if no new peak is detected 
#define decayPerLoop 1024 


const int LED[] = {9,10,11};



double vReal[SAMPLES], vImag[SAMPLES];
double sums[] = {1024,1024,1024}; 



arduinoFFT FFT = arduinoFFT();      


void setup()
{
  // define the analog voltage reference
  analogReference(DEFAULT);

  // set all digital pins with an LED to an output
  for(int i = i; i < sizeof(LED); i++)
  {
    pinMode(LED[i], OUTPUT);
  }
}

    
void loop()
{   
    // collect Samples
    for(int i=0; i < SAMPLES; i++)
    {
      int sample = analogRead(AUDIO); // read the voltage at the analog pin
      vReal[i] = sample/4-128;        // compress the data and getting rid of DC-Voltage 
      vImag[i] = 0;                   // reset old imaginary part of the FFT
    }
    
    // let the fourier transform do his magic
    FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
    FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
    FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);

    // calculate average loudness of the different frequency bands
    sums[0] = calculateBrightness(vReal, vImag, 1, 3, sums[0]);  //Bass
    sums[1] = calculateBrightness(vReal, vImag, 4, 40, sums[1]); //Mids
    sums[2] = calculateBrightness(vReal, vImag, 40, 63, sums[2]); //High

    // apply the brightness to the different led channels
    for(int i = 0; i < 3; i++){ analogWrite(LED[i], sums[i]); }

    // wait one millisecond for the next analysis
    delay(1);
}

double calculateBrightness(double vReal[], double vImag[], int first, int last, double oldSum){
  double newSum = 0;

  // sum up all absolute value of the complex numbers (it's just the pythagorean theorem)
  for(int i=first; i < last; i++)  
  {
    newSum += sqrt(vReal[i] * vReal[i] + vImag[i] * vImag[i]);
  }
  newSum = newSum/(last-first-1);         // divide to get the the average loudness
  newSum = constrain(newSum,0,5000);      // crop the number above 5000 
  newSum = map(newSum,0,5000,0,1024);     // map it between 0 and 1024

  oldSum -= decayPerLoop;                 // substract the decay from the old brightness
  if(newSum > oldSum){ oldSum = newSum; } // if the old brightness is now lower then the new, the new brightness will be outputted

  return oldSum;
}

Between line 42 and 47 a dataset of 64 samples will be created and saved in vReal. This data is the short audio signal, which will be analysed with FFT within the next three lines of code.

The result are two arrays: vReal and vImag. The numbers a certain index are the components for a complex number. But what is complex number?

A complex number has two different components - A real part and an imaginary part, which has the unit i. You can display them in a graph with the imaginary part on the horizontal axis and the imaginary part on the vertical axis 3 + 2i will looks like this:

komplexe-zahl

The absolute value can be calculated with the pythagorean theorem. 

complex_number

This value is the level of a certain frequency. To caculate the average of certain frequency bands we need sum them up, like in line 72. 

But which index resembles which frequency? This depens on the number of samples n and at which frequency f the Arduino is able to get the dataset. i ist the index in the arrays vReal and vImag.

fft_frequency

The Arduino has a clocking of 16MHz, my lightorgan performed best with 64 samples and an Arduino needs 13 cycles for one measurement. Thus, the frequency at which the Arduino can pick up a new dataset is:

calculate_fADC

Consequently, the frequency of a certain index i is:

calculate_fi

Since the first index doesn't deliver usefull data, the second the third index (600Hz and 900Hz) are my values for the bass, between 4 and 39 are my mids and everything above is the treble. In line 55 till 57 those indices are given to the calculateBrightness function, which calculates the average brigthness.

Lastly, the returning leves of brightness need to be applied to the LED channels as as PWM signal (line 60). PWM or pulse-width-modulation is a simple way of digital data transmission. The LED will blink with such a high speed that our brain will just notice different brigthness levels. The pulse-break ratio defines how long the LED stays on and off in one cycle. The longer the on-time the brighter the LED appairs. 

pwm-wave

The command analoWrite does most of the job for us. It takes a Integer from 0 to 255. 0 resembling OFF and 255 the maximum of brightness. Since the value of analogRead ranges from 0 to 1023 the recorded signal needs to be compressed, which happens by dividing by 4 (line 45).

As mentioned in the heading this lightorgan has also the ability to slowly dimm the LED channels and light up after new value. The value decayPerLoop defines how much brightness the LEDs will loose each loop. Is the current brightness higher the old will be overwritten (line 78 and 79).

To turn this of simply put the decay to 1023. In my personal view the fading looked best with a value of 20. Below lightorgan wasn't that responsive anymore. 

Optimization phase

You have it - A built and programmed lightorgan, but the result doesn't looks as good as you gave imagined it. This chapter is supposed to help you optimize your lightorgan to your audiosignal and other conditions.

The lightorgan isn't bright enough

Maybe your audiosignal itself isn't loud enough. I use the headphone output of my mixing desk which comes with additional amplifier. Set your audiosignal to its maximum to increase the brightness and also the resolution for your FFT.

The lightorgan doesn't show any change with a given signal 

Hier ist es ratsam sich die Helligkeitswerte und andere Variablen, wie die Samples, über den seriellen Monitor ausgeben zu lassen. Das kann Aufschluss auf falsche Anschlussbelegung an dem Analog- oder den Digitalpins legen. Mehr dazu hier. (Der serielle Monitor kann in der Arduino IDE mit Strg+Shift+M geöffnet werden)

Any remaining questions? Text me on Instagram or via E-Mail: info@the-techblog.de

The disadvantage of using FFT on the Arduino in a lightorgan project - Disclaimer

Laut dem Nyquist-Shannon-Abtasttheorem sollte die Abtastfrequenz mindestens das doppelte der abzutastenden Frequenz betragen. Wie wir oben jedoch festgestellt haben beträgt die Abtastfrequenz etwa 19,2kHz. Die effektive Frequenz liegt somit bei ungefähr 10kHz.

But humans can pick up frequencies up to 20kHz. Double the frequency the Arduino should be able to measure. Thus, the upper half of the frequency band analysed can be wrong.

If you consider this fact during your optimization phase and are satisfied with the result you can neglect this problem. 

some quick information

Cost: 5-20€

Buildtime: one day

Required Knowledge: 2/5