I remember about 20 years ago one of my first electronics projects was a light organ, with light spots of 220V; that sounds quite dangerous in hindsight. I built a 21st century version of that using the modest DSP capability of the Arduino UNO. Instead of the common FFT (to split a signal in its frequency components) i used a variant call the Fast Hartley Transformation (FHT) as described here, and an example here. My sketch is at the bottom of this page.
I used an amplified microphone signal on one of the Arduino’s analog inputs, A0, and 256 samples to calculate the frequency distribution. The FHT returns magnitudes in 128 frequency bins, with options for a linear or logarithmic scale. As my OLED screen has 128 pixels, i tried to display these 128 values but it seems the display (even if it’s an SPI connection) is too slow to draw the 128 bars, using the U8G library. So i looked into an interesting feature of the FHT library which is the optional octave output: each bin doubles the frequency, and with 256 samples we get 8 octaves. The first 2 octaves (below 280Hz) did not return any useful data (maybe sensitivity of my microphone) so i dropped them, and i display only 6 octave bars.
One important note: the version of the FHT library that i used did not work with Arduino IDE 1.6.x because of the way it uses PROGMEM, giving this compilation error:
In file included from spectrum-analyser-v1.ino:16:0:
/home/tom/sketchbook/libraries/FHT/FHT.h:72:10: error: ‘prog_int16_t’ does not name a type
PROGMEM prog_int16_t _cas_constants[] = {
I was lazy to update the library so went back to IDE 1.0.6 for this project, and that works just fine.
So i display 6 octaves on the OLED screen, subtracting a constant ‘noise level’ of the FHT output values. For the RGB LED, divided those 6 octaves into 3 categories for the Red, Green, Blue, subtracting the same constant noise level first. Then i squared those values to make the more responsive. A short video clip with music:
The sketch does not have a precise timing, but rather reads a new ADC value as soon as it is available. I tried to calculate this sampling frequency as follows: check micros() before the 256 sample loop, and after (see Serial.print in the sketch). I needed to comment out the line that says TIMSK0 = 0 because that disables timer0 used by the micros() function, and the interrupt disable/enable cli() and sei(). This gave me a rather constant interval of around 6620 microseconds, i.e. about 28 microseconds per sample = 35,700Hz sampling frequency.
That gives a Nyquist frequency of around 18kHz: that’s about as high as (some) humans can hear so that works out great! So in theory, we would have these frequency bins:
- 9,000-18,000 Hz
- 4,500-9,000 Hz
- 2,250-4,500 Hz
- 1,125-2,250 Hz
- 560-1,125 Hz
- 280-560 Hz
- 140-280 Hz (not displayed)
- 0-140 Hz (not displayed)
Another way to arrive at this is to start with the Nyquist frequency of 18kHz and divide by the 128 frequency bins that we would get with the normal FHT; that gives frequency bins of 140 Hz. What the Octave representation does is grouping the bins together progressively, as described in the fht_read_me.txt:
G. fht_mag_octave() – this outputs the RMS value of the bins in an octave
(doubling of frequencies) format. this is more useful in some ways, as it is
closer to how humans percieve sound. it doesnt take any variables, and doesnt
return any variables. the input is taken from fht_output[] and returned on
fht_oct_out[]. the data is represented in and 8b value of 16*log2(sqrt(mag)).
there are LOG_N bins. and they are given as follows:
FHT_N = 256 :: bins = [0, 1, 2:4, 5:8, 9:16, 17:32, 3:64, 65:128]
FHT_N = 128 :: bins = [0, 1, 2:4, 5:8, 9:16, 17:32, 3:64]
where (5:8) is a summation of all bins, 5 through 8. the data for each bin is
squared, imaginary and real parts, and then added with all the squared magnitudes
for the range. it is then divided down by the numbe of bins (which can be turned
off – see #defines below), and then the square root is taken, and then the log is
taken.
I tested with different tone frequencies and the results confirm the above theory pretty well as you can see in below pictures.
My sketch:
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
FHT library for frequency spectrum analyser http://wiki.openmusiclabs.com/wiki/ArduinoFHT
settings: frequency bins combined in octaves, see fht_read_me.txt for details
8 octaves, first 2 are not useful
USE IDE 1.0.x because of changes to PROGMEM causes error in 1.6.x
*/
// amplified MIC on A0
// RGB LED on D3, D5, D6
#define OCT_NORM 0 // 0: no normalisation, more high freq 1: divided by number of bins, less high freq
#define OCTAVE 1
#define FHT_N 256 // set to 256 point fht
#include <FHT.h> // include the library
#include “U8glib.h”
U8GLIB_SSD1306_128X64 u8g(10, 9); // OLED display: HW SPI CS = 10, A0/DC = 9 (Hardware Pins are SCK/D0 = 13 and MOSI/D1 = 11) + RST to Arduino D8
const int oled_rst = 8;
const int red = 3;
const int blue = 5;
const int green = 6;
const int noise_level = 50;
void setup() {
pinMode(oled_rst, OUTPUT); // reset OLED display
digitalWrite(oled_rst, LOW);
delay(100);
digitalWrite(oled_rst, HIGH);
drawIntro();
delay(2000);
Serial.begin(115200); // use the serial port
TIMSK0 = 0; // turn off timer0 for lower jitter // no for TIMING
ADCSRA = 0xe5; // set the adc to free running mode
ADMUX = 0x40; // use adc0
DIDR0 = 0x01; // turn off the digital input for adc0
}
void loop() {
while(1) { // reduces jitter
cli(); // UDRE interrupt slows this way down on arduino1.0 // no for TIMING
// unsigned long start_time = micros(); // yes for TIMING
for (int i = 0 ; i < FHT_N ; i++) { // save 256 samples
while(!(ADCSRA & 0x10)); // wait for adc to be ready
ADCSRA = 0xf5; // restart adc
byte m = ADCL; // fetch adc data
byte j = ADCH;
int k = (j << 8) | m; // form into an int
k -= 0x0200; // form into a signed int
k <<= 6; // form into a 16b signed int
fht_input[i] = k; // put real data into bins
}
// Serial.println(micros()-start_time); // yes for TIMING
fht_window(); // window the data for better frequency response
fht_reorder(); // reorder the data before doing the fht
fht_run(); // process the data in the fht
fht_mag_octave(); // take the output of the fht
sei(); // no for TIMING
drawBars();
RGB();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
void drawBars() {
u8g.firstPage(); // draw 6 bars
do {
for (int x=2; x< 8; x++) {
int h = max((fht_oct_out[x] – noise_level) / 4, 0); // scale the height
u8g.drawBox((x-2) * 20, 63-h, 15, h+1);
}
} while ( u8g.nextPage() );
}
///////////////////////////////////////////////////////////////////////////////////////////
void drawIntro() {
u8g.firstPage(); // draw intro
do {
u8g.setFont(u8g_font_fub14r);
u8g.setPrintPos(10, 15);
u8g.print(“Spectrum”);
u8g.setPrintPos(30, 30);
u8g.print(“analyser”);
u8g.setPrintPos(2, 60);
u8g.print(“BuffaloLabs”);
} while ( u8g.nextPage() );
}
///////////////////////////////////////////////////////////////////////////////////////////
void RGB() {
int red_level = min( pow(fht_oct_out[2]+fht_oct_out[3] – 2 * noise_level,2)/100, 255); // limit at 255
int blue_level = min( pow(fht_oct_out[4]+fht_oct_out[5] – 2 * noise_level,2)/100, 255); // limit at 255
int green_level = min( pow(fht_oct_out[6]+fht_oct_out[7] – 2 * noise_level,2)/100, 255); // limit at 255
analogWrite(red, 255-red_level); // for common positive RGB LEDs
analogWrite(blue, 255-blue_level);
analogWrite(green, 255-green_level);
}
How could I modify this to break out the channels to 3 frequencies pushing the signal out to relays? this would be to activate either LED strands or Bulbs like the old color organs.
Thank you!
Rick
Hi Rick, you could modify the RGB() function at the bottom, to use the red_level, green_level, blue_level to switch your relays, e.g.
if (red_level > 100) {
digitalWrite(red_relay_pin, HIGH); }
else {
digitalWrite(red_relay_pin, LOW); }
I assume you are using logic level relays that can be switched with the relatively weak Arduino output.
Yes, im using a solid state relay. So essentially, copy the code provided and alter for each color. Im assuming I’d need to int the relays as well, correct?
Would the level need to be different for each channel if I am looking to get separation between low, mid and high?
Thank you for your reply!
Also, I dont have a need for the screen / level display. If I were to not hook one up, would I encounter any errors? Im fairly new to arduino, but love vintage color organs and want to make a safer and more modern version.
yes declare each of your relay pins as OUTPUT
red_level represent low frequencies, blue_level mid freq, green_level high freq. the threshold for each can be same, or different, as you like.
Thank you so much!
Where did you get your mic and light? Also, how can I make a string of dedicated colored lights connected to separate pins? Such as, low tone are red on pin 2, mid are all blue on pin 5 and high notes are on pin 7 as green ?
mic https://detail.tmall.com/item.htm?spm=a312a.7700718.1998025129.5.7e82ae53ZUNwRv&abtest=_AB-LR32-PR32&pvid=79e0f99c-6e1a-47eb-b75e-01f29e78173d&pos=5&abbucket=_AB-M32_B5&acm=03054.1003.1.2431317&id=42556688044&scm=1007.12144.93797.23864_0
rgb led https://world.tmall.com/item/41251509024.htm?spm=a312a.7700718.1998025129.1.FIBujR&id=41251509024&pvid=4bc8f37b-c4b9-4a26-be6d-866089b4ae7f&abbucket=_AB-M32_B2&acm=03054.1003.1.587829&aldid=NgxRRuBQ&abtest=_AB-LR32-PR32&scm=1007.12559.21524.100200300000000&pos=1
if you want to connect more than a few LEDs to one Arduino pin, you’d better use a transistor or mosfet as switch so you can have more power to your LEDs
Here is another question for you. If I were to attach a neopixel strip in place of the current LED you have, is should technically work? All the LEDS would blink at the same rate im thinking.
yes neopixels could work too, so you could use 1 pin to drive an entire neopixel strip
then you just need to change the code to send the right colour to the right pixel, using the neopixel library
however, i am not sure how fast the neopixel library is and if it will affect the FFT calculations
Im now getting this error when compiling
Arduino: 1.8.5 (Windows 10), TD: 1.41, Board: “Arduino/Genuino Micro”
spectrum:81: error: stray ‘\342’ in program
int h = max((fht_oct_out[x] – noise_level) / 4, 0); // scale the height
^
spectrum:81: error: stray ‘\200’ in program
spectrum:81: error: stray ‘\223’ in program
spectrum:81: error: stray ‘\342’ in program
spectrum:81: error: stray ‘\200’ in program
spectrum:81: error: stray ‘\223’ in program
spectrum:94: error: stray ‘\342’ in program
u8g.print(“Spectrum�);
^
spectrum:94: error: stray ‘\200’ in program
spectrum:94: error: stray ‘\234’ in program
spectrum:94: error: stray ‘\342’ in program
spectrum:94: error: stray ‘\200’ in program
spectrum:94: error: stray ‘\235’ in program
spectrum:96: error: stray ‘\342’ in program
u8g.print(“analyser�);
^
spectrum:96: error: stray ‘\200’ in program
spectrum:96: error: stray ‘\234’ in program
spectrum:96: error: stray ‘\342’ in program
spectrum:96: error: stray ‘\200’ in program
spectrum:96: error: stray ‘\235’ in program
spectrum:98: error: stray ‘\342’ in program
u8g.print(“BuffaloLabs�);
^
spectrum:98: error: stray ‘\200’ in program
spectrum:98: error: stray ‘\234’ in program
spectrum:98: error: stray ‘\342’ in program
spectrum:98: error: stray ‘\200’ in program
spectrum:98: error: stray ‘\235’ in program
spectrum:105: error: stray ‘\342’ in program
int red_level = min( pow(fht_oct_out[2]+fht_oct_out[3] – 2 * noise_level,2)/100, 255); // limit at 255
^
spectrum:105: error: stray ‘\200’ in program
spectrum:105: error: stray ‘\223’ in program
spectrum:105: error: stray ‘\342’ in program
spectrum:105: error: stray ‘\200’ in program
spectrum:105: error: stray ‘\223’ in program
C:\Users\Rick\Desktop\spectrum\spectrum.ino: In function ‘void drawBars()’:
spectrum:78: error: ‘u8g’ was not declared in this scope
u8g.firstPage(); // draw 6 bars
^
In file included from sketch\spectrum.ino.cpp:1:0:
spectrum:81: error: expected ‘)’ before ‘noise_level’
int h = max((fht_oct_out[x] – noise_level) / 4, 0); // scale the height
^
C:\Users\Rick\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.6.20\cores\arduino/Arduino.h:93:20: note: in definition of macro ‘max’
#define max(a,b) ((a)>(b)?(a):(b))
^
spectrum:81: error: expected ‘)’ before ‘;’ token
int h = max((fht_oct_out[x] – noise_level) / 4, 0); // scale the height
^
spectrum:81: error: expected ‘)’ before ‘;’ token
C:\Users\Rick\Desktop\spectrum\spectrum.ino: In function ‘void drawIntro()’:
spectrum:90: error: ‘u8g’ was not declared in this scope
u8g.firstPage(); // draw intro
^
spectrum:92: error: ‘u8g_font_fub14r’ was not declared in this scope
u8g.setFont(u8g_font_fub14r);
^
spectrum:94: error: ‘Spectrum’ was not declared in this scope
u8g.print(“Spectrum�);
^
spectrum:96: error: ‘analyser’ was not declared in this scope
u8g.print(“analyser�);
^
spectrum:98: error: ‘BuffaloLabs’ was not declared in this scope
u8g.print(“BuffaloLabs�);
^
In file included from sketch\spectrum.ino.cpp:1:0:
C:\Users\Rick\Desktop\spectrum\spectrum.ino: In function ‘void RGB()’:
spectrum:105: error: expected ‘)’ before numeric constant
int red_level = min( pow(fht_oct_out[2]+fht_oct_out[3] – 2 * noise_level,2)/100, 255); // limit at 255
^
C:\Users\Rick\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.6.20\cores\arduino/Arduino.h:92:20: note: in definition of macro ‘min’
#define min(a,b) ((a)<(b)?(a):(b))
^
spectrum:105: error: expected ')' before numeric constant
int red_level = min( pow(fht_oct_out[2]+fht_oct_out[3] – 2 * noise_level,2)/100, 255); // limit at 255
^
C:\Users\Rick\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.6.20\cores\arduino/Arduino.h:92:28: note: in definition of macro 'min'
#define min(a,b) ((a) Preferences.
yes that’s a wordpress problem; when you do the copy/paste of the code, it introduces a bunch of ghost characters in the code; you will have to go through it line by line to remove them, sorry
I was able to fix it. I am going to try this over the weekend and use strings of LEDs for each of the 3 pins as you have it in your code. I briefly tried it with a single LED and it worked.
Hi, can you please share how you fixed it? because it’s very unclear…
Hi Sir,
can you please brief the Pin connection for Arduino and OLed and also the Mic Pin
please have a look in the code; the mic is on A0
Boa noite, qual versão da IDE é a mais adequada, não consigo rodar o código, já testei na IDE 1.0 e na mais nova e nada, em ambos dá uma lista muito grande falhas, mesmo colocando as bibliotecas, pode me ajudar, vou ficar muito grato.
hi, the FHT library only works on IDE 1.0 so that’s what i was using. not sure why it wouldn’t work for you if you don’t show the error log.
I have no need for the RGB or the OLED display. do you think i could modify this to perform another task when a specific frequency is heard? please help.
yes of course you can use the output of the FFT for any purpose, just look in the code how it uses the 6 frequency bins to display the 6 bars; you could use those variables to trigger any action
Great job friend. I like your video. Can I see a photo of how the microphone module and the display module are connected to the Arduiono Uno. Maybe you have an archive library
The code gives many errors when compiling I don’t know how to fix it, can someone put it in a text file or something?
Hi, this might not be the right forum to ask this question, but how would i set a Trigger on a certian frequency band with a set reference Amplitude. i have read it should be “easy” by if loops, but i dont get it how to set it up. any help?