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

Sunday, November 9, 2014

Arduino spectrum analyzer for music to colourful lighting using FHT and RGB LED

Arduino spectrum analyzer for music to colourful lighting using FHT and RGB LED

years back yours truly have made a contraption to convey the concept of fourier transform http://shin-ajaran.blogspot.sg/2011/07/diy-arduino-vu-spectrum-analyzer.html using Arduino, LOL shield and the FFT library. The piece of code is still hanging on the Internet, but the hardware has been re-purposed for the better of humanity.

Not long ago, yours truly come across the FHT (Fast Hartley Transform) by Open Music Lab http://wiki.openmusiclabs.com/wiki/ArduinoFHT while browsing the Internet for inspirations to continue with the current working life. This algorithm claims to be more efficient in terms of CPU cycles and memory footprint; well, true to speak because the premise is: FHT works on the "real" portion of the data whereas FFT works on both the "real" and "complex" portion of the data. Really excited by this discovery of the code library, yours truly can't wait to get his hand dirty on making a contraption that uses the above FHT. Following the instructions in the FHT wiki for installing the library and sample data output using processing is a breeze. This wiki also comes complete with a 128 channel spectrum visualizer written using processing; Scroll down until you hit "FHT_128_channel_analyser.zip" http://wiki.openmusiclabs.com/wiki/ArduinoFHT?action=AttachFile&do=view&target=FHT_128_channel_analyser.zip . A visualizer is very handy when it comes to deciding the "strategy" for the music to light algorithm.

Check out the video below for a demo of this make
















This make assumes the following parts come in handy
1. 1x Arduino
2. 1x RGB LED (common anode)
3. 1x 3D printed LED diffuser
4. 1x electret microphone & LM386 audio amplifier.















Prelude: making the amplifier for electret microphone.
An electret microphone http://en.wikipedia.org/wiki/Electret_microphone is a cheapo microphone that reads in analog signal generated by sound frequency. This analog signal has to be amplified, and then passed into a microprocessor based system (e.g Arduino) for ADC (Analog to Digital Conversion). Once data is digitized, humans can manipulate the signal with code, hence the terminology : digital signal processing.

This make assumes an LM386 as the audio amplifier http://www.ti.com/lit/ds/symlink/lm386.pdf for the electret microphone is available.
There are many manufacturers of LM386, one of the is TI. Refer to the link above for spec sheet and then scroll down to the diagram "amplifier with minimum parts". If you need help on making a LM386 based audio amplifier, this instructable http://www.instructables.com/id/Know-Your-IC-LM386/ is helpful on getting started.

Prelude2: For those that are still clueless what is happening, check out this very thoroughly written article on sound analysis https://bochovj.wordpress.com/2013/06/23/sound-analysis-in-arduino/.

Step1: The wiring
Connect output from electret & LM386 audio amplifier to A0 of arduino, and VCC and GND to arduino's VCC and GND. Connect common anode RGB LED to pin 3,4,5,6 of Arduino;with pin4 dedicated as the common anode, pin3,5,6 dedicated as PWM pin. It is good to add some 220ohm resistors across the pin3,5,6 for current limiting. Yours truly has none available, hence the omission in the picture below. These 4pins can be used as the input to a transistor switched 12V load to control LED light strips. The following picture describes the wiring, and LED diffuser with Fibre Optic cable











Step2: the code and the strategy of choosing which frequency is for what colour

There are several ways to map the frequency spectrum to the RGB colour spectrum. Using AnalogWrite() on R, G,B; each PWM pin is capable of a value from 0-255 to drive the individual LED in the RGB LED. Thus, the total combination of colours possible (in code) are 256*256*256 = 16777216; thats a whopping 16M worth of variations.

For the visually inclined, the RGB chart below is a good guide for giving an idea what is the final colour blend at the RGB LED output corresponding to the R, G, B value written by AnalogWrite().









Processing has a useful article on colour https://processing.org/tutorials/color/ which yours truly think it helps with visualizing colour using code.

The questions come begging: How to map audio frequency to the colour spectrum??
Years ago, yours truly did an attempt at mapping colour to frequency using the self made LOLs shield http://shin-ajaran.blogspot.sg/2012/03/arduino-music-to-rgb-with-lolss.html, but it is not visually appealing.

Drawing inspiration from yours truly secondary school physics: human voice ranges from 85Hz to 255Hz; male voice is at lower frequency bands 85Hz-180Hz whereas female voice is at higher frequency bands 165Hz to 255Hz. As for human hearing, it is from 20Hz to 20K Hz. Futhermore, each musical instruments has it's own frequency range, and as we know, music composes of a variety of frequency stemming for human voice and/or musical instruments. Hence, the choice of strategy will be reflected in the colour observed while a piece of music is played.

Strategy: mapping audio frequency to colour spectrum
1. Mapping of human hearing e.g 20Hz to 20K Hz to 16777216 of possible RGB colours
1a. Mapping of whole audio frequency bands to 6777216 of possible RGB colours.
2. Choosing 3 channels deliberately; one each from the low, mid, and high frequency bands as observed using the spectrum visualizer mentioned earlier. The 3 channels of low, mid, and high corresponds to Blue, Green, and Red; with the intensity of the colour corresponds to the amplitude of that chosen channel. The output of RGB LED will then be "blended".
3. Similar to 2, but instead of choosing the channels deliberately, this algo is to group frequency bands into larger low, mid, and high frequency bands; within each of this group of larger frequency bands , the amplitude that is used to turn on the corresponding LED is the result of averaging all the amplitude from the frequency bands.
4. Similar to 2,3, but first apply a Low Pass Filter at the frequency bands.
5. LED activated by predefined threshold on frequency band
6. .....
...
N. ......
It seems to yours truly, finding an ideal mapping of music genre to colour is going to be an never ending story.
Cut the chase, let's go to the code.
/*
fht_adc.pde
guest openmusiclabs.com 9.5.12
example sketch for testing the fht library.
it takes in data on ADC0 (Analog0) and processes them
with the fht. the data is sent out over the serial
port at 115.2kb. there is a pure data patch for
visualizing the data.
*/
//shin: mod with mapping of rgb to frequency
//how-to guide: http://shin-ajaran.blogspot.sg/2014/11/arduino-spectrum-analyzer-for-music-to.html
#define LOG_OUT 1 // use the log output function
#define FHT_N 256 // set to 256 point fht
#include <FHT.h> // include the library
//shin: common anode RGB connected to pin3,4,5,6; A0 is eletctret amplifier output
int ledG = 5;//pwm
int ledA = 4;//anode. pull high
int ledR = 3;//pwm
int ledB = 6;//pwm
//3 chn select at deliberate
int chnLow = 8;
int chnMid = 12;
int chnHigh = 28;
//max val
int valMin = 0;
int valMax = 190;
//bias to reduce on low and increase on high
int bias = 0;//+- bias to output
int valW=0;
//low pass then avg the 3 grp
uint16_t tempLow = 0;
uint16_t tempMid = 0;
uint16_t tempHigh = 0;
void setup() {
Serial.begin(115200); // use the serial port
TIMSK0 = 0; // turn off timer0 for lower jitter
ADCSRA = 0xe5; // set the adc to free running mode
ADMUX = 0x40; // use adc0 as A0
DIDR0 = 0x01; // turn off the digital input for adc0
pinMode(ledR, OUTPUT);
pinMode(ledA, OUTPUT);
pinMode(ledG, OUTPUT);
pinMode(ledB, OUTPUT);
//diagnostic led
diagLed();
}
void loop() {
//bias
randomSeed(ADMUX);
bias = random(70,150);
while(1) { // reduces jitter
cli(); // UDRE interrupt slows this way down on arduino1.0
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
}
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_log(); // take the output of the fht; formula =16*(log^2((img^2 + real^2)^1/2))
sei();
Serial.write(255); // send a start byte
Serial.write(fht_log_out, FHT_N/2); // send out the data; FHT_N/2 contains # of chn
//shin: chuck 128chn amplitude to RGB spectrum
//style1: chuck chn low,mid,high to R,G,B
//triChn(0);//no bias
triChn(bias);//with rnd bias
//style2: avg 20chn within low, mid, high band
//grpChnLowPass();
//style3: threshold chn low=blue, mid green, high red;
//treshChn();
}//end while
}//loop
void diagLed(){
//on rgb led blue light
digitalWrite(ledA,HIGH);
analogWrite(ledR, 0);
analogWrite(ledG, 255);
analogWrite(ledB, 255);
delay(1000);
digitalWrite(ledA,LOW);
delay(1000);
digitalWrite(ledA,HIGH);
analogWrite(ledR, 255);
analogWrite(ledG, 0);
analogWrite(ledB, 255);
delay(1000);
digitalWrite(ledA,LOW);
delay(1000);
digitalWrite(ledA,HIGH);
analogWrite(ledR, 255);
analogWrite(ledG, 255);
analogWrite(ledB, 0);
delay(1000);
}//end diagLed
void triChn(int bias){//read low, mid, high chn
fht_log_out[chnLow] = map(fht_log_out[chnLow], valMin, valMax, 0, 255); //low
fht_log_out[chnMid] = map(fht_log_out[chnMid], valMin, valMax, 0, 255); //mid
fht_log_out[chnHigh] = map(fht_log_out[chnHigh], valMin, valMax, 0, 255); //high
//with bias
analogWrite(ledB, fht_log_out[chnLow]);
analogWrite(ledG, fht_log_out[chnMid]-bias);
analogWrite(ledR, fht_log_out[chnHigh]+bias);
}
void grpChnLowPass(){//grp low pas 60 out of 128 chn into grp of low mid high, avg out in grp
int numChn = 12;
for (int i=0; i<numChn; i++) { //i = 256/8 = 32 bins of 8
//low chn
tempLow=fht_log_out[i]+tempLow;
//mid chn
tempMid=fht_log_out[(i+11)]+tempMid;
//high chn
tempHigh=fht_log_out[i+21]+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);
//output with fade effect1
//fadePin1(ledB, tempLow);
//fadePin1(ledG, tempMid);
//fadePin1(ledR, tempHigh);
//output with fade effect2
//fadePin2(ledB, ledG, ledR, tempHigh);
}//grpChnLowPass
void treshChn(){//on if over threshold low, mid, high
if(fht_log_out[chnLow]>150){
analogWrite(ledB, 0);
analogWrite(ledG, 255);
analogWrite(ledR, 255);
delay(50);
}
else if(fht_log_out[chnHigh]>50){
analogWrite(ledB, 255);
analogWrite(ledG, 255);
analogWrite(ledR, 0);
delay(50);
}
else if(fht_log_out[chnMid>110]){
analogWrite(ledB, 255);
analogWrite(ledG, 0);
analogWrite(ledR, 255);
delay(50);
}
else{
randomSeed(ADMUX);
valW = random(0,255);
analogWrite(ledB, valW);
valW = random(0,255);
analogWrite(ledG, valW);
valW = random(0,255);
analogWrite(ledR, valW);
delay(100);
}
}//end treshChn
void fadePin1(int ledPin, int sFade){
for(int fadeValue = sFade ; fadeValue > 1; fadeValue -=5) {
// sets the value (range from 0 to 255):
analogWrite(ledPin, fadeValue);
// wait for 30 milliseconds to see the dimming effect
delay(30);
}
}//end fadePin1
void fadePin2(int ledPinR,int ledPinG,int ledPinB, int sFade){
for(int fadeValue = sFade ; fadeValue > 1; fadeValue -=5) {
// sets the value (range from 0 to 255):
analogWrite(ledPinR, fadeValue);
analogWrite(ledPinG, fadeValue);
analogWrite(ledPinB, fadeValue);
// wait for 30 milliseconds to see the dimming effect
delay(30);
}
}//end fadePin
view raw myFHTnRGB hosted with ❤ by GitHub


Observations
No matter what are the music played during experimentation, it seems that the genre of the music maps to the corresponding biased group of frequency bands. Assuming the frequency bands are colour mapped eg blue for low, green for mid, red for high, definitely techno is going to appear more blue than red, and the counterexample eg opera is going to appear more red than blue. So the big Q: Which recipe for mapping of frequency to colour is "the best"? For this, yours truly don't have an answer, yet. It seems appreciating changing colour visually may differ from human to human.

Nonetheless, FHT is a really responsive algorithm implemented on Arduino. Check out the demo video below reacting to human voice.

Interested to pick up where I left? ping me!~~~~