@@ -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
3043void 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-
5049void 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-
122134void 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
197216void 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