Hi all, my name is Axel, long time forum observer, first time poster.
First, thanks to the moderators, your wisdom & patience is very much appreciated! Second, mrExplore, I feel ya, I'm on the same track you are on. I'd be curious to hear how far you got.
My setup: genuine Arduino Nano, Adafruit MAX9814 board (electret + amp w/ AGC), Adafruit WS2801 RGB LED pixel strip.
After much review of DSP, Fourier, Nyquist, interrupts, speed of sampling etc. the project is up + running, but not perfectly. The concept and vision is proven, but I can't use it as intended.
The desired outcome is: use the setup in a loud musical environment (ie electronic music venue), expected SPL around 120 dB(A) max, have the LEDs act as a spectrum visualizer, ie a bunch of LED pixels flashing red (the louder the brighter) to the baseline (around 120 bpm / 2 Hz) and a bunch of LED pixels flashing green to the high-hats.
There are 50 LED pixels and I assigned 6...7 of them per frequency bin. I use the open music labs fht or fft library and modified sample code (tried FHT + FFT, made no difference in outcome for me); using the octave processing, grouping several frequency bins into 8 total output bins based on doubling of frequency, quite handy. As far as I can tell, I sample 256 times at 38.5 kHz, so I'm catching sound frequencies up to 19 kHz. I don't have an oscilloscope to see if there is data bove 19 kHz in the input signal.
Essentially my theoretical math is this:
- sample rate 38.5 kHz, samples taken: 256
- 8 octave output bins: FHT_N = 256 : bins = [0, 1, 2:4, 5:8, 9:16, 17:32, 33:64, 65:128]
- 19.25 kHz / 128 bins = 150 Hz per bin
- bin 0: 0-150 Hz; bin 1: 150-300 Hz, bin 2: 300-600 Hz, bin 3: 600-1200 Hz, bin 4: 1200-2400 Hz, bin 5: 2400-4800 Hz, bin 6: 4800-9600 Hz, bin 7: 9600-19200 Hz roughly
Code notes:
- to reduce jitter I put in a minimum brightness minbri and only increase the brightness of the lights if the measured magnitude in a bin goes over a threshold
- since the MAX9814 sends analog signals biased around 1.25 V with a max of 2.45 V and min of 0.003 V I rescale the brightness ("shine") to 0...255 range (the WS2801 lights use that to range to determine brightness with 255 being max brightness).
Status: the thing works for bins 2-7, but not for bin 0 and 1 - see serial mon output below; 256 samples taken at a rate of 38.5kHz are taken during a time window of 0.00665 seconds. One complete sine wave at 20 Hz happens during a time window of 0.05 seconds. As far as I understand, the FFT only picks up "things" that actually have time to complete one complete sine wave during the taking of 256 samples. So that would mean that the FFT is "deaf" below a certain frequency, which I believe for me is 1/0.00665s --> 150 Hz. Is that right? That would explain why bin 0 acts up. And maybe why bin 1 acts up, idk.
In any case, my code sends debug info to the serial monitor, I get "reasonable" outputs for bins 2...7 and the lights react to frequencies seemingly properly. Tried it with an online tone generator, not a precise match. Since the lights "look" ok, I won't investigate further for now.
So this is my question, are the above assumptions generally correct? And does anyone have a suggestion on how to make the code work for bin 0 and bin 1?
FYI I took the setup off of the stationary power supply to see if noise comes in through the power source, but that didn't improve things.
Here's a typical output in the serial monitor in a quiet room (bins 0 and 1 are "odd"):
bin 0
207 [note: this is the fft_oct_out[0] value]
185 [note: this is the shine value sent to the RGB pixel]
bin 1
191
161
bin 2
62 [note: as 62 is below the thresh value of 80, the shine value is set to minbri of 3]
3
bin 3
43
3
bin 4
37
3
bin 5
34
3
bin 6
32
3
...
...
Here's my full code:
//code source openmusiclabs.com 8.18.12
//Axel's notes: modified code from open music labs to incorporate code for adafruit pixels
#define OCTAVE 1 // use the octave function
#define OCT_NORM 1 // normalize the output bins (try 1 for noramlization ON and 0 for normaliz off)
#define FFT_N 256 // set to 256 point fft
#include <FFT.h>
#include "Adafruit_WS2801.h" // lib for the RGB pixels
uint8_t dataPin = 2; // Yellow wire on Adafruit Pixels
uint8_t clockPin = 3; // Green wire on Adafruit Pixels
Adafruit_WS2801 strip = Adafruit_WS2801(50, dataPin, clockPin);
float thresh = 80; // sets the threshold above which input magnitude the lights will start changing in brightness
uint8_t shine = 5; // variable - actual brightness sent to pixels
uint8_t minbri = 3; // sets the resting brightness of the lights when magnitude is lower than thresh
float tempstore = 0; // variable for the ADC value rescaling math to work
void setup() {
Serial.begin(115200);
TIMSK0 = 0; // turn off timer0 for lower jitter - delay() and millis() killed
ADCSRA = 0xe5; // set the adc to free running mode
ADMUX = 0x40; // use adc0
DIDR0 = 0x01; // turn off the digital input for adc0
strip.begin();
strip.show(); // Update LED contents, to start they are all 'off'
}
void loop() {
while(1) { // reduces jitter
cli(); // UDRE interrupt slows this way down on arduino1.0
for (int i = 0 ; i < 512 ; i += 2) { // 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
fft_input[i] = k; // put real data into even bins
fft_input[i+1] = 0; // set odd bins to 0
}
fft_window(); // window the data for better frequency response
fft_reorder(); // reorder the data before doing the fft
fft_run(); // process the data in the fft
fft_mag_octave(); // take the output of the fft
sei(); // turn interrupts back on
// below code is to process the fft_mag_octave info and send it to the lights
// doing this for each bin individually
// BIN 0
tempstore = fft_oct_out[0];
if (tempstore > thresh) {shine = (((tempstore-thresh)/(255-thresh))*255);} else shine = minbri;
for (int s = 0 ; s <= 6 ; s++) {strip.setPixelColor(s, Color(shine,0,0));}
Serial.println("bin 0");
Serial.println(fft_oct_out[0]);
Serial.println(shine);
// BIN 1
tempstore = fft_oct_out[1];
if (tempstore > thresh) {shine = (((tempstore-thresh)/(255-thresh))*255);} else shine = minbri;
for (int s = 7 ; s <= 13 ; s++) {strip.setPixelColor(s, Color(0,shine,0));}
Serial.println("bin 1");
Serial.println(fft_oct_out[1]);
Serial.println(shine);
// BIN 2
tempstore = fft_oct_out[2];
if (tempstore > thresh) {shine = (((tempstore-thresh)/(255-thresh))*255);} else shine = minbri;
for (int s = 14 ; s <= 19 ; s++) {strip.setPixelColor(s, Color(0,0,shine));}
Serial.println("bin 2");
Serial.println(fft_oct_out[2]);
Serial.println(shine);
// BIN 3
tempstore = fft_oct_out[3];
if (tempstore > thresh) {shine = (((tempstore-thresh)/(255-thresh))*255);} else shine = minbri;
for (int s = 20 ; s <= 26 ; s++) {strip.setPixelColor(s, Color(shine,0,0));}
Serial.println("bin 3");
Serial.println(fft_oct_out[3]);
Serial.println(shine);
// BIN 4
tempstore = fft_oct_out[4];
if (tempstore > thresh) {shine = (((tempstore-thresh)/(255-thresh))*255);} else shine = minbri;
for (int s = 27 ; s <= 33 ; s++) {strip.setPixelColor(s, Color(0,shine,0));}
Serial.println("bin 4");
Serial.println(fft_oct_out[4]);
Serial.println(shine);
// BIN 5
tempstore = fft_oct_out[5];
if (tempstore > thresh) {shine = (((tempstore-thresh)/(255-thresh))*255);} else shine = minbri;
for (int s = 34 ; s <= 39 ; s++) { strip.setPixelColor(s, Color(0,0,shine));}
Serial.println("bin 5");
Serial.println(fft_oct_out[5]);
Serial.println(shine);
// BIN 6
tempstore = fft_oct_out[6];
if (tempstore > thresh) {shine = (((tempstore-thresh)/(255-thresh))*255);} else shine = minbri;
for (int s = 40 ; s <= 50 ; s++) {strip.setPixelColor(s, Color(shine,0,0));}
Serial.println("bin 6");
Serial.println(fft_oct_out[6]);
Serial.println(shine);
Serial.println("...");
Serial.println("...");
strip.show(); // final step: write all the pixels out at the proper brightness
}
}
// below function is to create the color value sent to the pixels (helper function)
uint32_t Color(byte r, byte g, byte b)
{
uint32_t c;
c = r;
c <<= 8;
c |= g;
c <<= 8;
c |= b;
return c;
}