/* Starry Night -
a music visualizer that simulates the starry night sky.
Parts of the code are written by Adafruit Industries. Distributed under the BSD license.
See https://github.com/adafruit/piccolo for original source.
Additional code written by Jun Huo.
*/
#include <avr /pgmspace.h>
#include <ffft .h>
#include <math .h>
#include <wire .h>
#ifdef __AVR_ATmega32U4__
#define ADC_CHANNEL 7
#else
#define ADC_CHANNEL 0
#endif
int16_t capture[FFT_N]; // Audio capture buffer
complex_t bfly_buff[FFT_N]; // FFT "butterfly" buffer
uint16_t spectrum[FFT_N/2]; // Spectrum output buffer
volatile byte samplePos = 0; // Buffer position counter
byte
peak[8], // Peak level of each column; used for falling dots
dotCount = 0, // Frame counter for delaying dot-falling speed
colCount = 0, // Frame counter for storing past column data
sparkCount = 0;
int
col[8][10], // Column levels for the prior 10 frames
minLvlAvg[8], // For dynamic adjustment of low & high ends of graph,
maxLvlAvg[8], // pseudo rolling averages for the prior few frames.
colDiv[8]; // Used when filtering FFT output to 8 columns
PROGMEM uint8_t
// This is low-level noise that's subtracted from each FFT output column:
noise[64]={ 8,6,6,5,3,4,4,4,3,4,4,3,2,3,3,4,
2,1,2,1,3,2,3,2,1,2,3,1,2,3,4,4,
3,2,2,2,2,2,2,1,3,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,4 },
// These are scaling quotients for each FFT output column, sort of a
// graphic EQ in reverse. Most music is pretty heavy at the bass end.
eq[64]={
255, 175,218,225,220,198,147, 99, 68, 47, 33, 22, 14, 8, 4, 2,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
//New filter, tuned for general piano music
//RED
col0data[] = { 3, 1,
130, 190, 20},
//RED/RED/GREEN
col1data[] = { 5, 2,
20, 160, 140, 20, 5},
//RED/GREEN
col2data[] = { 7, 4,
40, 170, 160, 80, 20, 10,
5, 1},
//GREEN
col3data[] = { 9, 5,
5, 20, 90, 170, 120,
60, 20, 10, 2, 1},
//GREEN/BLUE
col4data[] = { 13, 7,
4, 10, 60, 120,
160, 170, 160, 110, 60, 20, 10, 5, 3, 1},
//BLUE
col5data[] = { 19, 10,
3, 8, 20, 50, 110, 150, 170, 180, 170, 140,
90, 60, 40, 20, 10, 5, 2, 1, 1},
//BLUE/RED
col6data[] = { 23, 14,
1, 3, 8, 15, 35, 60,
100, 120, 150, 170, 180, 185, 160, 130, 100, 50,
20, 10, 5, 2, 1, 1, 1},
//BLUE/RED/RED
col7data[] = { 35, 18,
1, 2,
3, 5, 10, 20, 30, 60, 90, 120, 120, 160,
180, 185, 165, 140, 120, 100, 80, 60, 45, 35,
25, 20, 15, 10, 7, 5, 3, 2, 2, 1,
1, 1, 1},
// And then this points to the start of the data for each of the columns:
*colData[] = {
col0data, col1data, col2data, col3data,
col4data, col5data, col6data, col7data };
int nPins = 8;
int ledPins[] = {6,7,8,9,10,11,12,13};
float brightness[] = {0,0,0,0,0,0,0,0};
float fadeFactor = 2.0;
boolean newSpark = false;
int rgb1[] = {44,45,46};
int rgb2[] = {3,4,5};
float color1[] = {0,0,0};
float color2[] = {0,0,0};
int vol[60];
int auroraWait = 0;
int waitThreshold = 0;
int auroraThreshold = 0;
int auroraCount = 0;
boolean playAurora = false;
void setup() {
Serial.begin(9600);
uint8_t i, j, nBins, binNum, *data;
memset(peak, 0, sizeof(peak));
memset(col , 0, sizeof(col));
memset(brightness,0,sizeof(brightness));
memset(vol, 0, sizeof(vol));
for(i=0; i<8; i++) {
minLvlAvg[i] = 0;
maxLvlAvg[i] = 512;
data = (uint8_t *)pgm_read_word(&colData[i]);
nBins = pgm_read_byte(&data[0]) + 2;
binNum = pgm_read_byte(&data[1]);
for(colDiv[i]=0, j=2; j<nbins ; j++)
colDiv[i] += pgm_read_byte(&data[j]);
}
for (int k=0; k<60; k++) {
vol[k] = 0;
}
// Init ADC free-run mode; f = ( 16MHz/prescaler ) / 13 cycles/conversion
ADMUX = ADC_CHANNEL; // Channel sel, right-adj, use AREF pin
ADCSRA = _BV(ADEN) | // ADC enable
_BV(ADSC) | // ADC start
_BV(ADATE) | // Auto trigger
_BV(ADIE) | // Interrupt enable
_BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128:1 / 13 = 9615 Hz
ADCSRB = 0; // Free run mode, no high MUX bit
DIDR0 = 1 << ADC_CHANNEL; // Turn off digital input for ADC pin
TIMSK0 = 0; // Timer0 off
sei(); // Enable interrupts
}
void loop() {
uint8_t i, x, L, *data, nBins, binNum, weighting, c;
uint16_t minLvl, maxLvl;
int level, y, sum;
while(ADCSRA & _BV(ADIE)); // Wait for audio sampling to finish
fft_input(capture, bfly_buff); // Samples -> complex #s
samplePos = 0; // Reset sample counter
ADCSRA |= _BV(ADIE); // Resume sampling interrupt
fft_execute(bfly_buff); // Process complex data
fft_output(bfly_buff, spectrum); // Complex -> spectrum
// Remove noise and apply EQ levels
for(x=0; x<fft_n /2; x++) {
L = pgm_read_byte(&noise[x]);
spectrum[x] = (spectrum[x] <= L) ? 0 :
(((spectrum[x] - L) * (256L - pgm_read_byte(&eq[x]))) >> 8);
}
int oldPeakSum = 0;
int newPeakSum = 0;
int highestPeakIndex = -1;
int highestPeak = -1;
// Downsample spectrum output to 8 columns:
for(x=0; x<8; x++) {
int oldPeak = peak[x];
oldPeakSum+=oldPeak;
data = (uint8_t *)pgm_read_word(&colData[x]);
nBins = pgm_read_byte(&data[0]) + 2;
binNum = pgm_read_byte(&data[1]);
for(sum=0, i=2; i<nbins ; i++)
sum += spectrum[binNum++] * pgm_read_byte(&data[i]); // Weighted
col[x][colCount] = sum / colDiv[x]; // Average
minLvl = maxLvl = col[x][0];
for(i=1; i<10; i++) { // Get range of prior 10 frames
if(col[x][i] < minLvl) minLvl = col[x][i];
else if(col[x][i] > maxLvl) maxLvl = col[x][i];
}
if((maxLvl - minLvl) < 8) maxLvl = minLvl + 8;
minLvlAvg[x] = (minLvlAvg[x] * 7 + minLvl) >> 3; // Dampen min/max levels
maxLvlAvg[x] = (maxLvlAvg[x] * 7 + maxLvl) >> 3; // (fake rolling average)
// Second fixed-point scale based on dynamic min/max levels:
level = 10L * (col[x][colCount] - minLvlAvg[x]) /
(long)(maxLvlAvg[x] - minLvlAvg[x]);
// Clip output and convert to byte:
if(level < 0L) c = 0;
else if(level > 8) c = 8; // Allow dot to go a couple pixels off top
else c = (uint8_t)level;
if(c > peak[x]) peak[x] = c; // Keep dot on top
y = 8 - peak[x];
int newPeak = peak[x];
newPeakSum+=newPeak;
if (newPeak>0 && newPeak>highestPeak) {
highestPeakIndex = x;
highestPeak = newPeak;
}
}
if (oldPeakSum< (newPeakSum-2) && newSpark==false) {
newSpark = true;
}
int currVolSum = 0;
for (int v=59; v>0; v--) {
vol[v] = vol[v-1];
currVolSum+=vol[v];
}
vol[0] = abs(512-int(ADC));
currVolSum+=vol[0];
int currVolAvg = currVolSum/60;
// int currVolume = abs(512-int(ADC));
Serial.println(currVolAvg);
int newColor0 = 0, newColor1 = 0, newColor2 = 0;
if (currVolAvg>64) {if (highestPeakIndex==0) {
newColor0 = 80;
newColor1 = 20;
newColor2 = 20;
} else if (highestPeakIndex==1) {
newColor0 = 80;
newColor1 = 60;
newColor2 = 20;
} else if (highestPeakIndex==2) {
newColor0 = 70;
newColor1 = 70;
newColor2 = 20;
} else if (highestPeakIndex==3) {
newColor0 = 30;
newColor1 = 100;
newColor2 = 30;
} else if (highestPeakIndex==4) {
newColor0 = 20;
newColor1 = 90;
newColor2 = 90;
} else if (highestPeakIndex==5) {
newColor0 = 20;
newColor1 = 60;
newColor2 = 90;
} else if (highestPeakIndex==6) {
newColor0 = 20;
newColor1 = 20;
newColor2 = 100;
} else {
newColor0 = 60;
newColor1 = 20;
newColor2 = 90;
}
}
if (newSpark==true) {
int led = random(nPins);
float b = 255.0;
brightness[led] = b;
newSpark = false;
}
int starsLight = 0.0;
for (int s=0; s<npins ; s++) {
starsLight+=brightness[s];
}
for (int j=0; j<nPins; j++) {
analogWrite(ledPins[j],brightness[j]);
}
if (starsLight>0.0) auroraWait++;
else auroraWait-=(waitThreshold/20);
if (starsLight>0.0 && auroraWait>=waitThreshold){
playAurora = true;
} else {
playAurora = false;
}
// Every third frame, make the peak pixels drop by 1:
if(++dotCount >= 3) {
dotCount = 0;
for(x=0; x<8; x++) {
if(peak[x] > 0) peak[x]--;
}
}
if (++sparkCount>=50) {
sparkCount = 0;
if (newSpark==true) newSpark = false;
}
if (++auroraCount>=30) {
auroraCount = 0;
if (playAurora && newColor0+newColor1+newColor2>0) {
color2[0] = color1[0];
color2[1] = color1[1];
color2[2] = color1[2];
color1[0] = float(newColor0);
color1[1] = float(newColor1);
color1[2] = float(newColor2);
}
}
analogWrite(rgb1[0],(color1[0]));
analogWrite(rgb1[1],(color1[1]));
analogWrite(rgb1[2],(color1[2]));
if (color1[0]+color1[1]+color1[2]>0.0) {
analogWrite(rgb2[0],color2[0]);
analogWrite(rgb2[1],color2[1]);
analogWrite(rgb2[2],color2[2]);
}
for (int p=0; p</npins><npins ; p++) {
float br = brightness[p];
br = (br-fadeFactor <= 0) ?0 : (br-=fadeFactor);
brightness[p] = br;
}
if(++colCount >= 10) colCount = 0;
for (int q=0; q<3; q++) {
float newFade = float(fadeFactor)/5.0;
float br1 = color1[q];
br1 = (br1-newFade< =0) ?0: (br1-=newFade);
color1[q] = br1;
float br2 = color2[q];
br2 = (br2-newFade<=0) ?0: (br2-=newFade);
color2[q] = br2;
}
}
ISR(ADC_vect) { // Audio-sampling interrupt
static const int16_t noiseThreshold = 4;
int16_t sample = ADC; // 0-1023
capture[samplePos] =
((sample > (512-noiseThreshold)) &&
(sample < (512+noiseThreshold))) ? 0 :
sample - 512; // Sign-convert for FFT; -512 to +511
if(++samplePos >= FFT_N) ADCSRA &= ~_BV(ADIE); // Buffer full, interrupt off
}
</npins></nbins></fft_n></nbins></wire></math></ffft></avr> |