added arduino, modified build
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user