Tuesday, November 11, 2014

ATtiny85 spectrum analyzer for music to RGB LED with modified FFT


ATtiny85 spectrum analyzer for music to RGB LED with FFT

Excited with the new discovery of FHT library. Yours truly definitely want to give it a try on an ATtiny85. After hours massaging the code to make it to work, sadly, none come to functionality (yet). According to this site http://forum.dev.arduino.cc/index.php?topic=261759.0, ATtiny has as small footprint ""ATtiny85 on-board, 8K of flash, 512 byte of SRAM, 512 bytes of EEPROM"" hence can't work with FHT. However, yours truly beg to differ. He noticed that some code in the FHT.cpp can't compile due to reduced instruction sets on ATtiny85. More about that after some workaround can be found.












Nonetheless, yours truly really want to have some fun with sound to light on Arduino's poor cousin, the ATtiny85. Life is so boring without some LEDs' goodness. Just come to recall that, years ago yours truly have done a DIY spectrum analyzer using a modified FFT that use 8bits only, runs off an arduino and LOL shield http://shin-ajaran.blogspot.sg/2011/07/diy-arduino-vu-spectrum-analyzer.html. This modified 8 bit FFT came for a forum discussion http://forum.arduino.cc/index.php?topic=38153.0 . Reusing the same library but   has to be modified to for Arduino IDE v1.06 and later; reusing the electret amplifier mentioned in an earlier post http://shin-ajaran.blogspot.sg/2014/11/arduino-spectrum-analyzer-for-music-to.html; reusing the MitG PCB which is a breakout board for ATtiny85 made earlier http://shin-ajaran.blogspot.sg/2014/01/setting-up-hardware-for-using-arduino.html to make the contraption in the following diagram. 

















code here:
/*
fixFFT http://forum.arduino.cc/index.php/topic,38153.0.html
this should give an fft with
sampling rate: 1ms
frequency resolution: 500Hz
lowest frequency: 7.8Hz
*/
//shin: fft with attiny85, rgb led blend with frequency
#include <fix_fft.h>
int ledG = 1;//pwm
int ledR = 0;//pwm
int ledB = 2;//pwm
int mic = A2; //electret
char im[128];
char data[128];
char data_avgs[128];
//mix max val to map fft
int valMin = 0;
int valMax = 30;
//bias to reduce on low and increase on high
int bias = 0;//+- bias to output
//3 chn selected deliberately out of 64 bins by fft
int chnLow = 0; //blue
int chnMid = 6; //green
int chnHigh = 11; //red
//temp var for bands
int tempLow = 0;
int tempMid = 0;
int tempHigh = 0;
void setup(){
pinMode(ledG, OUTPUT);
pinMode(ledR, OUTPUT);
pinMode(ledB, OUTPUT);
pinMode(mic, INPUT);
}
void loop(){
int static i = 0;
static long tt;
int val;
randomSeed(analogRead(mic));
bias = random(70,150);
for (i=0; i < 128; i++){
val = analogRead(mic);
//data[i] = val / 4 - 128;
data[i] = val;//quick and dirty data.
im[i] = 0;
i++;
}//end for
//this could be done with the fix_fftr function without the im array.
fix_fft(data,im,7,0);
/*
Performs foward or inverse Fourier transform.
//fix_fft (char fr[], char fi[], int m, int inverse)
//fr is a real data set,
//fi is the imaginary data set,
// m is log2(n) where n is number of data points (log2 (128) = 7)
//0 is set for forward transform. 1 would be inverse transform. Apparently inverse does not work,
*/
// I am only interessted in the absolute value of the transformation
for (i=0; i< 64;i++){//real val is for the amplitude
data[i] = sqrt(data[i] * data[i] + im[i] * im[i]);
}//end for
//do something with the data values 1..64 and ignore im
//data avg moved to individual func
//shin: styles for colour mapping to frequency
//triChn(bias);
//triChn(0);//no bias
//style2
//grpChnLowPass();
//style3
treshChn();
}//end loop
void triChn(int bias){//read low, mid, high chn
for (int i=0; i<14; i++) {//
data_avgs[i] = data[i*4] + data[i*4 + 1] + data[i*4 + 2] + data[i*4 + 3]; // average together
//data_avgs[i] = map(data_avgs[i], 0, 30, 0, 9);// remap values for LOL(9rowx14col led)
data_avgs[i] = map(data_avgs[i], valMin, valMax, 0, 255);// remap values for RGB LED
}
//bias to give more blue to emphasise low freq
analogWrite(ledB, data_avgs[chnLow]);
analogWrite(ledG, data_avgs[chnMid]-(bias/2));
analogWrite(ledR, data_avgs[chnHigh]-bias);
}
void grpChnLowPass(){//grp low pas 32 out of 64 bins into grp of low mid high, avg out in grp
for (int i=0; i<14; i++) {//
data_avgs[i] = data[i*4] + data[i*4 + 1] + data[i*4 + 2] + data[i*4 + 3]; // average together
//data_avgs[i] = map(data_avgs[i], 0, 30, 0, 9);// remap values for LOL(9rowx14col led)
data_avgs[i] = map(data_avgs[i], valMin, valMax, 0, 255);// remap values for RGB LED
}
int numChn = 4;
for (int i=0; i<numChn; i++) { //14 bins grp into3 bands
//low chn
tempLow=data_avgs[i]+tempLow;
//mid chn
tempMid=data_avgs[(i+4)]+tempMid;
//high chn
tempHigh=data_avgs[i+8]+tempHigh;
}//end for
//avg across the grp
tempLow = tempLow / numChn;
tempMid = tempMid / numChn;
tempHigh = tempHigh / numChn;
//map freq to rgb on pwm pin
tempLow = map(tempLow, valMin, valMax, 0, 255);
tempMid = map(tempMid, valMin, valMax, 0, 255);
tempHigh = map(tempHigh, valMin, valMax, 0, 255);
//output to rgb
analogWrite(ledB, tempLow);
analogWrite(ledG, tempMid);
analogWrite(ledR, tempHigh);
}//grpChnLowPass
void treshChn(){//on if over threshold low, mid, high
for (int i=0; i<14; i++) {//only take lower half of real freq band eg 32 out of 64
data_avgs[i] = data[i*4] + data[i*4 + 1] + data[i*4 + 2] + data[i*4 + 3]; // average together
//data_avgs[i] = map(data_avgs[i], 0, 30, 0, 9);// remap values for LOL(9rowx14col led)
data_avgs[i] = map(data_avgs[i], valMin, valMax, 0, 255);// remap values for RGB LED
}
tempLow = data_avgs[chnLow];
tempMid = data_avgs[chnMid];
tempHigh =data_avgs[chnHigh];
if(tempLow>150){
//more low gives blue
analogWrite(ledB, tempLow);
analogWrite(ledG, 255);
analogWrite(ledR, 255);
//delay(100);
}
else if (tempMid>150){
//more mid gives green
analogWrite(ledB, 255);
analogWrite(ledG, tempMid);
analogWrite(ledR, 255);
//delay(100);
}
else if (tempHigh>150){
//more high gives red
analogWrite(ledB, 255);
analogWrite(ledG, 255);
analogWrite(ledR, tempHigh);
//delay(100);
}
else{
/*
//random colour not visual appealing to music
tempLow=random(0,255);
tempMid=random(0,255);
tempHigh=random(0,255);
*/
analogWrite(ledB, tempLow-70);
analogWrite(ledG, tempMid-100);
analogWrite(ledR, tempHigh-150);
//delay(100);
}
}//end treshChn
youtube video here, the light changes according to the tone of the phone.

edit: after some code massaging, the colours appear somewhat according to what yours truly have in mind.


first try: Sadly, the colour changes are too subtle to be captured by a cheapo phone camera

1 comment:

johny radio said...

hi, very exciting project! how can one reduce the frequency resolution from 500 hz to, say, .005 hz?

-does NOT need to be real time-- it's ok if the analysis takes minutes.

(lowest freq needed is 20 hz)

thx!