@@ -13,14 +13,13 @@ extern PDMClass PDM; // Mic
1313#define NUM_SAMPLES 512 // Audio & FFT buffer, MUST be a power of two
1414#define SPECTRUM_SIZE (NUM_SAMPLES / 2 ) // Output spectrum is 1/2 of FFT output
1515
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
1620
17- short sampleBuffer[NUM_SAMPLES]; // buffer to read samples into, each sample is 16-bits
18-
19- // short sbuf[2][NUM_SAMPLES];
20- // uint8_t sbuf_idx = 0;
21-
22- volatile int samplesRead; // number of samples read (set in interrupt)
23-
21+ // short sampleBuffer[NUM_SAMPLES]; // buffer to read samples into, each sample is 16-bits
22+ short *sampleBuffer;
2423
2524// Bottom of spectrum tends to be noisy, while top often exceeds musical
2625// range and is just harmonics, so clip both ends off:
@@ -34,13 +33,13 @@ void err(char *str, uint8_t hz) {
3433 for (;;) digitalWrite (LED_BUILTIN, (millis () * hz / 500 ) & 1 );
3534}
3635
37- float data[SPECTRUM_SIZE];
3836
3937struct {
4038 int first_bin;
4139 int num_bins;
4240 float *bin_weights;
4341 uint32_t color;
42+ float top;
4443 float dot;
4544 float velocity;
4645} column_table[18 ];
@@ -89,18 +88,19 @@ Serial.printf("%d %f %f\n", spectrum_bits, low_frac, frac_range);
8988 Serial.println ();
9089 Serial.println (column);
9190 for (int i=0 ; i<num_bins; i++) {
92- column_table[column].bin_weights [i] = column_table[column].bin_weights [i] / total_weight * (0.6 + (float )i / 18.0 * 1.8 );
91+ column_table[column].bin_weights [i] = column_table[column].bin_weights [i] / total_weight * (0.6 + (float )i / 18.0 * 2.0 );
9392 Serial.printf (" %f\n " , column_table[column].bin_weights [i]);
9493 }
9594 column_table[column].first_bin = first_bin;
9695 column_table[column].num_bins = num_bins;
9796 column_table[column].color = glasses.color565 (glasses.ColorHSV (57600UL * column / 18 , 255 , 255 ));
98- column_table[column].dot = 5.0 ;
97+ column_table[column].top = 6.0 ;
98+ column_table[column].dot = 6.0 ;
9999 column_table[column].velocity = 0.0 ;
100100 }
101101
102102 for (int i=0 ; i<SPECTRUM_SIZE; i++) {
103- data [i] = 0.0 ;
103+ spectrum [i] = 0.0 ;
104104 }
105105
106106 // Configure glasses for max brightness, enable output
@@ -120,110 +120,96 @@ float dynamic_level = 6.0;
120120volatile bool mic_on = false ;
121121
122122void loop () { // Repeat forever...
123- int samplesRemaining = NUM_SAMPLES;
124- samplesRead = 0 ;
125- mic_on = true ;
126- while (samplesRemaining) {
127- if (samplesRead) { // Set in onPDMdata()
128- samplesRemaining -= samplesRead;
129- samplesRead = 0 ;
130- }
131- yield ();
132- }
133- mic_on = false ;
134123
135- // To do: could record into alternating buffers
124+ short *audio_data;
136125
137- ZeroFFT (sampleBuffer, NUM_SAMPLES);
126+ while (mic_on) yield (); // Wait for next buffer to finish recording
127+ // Full buffer received -- active_buf is index to new data
128+ audio_data = &audio_buf[active_buf][0 ]; // New data is here
129+ active_buf = 1 - active_buf; // Swap buffers to record into other,
130+ mic_on = true ; // and start recording next batch
138131
132+ // Perform FFT operation on newly-received data,
133+ // results go back into the same buffer.
134+ ZeroFFT (audio_data, NUM_SAMPLES);
139135
140- // Convert FFT output to spectrum
141- for (int i=0 ; i<SPECTRUM_SIZE; i++) {
142- // data[i] = (data[i] * 0.25) + ((float)sampleBuffer[i] * 0.75);
143- data[i] = (data[i] * 0.2 ) + ((sampleBuffer[i] ? log ((float )sampleBuffer[i]) : 0.0 ) * 0.8 );
144- // data[i] = (float)sampleBuffer[i];
136+ // Convert FFT output to spectrum. log(y) looks better than raw data.
137+ for (int i=LOW_BIN; i<=HIGH_BIN; i++) {
138+ spectrum[i] = (audio_data[i] > 0 ) ? log ((float )audio_data[i]) : 0.0 ;
145139 }
146140
147- float lower = data[0 ], upper = data[0 ];
148- for (int i=1 ; i<SPECTRUM_SIZE; i++) {
149- if (data[i] < lower) lower = data[i];
150- if (data[i] > upper) upper = data[i];
141+ // Find min & max range of spectrum values
142+ float lower = spectrum[LOW_BIN], upper = spectrum[LOW_BIN];
143+ for (int i=LOW_BIN+1 ; i<=HIGH_BIN; i++) {
144+ if (spectrum[i] < lower) lower = spectrum[i];
145+ if (spectrum[i] > upper) upper = spectrum[i];
151146 }
152- // Serial.printf("%f %f\n", lower, upper);
153- // if (lower < 4) lower = 4;
154- // if (upper < 10) upper = 10;
155- if (upper < 4.5 ) upper = 4.5 ; // because log
156-
157-
147+ // Serial.printf("%f %f\n", lower, upper);
148+ if (upper < 3.2 ) upper = 3.2 ;
158149
159150 if (upper > dynamic_level) {
160151 // Got louder. Move level up quickly but allow initial "bump."
161- dynamic_level = upper * 0.5 + dynamic_level * 0.5 ;
152+ dynamic_level = dynamic_level * 0.4 + upper * 0.6 ;
162153 } else {
163154 // Got quieter. Ease level down, else too many bumps.
164- dynamic_level = dynamic_level * 0.7 + lower * 0.3 ;
155+ dynamic_level = dynamic_level * 0.75 + lower * 0.25 ;
165156 }
166- // dynamic_level = 20.0;
167- // dynamic_level = upper;
168157
169158 // Apply vertical scale to spectrum data. Results may exceed
170159 // matrix height...that's OK, adds impact!
171- float scale = 10 .0 / (dynamic_level - lower);
172- for (int i=0 ; i<SPECTRUM_SIZE ; i++) {
173- data [i] = (data [i] - lower) * scale;
160+ float scale = 12 .0 / (dynamic_level - lower);
161+ for (int i=LOW_BIN ; i<=HIGH_BIN ; i++) {
162+ spectrum [i] = (spectrum [i] - lower) * scale;
174163 }
175164
176165 glasses.fill (0 );
177166 for (int column=0 ; column<18 ; column++) {
178167 int first_bin = column_table[column].first_bin ;
179168 float column_top = 7.0 ;
180169 for (int bin_offset=0 ; bin_offset<column_table[column].num_bins ; bin_offset++) {
181- column_top -= data [first_bin + bin_offset] * column_table[column].bin_weights [bin_offset];
170+ column_top -= spectrum [first_bin + bin_offset] * column_table[column].bin_weights [bin_offset];
182171 }
172+ // Column tops are filtered to appear less 'twitchy' --
173+ // last data still has a 30% influence on current positions.
174+ column_top = (column_top * 0.7 ) + (column_table[column].top * 0.3 );
175+ column_table[column].top = column_top;
176+
183177 if (column_top < column_table[column].dot ) {
184178 column_table[column].dot = column_top - 0.5 ;
185179 column_table[column].velocity = 0.0 ;
186180 } else {
187181 column_table[column].dot += column_table[column].velocity ;
188- column_table[column].velocity += 0.01 ;
182+ column_table[column].velocity += 0.02 ;
189183 }
190184
191185 int itop = (int )column_top;
192- glasses.drawLine (column, itop, column, itop + 50 , column_table[column].color );
186+ glasses.drawLine (column, itop, column, itop + 20 , column_table[column].color );
193187 glasses.drawPixel (column, (int )column_table[column].dot , 0xE410 );
194188 }
195189
196190 glasses.show ();
197191
198192 frames += 1 ;
199193 uint32_t elapsed = millis () - start_time;
200- // Serial.println(frames * 1000 / elapsed);
194+ Serial.println (frames * 1000 / elapsed);
201195}
202196
203- int16_t bitbucket[512 ];
204197void onPDMdata () {
205- pinMode (LED_BUILTIN, OUTPUT);
206- digitalWrite (LED_BUILTIN, (millis () * 2 / 500 ) & 1 );
207-
208- int bytesAvailable = PDM.available ();
209- if (mic_on) {
210- // wait, what? shouldn't this increment until full?
211- // yes it should. No wonder.
212- if (bytesAvailable) {
213- int maxbytes = (NUM_SAMPLES - samplesRead) * 2 ;
214- if (bytesAvailable > maxbytes) bytesAvailable = maxbytes;
215- PDM.read (&sampleBuffer[samplesRead], bytesAvailable);
216- // PDM.read(sampleBuffer, bytesAvailable);
217- samplesRead = bytesAvailable / 2 ;
218- }
219- } else {
220- if (bytesAvailable) {
221- PDM.read (bitbucket, bytesAvailable);
198+ digitalWrite (LED_BUILTIN, millis () & 1024 ); // Debug heartbeat
199+ if (int bytes_to_read = PDM.available ()) {
200+ if (mic_on) {
201+ int byte_limit = (NUM_SAMPLES - samples_read) * 2 ; // Space remaining,
202+ bytes_to_read = min (bytes_to_read, byte_limit); // don't overflow!
203+ PDM.read (&audio_buf[active_buf][samples_read], bytes_to_read);
204+ samples_read += bytes_to_read / 2 ; // Increment counter
205+ if (samples_read >= NUM_SAMPLES) { // Buffer full?
206+ mic_on = false ; // Stop and
207+ samples_read = 0 ; // reset counter for next time
208+ }
209+ } else {
210+ // Mic is off (code is busy) - must read but discard data.
211+ // audio_buf[2] is a 'bit bucket' for this.
212+ PDM.read (audio_buf[2 ], bytes_to_read);
222213 }
223214 }
224- // When buffer is full...
225- // indicate to calling code that it's ready
226- // stop recording
227- // calling code will indicate that next buffer is ready
228-
229215}
0 commit comments