-
Notifications
You must be signed in to change notification settings - Fork 0
/
Cochlea.ino
343 lines (284 loc) · 12.7 KB
/
Cochlea.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/* ==================================================================
Cochlea.ino The Visual Ear.
(c) 2020 Phil Malone
This version has the following attributes:
LED Type DotStar
LED Num 25
LED Data 12
LED Clk 14
Number of LEDs 42
Mic Type I2S
Mic Clk 26
Mic Data 25
Mic WS 33
Sample Freq 36 kHz
Samples per burst 1024
Number of Bursts 6
Bursts per FFT 4
* Windowing types
* FFT_WIN_TYP_RECTANGLE
* FFT_WIN_TYP_HAMMING
* FFT_WIN_TYP_HANN
* FFT_WIN_TYP_TRIANGLE
* FFT_WIN_TYP_NUTTALL
* FFT_WIN_TYP_BLACKMAN
* FFT_WIN_TYP_BLACKMAN_NUTTALL
* FFT_WIN_TYP_BLACKMAN_HARRIS
* FFT_WIN_TYP_FLT_TOP
* FFT_WIN_TYP_WELCH
================================================================== */
#define FASTLED_INTERNAL
#include <FastLED.h>
#include "driver/i2s.h"
#include "arduinoFFT_float.h"
TaskHandle_t AudioTask;
TaskHandle_t FFTTask;
// ================= Multi-Task Shared Data =================
// -- Audio Constants
#define MIC_CLOCK_PIN 26 // Serial Clock (SCK)
#define MIC_DATA_PIN 25 // Serial Data (SD)
#define MIC_SEL_PIN 33 // Word Select (WS)
#define UNUSED_AUDIO_BITS 16 // Bits do discard from the 32 bit audio sample.
const uint16_t SAMPLING_FREQ = 36000; // Frequency at which microphone is sampled
const uint16_t BURST_SAMPLES = 512; // Number of audio samples taken in one "Burst"
const uint16_t BURST_SAMPLES_AVG = 9; // Bit shift required to average one full Burst
const uint16_t BURSTS_PER_AUDIO = 8; // Number of Burst Buffers used to create a single Audio Packet
const uint16_t EXTRA_BURSTS = 2; // Extra Burst packets to avoid overlap
const uint16_t NUM_BURSTS = (BURSTS_PER_AUDIO + EXTRA_BURSTS);
const uint16_t SIZEOF_BURST = (BURST_SAMPLES << 2); // Number of bytes in a Burst Buffer
// -- FFT Constants
const uint16_t FFT_SAMPLES = BURST_SAMPLES * BURSTS_PER_AUDIO; // Number of samples used to do FFT. (BURST_SAMPLES * BURSTS_PER_AUDIO)
const uint16_t FREQ_BINS = (FFT_SAMPLES >> 1); // Number or resulting Frequency Bins after FFT is done
// -- LED Display Constants
#define NUM_BANDS 59 // Number of frequency bands being displayed as LEDs = Number of LEDs
#define NUM_LEDS (NUM_BANDS * 2) // Number of frequency bands being displayed as LEDs = Number of LEDs
#define LED_DATA_PIN 12
#define LED_CLOCK_PIN 14
#define BRIGHTNESS 255 // Max LED Brightness
#define GAIN_DIVIDE 1400 // Brighness control used to reduce frequency band magnitude to get LED brightness
#define START_NOISE_FLOOR 3000 // Frequency Bin Magnitudes below this value will not get summed into Bands. (Initial high value)
#define BASE_NOISE_FLOOR 1000 // Frequency Bin Magnitudes below this value will not get summed into Bands. (Final minimumm value)
#define BAND_HUE_STEP (200/NUM_BANDS) // How much the LED Hue changes for each band.
//================= Shared Data =================
// Timing Data
uint32_t audioCyclePeriod;
uint32_t FFTCyclePeriod;
uint32_t FFTConvertTime;
// -- Audio Data
const i2s_port_t I2S_PORT = I2S_NUM_0;
int8_t nowFilling = 0;
int32_t audioBuffers[NUM_BURSTS][BURST_SAMPLES]; // Storage for multiple Bursts of audio input. Allows overlaid save and read operations.
// -- FFT Data
int8_t windowing_type = FFT_WIN_TYP_HANN ;
int8_t nowProcessing = -1;
float vReal[FFT_SAMPLES];
float vImag[FFT_SAMPLES];
float weights[FFT_SAMPLES];
// -- LED Display Data
uint32_t bandValues[NUM_BANDS];
uint16_t bandMaxBin[NUM_BANDS] = {6,7,8,9,10,11,12,13,15,16,18,20,22,24,26,29,32,35,39,43,48,53,58,64,71,78,86,95,105,116,128,142,157,173,191,211,233,257,284,313,346,382,421,465,514,567,626,691,763,843,930,1027,1134,1252,1383,1526,1685,1861,2047};
// -- Object Constructors
arduinoFFT_float FFT = arduinoFFT_float(vReal, vImag, FFT_SAMPLES, SAMPLING_FREQ);
CRGB leds[NUM_LEDS];
// -- General Setup
void setup() {
// Setup High Speed Serial
Serial.begin(500000);
initDisplay();
Serial.println("\nCochlea Started.");
// Create Audio Sampling Task, and wait for Microphone to stabilize
xTaskCreatePinnedToCore(
AudioSample, /* Task function. */
"AudioSample", /* name of task. */
20000, /* Stack size of task */
NULL, /* parameter of the task */
1, /* priority of the task */
&AudioTask, /* Task handle to keep track of created task */
1); /* pin task to core 1 */
delay(2000);
// Create FFT and Display task
xTaskCreatePinnedToCore(
FFTcode, /* Task function. */
"FFT", /* name of task. */
40000, /* Stack size of task */
NULL, /* parameter of the task */
1, /* priority of the task */
&FFTTask, /* Task handle to keep track of created task */
0); /* pin task to core 0 */
delay(100);
}
// ###############################################################
// Core 1 Task.
// AudioSample: Sample the microphone and fill Burst buffers with audio
// ###############################################################
void AudioSample( void * pvParameters ){
int32_t sample;
size_t bytesRead;
uint32_t thisCycle;
uint32_t lastCycle;
// The I2S config as per the example
const i2s_config_t i2s_config = {
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), // Receive, not transfer
.sample_rate = SAMPLING_FREQ, //
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // Chosen Mic puts out 32 bits per channel
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // use left channel
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // Interrupt level 1
.dma_buf_count = 4, // number of buffers
.dma_buf_len = 128 // 128 samples per buffer (minimum)
};
// The pin config as per the setup
const i2s_pin_config_t pin_config = {
.bck_io_num = MIC_CLOCK_PIN, // Serial Clock (SCK)
.ws_io_num = MIC_SEL_PIN, // Word Select (WS)
.data_out_num = I2S_PIN_NO_CHANGE, // not used (only for speakers)
.data_in_num = MIC_DATA_PIN // Serial Data (SD)
};
Serial.print("Audio Sample Started on core ");
Serial.println(xPortGetCoreID());
// Configure the I2S driver and pins.
i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
i2s_set_pin(I2S_PORT, &pin_config);
delay(10);
// ======= Audio Sample LOOP =============
// Repeatedly fill sucessive Burst Buffers. Try not to have ANY discontinuity.
while(true){
thisCycle = micros();
audioCyclePeriod = (thisCycle - lastCycle);
lastCycle = thisCycle;
// Start filling the current Burst Buffer
i2s_read(I2S_PORT, (char *)(audioBuffers[nowFilling]), SIZEOF_BURST, &bytesRead, 100);
// Move onto next burst buffer if all bytes were read
if (bytesRead == SIZEOF_BURST)
nowFilling = (nowFilling + 1) % NUM_BURSTS ;
}
}
// ###############################################################
// Core 0 Task.
// FFTcode: Process each new Burst buffer and display it.
// ###############################################################
void FFTcode( void * pvParameters ){
uint32_t thisCycle;
uint32_t lastCycle;
Serial.print("FFT Started on core ");
Serial.println(xPortGetCoreID());
// Load up the windowing weights. Generate an array of weights based on type.
for (int i=0; i < FFT_SAMPLES; i++) {
weights[i] = 1.0;
}
FFT.Windowing(weights, FFT_SAMPLES, windowing_type, FFT_FORWARD);
// ======= FFT & Display LOOP =============
while(true){
// Wait for a new Burst buffer to start processing
if (nowProcessing != nowFilling) {
nowProcessing = nowFilling;
thisCycle = micros();
FFTCyclePeriod = (thisCycle - lastCycle);
lastCycle = thisCycle;
// Process the prior Burts as a single audio packet, then display the band intensities.
ComputeMyFFT();
updateDisplay();
FFTConvertTime = micros() - thisCycle;
// Give time for idle task to run.
}
delay(1);
}
}
// -- Display Processing & cycle times
void loop() {
Serial.print("AC=");
Serial.print(audioCyclePeriod);
Serial.print(" uS, FFTC=");
Serial.print(FFTCyclePeriod);
Serial.print(" uS, FFT=");
Serial.print(FFTConvertTime);
Serial.println(" uS");
delay(1000);
}
// -- Process a group of Audio bursts, and generate an array of frequency bins.
void ComputeMyFFT(void) {
int32_t bufferDC = 0;
int32_t * burstP;
// Determine DC component from Burst 0
burstP = audioBuffers[0];
for (uint16_t i = 0; i < BURST_SAMPLES; i++) {
bufferDC += (*burstP++ >> UNUSED_AUDIO_BITS);
}
bufferDC = bufferDC >> BURST_SAMPLES_AVG;
// transfer audio burts into "real values" while removing DC component.
for (uint8_t b = 0; b < BURSTS_PER_AUDIO; b++) {
// Get pointer for each sucessive Burst
burstP = audioBuffers[(nowProcessing + NUM_BURSTS + b - BURSTS_PER_AUDIO) % NUM_BURSTS];
uint16_t burstOffset = BURST_SAMPLES * b;
// Transfer audio samples from burst to Audio Packet and apply FFT wondow weight
for (uint16_t i = 0; i < BURST_SAMPLES; i++) {
vReal[burstOffset + i] = (float)((*burstP++ >> UNUSED_AUDIO_BITS ) - bufferDC) * weights[burstOffset + i];
}
}
// Clear out imaginary values and run the FFT and then convert to magnitudes
memset((void *)vImag, 0, sizeof(vImag));
FFT.Compute(FFT_FORWARD);
FFT.ComplexToMagnitude();
}
// Configure the LED string and preload the color values for each band.
void initDisplay(void) {
FastLED.addLeds<DOTSTAR, LED_DATA_PIN, LED_CLOCK_PIN, BGR>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
// preload the hue into each LED and set the saturation to full and brightness to low.
// This is a startup test to show that ALL LEDs are capable of displaying their base color.
for (int i = 0; i < NUM_BANDS; i++) {
leds[(i*2) ] = CHSV(i * BAND_HUE_STEP , 255, 100);
// leds[(i*2) + 1] = CHSV(i * BAND_HUE_STEP , 255, 100);
}
FastLED.show();
}
// Update the LED string based on the intensities of all the Frequency bins.
void updateDisplay (void){
uint16_t ledBrightness;
// Allocate FFT results into LED Band buckets (
fillBandBuckets ();
// Process the LED buckets into LED Intensities
for (byte band = 0; band < NUM_BANDS; band++) {
// Scale the bars for the display
ledBrightness = bandValues[band] / GAIN_DIVIDE; // To Do: Adjust this value with a port or buttons
if (ledBrightness > BRIGHTNESS)
ledBrightness = BRIGHTNESS;
// Display LED bucket in the correct Hue.
leds[(band * 2) ].setHSV(band * BAND_HUE_STEP, 255, ledBrightness);
// leds[(band * 2) + 1].setHSV(band * BAND_HUE_STEP, 255, ledBrightness);
}
// Update LED display
FastLED.show();
}
// Group Frequency Bins into Band Buckets based on the maximum nun number for each band
// Each band covers more bind because bins are linear and bands are logorithmic.
void fillBandBuckets (void){
uint16_t frequency;
uint16_t bucket;
uint16_t minBucket = 1; // skip over the DC level, and start with second freq.
uint16_t maxBucket = 0;
uint32_t bandValue;
uint32_t noiseFloor;
// zero out all the LED band magnitudes.
memset(bandValues, 0, sizeof(bandValues));
// Cycle through each of the LED bands. Set initial noise threshold high and drop down.
noiseFloor = START_NOISE_FLOOR;
minBucket = bandMaxBin[0];
for (int band = 0; band < NUM_BANDS; band++){
// get the new maximum freq for this band.
maxBucket = bandMaxBin[band];
// Accumulate freq values from all bins that match this LED band,
for (int bucket = minBucket; bucket <= maxBucket; bucket++) {
bandValue = (uint32_t)vReal[bucket];
if (bandValue > noiseFloor) {
bandValues[band] += bandValue;
}
}
// slide the max of this band to the min of next band.
minBucket = maxBucket + 1;
// Adjust Noise Floor
if (noiseFloor > BASE_NOISE_FLOOR) {
noiseFloor = 95 * noiseFloor / 100; // equiv 0.95 factor.
}
}
}