Skip to content

Commit d2ac0e2

Browse files
Tidy up & comments
1 parent 43df659 commit d2ac0e2

1 file changed

Lines changed: 82 additions & 63 deletions

File tree

EyeLights_Audio_Spectrum/EyeLights_Audio_Spectrum.ino

Lines changed: 82 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,37 @@ Uses onboard microphone and a lot of math to react to music.
77
#include <PDM.h> // For microphone
88
#include <Adafruit_ZeroFFT.h> // For math
99

10-
Adafruit_EyeLights_buffered glasses; // Buffered for smooth animation
11-
extern PDMClass PDM; // Mic
10+
// FFT/SPECTRUM CONFIG ----
1211

1312
#define NUM_SAMPLES 512 // Audio & FFT buffer, MUST be a power of two
1413
#define SPECTRUM_SIZE (NUM_SAMPLES / 2) // Output spectrum is 1/2 of FFT output
15-
16-
short audio_buf[3][NUM_SAMPLES]; // Audio input buffers, 16-bit signed
17-
uint8_t active_buf = 0; // Buffer # into which audio is currently recording
18-
volatile int samples_read = 0; // # of samples read into current buffer thus far
19-
float spectrum[SPECTRUM_SIZE]; // FFT results are stored & further processed here
20-
21-
//short sampleBuffer[NUM_SAMPLES]; // buffer to read samples into, each sample is 16-bits
22-
short *sampleBuffer;
23-
2414
// Bottom of spectrum tends to be noisy, while top often exceeds musical
2515
// range and is just harmonics, so clip both ends off:
26-
#define LOW_BIN 3 // Lowest bin of spectrum that contributes to graph
27-
#define HIGH_BIN 180 // Highest bin "
16+
#define LOW_BIN 5 // Lowest bin of spectrum that contributes to graph
17+
#define HIGH_BIN 150 // Highest bin "
18+
19+
// GLOBAL VARIABLES -------
20+
21+
Adafruit_EyeLights_buffered glasses; // LED matrix is buffered for smooth animation
22+
extern PDMClass PDM; // Microphone
23+
short audio_buf[3][NUM_SAMPLES]; // Audio input buffers, 16-bit signed
24+
uint8_t active_buf = 0; // Buffer # into which audio is currently recording
25+
volatile int samples_read = 0; // # of samples read into current buffer thus far
26+
volatile bool mic_on = false; // true when reading from mic, false when full/stopped
27+
float spectrum[SPECTRUM_SIZE]; // FFT results are stored & further processed here
28+
float dynamic_level = 10.0; // For adapting to changing audio volume
29+
int frames; // For frames-per-second calculation
30+
uint32_t start_time; // Ditto
31+
32+
struct { // Values associated with each column of the matrix
33+
int first_bin; // First spectrum bin index affecting column
34+
int num_bins; // Number of spectrum bins affecting column
35+
float *bin_weights; // List of spectrum bin weightings
36+
uint32_t color; // GFX-style 'RGB565' color for column
37+
float top; // Current column top position
38+
float dot; // Current column 'falling dot' position
39+
float velocity; // Current velocity of falling dot
40+
} column_table[18];
2841

2942
// Crude error handler, prints message to Serial console, flashes LED
3043
void err(char *str, uint8_t hz) {
@@ -33,44 +46,44 @@ void err(char *str, uint8_t hz) {
3346
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
3447
}
3548

36-
37-
struct {
38-
int first_bin;
39-
int num_bins;
40-
float *bin_weights;
41-
uint32_t color;
42-
float top;
43-
float dot;
44-
float velocity;
45-
} column_table[18];
46-
47-
int frames;
48-
uint32_t start_time;
49-
5049
void setup() { // Runs once at program start...
5150

52-
// Initialize hardware
5351
Serial.begin(115200);
54-
while(!Serial);
52+
//while(!Serial);
5553
if (! glasses.begin()) err("IS3741 not found", 2);
5654

57-
uint8_t spectrum_bits = (int)log2f((float)SPECTRUM_SIZE);
55+
// FFT/SPECTRUM SETUP -----
56+
57+
uint8_t spectrum_bits = (int)log2f((float)SPECTRUM_SIZE); // e.g. 8 = 256 bin spectrum
58+
// Scale LOW_BIN and HIGH_BIN to 0.0 to 1.0 equivalent range in spectrum
5859
float low_frac = log2f((float)LOW_BIN) / (float)spectrum_bits;
5960
float frac_range = log2((float)HIGH_BIN) / (float)spectrum_bits - low_frac;
60-
Serial.printf("%d %f %f\n", spectrum_bits, low_frac, frac_range);
61+
// Serial.printf("%d %f %f\n", spectrum_bits, low_frac, frac_range);
62+
63+
// To keep the display lively, tables are precomputed where each column of
64+
// the matrix (of which there are few) is the sum value and weighting of
65+
// several bins from the FFT spectrum output (of which there are many).
66+
// The tables also help visually linearize the output so octaves are evenly
67+
// spaced, as on a piano keyboard, whereas the source spectrum data is
68+
// spaced by frequency in Hz.
6169

6270
for (int column=0; column<18; column++) {
71+
// Determine the lower and upper frequency range for this column, as
72+
// fractions within the scaled 0.0 to 1.0 spectrum range. 0.95 below
73+
// creates slight frequency overlap between columns, looks nicer.
6374
float lower = low_frac + frac_range * ((float)column / 18.0 * 0.95);
6475
float upper = low_frac + frac_range * ((float)(column + 1) / 18.0);
65-
float mid = (lower + upper) * 0.5; // Center of lower-to-upper range
76+
float mid = (lower + upper) * 0.5; // Center of lower-to-upper range
6677
float half_width = (upper - lower) * 0.5 + 1e-2; // 1/2 of lower-to-upper range
6778
// Map fractions back to spectrum bin indices that contribute to column
6879
int first_bin = int(pow(2, (float)spectrum_bits * lower) + 1e-4);
6980
int last_bin = int(pow(2, (float)spectrum_bits * upper) + 1e-4);
70-
Serial.printf("%d %d %d\n", column, first_bin, last_bin);
71-
float total_weight = 0.0;
81+
//Serial.printf("%d %d %d\n", column, first_bin, last_bin);
82+
float total_weight = 0.0; // Accumulate weight for this bin
7283
int num_bins = last_bin - first_bin + 1;
84+
// Allocate space for bin weights for column, stop everything if out of RAM.
7385
column_table[column].bin_weights = (float *)malloc(num_bins * sizeof(float));
86+
if (column_table[column].bin_weights == NULL) err("Malloc fail", 10);
7487
for (int bin_index = first_bin; bin_index <= last_bin; bin_index++) {
7588
// Find distance from column's overall center to individual bin's
7689
// center, expressed as 0.0 (bin at center) to 1.0 (bin at limit of
@@ -85,23 +98,26 @@ Serial.printf("%d %f %f\n", spectrum_bits, low_frac, frac_range);
8598
total_weight += bin_weight;
8699
}
87100
}
88-
Serial.println();
89-
Serial.println(column);
101+
//Serial.println(column);
102+
// Scale bin weights so total is 1.0 for each column, but then mute
103+
// lower columns slightly and boost higher columns. It graphs better.
90104
for (int i=0; i<num_bins; i++) {
91-
column_table[column].bin_weights[i] = column_table[column].bin_weights[i] / total_weight * (0.6 + (float)i / 18.0 * 2.0);
92-
Serial.printf(" %f\n", column_table[column].bin_weights[i]);
105+
column_table[column].bin_weights[i] = column_table[column].bin_weights[i] /
106+
total_weight * (0.6 + (float)i / 18.0 * 2.0);
107+
//Serial.printf(" %f\n", column_table[column].bin_weights[i]);
93108
}
94109
column_table[column].first_bin = first_bin;
95110
column_table[column].num_bins = num_bins;
96-
column_table[column].color = glasses.color565(glasses.ColorHSV(57600UL * column / 18, 255, 255));
97-
column_table[column].top = 6.0;
111+
column_table[column].color = glasses.color565(glasses.ColorHSV(
112+
57600UL * column / 18, 255, 255)); // Red (0) to purple (57600)
113+
column_table[column].top = 6.0; // Start off bottom of graph
98114
column_table[column].dot = 6.0;
99115
column_table[column].velocity = 0.0;
100116
}
101117

102-
for (int i=0; i<SPECTRUM_SIZE; i++) {
103-
spectrum[i] = 0.0;
104-
}
118+
for (int i=0; i<SPECTRUM_SIZE; i++) spectrum[i] = 0.0;
119+
120+
// HARDWARE SETUP ---------
105121

106122
// Configure glasses for max brightness, enable output
107123
glasses.setLEDscaling(0xFF);
@@ -115,87 +131,90 @@ Serial.printf("%d %f %f\n", spectrum_bits, low_frac, frac_range);
115131
start_time = millis();
116132
}
117133

118-
float dynamic_level = 6.0;
119-
120-
volatile bool mic_on = false;
121-
122134
void loop() { // Repeat forever...
123135

124-
short *audio_data;
136+
short *audio_data; // Pointer to newly-received audio
125137

126138
while (mic_on) yield(); // Wait for next buffer to finish recording
127139
// Full buffer received -- active_buf is index to new data
128140
audio_data = &audio_buf[active_buf][0]; // New data is here
129-
active_buf = 1 - active_buf; // Swap buffers to record into other,
141+
active_buf = 1 - active_buf; // Swap buffers to record into other one,
130142
mic_on = true; // and start recording next batch
131143

132144
// Perform FFT operation on newly-received data,
133145
// results go back into the same buffer.
134146
ZeroFFT(audio_data, NUM_SAMPLES);
135147

136148
// Convert FFT output to spectrum. log(y) looks better than raw data.
149+
// Only LOW_BIN to HIGH_BIN elements are needed.
137150
for(int i=LOW_BIN; i<=HIGH_BIN; i++) {
138151
spectrum[i] = (audio_data[i] > 0) ? log((float)audio_data[i]) : 0.0;
139152
}
140153

141-
// Find min & max range of spectrum values
154+
// Find min & max range of spectrum bin values, with limits.
142155
float lower = spectrum[LOW_BIN], upper = spectrum[LOW_BIN];
143156
for (int i=LOW_BIN+1; i<=HIGH_BIN; i++) {
144157
if (spectrum[i] < lower) lower = spectrum[i];
145158
if (spectrum[i] > upper) upper = spectrum[i];
146159
}
147160
//Serial.printf("%f %f\n", lower, upper);
148-
if (upper < 3.2) upper = 3.2;
161+
if (upper < 2.5) upper = 2.5;
149162

163+
// Adjust dynamic level to current spectrum output, keeps the graph
164+
// 'lively' as ambient volume changes. Sparkle but don't saturate.
150165
if (upper > dynamic_level) {
151166
// Got louder. Move level up quickly but allow initial "bump."
152-
dynamic_level = dynamic_level * 0.4 + upper * 0.6;
167+
dynamic_level = dynamic_level * 0.5 + upper * 0.5;
153168
} else {
154169
// Got quieter. Ease level down, else too many bumps.
155170
dynamic_level = dynamic_level * 0.75 + lower * 0.25;
156171
}
157172

158173
// Apply vertical scale to spectrum data. Results may exceed
159174
// matrix height...that's OK, adds impact!
160-
float scale = 12.0 / (dynamic_level - lower);
175+
float scale = 15.0 / (dynamic_level - lower);
161176
for (int i=LOW_BIN; i<=HIGH_BIN; i++) {
162177
spectrum[i] = (spectrum[i] - lower) * scale;
163178
}
164179

180+
// Clear screen, filter and draw each column of the display...
165181
glasses.fill(0);
166182
for(int column=0; column<18; column++) {
167183
int first_bin = column_table[column].first_bin;
184+
// Start BELOW matrix and accumulate bin weights UP, saves math
168185
float column_top = 7.0;
169186
for (int bin_offset=0; bin_offset<column_table[column].num_bins; bin_offset++) {
170187
column_top -= spectrum[first_bin + bin_offset] * column_table[column].bin_weights[bin_offset];
171188
}
172-
// Column tops are filtered to appear less 'twitchy' --
189+
// Column top positions are filtered to appear less 'twitchy' --
173190
// last data still has a 30% influence on current positions.
174191
column_top = (column_top * 0.7) + (column_table[column].top * 0.3);
175192
column_table[column].top = column_top;
176193

177-
if(column_top < column_table[column].dot) {
178-
column_table[column].dot = column_top - 0.5;
179-
column_table[column].velocity = 0.0;
194+
if(column_top < column_table[column].dot) { // Above current falling dot?
195+
column_table[column].dot = column_top - 0.5; // Move dot up
196+
column_table[column].velocity = 0.0; // and clear out velocity
180197
} else {
181-
column_table[column].dot += column_table[column].velocity;
182-
column_table[column].velocity += 0.02;
198+
column_table[column].dot += column_table[column].velocity; // Move dot down
199+
column_table[column].velocity += 0.015; // and accelerate
183200
}
184201

185-
int itop = (int)column_top;
202+
// Draw column and peak dot
203+
int itop = (int)column_top; // Quantize column top to pixel space
186204
glasses.drawLine(column, itop, column, itop + 20, column_table[column].color);
187205
glasses.drawPixel(column, (int)column_table[column].dot, 0xE410);
188206
}
189207

190-
glasses.show();
208+
glasses.show(); // Buffered mode MUST use show() to refresh matrix
191209

192210
frames += 1;
193211
uint32_t elapsed = millis() - start_time;
194-
Serial.println(frames * 1000 / elapsed);
212+
//Serial.println(frames * 1000 / elapsed);
195213
}
196214

215+
// PDM mic interrupt handler, called when new data is ready
197216
void onPDMdata() {
198-
digitalWrite(LED_BUILTIN, millis() & 1024); // Debug heartbeat
217+
//digitalWrite(LED_BUILTIN, millis() & 1024); // Debug heartbeat
199218
if (int bytes_to_read = PDM.available()) {
200219
if (mic_on) {
201220
int byte_limit = (NUM_SAMPLES - samples_read) * 2; // Space remaining,

0 commit comments

Comments
 (0)