Skip to content

Commit 0820f21

Browse files
authored
Merge pull request #663 from adafruit/TheKitty-patch-25
Create Piccolo.ino
2 parents 9d38592 + 01d5817 commit 0820f21

1 file changed

Lines changed: 237 additions & 0 deletions

File tree

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
PICCOLO is a tiny Arduino-based audio visualizer.
3+
Hardware requirements:
4+
- Most Arduino or Arduino-compatible boards (ATmega 328P or better).
5+
- Adafruit Bicolor LED Matrix with I2C Backpack (ID: 902)
6+
- Adafruit Electret Microphone Amplifier (ID: 1063)
7+
- Optional: battery for portable use (else power through USB)
8+
Software requirements:
9+
- elm-chan's ffft library for Arduino
10+
Connections:
11+
- 3.3V to mic amp+ and Arduino AREF pin <-- important!
12+
- GND to mic amp-
13+
- Analog pin 0 to mic amp output
14+
- +5V, GND, SDA (or analog 4) and SCL (analog 5) to I2C Matrix backpack
15+
Written by Adafruit Industries. Distributed under the BSD license --
16+
see license.txt for more information. This paragraph must be included
17+
in any redistribution.
18+
ffft library is provided under its own terms -- see ffft.S for specifics.
19+
*/
20+
21+
// IMPORTANT: FFT_N should be #defined as 128 in ffft.h.
22+
23+
#include <avr/pgmspace.h>
24+
#include <ffft.h>
25+
#include <math.h>
26+
#include <Wire.h>
27+
#include <Adafruit_GFX.h>
28+
#include <Adafruit_LEDBackpack.h>
29+
30+
// Microphone connects to Analog Pin 0. Corresponding ADC channel number
31+
// varies among boards...it's ADC0 on Uno and Mega, ADC7 on Leonardo.
32+
// Other boards may require different settings; refer to datasheet.
33+
#ifdef __AVR_ATmega32U4__
34+
#define ADC_CHANNEL 7
35+
#else
36+
#define ADC_CHANNEL 0
37+
#endif
38+
39+
int16_t capture[FFT_N]; // Audio capture buffer
40+
complex_t bfly_buff[FFT_N]; // FFT "butterfly" buffer
41+
uint16_t spectrum[FFT_N/2]; // Spectrum output buffer
42+
volatile byte samplePos = 0; // Buffer position counter
43+
44+
byte
45+
peak[8], // Peak level of each column; used for falling dots
46+
dotCount = 0, // Frame counter for delaying dot-falling speed
47+
colCount = 0; // Frame counter for storing past column data
48+
int
49+
col[8][10], // Column levels for the prior 10 frames
50+
minLvlAvg[8], // For dynamic adjustment of low & high ends of graph,
51+
maxLvlAvg[8], // pseudo rolling averages for the prior few frames.
52+
colDiv[8]; // Used when filtering FFT output to 8 columns
53+
54+
/*
55+
These tables were arrived at through testing, modeling and trial and error,
56+
exposing the unit to assorted music and sounds. But there's no One Perfect
57+
EQ Setting to Rule Them All, and the graph may respond better to some
58+
inputs than others. The software works at making the graph interesting,
59+
but some columns will always be less lively than others, especially
60+
comparing live speech against ambient music of varying genres.
61+
*/
62+
static const uint8_t PROGMEM
63+
// This is low-level noise that's subtracted from each FFT output column:
64+
noise[64]={ 8,6,6,5,3,4,4,4,3,4,4,3,2,3,3,4,
65+
2,1,2,1,3,2,3,2,1,2,3,1,2,3,4,4,
66+
3,2,2,2,2,2,2,1,3,2,2,2,2,2,2,2,
67+
2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,4 },
68+
// These are scaling quotients for each FFT output column, sort of a
69+
// graphic EQ in reverse. Most music is pretty heavy at the bass end.
70+
eq[64]={
71+
255, 175,218,225,220,198,147, 99, 68, 47, 33, 22, 14, 8, 4, 2,
72+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
73+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
74+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
75+
// When filtering down to 8 columns, these tables contain indexes
76+
// and weightings of the FFT spectrum output values to use. Not all
77+
// buckets are used -- the bottom-most and several at the top are
78+
// either noisy or out of range or generally not good for a graph.
79+
col0data[] = { 2, 1, // # of spectrum bins to merge, index of first
80+
111, 8 }, // Weights for each bin
81+
col1data[] = { 4, 1, // 4 bins, starting at index 1
82+
19, 186, 38, 2 }, // Weights for 4 bins. Got it now?
83+
col2data[] = { 5, 2,
84+
11, 156, 118, 16, 1 },
85+
col3data[] = { 8, 3,
86+
5, 55, 165, 164, 71, 18, 4, 1 },
87+
col4data[] = { 11, 5,
88+
3, 24, 89, 169, 178, 118, 54, 20, 6, 2, 1 },
89+
col5data[] = { 17, 7,
90+
2, 9, 29, 70, 125, 172, 185, 162, 118, 74,
91+
41, 21, 10, 5, 2, 1, 1 },
92+
col6data[] = { 25, 11,
93+
1, 4, 11, 25, 49, 83, 121, 156, 180, 185,
94+
174, 149, 118, 87, 60, 40, 25, 16, 10, 6,
95+
4, 2, 1, 1, 1 },
96+
col7data[] = { 37, 16,
97+
1, 2, 5, 10, 18, 30, 46, 67, 92, 118,
98+
143, 164, 179, 185, 184, 174, 158, 139, 118, 97,
99+
77, 60, 45, 34, 25, 18, 13, 9, 7, 5,
100+
3, 2, 2, 1, 1, 1, 1 },
101+
// And then this points to the start of the data for each of the columns:
102+
* const colData[] = {
103+
col0data, col1data, col2data, col3data,
104+
col4data, col5data, col6data, col7data };
105+
106+
Adafruit_BicolorMatrix matrix = Adafruit_BicolorMatrix();
107+
108+
void setup() {
109+
uint8_t i, j, nBins, binNum, *data;
110+
111+
memset(peak, 0, sizeof(peak));
112+
memset(col , 0, sizeof(col));
113+
114+
for(i=0; i<8; i++) {
115+
minLvlAvg[i] = 0;
116+
maxLvlAvg[i] = 512;
117+
data = (uint8_t *)pgm_read_word(&colData[i]);
118+
nBins = pgm_read_byte(&data[0]) + 2;
119+
binNum = pgm_read_byte(&data[1]);
120+
for(colDiv[i]=0, j=2; j<nBins; j++)
121+
colDiv[i] += pgm_read_byte(&data[j]);
122+
}
123+
124+
matrix.begin(0x70);
125+
126+
// Init ADC free-run mode; f = ( 16MHz/prescaler ) / 13 cycles/conversion
127+
ADMUX = ADC_CHANNEL; // Channel sel, right-adj, use AREF pin
128+
ADCSRA = _BV(ADEN) | // ADC enable
129+
_BV(ADSC) | // ADC start
130+
_BV(ADATE) | // Auto trigger
131+
_BV(ADIE) | // Interrupt enable
132+
_BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128:1 / 13 = 9615 Hz
133+
ADCSRB = 0; // Free run mode, no high MUX bit
134+
DIDR0 = 1 << ADC_CHANNEL; // Turn off digital input for ADC pin
135+
TIMSK0 = 0; // Timer0 off
136+
137+
sei(); // Enable interrupts
138+
}
139+
140+
void loop() {
141+
uint8_t i, x, L, *data, nBins, binNum, weighting, c;
142+
uint16_t minLvl, maxLvl;
143+
int level, y, sum;
144+
145+
while(ADCSRA & _BV(ADIE)); // Wait for audio sampling to finish
146+
147+
fft_input(capture, bfly_buff); // Samples -> complex #s
148+
samplePos = 0; // Reset sample counter
149+
ADCSRA |= _BV(ADIE); // Resume sampling interrupt
150+
fft_execute(bfly_buff); // Process complex data
151+
fft_output(bfly_buff, spectrum); // Complex -> spectrum
152+
153+
// Remove noise and apply EQ levels
154+
for(x=0; x<FFT_N/2; x++) {
155+
L = pgm_read_byte(&noise[x]);
156+
spectrum[x] = (spectrum[x] <= L) ? 0 :
157+
(((spectrum[x] - L) * (256L - pgm_read_byte(&eq[x]))) >> 8);
158+
}
159+
160+
// Fill background w/colors, then idle parts of columns will erase
161+
matrix.fillRect(0, 0, 8, 3, LED_RED); // Upper section
162+
matrix.fillRect(0, 3, 8, 2, LED_YELLOW); // Mid
163+
matrix.fillRect(0, 5, 8, 3, LED_GREEN); // Lower section
164+
165+
// Downsample spectrum output to 8 columns:
166+
for(x=0; x<8; x++) {
167+
data = (uint8_t *)pgm_read_word(&colData[x]);
168+
nBins = pgm_read_byte(&data[0]) + 2;
169+
binNum = pgm_read_byte(&data[1]);
170+
for(sum=0, i=2; i<nBins; i++)
171+
sum += spectrum[binNum++] * pgm_read_byte(&data[i]); // Weighted
172+
col[x][colCount] = sum / colDiv[x]; // Average
173+
minLvl = maxLvl = col[x][0];
174+
for(i=1; i<10; i++) { // Get range of prior 10 frames
175+
if(col[x][i] < minLvl) minLvl = col[x][i];
176+
else if(col[x][i] > maxLvl) maxLvl = col[x][i];
177+
}
178+
// minLvl and maxLvl indicate the extents of the FFT output, used
179+
// for vertically scaling the output graph (so it looks interesting
180+
// regardless of volume level). If they're too close together though
181+
// (e.g. at very low volume levels) the graph becomes super coarse
182+
// and 'jumpy'...so keep some minimum distance between them (this
183+
// also lets the graph go to zero when no sound is playing):
184+
if((maxLvl - minLvl) < 8) maxLvl = minLvl + 8;
185+
minLvlAvg[x] = (minLvlAvg[x] * 7 + minLvl) >> 3; // Dampen min/max levels
186+
maxLvlAvg[x] = (maxLvlAvg[x] * 7 + maxLvl) >> 3; // (fake rolling average)
187+
188+
// Second fixed-point scale based on dynamic min/max levels:
189+
level = 10L * (col[x][colCount] - minLvlAvg[x]) /
190+
(long)(maxLvlAvg[x] - minLvlAvg[x]);
191+
192+
// Clip output and convert to byte:
193+
if(level < 0L) c = 0;
194+
else if(level > 10) c = 10; // Allow dot to go a couple pixels off top
195+
else c = (uint8_t)level;
196+
197+
if(c > peak[x]) peak[x] = c; // Keep dot on top
198+
199+
if(peak[x] <= 0) { // Empty column?
200+
matrix.drawLine(x, 0, x, 7, LED_OFF);
201+
continue;
202+
} else if(c < 8) { // Partial column?
203+
matrix.drawLine(x, 0, x, 7 - c, LED_OFF);
204+
}
205+
206+
// The 'peak' dot color varies, but doesn't necessarily match
207+
// the three screen regions...yellow has a little extra influence.
208+
y = 8 - peak[x];
209+
if(y < 2) matrix.drawPixel(x, y, LED_RED);
210+
else if(y < 6) matrix.drawPixel(x, y, LED_YELLOW);
211+
else matrix.drawPixel(x, y, LED_GREEN);
212+
}
213+
214+
matrix.writeDisplay();
215+
216+
// Every third frame, make the peak pixels drop by 1:
217+
if(++dotCount >= 3) {
218+
dotCount = 0;
219+
for(x=0; x<8; x++) {
220+
if(peak[x] > 0) peak[x]--;
221+
}
222+
}
223+
224+
if(++colCount >= 10) colCount = 0;
225+
}
226+
227+
ISR(ADC_vect) { // Audio-sampling interrupt
228+
static const int16_t noiseThreshold = 4;
229+
int16_t sample = ADC; // 0-1023
230+
231+
capture[samplePos] =
232+
((sample > (512-noiseThreshold)) &&
233+
(sample < (512+noiseThreshold))) ? 0 :
234+
sample - 512; // Sign-convert for FFT; -512 to +511
235+
236+
if(++samplePos >= FFT_N) ADCSRA &= ~_BV(ADIE); // Buffer full, interrupt off
237+
}

0 commit comments

Comments
 (0)