This post is a continuation of my previous post about sound analysis in Arduino.
In this post I will explain how I concretely managed to synchronize a light to the low frequencies of music (that is the bass).
As seen in my previous post, there are several alternatives for doing this job, so I will focus on a particular selection of tools:
The circuit:
The easy one: I use the Sparkfun’s electret kit.
The algorithm:
I will try to create a FIR filter for selecting only the low frequencies of the signal, and I will not tweak the analog-to-digital sampling frequency.
As the circuit is trivial, let’s see the details about the algorithm. For designing the filter I have used a wonderful free online tool, TFilter, from Iowegian.
The tool allows specifying the sampling frequency and the frequency response of the filter.
In my case I chose to allow passing frequencies for 0 to 200Hz and to block frequencies from 300Hz on. You can see the graph of the frequency response of my filter below:
As for the sampling frequency it’s tricky. It, in fact, depends on the code which then depends on the filter that you have. Generating the right filter is therefore an iterative process: you generate a first version of the filter with a certain sampling frequency (say 5000Hz), you import the code from the online tool (the code is automatically generated for you in the “source code” tab) and stick into your sketch, then you run a sketch that measures the actual sampling frequency, for instance by filtering a thousand samples and measuring the time needed for that. An example of such code is the following:
#include "LowPassFilter.c" //The number of samples to buffer before analyzing them int samplesN = 1000; int micPin = 0; LowPassFilter* filter; void setup(){ Serial.begin(9600); filter = new LowPassFilter(); LowPassFilter_init(filter); } void loop(){ long pow = 0; long filtpow = 0; int peak = 0; long start = millis(); for(int k=0; k<samplesN; k++){ int val = analogRead(micPin); LowPassFilter_put(filter, val); int filtered = LowPassFilter_get(filter); pow += ((long)val*(long)val)/samplesN; filtpow += ((long)filtered*(long)filtered)/samplesN; peak = max(peak, filtered); } long end = millis(); float freq = ((float)samplesN * (float)1000) / ((float)end - (float)start); Serial.print(freq); Serial.print(" "); Serial.print(pow); Serial.print(" "); Serial.print(filtpow); Serial.print(" "); Serial.println(peak); }
This code takes 1000 samples, filters them and computes the power of the signal, both the filtered and unfiltered ones and the sampling frequency. The power is simply the sum of the second power of the samples divided by the number of samples.
Note that I am using the integer implementation of the filter (in the source code tab of TFilter you can select floating points or integers in the “Number format” field) because integers arithmetic is much faster than floating points one.
Once you know what sampling frequency you go back to your TFilter tool and redesign the filter with the actual sampling frequency, but, given that the specifications are changed, it is very likely that the length of your filter will be different now, fact that will affect the computation required to run it and, therefore, the sampling frequency again!
So you need to do this work of: designing the filter + measuring actual sampling frequency + adjusting the filter, in some iterations (2 or 3 should be enough).
In my case the final sampling frequency is 2500Hz and the filter has 31 “taps” (the more complex is the filter the more taps you need, the more time you need to make it run at each sample).
So let’s check that it really filters the signal around 200-300Hz. I connected a pair of headphones to the microphone and measured the power of the signal while emitting some sine signals through them. The results is shown in the following table:
noise | 50 Hz | 100 Hz | 200 Hz | 300 Hz | 500 Hz | 800 Hz | |
total power | 265760 | 307555 | 427998 | 458491 | 464047 | 466989 | 483063 |
filtered power | 163604 | 198169 | 378124 | 275661 | 161191 | 162059 | 181106 |
peak | 623 | 772 | 1087 | 950 | 548 | 537 | 613 |
filter/unfilter(db) | -2.11 | -1.91 | -0.54 | -2.21 | -4.59 | -4.60 | -4.26 |
in terms of decibel it is not very impressive, but it does some filtering though. The problem here is that the circuit gets a lot of noise and a significant part of it goes into the low frequencies so, for instance, when you push a 800Hz sine the Arduino gets the 800Hz + the noise, therefore the total power is not so low.
Now let’s do something with this signal, for instance synchronizing a light on the bass of the music. For that we can use the measurement of the power or, even a simpler measurement, the peak.
To make it more responsive, instead of sampling 1000 samples, we can get 200 instead, and, in order to make it adaptive to the noise and general sound level we can play this trick: as the peak will move between a minimum and a maximum, we take the current peak and map the interval between the average and the maximum peak to a 0-1023 interval.
The final code appears something like this:
#include "LowPassFilter.c" //The number of samples to buffer before analyzing them int samplesN = 200; int micPin = 0; LowPassFilter* filter; void setup(){ Serial.begin(115200); filter = new LowPassFilter(); LowPassFilter_init(filter); } int index = 0; int maxpeak = 0 ; int minPeak = 1023; void loop(){ int peak = 0; for(int k=0; k<samplesN; k++){ int val = analogRead(micPin); LowPassFilter_put(filter, val); int filtered = LowPassFilter_get(filter); peak = max(peak, filtered); } maxpeak = max(maxpeak, peak); minPeak = min(minPeak, peak); index++; if(index == 1000){ maxpeak = 0; minPeak = 1023; } int lvl = map(peak, minPeak, maxpeak, 0, 1023); Serial.println(lvl); }
It’s a pretty simple code as you see. Remember to also add the .h and .c files generated by the TFilter tool and also check that the sampling frequency hasn’t changed (which probably has). Please note also that there is a counter (index) that, each 1000 iterations, resets the minimum and maximum detected values. This helps if the sound level changes and you want your code to adapt to it.
From this code you can also make a sort of beat detector, for instance by setting a threshold and counting the number of times the peak goes on top of the threshold in a certain time window, but it would require more elaboration for sure.
In this code I am not activating any light, I am just sending the data to the computer to see how it is working. As the numbers run really fast on the serial monitor, I have created a simple Processing script that acts as a sort of equalizer bar, the code is here:
import processing.serial.*; Serial myPort; float MIN_VAL = 0; float MAX_VAL = 1023; void setup () { size(300, 500); println(Serial.list()); // Open whatever port is the one you're using. myPort = new Serial(this, Serial.list()[0], 115200); // don't generate a serialEvent() unless you get a newline character: myPort.bufferUntil('\n'); background(0); } void draw () { // everything happens in the serialEvent() } void serialEvent (Serial myPort) { String inString = myPort.readStringUntil('\n'); if (inString != null) { // trim off any whitespace: inString = trim(inString); // convert to a float and map to the screen height: float inval = float(inString); inval = map(inval, MIN_VAL, MAX_VAL, 0, height-20); background(0); rect(10,10, width-20, inval); } }
I have tested with some disco music and I have o say that the effect is quite good, when the bass is pumping you really see the bar going up and down at the same tempo, though, I have to say, you can also see some delay, which does not matter as long as you want to make some light controller anyway. By reducing the number of acquired samples to something less than 200 (for instance 100) you get a more responsive system but also a more sensible one, which makes the bar moving really fast and sometimes you see the noise clearly getting into the animation.
Great tutorial!
Is there a way to filter out any frequencies higher than the maximum one? After reaching the half sample rate frequency, my leds are doing some strange flickering.
Thanks!
I am not sure what your problem is, the digital filter is supposed to do what its name says: filtering out the higher frequencies, so your LEDs should not react to frequencies higher than the cutoff. One important thing is to check what your actual sampling frequency once all the code is uploaded to Arduino, if the frequency is not what you used to design the digital filter, then you will need to re-design the filter again, and re-measure the sampling frequency, in an iterative process, until you converge.
Hi, bochovj.
I’ve been searching for a project like this for some time now so I was really happy to find your blog.
But I can’t get it to compile. No matter where I place #include “LowPassFilter.c”, the compiler says it doesn’t exist. I’m on Windows 7 Home Premium and using an Arduino Nano. I’ve also tried the last three or four versions of the IDE and some third-party alternatives. No joy.
Any suggestions?
Hi,
First create two new files in the Arduino Sketch. You can see hoe to do it in the Arduino manual: “A sketch can contain multiple files (tabs). To manage them, click on the right-facing arrow just above the scroll bar near the top of the environment.”. Call them LowPassFilter.c and LowPassFilter.h (or whatever). Then design your filter on TFilter, then click on “source code” (top left side of the webpage) and copy the content of “SampleFilter.c” and “SampleFilter.h” to your LowPassFilter.c and LowPassFilter.h. That should be it.
Hi, bochovj.
I must be doing something bone-headed. I’m using Arduino 1.6.1 and although there is no “right-facing arrow just above the scroll bar near the top of the environment”, there is a down-facing arrow that let’s me manage tabs. I’m trying to take this one step at a time so I can try to understand what I’m doing. Or doing wrong.
I’m trying to use your sketch as it appears above (tried everything I can see), and without any tabs, when I run Verify I get:
“bass_detector.ino:1:27: fatal error: LowPassFilter.c: No such file or directory”
So I add a tab and the file “LowPassFilter.c” run Verify and I get:
“LowPassFilter.c:1:26: fatal error: SampleFilter.h: No such file or directory”
Then I add another tab and the file “SampleFilter.h” and re-run Verify and it goes back to:
“bass_detector.ino:1:27: fatal error: LowPassFilter.c: No such file or directory”
What?!? So I’m right back to where I started. 😦 Does that make sense to you?
I don’t know, if you have added the files, and copied the content of the source of TFilter it should work. I can’t help more than this here.
This works very well, I liked your tutorial a lot. Just a note, you need to reset index inside the if(index == 1000).
Happy! Happy! Joy! Joy!
I got it figured out. Just needed to rename some labels. Now the fun *really* begins. Thanks!
Hey ,
nice tutorial but a bit hard to understand for me ( German)
My question is: Can you replace the microfone with an audio jack?
Do i then still need to amplifie the signal and is ther still the problem with the noise?
Hi, you can definitely just plug an audio jack but you have to do 2 things: 1) share the catode (usually the “sleeve” of the jack) with the Arduino ground pin and 2) be sure that the signal comes in the 0 to 5V voltage (use a multimeter to see what’s the maximum voltage of your signal).
I measured 0.2 V AC but 0V DC
is it possible to connect the sleeve to a voltage divider (2,5V)
I would then have a signal range from 0.4 V is that enough for the program to work? and dos this not blow up my Uno?
You would definitely need a voltage divider to raise the mean to 2.5V and maybe also an amplifier, as otherwise you signal will vary only between 2.3 and 2.7V.
Is it a library “LowPassFilter.c “/ Where can i download it?
Thanks a lot!
it is generated by the filter design tool
Can you help me please to generate code for clap. It high value from 0-4500 Hz and from 18 000 Hz to 21 000 Hz.