The code. Comments removed
#include <avr/pgmspace.h>
#include <ffft.h>
#include <math.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_LEDBackpack.h>
#ifdef __AVR_ATmega32U4__
#define ADC_CHANNEL 7
#else
#define ADC_CHANNEL 0
#endif
int16_t capture[FFT_N];
struct _tag_complex_t bfly_buff[FFT_N];
uint16_t spectrum[FFT_N / 2];
volatile byte samplePos = 0;
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
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
static const uint8_t PROGMEM
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
},
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
},
col0data[] = { 2, 1, // # of spectrum bins to merge, index of first
111, 8
}, // Weights for each bin
col1data[] = { 4, 1, // 4 bins, starting at index 1
19, 186, 38, 2
}, // Weights for 4 bins. Got it now?
col2data[] = { 5, 2,
11, 156, 118, 16, 1
},
col3data[] = { 8, 3,
5, 55, 165, 164, 71, 18, 4, 1
},
col4data[] = { 11, 5,
3, 24, 89, 169, 178, 118, 54, 20, 6, 2, 1
},
col5data[] = { 17, 7,
2, 9, 29, 70, 125, 172, 185, 162, 118, 74,
41, 21, 10, 5, 2, 1, 1
},
col6data[] = { 25, 11,
1, 4, 11, 25, 49, 83, 121, 156, 180, 185,
174, 149, 118, 87, 60, 40, 25, 16, 10, 6,
4, 2, 1, 1, 1
},
col7data[] = { 37, 16,
1, 2, 5, 10, 18, 30, 46, 67, 92, 118,
143, 164, 179, 185, 184, 174, 158, 139, 118, 97,
77, 60, 45, 34, 25, 18, 13, 9, 7, 5,
3, 2, 2, 1, 1, 1, 1
},
// And then this points to the start of the data for each of the columns:
* const colData[] = {
col0data, col1data, col2data, col3data,
col4data, col5data, col6data, col7data
};
Adafruit_BicolorMatrix matrix = Adafruit_BicolorMatrix();
void setup() {
uint8_t i, j, nBins, binNum, *data;
memset(peak, 0, sizeof(peak));
memset(col , 0, sizeof(col));
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]);
}
matrix.begin(0x70);
// 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);
}
// Fill background w/colors, then idle parts of columns will erase
matrix.fillRect(0, 0, 8, 3, LED_RED); // Upper section
matrix.fillRect(0, 3, 8, 2, LED_YELLOW); // Mid
matrix.fillRect(0, 5, 8, 3, LED_GREEN); // Lower section
// Downsample spectrum output to 8 columns:
for (x = 0; x < 8; x++) {
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 > 10) c = 10; // 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
if (peak[x] <= 0) { // Empty column?
matrix.drawLine(x, 0, x, 7, LED_OFF);
continue;
} else if (c < 8) { // Partial column?
matrix.drawLine(x, 0, x, 7 - c, LED_OFF);
}
// The 'peak' dot color varies, but doesn't necessarily match
// the three screen regions...yellow has a little extra influence.
y = 8 - peak[x];
if (y < 2) matrix.drawPixel(x, y, LED_RED);
else if (y < 6) matrix.drawPixel(x, y, LED_YELLOW);
else matrix.drawPixel(x, y, LED_GREEN);
}
matrix.writeDisplay();
// 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 (++colCount >= 10) colCount = 0;
}
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
}