added arduino, modified build

This commit is contained in:
2020-02-02 15:28:36 -08:00
parent 0189d519c6
commit 6480bc593f
3583 changed files with 1305025 additions and 247 deletions

View File

@@ -0,0 +1,464 @@
// Circuit Playground Birthday Candles
//
// All the NeoPixels will flicker like candles and the speaker will
// play the tune to Happy Birthday continuously. Blow on the board
// to slowly blow out all the candles and hear a victory song!
// Controls:
// - Slide switch: Move this to +/on to hear music or -/off to disable sound.
// - Left button: Hold this down and press reset to use a rainbow flicker color.
// - Right button: Hold this down and press reset to use a simple solid color (no flicker).
// - If neither left or right button are pressed at startup then a flame-like
// flickering animation will be used.
// After all the candles are blown out press reset to reset and start over!
//
// Author: Tony DiCola
// License: MIT (https://opensource.org/licenses/MIT)
#include <Adafruit_CircuitPlayground.h>
// General configuration defines:
#define BREATH_THRESHOLD 92 // Peak to peak sound pressure level that
// determines if someone is blowing on the board.
// You can open the serial console to see the sound
// levels and adjust as necessary!
#define FLAME_LIFE_MS 200 // Amount of time (in milliseconds) that each
// candle flame takes to blow out. Increase this
// to make it harder/slower to blow them all out
// and decrease it to make it easier/faster.
#define FLAME_HUE 35 // Primary hue of the flame. This is a value in
// degrees from 0.0 to 360.0, see HSV color space
// for different hue values:
// https://en.wikipedia.org/wiki/HSL_and_HSV#/media/File:Hsl-hsv_models.svg
// The value 35 degrees is a nice orange in the
// middle of red and yellow hues that will look like
// a flame flickering as the hues animate.
// For the flicker effect the pixels will cycle
// around hues that are +/-10 degrees of this value.
// For the solid effect the pixels will be set
// to this hue (at full saturation and value).
// Rainbow effect ignores this hue config and
// cycles through all of them.
#define LIT_CANDLES 10 // The number of candles to start with lit. By
// default all 10 candles/pixels will be lit but
// adjust down to light less for a young kid's
// birthday.
// A few music note frequencies as defined in this tone example:
// https://www.arduino.cc/en/Tutorial/toneMelody
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
// Define note durations. You only need to adjust the whole note
// time and other notes will be subdivided from it directly.
#define WHOLE 2200 // Length of time in milliseconds of a whole note (i.e. a full bar).
#define HALF WHOLE/2
#define QUARTER HALF/2
#define EIGHTH QUARTER/2
#define EIGHTH_TRIPLE QUARTER/3
#define SIXTEENTH EIGHTH/2
// Color gamma correction table, this makes the hues of the NeoPixels
// a little more accurate by accounting for our eye's non-linear color
// sensitivity. See this great guide for more details:
// https://learn.adafruit.com/led-tricks-gamma-correction/the-issue
const uint8_t PROGMEM gamma8[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };
// Global program state.
int lit = LIT_CANDLES; // How many candles are still lit. Once all are extinguished this falls to -1.
float frequencies[10] = {0}; // Random frequencies and phases will be generated for each pixel to
float phases[10] = {0}; // define an organic but random looking flicker effect.
enum AnimationType { SOLID, FLICKER, RAINBOW }; // Which type of animation is currently running.
AnimationType animation = FLICKER;
// Play a note of the specified frequency and for the specified duration.
// Hold is an optional bool that specifies if this note should be held a
// little longer, i.e. for eigth notes that are tied together.
// While waiting for a note to play the waitBreath delay function is used
// so breath detection and pixel animation continues to run. No tones
// will play if the slide switch is in the -/off position or all the
// candles have been blown out.
void playNote(int frequency, int duration, bool hold=false, bool measure=true) {
// Check if the slide switch is off or the candles have been blown out
// and stop immediately without playing anything.
if (!CircuitPlayground.slideSwitch() || lit < 0) {
return;
}
if (hold) {
// For a note that's held play it a little longer than the specified duration
// so it blends into the next tone (but there's still a small delay to
// hear the next note).
CircuitPlayground.playTone(frequency, duration + duration/32, false);
}
else {
// For a note that isn't held just play it for the specified duration.
CircuitPlayground.playTone(frequency, duration, false);
}
// Use waitBreath instead of Arduino's delay because breath detection and
// pixel animation needs to occur while music plays. However skip this logic
// if not asked for (measure = false, only needed when playing celebration
// song so as to not continue waiting for breaths).
if (measure) {
waitBreath(duration + duration/16);
}
else {
delay(duration + duration/16);
}
}
// Delay for the specified number of milliseconds while at the same time
// listening for a continuous loud sound on from the microphone, i.e. someone
// blowing directly on it, and animating the NeoPixels. When a breath is detected
// the number of lit candles will gradually decrease. Once the number of lit
// candles hits zero then a celebration tune is played and everything stops.
void waitBreath(uint32_t milliseconds) {
float peakToPeak = measurePeak(milliseconds);
// Serial.println(peakToPeak);
while (peakToPeak >= BREATH_THRESHOLD) {
// Decrement the number of lit candles and keep it from going below
// the value -1 (a sentinel that indicates all the candles are blown out
// and no music playback, etc. should occur anymore).
lit = max(lit-1, -1);
// For the simple solid color animation (i.e. no flickering) only update
// the pixels when the lit pixel count changes. This allows the tone
// playback to sound better because the pixels don't need to be updated
// during delays and music note playback (the pixel writing messes with
// interrupts that drive tone playback and cause scratchier sounding tones).
if (animation == SOLID) {
showLitSolid();
}
// Check if we just hit 0 candles lit up, i.e. they're all blown out.
// Turn off the pixels and play a little celebration tune when it
// happens, then indicate the candles are blown out with the -1 sentinel value.
if (lit == 0) {
CircuitPlayground.clearPixels();
celebrateSong();
lit = -1;
}
// Keep measuring the peak to peak sound value for a period of time
// that it takes to blow out another candle.
peakToPeak = measurePeak(FLAME_LIFE_MS);
}
}
// Song to play when the candles are blown out.
void celebrateSong() {
// Play a little charge melody, from:
// https://en.wikipedia.org/wiki/Charge_(fanfare)
// Note the explicit boolean parameters in particular the measure=false
// at the end. This means the notes will play without any breath measurement
// logic. Without this false value playNote will try to keep waiting for candles
// to blow out during the celebration song!
playNote(NOTE_G4, EIGHTH_TRIPLE, true, false);
playNote(NOTE_C5, EIGHTH_TRIPLE, true, false);
playNote(NOTE_E5, EIGHTH_TRIPLE, false, false);
playNote(NOTE_G5, EIGHTH, true, false);
playNote(NOTE_E5, SIXTEENTH, false);
playNote(NOTE_G5, HALF, false);
}
// Measure the peak to peak sound pressure level from the microphone
// for a specified number of milliseconds.
// While measuring the current NeoPixel animation will be updated too.
float measurePeak(uint32_t milliseconds) {
float soundMax = 0;
// Loop until the specified number of milliseconds have ellapsed.
uint32_t start = millis();
uint32_t current = start;
while ((current - start) < milliseconds) {
// Inside the loop check the sound pressure level 10ms at a time
float sample = CircuitPlayground.mic.soundPressureLevel(10);
Serial.println(sample);
soundMax = max(sample, soundMax);
// Be sure to drive the NeoPixel animation too.
animatePixels(current);
current = millis();
}
return soundMax;
}
// Perform a frame of animation on the NeoPixels.
// Current is the current monotonically increasing time in milliseconds.
void animatePixels(uint32_t current) {
switch (animation) {
case FLICKER:
showLitFlicker(current);
break;
case RAINBOW:
showLitRainbow(current);
break;
// Ignore the SOLID case as it has no animation.
// This makes the audio smoother since it doesn't get interrupted by
// NeoPixel writing like the other animations. The pixels are instead
// changed only once when the number of lit candles changes (see the
// waitBreath function's loop).
}
}
// Helper to change the color of a NeoPixel on the Circuit Playground board.
// Will automatically convert from HSV color space to RGB and apply gamma
// correction.
float setPixelHSV(int i, float h, float s, float v) {
// Convert HSV to RGB
float r, g, b = 0.0;
HSVtoRGB(&r, &g, &b, h, s, v);
// Lookup gamma correct RGB colors (also convert from 0...1.0 RGB range to 0...255 byte range).
uint8_t r1 = pgm_read_byte(&gamma8[int(r*255.0)]);
uint8_t g1 = pgm_read_byte(&gamma8[int(g*255.0)]);
uint8_t b1 = pgm_read_byte(&gamma8[int(b*255.0)]);
// Set the color of the pixel.
CircuitPlayground.strip.setPixelColor(i, r1, g1, b1);
}
// Rainbow candle flicker animation.
// Uses a sine wave with random frequency and phase (computed ahead of time in setup)
// to smoothly modulate the hue of each NeoPixel candle flame over time.
void showLitRainbow(uint32_t current) {
// Convert time from milliseconds to seconds.
float t = current/1000.0;
// Loop through each pixel and compute its color.
for (int i=0; i<10; ++i) {
if (i < lit) {
// This pixel should be lit, so compute its hue from the sine wave
// equation and set the color accordingly. Notice the frequency
// is scaled down by 10 to 'slow down' the rainbow flicker animation.
// This lets the same random frequencies be shared between fast candle
// flame effects and this slower rainbow flicker effect.
float x = sin(2.0*PI*frequencies[i]/10.0*t + phases[i]);
// Interpolate the sine wave between all 360 degree hue values.
float h = lerp(x, -1.0, 1.0, 0.0, 360.0);
setPixelHSV(i, h, 1.0, 1.0);
}
else {
// This pixel has been blown out, just turn it off.
setPixelHSV(i, 0, 0, 0);
}
}
CircuitPlayground.strip.show();
}
// Flickering candle animation effect.
// Uses a noisey sine wave to modulate the hue of each pixel within a range
// of flame colors. The sine wave has some low and high frequency components
// which give it an organice and seemingly random appearance.
void showLitFlicker(uint32_t current) {
// First determine the low and high bounds of the flicker hues.
// These are +/- 10 degrees of the specified target hue and will
// wrap around to the start/end as appropriate.
float lowHue = fmod(FLAME_HUE - 10.0, 360.0);
float highHue = fmod(FLAME_HUE + 10.0, 360.0);
// Convert time from milliseconds to seconds.
float t = current/1000.0;
// Loop through each pixel and compute its color.
for (int i=0; i<10; ++i) {
if (i < lit) {
// This pixel should be lit, so compute its hue by composing
// a low frequency / slowly changing sine wave with a high
// frequency / fast changing cosine wave. This means the candle will
// pulse and jump around in an organice but random looking way.
// The frequencies and phases of the waves are randomly generated at
// startup in the setup function.
// Low frequency wave is a sine wave with random freuqency between 1 and 4,
// and offset by a random phase to keep pixels from all starting at the same
// color:
float lowFreq = sin(2.0*PI*frequencies[i]*t + phases[i]);
// High frequency is a faster changing cosine wave that uses a different
// pixel's random frequency.
float highFreq = cos(3.0*PI*frequencies[(i+5)%10]*t);
// Add the low and high frequency waves together, then interpolate their value
// to a hue that's +/-20% of the configured target hue.
float h = lerp(lowFreq+highFreq, -2.0, 2.0, lowHue, highHue);
setPixelHSV(i, h, 1.0, 1.0);
}
else {
// This pixel has been blown out, just turn it off.
setPixelHSV(i, 0, 0, 0);
}
}
CircuitPlayground.strip.show();
}
// Simple solid lit candle effect.
// No animation, each pixel is lit with the specified flame hue until they're all blown out.
void showLitSolid() {
for (int i=0; i<10; ++i) {
if (i < lit) {
// This pixel should be lit.
setPixelHSV(i, FLAME_HUE, 1.0, 1.0);
}
else {
// This pixel has been blown out, just turn it off.
setPixelHSV(i, 0, 0, 0);
}
}
CircuitPlayground.strip.show();
}
// HSV to RGB color space conversion function taken directly from:
// https://www.cs.rit.edu/~ncs/color/t_convert.html
void HSVtoRGB( float *r, float *g, float *b, float h, float s, float v )
{
int i;
float f, p, q, t;
if( s == 0 ) {
// achromatic (grey)
*r = *g = *b = v;
return;
}
h /= 60; // sector 0 to 5
i = floor( h );
f = h - i; // factorial part of h
p = v * ( 1 - s );
q = v * ( 1 - s * f );
t = v * ( 1 - s * ( 1 - f ) );
switch( i ) {
case 0:
*r = v;
*g = t;
*b = p;
break;
case 1:
*r = q;
*g = v;
*b = p;
break;
case 2:
*r = p;
*g = v;
*b = t;
break;
case 3:
*r = p;
*g = q;
*b = v;
break;
case 4:
*r = t;
*g = p;
*b = v;
break;
default: // case 5:
*r = v;
*g = p;
*b = q;
break;
}
}
// Linear interpolation of value y within y0...y1 given x and x0...x1.
float lerp(float x, float x0, float x1, float y0, float y1) {
return y0 + (x-x0)*((y1-y0)/(x1-x0));
}
void setup() {
// Initialize serial output and Circuit Playground library.
Serial.begin(115200);
CircuitPlayground.begin();
// Check if a button is being pressed at startup and change the
// animation mode accordingly.
if (CircuitPlayground.leftButton()) {
// Rainbow animation on left button press at startup.
animation = RAINBOW;
}
else if (CircuitPlayground.rightButton()) {
// Solid color animation on right button press at startup.
animation = SOLID;
// Since the solid animation doesn't update every frame, bootstrap it by
// turning all the pixels on at the start. As candles are blown out the
// pixels will be updated (see the waitBreath function's loop).
showLitSolid();
}
else {
// Otherwise default to flicker animation.
animation = FLICKER;
}
// Read the sound sensor and use it to initialize the random number generator.
randomSeed(CircuitPlayground.soundSensor());
// Precompute random frequency and phase values for each pixel.
// This gives the flicker an organic but random looking appearance.
for (int i=0; i<10; ++i) {
// Generate random floating point frequency values between 1.0 and 4.0.
frequencies[i] = random(1000, 4000)/1000.0;
// Generate random floating point phase values between 0 and 2*PI.
phases[i] = random(1000)/1000.0 * 2.0 * PI;
}
}
void loop() {
// Play happy birthday tune, from:
// http://www.irish-folk-songs.com/happy-birthday-tin-whistle-sheet-music.html#.WXFJMtPytBw
// Inside each playNote call it will play a note and drive the NeoPixel animation
// and check for a breath against the sound sensor. Once all the candles are blown out
// the playNote calls will stop playing music.
playNote(NOTE_D4, EIGHTH, true);
playNote(NOTE_D4, EIGHTH);
playNote(NOTE_E4, QUARTER); // Bar 1
playNote(NOTE_D4, QUARTER);
playNote(NOTE_G4, QUARTER);
playNote(NOTE_FS4, HALF); // Bar 2
playNote(NOTE_D4, EIGHTH, true);
playNote(NOTE_D4, EIGHTH);
playNote(NOTE_E4, QUARTER); // Bar 3
playNote(NOTE_D4, QUARTER);
playNote(NOTE_A4, QUARTER);
playNote(NOTE_G4, HALF); // Bar 4
playNote(NOTE_D4, EIGHTH, true);
playNote(NOTE_D4, EIGHTH);
playNote(NOTE_D5, QUARTER); // Bar 5
playNote(NOTE_B4, QUARTER);
playNote(NOTE_G4, QUARTER);
playNote(NOTE_FS4, QUARTER); // Bar 6
playNote(NOTE_E4, QUARTER);
playNote(NOTE_C5, EIGHTH, true);
playNote(NOTE_C5, EIGHTH);
playNote(NOTE_B4, QUARTER); // Bar 7
playNote(NOTE_G4, QUARTER);
playNote(NOTE_A4, QUARTER);
playNote(NOTE_G4, HALF); // Bar 8
// One second pause before repeating the loop and playing
// the tune again. Use waitBreath instead of delay so the
// pixel animation and breath check continues to happen.
waitBreath(1000);
}

View File

@@ -0,0 +1,205 @@
// FFT-based audio visualizer for Adafruit Circuit Playground: uses the
// built-in mic on A4, 10x NeoPixels for display. Built on the ELM-Chan
// FFT library for AVR microcontrollers.
// The fast Fourier transform (FFT) algorithm converts a signal from the
// time domain to the frequency domain -- e.g. turning a sampled audio
// signal into a visualization of frequencies and magnitudes -- an EQ meter.
// The FFT algorithm itself is handled in the Circuit Playground library;
// the code here is mostly for converting that function's output into
// animation. In most AV gear it's usually done with bargraph displays;
// with a 1D output (the 10 NeoPixels) we need to get creative with color
// and brightness...it won't look great in every situation (seems to work
// best with LOUD music), but it's colorful and fun to look at. So this
// code is mostly a bunch of tables and weird fixed-point (integer) math
// that probably doesn't make much sense even with all these comments.
#include <Adafruit_CircuitPlayground.h>
#include <Wire.h>
#include <SPI.h>
// GLOBAL STUFF ------------------------------------------------------------
// Displaying EQ meter output straight from the FFT may be 'correct,' but
// isn't always visually interesting (most bins spend most time near zero).
// Dynamic level adjustment narrows in on a range of values so there's
// always something going on. The upper and lower range are based on recent
// audio history, and on a per-bin basis (some may be more active than
// others, so this keeps one or two "loud" bins from spoiling the rest.
#define BINS 10 // FFT output is filtered down to this many bins
#define FRAMES 4 // This many FFT cycles are averaged for leveling
uint8_t lvl[FRAMES][BINS], // Bin levels for the prior #FRAMES frames
avgLo[BINS], // Pseudo rolling averages for bins -- lower and
avgHi[BINS], // upper limits -- for dynamic level adjustment.
frameIdx = 0; // Counter for lvl storage
// CALIBRATION CONSTANTS ---------------------------------------------------
const uint8_t PROGMEM
// Low-level noise initially subtracted from each of 32 FFT bins
noise[] = { 0x04,0x03,0x03,0x03, 0x02,0x02,0x02,0x02,
0x02,0x02,0x02,0x02, 0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01 },
// FFT bins (32) are then filtered down to 10 output bins (to match the
// number of NeoPixels on Circuit Playground). 10 arrays here, one per
// output bin. First element of each is the number of input bins to
// merge, second element is index of first merged bin, remaining values
// are scaling weights as each input bin is merged into output. The
// merging also "de-linearizes" the FFT output, so it's closer to a
// logarithmic scale with octaves evenly-ish spaced, music looks better.
bin0data[] = { 1, 2, 147 },
bin1data[] = { 2, 2, 89, 14 },
bin2data[] = { 2, 3, 89, 14 },
bin3data[] = { 4, 3, 15, 181, 58, 3 },
bin4data[] = { 4, 4, 15, 181, 58, 3 },
bin5data[] = { 6, 5, 6, 89, 185, 85, 14, 2 },
bin6data[] = { 7, 7, 5, 60, 173, 147, 49, 9, 1 },
bin7data[] = { 10, 8, 3, 23, 89, 170, 176, 109, 45, 14, 4, 1 },
bin8data[] = { 13, 11, 2, 12, 45, 106, 167, 184, 147, 89, 43, 18, 6, 2, 1 },
bin9data[] = { 18, 14, 2, 6, 19, 46, 89, 138, 175, 185, 165, 127, 85, 51, 27, 14, 7, 3, 2, 1 },
// Pointers to 10 bin arrays, because PROGMEM arrays-of-arrays are weird:
* const binData[] = { bin0data, bin1data, bin2data, bin3data, bin4data,
bin5data, bin6data, bin7data, bin8data, bin9data },
// R,G,B values for color wheel covering 10 NeoPixels:
reds[] = { 0xAD, 0x9A, 0x84, 0x65, 0x00, 0x00, 0x00, 0x00, 0x65, 0x84 },
greens[] = { 0x00, 0x66, 0x87, 0x9E, 0xB1, 0x87, 0x66, 0x00, 0x00, 0x00 },
blues[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xE4, 0xFF, 0xE4, 0xC3 },
gamma8[] = { // Gamma correction improves the appearance of midrange colors
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06,
0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09,
0x0A, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0E,
0x0E, 0x0F, 0x0F, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x13, 0x13, 0x14,
0x14, 0x15, 0x15, 0x16, 0x16, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1B,
0x1B, 0x1C, 0x1D, 0x1D, 0x1E, 0x1F, 0x1F, 0x20, 0x21, 0x22, 0x22, 0x23,
0x24, 0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2A, 0x2B, 0x2C, 0x2D,
0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x44, 0x45, 0x46,
0x47, 0x48, 0x49, 0x4B, 0x4C, 0x4D, 0x4E, 0x50, 0x51, 0x52, 0x54, 0x55,
0x56, 0x58, 0x59, 0x5A, 0x5C, 0x5D, 0x5E, 0x60, 0x61, 0x63, 0x64, 0x66,
0x67, 0x69, 0x6A, 0x6C, 0x6D, 0x6F, 0x70, 0x72, 0x73, 0x75, 0x77, 0x78,
0x7A, 0x7C, 0x7D, 0x7F, 0x81, 0x82, 0x84, 0x86, 0x88, 0x89, 0x8B, 0x8D,
0x8F, 0x91, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA4,
0xA6, 0xA8, 0xAA, 0xAC, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC,
0xBF, 0xC1, 0xC3, 0xC5, 0xC7, 0xCA, 0xCC, 0xCE, 0xD1, 0xD3, 0xD5, 0xD7,
0xDA, 0xDC, 0xDF, 0xE1, 0xE3, 0xE6, 0xE8, 0xEB, 0xED, 0xF0, 0xF2, 0xF5,
0xF7, 0xFA, 0xFC, 0xFF };
const uint16_t PROGMEM
// Scaling values applied to each FFT bin (32) after noise subtraction
// but prior to merging/filtering. When multiplied by these values,
// then divided by 256, these tend to produce outputs in the 0-255
// range (VERY VERY "ISH") at normal listening levels. These were
// determined empirically by throwing lots of sample audio at it.
binMul[] = { 405, 508, 486, 544, 533, 487, 519, 410,
481, 413, 419, 410, 397, 424, 412, 411,
511, 591, 588, 577, 554, 529, 524, 570,
546, 559, 511, 552, 439, 488, 483, 547, },
// Sums of bin weights for bin-merging tables above.
binDiv[] = { 147, 103, 103, 257, 257, 381, 444, 634, 822, 1142 };
// SETUP FUNCTION - runs once ----------------------------------------------
void setup() {
CircuitPlayground.begin();
CircuitPlayground.setBrightness(255);
CircuitPlayground.clearPixels();
// Initialize rolling average ranges
uint8_t i;
for(i=0; i<BINS; i++) {
avgLo[i] = 0;
avgHi[i] = 255;
}
for(i=0; i<FRAMES; i++) {
memset(&lvl[i], 127, sizeof(lvl[i]));
}
}
// LOOP FUNCTION - runs over and over - does animation ---------------------
void loop() {
uint16_t spectrum[32]; // FFT spectrum output buffer
CircuitPlayground.mic.fft(spectrum);
// spectrum[] is now raw FFT output, 32 bins.
// Remove noise and apply EQ levels
uint8_t i, N;
uint16_t S;
for(i=0; i<32; i++) {
N = pgm_read_byte(&noise[i]);
if(spectrum[i] > N) { // Above noise threshold: scale & clip
S = ((spectrum[i] - N) *
(uint32_t)pgm_read_word(&binMul[i])) >> 8;
spectrum[i] = (S < 255) ? S : 255;
} else { // Below noise threshold: clip
spectrum[i] = 0;
}
}
// spectrum[] is now noise-filtered, scaled & clipped
// FFT output, in range 0-255, still 32 bins.
// Filter spectrum[] from 32 elements down to 10,
// make pretty colors out of it:
uint16_t sum, level;
uint8_t j, minLvl, maxLvl, nBins, binNum, *data;
for(i=0; i<BINS; i++) { // For each output bin (and each pixel)...
data = (uint8_t *)pgm_read_word(&binData[i]);
nBins = pgm_read_byte(&data[0]); // Number of input bins to merge
binNum = pgm_read_byte(&data[1]); // Index of first input bin
data += 2;
for(sum=0, j=0; j<nBins; j++) {
sum += spectrum[binNum++] * pgm_read_byte(&data[j]); // Total
}
sum /= pgm_read_word(&binDiv[i]); // Average
lvl[frameIdx][i] = sum; // Save for rolling averages
minLvl = maxLvl = lvl[0][i]; // Get min and max range for bin
for(j=1; j<FRAMES; j++) { // from prior stored frames
if(lvl[j][i] < minLvl) minLvl = lvl[j][i];
else if(lvl[j][i] > maxLvl) maxLvl = lvl[j][i];
}
// minLvl and maxLvl indicate the extents of the FFT output for this
// bin over the past few frames, used for vertically scaling the output
// graph (so it looks interesting regardless of volume level). If too
// close together though (e.g. at very low volume levels) the graph
// becomes super coarse and 'jumpy'...so keep some minimum distance
// between them (also lets the graph go to zero when no sound playing):
if((maxLvl - minLvl) < 23) {
maxLvl = (minLvl < (255-23)) ? minLvl + 23 : 255;
}
avgLo[i] = (avgLo[i] * 7 + minLvl) / 8; // Dampen min/max levels
avgHi[i] = (maxLvl >= avgHi[i]) ? // (fake rolling averages)
(avgHi[i] * 3 + maxLvl) / 4 : // Fast rise
(avgHi[i] * 31 + maxLvl) / 32; // Slow decay
// Second fixed-point scale then 'stretches' each bin based on
// dynamic min/max levels to 0-256 range:
level = 1 + ((sum <= avgLo[i]) ? 0 :
256L * (sum - avgLo[i]) / (long)(avgHi[i] - avgLo[i]));
// Clip output and convert to color:
if(level <= 255) {
uint8_t r = (pgm_read_byte(&reds[i]) * level) >> 8,
g = (pgm_read_byte(&greens[i]) * level) >> 8,
b = (pgm_read_byte(&blues[i]) * level) >> 8;
CircuitPlayground.strip.setPixelColor(i,
pgm_read_byte(&gamma8[r]),
pgm_read_byte(&gamma8[g]),
pgm_read_byte(&gamma8[b]));
} else { // level = 256, show white pixel OONTZ OONTZ
CircuitPlayground.strip.setPixelColor(i, 0x56587F);
}
}
CircuitPlayground.strip.show();
if(++frameIdx >= FRAMES) frameIdx = 0;
}

View File

@@ -0,0 +1,137 @@
/* This example shows how to use the FFT library with a Circuit Playground
* Express. Requires installing the "Adafruit Zero FFT library" (use the
* Arduino Library Manager to install it!
*
* The LEDs will map around the circle when frequencies between FREQ_MIN
* and FREQ_MAX are detected
*/
#include <Adafruit_CircuitPlayground.h>
#include "Adafruit_ZeroFFT.h"
//this must be a power of 2
#define DATA_SIZE 256
#define NUM_PIXELS 10
//the sample rate
#define FS 22000
//the lowest frequency that will register on the meter
#define FREQ_MIN 600
//the highest frequency that will register on the meter
#define FREQ_MAX 3000
#define MIN_INDEX FFT_INDEX(FREQ_MIN, FS, DATA_SIZE)
#define MAX_INDEX FFT_INDEX(FREQ_MAX, FS, DATA_SIZE)
#define SCALE_FACTOR 32
int16_t pixelData[NUM_PIXELS + 1];
int16_t inputData[DATA_SIZE];
const uint8_t
// R,G,B values for color wheel covering 10 NeoPixels:
reds[] = { 0xAD, 0x9A, 0x84, 0x65, 0x00, 0x00, 0x00, 0x00, 0x65, 0x84 },
greens[] = { 0x00, 0x66, 0x87, 0x9E, 0xB1, 0x87, 0x66, 0x00, 0x00, 0x00 },
blues[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xE4, 0xFF, 0xE4, 0xC3 },
gamma8[] = { // Gamma correction improves the appearance of midrange colors
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06,
0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09,
0x0A, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0E,
0x0E, 0x0F, 0x0F, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x13, 0x13, 0x14,
0x14, 0x15, 0x15, 0x16, 0x16, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1B,
0x1B, 0x1C, 0x1D, 0x1D, 0x1E, 0x1F, 0x1F, 0x20, 0x21, 0x22, 0x22, 0x23,
0x24, 0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2A, 0x2B, 0x2C, 0x2D,
0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x44, 0x45, 0x46,
0x47, 0x48, 0x49, 0x4B, 0x4C, 0x4D, 0x4E, 0x50, 0x51, 0x52, 0x54, 0x55,
0x56, 0x58, 0x59, 0x5A, 0x5C, 0x5D, 0x5E, 0x60, 0x61, 0x63, 0x64, 0x66,
0x67, 0x69, 0x6A, 0x6C, 0x6D, 0x6F, 0x70, 0x72, 0x73, 0x75, 0x77, 0x78,
0x7A, 0x7C, 0x7D, 0x7F, 0x81, 0x82, 0x84, 0x86, 0x88, 0x89, 0x8B, 0x8D,
0x8F, 0x91, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA4,
0xA6, 0xA8, 0xAA, 0xAC, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC,
0xBF, 0xC1, 0xC3, 0xC5, 0xC7, 0xCA, 0xCC, 0xCE, 0xD1, 0xD3, 0xD5, 0xD7,
0xDA, 0xDC, 0xDF, 0xE1, 0xE3, 0xE6, 0xE8, 0xEB, 0xED, 0xF0, 0xF2, 0xF5,
0xF7, 0xFA, 0xFC, 0xFF };
// the setup routine runs once when you press reset:
void setup() {
CircuitPlayground.begin();
}
// Track low and high levels for dynamic adjustment
int minLvl = 0, maxLvl = 1000;
void loop() {
int i;
CircuitPlayground.mic.capture(inputData, DATA_SIZE);
// Center data on average amplitude
int32_t avg = 0;
for(i=0; i<DATA_SIZE; i++) avg += inputData[i];
avg /= DATA_SIZE;
// Scale for FFT
for(i=0; i<DATA_SIZE; i++)
inputData[i] = (inputData[i] - avg) * SCALE_FACTOR;
//run the FFT
ZeroFFT(inputData, DATA_SIZE);
// Sum inputData[] (FFT output) into pixelData[] bins.
// Note that pixelData[] has one element more than the number of
// pixels actually displayed -- this is on purpose, as the last
// element tends to be zero and not visually interesting.
memset(pixelData, 0, sizeof pixelData); // Clear pixelData[] buffer
for(i=MIN_INDEX; i<=MAX_INDEX; i++){
int ix = map(i, MIN_INDEX, MAX_INDEX, 0, NUM_PIXELS);
pixelData[ix] += inputData[i];
}
// Figure the max and min levels for the current frame
int most = pixelData[0], least = pixelData[0];
for(i=1; i<NUM_PIXELS; i++) {
if(pixelData[i] > most) most = pixelData[i];
if(pixelData[i] < least) least = pixelData[i];
}
// Always have some minimum space between, else it's too "jumpy"
if((most - least) < 15) most = least + 15;
// Dynamic max/min is sort of a fake rolling average...
maxLvl = (most > maxLvl) ?
(maxLvl * 3 + most + 1) / 4 : // Climb fast
(maxLvl * 31 + most + 15) / 32; // Fall slow
minLvl = (least < minLvl) ?
(minLvl * 3 + least + 3) / 4 : // Fall fast
(minLvl * 31 + least + 31) / 32; // Climb slow
//display the data
int n;
for(i=0; i<NUM_PIXELS; i++) {
// Scale pixel data to 0-511ish range based on dynamic levels
n = map(pixelData[i], minLvl, maxLvl, 0, 511);
if(n < 0) n = 0;
else if(n > 511) n = 511;
int r, g, b;
if(n < 256) {
// Lower half of range: interp from black to RGB pixel color
r = map(n, 0, 255, 0, reds[i]);
g = map(n, 0, 255, 0, greens[i]);
b = map(n, 0, 255, 0, blues[i]);
} else {
// Upper half of range: interp from RGB color to white
r = map(n, 256, 511, reds[i] , 255);
g = map(n, 256, 511, greens[i], 255);
b = map(n, 256, 511, blues[i] , 255);
}
CircuitPlayground.strip.setPixelColor(i, gamma8[r], gamma8[g], gamma8[b]);
}
CircuitPlayground.strip.show();
}

View File

@@ -0,0 +1,156 @@
// Audio level visualizer for Adafruit Circuit Playground: uses the
// built-in microphone, 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 <Adafruit_CircuitPlayground.h>
#include <Wire.h>
#include <SPI.h>
// 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(128);
CircuitPlayground.clearPixels();
for(uint8_t i=0; i<FRAMES; i++) lvl[i] = 256;
}
// COLOR TABLES for animation ----------------------------------------------
const uint8_t PROGMEM
reds[] = { 0x9A, 0x75, 0x00, 0x00, 0x00, 0x65, 0x84, 0x9A, 0xAD, 0xAD },
greens[] = { 0x00, 0x00, 0x00, 0x87, 0xB1, 0x9E, 0x87, 0x66, 0x00, 0x00 },
blues[] = { 0x95, 0xD5, 0xFF, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
gamma8[] = { // Gamma correction improves the appearance of midrange colors
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06,
0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09,
0x0A, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0E,
0x0E, 0x0F, 0x0F, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x13, 0x13, 0x14,
0x14, 0x15, 0x15, 0x16, 0x16, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1B,
0x1B, 0x1C, 0x1D, 0x1D, 0x1E, 0x1F, 0x1F, 0x20, 0x21, 0x22, 0x22, 0x23,
0x24, 0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2A, 0x2B, 0x2C, 0x2D,
0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x44, 0x45, 0x46,
0x47, 0x48, 0x49, 0x4B, 0x4C, 0x4D, 0x4E, 0x50, 0x51, 0x52, 0x54, 0x55,
0x56, 0x58, 0x59, 0x5A, 0x5C, 0x5D, 0x5E, 0x60, 0x61, 0x63, 0x64, 0x66,
0x67, 0x69, 0x6A, 0x6C, 0x6D, 0x6F, 0x70, 0x72, 0x73, 0x75, 0x77, 0x78,
0x7A, 0x7C, 0x7D, 0x7F, 0x81, 0x82, 0x84, 0x86, 0x88, 0x89, 0x8B, 0x8D,
0x8F, 0x91, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA4,
0xA6, 0xA8, 0xAA, 0xAC, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC,
0xBF, 0xC1, 0xC3, 0xC5, 0xC7, 0xCA, 0xCC, 0xCE, 0xD1, 0xD3, 0xD5, 0xD7,
0xDA, 0xDC, 0xDF, 0xE1, 0xE3, 0xE6, 0xE8, 0xEB, 0xED, 0xF0, 0xF2, 0xF5,
0xF7, 0xFA, 0xFC, 0xFF };
// LOOP FUNCTION - runs over and over - does animation ---------------------
void loop() {
uint8_t i, r, g, b;
uint16_t minLvl, maxLvl, a, scaled;
int16_t p;
p = CircuitPlayground.mic.soundPressureLevel(10); // 10 ms
p = map(p, 56, 140, 0, 350); // Scale to 0-350 (may overflow)
a = constrain(p, 0, 350); // Clip to 0-350 range
sum -= lvl[lvlIdx];
lvl[lvlIdx] = a;
sum += a; // Sum of lvl[] array
minLvl = maxLvl = lvl[0]; // Calc min, max of lvl[]...
for(i=1; i<FRAMES; i++) {
if(lvl[i] < minLvl) minLvl = lvl[i];
else if(lvl[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;
}

View File

@@ -0,0 +1,18 @@
/* This example samples the microphone for 50 milliseconds repeatedly
* and returns the max sound pressure level in dB SPL
* https://en.wikipedia.org/wiki/Sound_pressure
*
* open the serial plotter window in the arduino IDE for a nice graph
* of sound pressure level over time.
*/
#include <Adafruit_CircuitPlayground.h>
void setup() {
CircuitPlayground.begin();
Serial.begin(115200);
}
void loop() {
Serial.println(CircuitPlayground.mic.soundPressureLevel(50));
}

View File

@@ -0,0 +1,119 @@
/*
LED VU meter for Circuit Playground
This is a port of the Adafruit Amplitie project to Circuit Playground.
Based on code for the adjustable sensitivity version of amplitie from:
https://learn.adafruit.com/led-ampli-tie/the-code
Hardware requirements:
- Circuit Playground
Software requirements:
- Adafruit Circuit Playground library
Written by Adafruit Industries. Distributed under the BSD license.
This paragraph must be included in any redistribution.
*/
#include <Adafruit_CircuitPlayground.h>
#include <Wire.h>
#include <SPI.h>
#define SAMPLE_WINDOW 10 // Sample window for average level
#define PEAK_HANG 24 // Time of pause before peak dot falls
#define PEAK_FALL 4 // Rate of falling peak dot
#define INPUT_FLOOR 56 // Lower range of mic sensitivity in dB SPL
#define INPUT_CEILING 110 // Upper range of mic sensitivity in db SPL
byte peak = 16; // Peak level of column; used for falling dots
unsigned int sample;
byte dotCount = 0; //Frame counter for peak dot
byte dotHangCount = 0; //Frame counter for holding peak dot
float mapf(float x, float in_min, float in_max, float out_min, float out_max);
void setup()
{
CircuitPlayground.begin();
}
void loop()
{
int numPixels = CircuitPlayground.strip.numPixels();
float peakToPeak = 0; // peak-to-peak level
unsigned int c, y;
//get peak sound pressure level over the sample window
peakToPeak = CircuitPlayground.mic.soundPressureLevel(SAMPLE_WINDOW);
//limit to the floor value
peakToPeak = max(INPUT_FLOOR, peakToPeak);
// Serial.println(peakToPeak);
//Fill the strip with rainbow gradient
for (int i=0;i<=numPixels-1;i++){
CircuitPlayground.strip.setPixelColor(i,Wheel(map(i,0,numPixels-1,30,150)));
}
c = mapf(peakToPeak, INPUT_FLOOR, INPUT_CEILING, numPixels, 0);
// Turn off pixels that are below volume threshold.
if(c < peak) {
peak = c; // Keep dot on top
dotHangCount = 0; // make the dot hang before falling
}
if (c <= numPixels) { // Fill partial column with off pixels
drawLine(numPixels, numPixels-c, CircuitPlayground.strip.Color(0, 0, 0));
}
// Set the peak dot to match the rainbow gradient
y = numPixels - peak;
CircuitPlayground.strip.setPixelColor(y-1,Wheel(map(y,0,numPixels-1,30,150)));
CircuitPlayground.strip.show();
// Frame based peak dot animation
if(dotHangCount > PEAK_HANG) { //Peak pause length
if(++dotCount >= PEAK_FALL) { //Fall rate
peak++;
dotCount = 0;
}
}
else {
dotHangCount++;
}
}
//Used to draw a line between two points of a given color
void drawLine(uint8_t from, uint8_t to, uint32_t c) {
uint8_t fromTemp;
if (from > to) {
fromTemp = from;
from = to;
to = fromTemp;
}
for(int i=from; i<=to; i++){
CircuitPlayground.strip.setPixelColor(i, c);
}
}
float mapf(float x, float in_min, float in_max, float out_min, float out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
if(WheelPos < 85) {
return CircuitPlayground.strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
else if(WheelPos < 170) {
WheelPos -= 85;
return CircuitPlayground.strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
else {
WheelPos -= 170;
return CircuitPlayground.strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
}