source: arduino-1-6-7/trunk/fuentes/arduino-ide-amd64/libraries/Adafruit_CircuitPlayground/examples/Microphone/mic_fft/mic_fft.ino @ 4837

Last change on this file since 4837 was 4837, checked in by daduve, 3 years ago

Adding new version

File size: 9.9 KB
Line 
1// FFT-based audio visualizer for Adafruit Circuit Playground: uses the
2// built-in mic on A4, 10x NeoPixels for display.  Built on the ELM-Chan
3// FFT library for AVR microcontrollers.
4
5// The fast Fourier transform (FFT) algorithm converts a signal from the
6// time domain to the frequency domain -- e.g. turning a sampled audio
7// signal into a visualization of frequencies and magnitudes -- an EQ meter.
8
9// The FFT algorithm itself is handled in the Circuit Playground library;
10// the code here is mostly for converting that function's output into
11// animation.  In most AV gear it's usually done with bargraph displays;
12// with a 1D output (the 10 NeoPixels) we need to get creative with color
13// and brightness...it won't look great in every situation (seems to work
14// best with LOUD music), but it's colorful and fun to look at.  So this
15// code is mostly a bunch of tables and weird fixed-point (integer) math
16// that probably doesn't make much sense even with all these comments.
17
18#include <Adafruit_CircuitPlayground.h>
19#include <Wire.h>
20#include <SPI.h>
21
22// GLOBAL STUFF ------------------------------------------------------------
23
24// Displaying EQ meter output straight from the FFT may be 'correct,' but
25// isn't always visually interesting (most bins spend most time near zero).
26// Dynamic level adjustment narrows in on a range of values so there's
27// always something going on.  The upper and lower range are based on recent
28// audio history, and on a per-bin basis (some may be more active than
29// others, so this keeps one or two "loud" bins from spoiling the rest.
30
31#define BINS   10          // FFT output is filtered down to this many bins
32#define FRAMES 4           // This many FFT cycles are averaged for leveling
33uint8_t lvl[FRAMES][BINS], // Bin levels for the prior #FRAMES frames
34        avgLo[BINS],       // Pseudo rolling averages for bins -- lower and
35        avgHi[BINS],       // upper limits -- for dynamic level adjustment.
36        frameIdx = 0;      // Counter for lvl storage
37
38// CALIBRATION CONSTANTS ---------------------------------------------------
39
40const uint8_t PROGMEM
41  // Low-level noise initially subtracted from each of 32 FFT bins
42  noise[]    = { 0x04,0x03,0x03,0x03, 0x02,0x02,0x02,0x02,
43                 0x02,0x02,0x02,0x02, 0x01,0x01,0x01,0x01,
44                 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01,
45                 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01 },
46  // FFT bins (32) are then filtered down to 10 output bins (to match the
47  // number of NeoPixels on Circuit Playground).  10 arrays here, one per
48  // output bin.  First element of each is the number of input bins to
49  // merge, second element is index of first merged bin, remaining values
50  // are scaling weights as each input bin is merged into output.  The
51  // merging also "de-linearizes" the FFT output, so it's closer to a
52  // logarithmic scale with octaves evenly-ish spaced, music looks better.
53  bin0data[] = { 1, 2, 147 },
54  bin1data[] = { 2, 2, 89, 14 },
55  bin2data[] = { 2, 3, 89, 14 },
56  bin3data[] = { 4, 3, 15, 181, 58, 3 },
57  bin4data[] = { 4, 4, 15, 181, 58, 3 },
58  bin5data[] = { 6, 5, 6, 89, 185, 85, 14, 2 },
59  bin6data[] = { 7, 7, 5, 60, 173, 147, 49, 9, 1 },
60  bin7data[] = { 10, 8, 3, 23, 89, 170, 176, 109, 45, 14, 4, 1 },
61  bin8data[] = { 13, 11, 2, 12, 45, 106, 167, 184, 147, 89, 43, 18, 6, 2, 1 },
62  bin9data[] = { 18, 14, 2, 6, 19, 46, 89, 138, 175, 185, 165, 127, 85, 51, 27, 14, 7, 3, 2, 1 },
63  // Pointers to 10 bin arrays, because PROGMEM arrays-of-arrays are weird:
64  * const binData[] = { bin0data, bin1data, bin2data, bin3data, bin4data,
65                        bin5data, bin6data, bin7data, bin8data, bin9data },
66  // R,G,B values for color wheel covering 10 NeoPixels:
67  reds[]   = { 0xAD, 0x9A, 0x84, 0x65, 0x00, 0x00, 0x00, 0x00, 0x65, 0x84 },
68  greens[] = { 0x00, 0x66, 0x87, 0x9E, 0xB1, 0x87, 0x66, 0x00, 0x00, 0x00 },
69  blues[]  = { 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xE4, 0xFF, 0xE4, 0xC3 },
70  gamma8[] = { // Gamma correction improves the appearance of midrange colors
71    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
72    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
73    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
74    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03,
75    0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06,
76    0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09,
77    0x0A, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0E,
78    0x0E, 0x0F, 0x0F, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x13, 0x13, 0x14,
79    0x14, 0x15, 0x15, 0x16, 0x16, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1B,
80    0x1B, 0x1C, 0x1D, 0x1D, 0x1E, 0x1F, 0x1F, 0x20, 0x21, 0x22, 0x22, 0x23,
81    0x24, 0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2A, 0x2B, 0x2C, 0x2D,
82    0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
83    0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x44, 0x45, 0x46,
84    0x47, 0x48, 0x49, 0x4B, 0x4C, 0x4D, 0x4E, 0x50, 0x51, 0x52, 0x54, 0x55,
85    0x56, 0x58, 0x59, 0x5A, 0x5C, 0x5D, 0x5E, 0x60, 0x61, 0x63, 0x64, 0x66,
86    0x67, 0x69, 0x6A, 0x6C, 0x6D, 0x6F, 0x70, 0x72, 0x73, 0x75, 0x77, 0x78,
87    0x7A, 0x7C, 0x7D, 0x7F, 0x81, 0x82, 0x84, 0x86, 0x88, 0x89, 0x8B, 0x8D,
88    0x8F, 0x91, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA4,
89    0xA6, 0xA8, 0xAA, 0xAC, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC,
90    0xBF, 0xC1, 0xC3, 0xC5, 0xC7, 0xCA, 0xCC, 0xCE, 0xD1, 0xD3, 0xD5, 0xD7,
91    0xDA, 0xDC, 0xDF, 0xE1, 0xE3, 0xE6, 0xE8, 0xEB, 0xED, 0xF0, 0xF2, 0xF5,
92    0xF7, 0xFA, 0xFC, 0xFF };
93const uint16_t PROGMEM
94  // Scaling values applied to each FFT bin (32) after noise subtraction
95  // but prior to merging/filtering.  When multiplied by these values,
96  // then divided by 256, these tend to produce outputs in the 0-255
97  // range (VERY VERY "ISH") at normal listening levels.  These were
98  // determined empirically by throwing lots of sample audio at it.
99  binMul[] = { 405, 508, 486, 544, 533, 487, 519, 410,
100               481, 413, 419, 410, 397, 424, 412, 411,
101               511, 591, 588, 577, 554, 529, 524, 570,
102               546, 559, 511, 552, 439, 488, 483, 547, },
103  // Sums of bin weights for bin-merging tables above.
104  binDiv[]   = { 147, 103, 103, 257, 257, 381, 444, 634, 822, 1142 };
105
106// SETUP FUNCTION - runs once ----------------------------------------------
107
108void setup() {
109  CircuitPlayground.begin();
110  CircuitPlayground.setBrightness(255);
111  CircuitPlayground.clearPixels();
112
113  // Initialize rolling average ranges
114  uint8_t i;
115  for(i=0; i<BINS; i++) {
116    avgLo[i] = 0;
117    avgHi[i] = 255;
118  }
119  for(i=0; i<FRAMES; i++) {
120    memset(&lvl[i], 127, sizeof(lvl[i]));
121  }
122}
123
124// LOOP FUNCTION - runs over and over - does animation ---------------------
125
126void loop() {
127  uint16_t spectrum[32]; // FFT spectrum output buffer
128
129  CircuitPlayground.mic.fft(spectrum);
130
131  // spectrum[] is now raw FFT output, 32 bins.
132
133  // Remove noise and apply EQ levels
134  uint8_t  i, N;
135  uint16_t S;
136  for(i=0; i<32; i++) {
137    N = pgm_read_byte(&noise[i]);
138    if(spectrum[i] > N) { // Above noise threshold: scale & clip
139      S           = ((spectrum[i] - N) *
140                     (uint32_t)pgm_read_word(&binMul[i])) >> 8;
141      spectrum[i] = (S < 255) ? S : 255;
142    } else { // Below noise threshold: clip
143      spectrum[i] = 0;
144    }
145  }
146  // spectrum[] is now noise-filtered, scaled & clipped
147  // FFT output, in range 0-255, still 32 bins.
148
149  // Filter spectrum[] from 32 elements down to 10,
150  // make pretty colors out of it:
151
152  uint16_t sum, level;
153  uint8_t  j, minLvl, maxLvl, nBins, binNum, *data;
154
155  for(i=0; i<BINS; i++) { // For each output bin (and each pixel)...
156    data   = (uint8_t *)pgm_read_word(&binData[i]);
157    nBins  = pgm_read_byte(&data[0]); // Number of input bins to merge
158    binNum = pgm_read_byte(&data[1]); // Index of first input bin
159    data  += 2;
160    for(sum=0, j=0; j<nBins; j++) {
161      sum += spectrum[binNum++] * pgm_read_byte(&data[j]); // Total
162    }
163    sum /= pgm_read_word(&binDiv[i]);                      // Average
164    lvl[frameIdx][i] = sum;      // Save for rolling averages
165    minLvl = maxLvl = lvl[0][i]; // Get min and max range for bin
166    for(j=1; j<FRAMES; j++) {    // from prior stored frames
167      if(lvl[j][i] < minLvl)      minLvl = lvl[j][i];
168      else if(lvl[j][i] > maxLvl) maxLvl = lvl[j][i];
169    }
170
171    // minLvl and maxLvl indicate the extents of the FFT output for this
172    // bin over the past few frames, used for vertically scaling the output
173    // graph (so it looks interesting regardless of volume level).  If too
174    // close together though (e.g. at very low volume levels) the graph
175    // becomes super coarse and 'jumpy'...so keep some minimum distance
176    // between them (also lets the graph go to zero when no sound playing):
177    if((maxLvl - minLvl) < 23) {
178      maxLvl = (minLvl < (255-23)) ? minLvl + 23 : 255;
179    }
180    avgLo[i] = (avgLo[i] * 7 + minLvl) / 8; // Dampen min/max levels
181    avgHi[i] = (maxLvl >= avgHi[i]) ?       // (fake rolling averages)
182      (avgHi[i] *  3 + maxLvl) /  4 :       // Fast rise
183      (avgHi[i] * 31 + maxLvl) / 32;        // Slow decay
184
185    // Second fixed-point scale then 'stretches' each bin based on
186    // dynamic min/max levels to 0-256 range:
187    level = 1 + ((sum <= avgLo[i]) ? 0 :
188                 256L * (sum - avgLo[i]) / (long)(avgHi[i] - avgLo[i]));
189    // Clip output and convert to color:
190    if(level <= 255) {
191      uint8_t r = (pgm_read_byte(&reds[i])   * level) >> 8,
192              g = (pgm_read_byte(&greens[i]) * level) >> 8,
193              b = (pgm_read_byte(&blues[i])  * level) >> 8;
194      CircuitPlayground.strip.setPixelColor(i,
195        pgm_read_byte(&gamma8[r]),
196        pgm_read_byte(&gamma8[g]),
197        pgm_read_byte(&gamma8[b]));
198    } else { // level = 256, show white pixel OONTZ OONTZ
199      CircuitPlayground.strip.setPixelColor(i, 0x56587F);
200    }
201  }
202  CircuitPlayground.strip.show();
203
204  if(++frameIdx >= FRAMES) frameIdx = 0;
205}
Note: See TracBrowser for help on using the repository browser.