// Audio level visualizer for Adafruit Circuit Playground: uses the // built-in mic on A4, 10x NeoPixels for display. Like the FFT example, // the real work is done in the Circuit Playground library via the 'mic' // object; this code is almost entirely just dressing up the output with // a lot of averaging and scaling math and colors. #include #include #include // GLOBAL STUFF ------------------------------------------------------------ // To keep the display 'lively,' an upper and lower range of volume // levels are dynamically adjusted based on recent audio history, and // the graph is fit into this range. #define FRAMES 8 uint16_t lvl[FRAMES], // Audio level for the prior #FRAMES frames avgLo = 6, // Audio volume lower end of range avgHi = 512, // Audio volume upper end of range sum = 256 * FRAMES; // Sum of lvl[] array uint8_t lvlIdx = 0; // Counter into lvl[] array int16_t peak = 0; // Falling dot shows recent max int8_t peakV = 0; // Velocity of peak dot // SETUP FUNCTION - runs once ---------------------------------------------- void setup() { CircuitPlayground.begin(); CircuitPlayground.setBrightness(255); CircuitPlayground.clearPixels(); for(uint8_t i=0; i maxLvl) maxLvl = lvl[i]; } // Keep some minimum distance between min & max levels, // else the display gets "jumpy." if((maxLvl - minLvl) < 40) { maxLvl = (minLvl < (512-40)) ? minLvl + 40 : 512; } avgLo = (avgLo * 7 + minLvl + 2) / 8; // Dampen min/max levels avgHi = (maxLvl >= avgHi) ? // (fake rolling averages) (avgHi * 3 + maxLvl + 1) / 4 : // Fast rise (avgHi * 31 + maxLvl + 8) / 32; // Slow decay a = sum / FRAMES; // Average of lvl[] array if(a <= avgLo) { // Below min? scaled = 0; // Bargraph = zero } else { // Else scale to fixed-point coordspace 0-2560 scaled = 2560L * (a - avgLo) / (avgHi - avgLo); if(scaled > 2560) scaled = 2560; } if(scaled >= peak) { // New peak peakV = (scaled - peak) / 4; // Give it an upward nudge peak = scaled; } uint8_t whole = scaled / 256, // Full-brightness pixels (0-10) frac = scaled & 255; // Brightness of fractional pixel int whole2 = peak / 256, // Index of peak pixel frac2 = peak & 255; // Between-pixels position of peak uint16_t a1, a2; // Scaling factors for color blending for(i=0; i<10; i++) { // For each NeoPixel... if(i <= whole) { // In currently-lit area? r = pgm_read_byte(&reds[i]), // Look up pixel color g = pgm_read_byte(&greens[i]), b = pgm_read_byte(&blues[i]); if(i == whole) { // Fraction pixel at top of range? a1 = (uint16_t)frac + 1; // Fade toward black r = (r * a1) >> 8; g = (g * a1) >> 8; b = (b * a1) >> 8; } } else { r = g = b = 0; // In unlit area } // Composite the peak pixel atop whatever else is happening... if(i == whole2) { // Peak pixel? a1 = 256 - frac2; // Existing pixel blend factor 1-256 a2 = frac2 + 1; // Peak pixel blend factor 1-256 r = ((r * a1) + (0x84 * a2)) >> 8; // Will g = ((g * a1) + (0x87 * a2)) >> 8; // it b = ((b * a1) + (0xC3 * a2)) >> 8; // blend? } else if(i == (whole2-1)) { // Just below peak pixel a1 = frac2 + 1; // Opposite blend ratios to above, a2 = 256 - frac2; // but same idea r = ((r * a1) + (0x84 * a2)) >> 8; g = ((g * a1) + (0x87 * a2)) >> 8; b = ((b * a1) + (0xC3 * a2)) >> 8; } CircuitPlayground.strip.setPixelColor(i, pgm_read_byte(&gamma8[r]), pgm_read_byte(&gamma8[g]), pgm_read_byte(&gamma8[b])); } CircuitPlayground.strip.show(); peak += peakV; if(peak <= 0) { peak = 0; peakV = 0; } else if(peakV >= -126) { peakV -= 2; } if(++lvlIdx >= FRAMES) lvlIdx = 0; }