The late night tinkering projects #10: fun with Fourier!

Photo by Teigan Rodger on Unsplash

With some signal processing on Arduino, you can achieve some nice lights effects! Curious about the code? Check the source here!

One of the most common applications of lights effects is getting voice or music with a microphone and to light some led to obtain some nice visual effect.

In particular, the audio input is processed in order to get frequency and maybe to light some particular light when the music (or the voice) hit some particular (peak) frequency.

Surfing on sine wave: Fourier transform

No need to deep dive in math, Fourier transform shift the wave representation from time domain to frequency domain.

With Fourier, the wave is decomposed as a sum of sinusoids, each one with its own frequency, amplitude and phase (and the Fourier theorem itself states that this decomposition operation is possible for every wave). Since making a Fourier transform is heavy task, libraries usually perform Fast Fourier Transform, an optimized version of the Fourier Transform algorithm.

Source: wikipedia

To keep it simple, you usually feed the FFT with a wave and obtain an array of bins as an output, where each bin represent a frequency range and a value will be present in that bin if a signal within the bin frequency is present.

When dealing with FFT, there are some concepts developers have to deal with:

  • sampling frequency: must be at least twice the frequency we want to “capture” (due to Shannon-Nyquist theorem)
  • number of samples: to improve algorithm optimization, this number has to be in a power of 2. The higher this number, better the resolution (more bins->narrower frequency ranges)

From theory to practice

Arduino Nano 33 BLE Sense comes with nice on-board RGB leds and an integrated microphone so all is ready to experiment.

If you don’t have this particular Arduino nano, you can also connect your own lights and microphone.

To capture audio and detect the frequency, you are going to include two libraries:

#include <PDM.h> //to get microphone input
#include <arduinoFFT.h> //for the Fourier transform

The PDM library allows you to use PDM (Pulse-density modulation) microphones and that’s just the kind of microphone it’s possible to find on Arduino Nano 33 BLE Sense board; ArduinoFFT is a Fast Fourier Tranform library.

First of all, you define the sampling rate and the number of samples for FFT:

#define SAMPLES 256 //Must be a power of 2
#define SAMPLING_FREQUENCY 16000

Sometimes, searching for online examples, it’s possible to find “the must be less than 10000 due to ADC” warning comment, referred to SAMPLING_FREQUENCY. This is a limit that you can hit when reading input from pins (which is not this case), since reading from a pin requires a certain amount of time, thus limiting sampling rate.

In the setup function of the sketch, we are going to initialize leds, PDM and FFT with some more variable:

short sampleBuffer[SAMPLES];
volatile int samplesRead;
double vReal[SAMPLES];
double vImag[SAMPLES];
void onPDMdata(void);const int ledPin = 22; //red
const int ledPin2 = 23; //green
const int ledPin3 = 24; //blue
void setup() {
Serial.begin(115200);
while (!Serial) {
; // wait for serial port to connect.
}
PDM.onReceive(onPDMdata);
PDM.setBufferSize(SAMPLES);
if (!PDM.begin(1, 16000)) {
Serial.println("Failed to start PDM!");
while (1);
}
pinMode(ledPin, OUTPUT);
pinMode(ledPin2 , OUTPUT);
pinMode(ledPin3, OUTPUT);
digitalWrite(ledPin, HIGH);
digitalWrite(ledPin2, HIGH);
digitalWrite(ledPin3, HIGH);
}

onPDMdata is a callback function invoked when data is available to be read from the microphone:

void onPDMdata()
{
int bytesAvailable = PDM.available();
PDM.read(sampleBuffer, bytesAvailable);
samplesRead = bytesAvailable / 2;
}

In the loop() function, you consume the sampleBuffer and calculate the FFT, requesting the peakFrequency(). You also define three additional function to light each and every led. Every led will be lit when the peak is in a particular predefined frequency range:

void lightOne() {
digitalWrite(ledPin, LOW);
digitalWrite(ledPin2, HIGH);
digitalWrite(ledPin3, HIGH);
}
void lightTwo() {
digitalWrite(ledPin, HIGH);
digitalWrite(ledPin2, LOW);
digitalWrite(ledPin3, HIGH);
}
void lightThree() {
digitalWrite(ledPin, HIGH);
digitalWrite(ledPin2, HIGH);
digitalWrite(ledPin3, LOW);
}
void loop() {
if (samplesRead) {
for (int i = 0; i < SAMPLES; i++) {
vReal[i] = sampleBuffer[i];
vImag[i] = 0;
}
FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
double peak = FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);Serial.println(peak); if (peak <=600)
lightOne();
if (peak >600 && peak < 1200)
lightTwo();
if (peak >= 1200)
lightThree();
samplesRead = 0;
}
}

Testing

Reading the serial plotter you will see the peak frequency over the time.

Since it’s impossible to have complete quiet in the room you will see the plot moving around a certain frequency, depending on input sound and the light will probably be red.

To test the sketch…put your phones on Arduino!

Head on this site where there is a tone generator. Now let’s start by generating a wave with a frequency of 440Hz (A4):

You should see the plot around 440Hz in the serial plot.

Now go on moving the slide from 0 to 1400 Hz: you will see the red light before, then the green light and, exceeding 1200Hz, the blue.

Testing the FFT

You can have fun putting some music on and adjusting intervals to achieve nicer light effects!

Time to start the party!

Full-time Human-computer interpreter