Skip to content

Instantly share code, notes, and snippets.

@sruli
Created May 18, 2018 10:57
Show Gist options
  • Save sruli/e0747ddd425616bb0f4beb2240b6e173 to your computer and use it in GitHub Desktop.
Save sruli/e0747ddd425616bb0f4beb2240b6e173 to your computer and use it in GitHub Desktop.
//Program by Michael Bartlett
//Libraries
#include <Adafruit_NeoPixel.h> //Library to simplify interacting with the LED strand
#ifdef __AVR__
#include <avr/power.h> //Includes the library for power reduction registers if your chip supports them.
#endif //More info: http://www.nongnu.org/avr-libc/user-manual/group__avr__power.htlm
//Constants (change these as necessary)
#define LED_PIN A5 //Pin for the pixel strand. Does not have to be analog.
#define LED_TOTAL 54 //Change this to the number of LEDs in your strand.
#define LED_HALF LED_TOTAL/2
#define AUDIO_PIN A0 //Pin for the envelope of the sound detector
//////////<Globals>
// These values either need to be remembered from the last pass of loop() or
// need to be accessed by several functions in one pass, so they need to be global.
Adafruit_NeoPixel strand = Adafruit_NeoPixel(LED_TOTAL, LED_PIN, NEO_GRB + NEO_KHZ800); //LED strand objetc
uint16_t gradient = 0; //Used to iterate and loop through each color palette gradually
uint8_t volume = 0; //Holds the volume level read from the sound detector.
uint8_t last = 0; //Holds the value of volume from the previous loop() pass.
float maxVol = 15; //Holds the largest volume recorded thus far to proportionally adjust the visual's responsiveness.
float avgVol = 0; //Holds the "average" volume-level to proportionally adjust the visual experience.
float avgBump = 0; //Holds the "average" volume-change to trigger a "bump."
bool bump = false; //Used to pass if there was a "bump" in volume
//////////</Globals>
//////////<Standard Functions>
void setup() { //Like it's named, this gets ran before any other function.
Serial.begin(9600); //Sets data rate for serial data transmission.
strand.begin(); //Initialize the LED strand object.
strand.show(); //Show a blank strand, just to get the LED's ready for use.
}
void loop() { //This is where the magic happens. This loop produces each frame of the visual.
volume = analogRead(AUDIO_PIN); //Record the volume level from the sound detector
avgVol = (avgVol + volume) / 2.0; //Take our "average" of volumes.
//Sets a threshold for volume.
// In practice I've found noise can get up to 15, so if it's lower, the visual thinks it's silent.
// Also if the volume is less than average volume / 2 (essentially an average with 0), it's considered silent.
if (volume < avgVol / 2.0 || volume < 15) volume = 0;
//If the current volume is larger than the loudest value recorded, overwrite
if (volume > maxVol) maxVol = volume;
//This is where "gradient" is reset to prevent overflow.
if (gradient > 1529) {
gradient %= 1530;
//Everytime a palette gets completed is a good time to readjust "maxVol," just in case
// the song gets quieter; we also don't want to lose brightness intensity permanently
// because of one stray loud sound.
maxVol = (maxVol + volume) / 2.0;
}
//If there is a decent change in volume since the last pass, average it into "avgBump"
if (volume - last > avgVol - last && avgVol - last > 0) avgBump = (avgBump + (volume - last)) / 2.0;
//if there is a notable change in volume, trigger a "bump"
bump = (volume - last) > avgBump;
Pulse(); //Calls the visual to be displayed with the globals as they are.
gradient++; //Increments gradient
last = volume; //Records current volume for next pass
delay(30); //Paces visuals so they aren't too fast to be enjoyable
}
//////////</Standard Functions>
//////////<Helper Functions>
//PULSE
//Pulse from center of the strand
void Pulse() {
fade(0.75); //Listed below, this function simply dims the colors a little bit each pass of loop()
//Advances the gradient to the next noticeable color if there is a "bump"
if (bump) gradient += 64;
//If it's silent, we want the fade effect to take over, hence this if-statement
if (volume > 0) {
uint32_t col = Rainbow(gradient); //Our retrieved 32-bit color
//These variables determine where to start and end the pulse since it starts from the middle of the strand.
// The quantities are stored in variables so they only have to be computed once.
int start = LED_HALF - (LED_HALF * (volume / maxVol));
int finish = LED_HALF + (LED_HALF * (volume / maxVol)) + strand.numPixels() % 2;
//Listed above, LED_HALF is simply half the number of LEDs on your strand. ↑ this part adjusts for an odd quantity.
for (int i = start; i < finish; i++) {
//"damp" creates the fade effect of being dimmer the farther the pixel is from the center of the strand.
// It returns a value between 0 and 1 that peaks at 1 at the center of the strand and 0 at the ends.
float damp = float(
((finish - start) / 2.0) -
abs((i - start) - ((finish - start) / 2.0))
)
/ float((finish - start) / 2.0);
//Sets the each pixel on the strand to the appropriate color and intensity
// strand.Color() takes 3 values between 0 & 255, and returns a 32-bit integer.
// Notice "knob" affecting the brightness, as in the rest of the visuals.
// Also notice split() being used to get the red, green, and blue values.
strand.setPixelColor(i, strand.Color(
split(col, 0) * pow(damp, 2.0),
split(col, 1) * pow(damp, 2.0),
split(col, 2) * pow(damp, 2.0)
));
}
//Sets the max brightness of all LEDs. If it's loud, it's brighter.
// "knob" was not used here because it occasionally caused minor errors in color display.
strand.setBrightness(255.0 * pow(volume / maxVol, 2));
}
//This command actually shows the lights. If you make a new visualization, don't forget this!
strand.show();
}
//Fades lights by multiplying them by a value between 0 and 1 each pass of loop().
void fade(float damper) {
//"damper" must be between 0 and 1, or else you'll end up brightening the lights or doing nothing.
if (damper >= 1) damper = 0.99;
for (int i = 0; i < strand.numPixels(); i++) {
//Retrieve the color at the current position.
uint32_t col = (strand.getPixelColor(i)) ? strand.getPixelColor(i) : strand.Color(0, 0, 0);
//If it's black, you can't fade that any further.
if (col == 0) continue;
float colors[3]; //Array of the three RGB values
//Multiply each value by "damper"
for (int j = 0; j < 3; j++) colors[j] = split(col, j) * damper;
//Set the dampened colors back to their spot.
strand.setPixelColor(i, strand.Color(colors[0] , colors[1], colors[2]));
}
}
uint8_t split(uint32_t color, uint8_t i ) {
//0 = Red, 1 = Green, 2 = Blue
if (i == 0) return color >> 16;
if (i == 1) return color >> 8;
if (i == 2) return color >> 0;
return -1;
}
//This function simply take a value and returns a gradient color
// in the form of an unsigned 32-bit integer
//The gradient returns a different, changing color for each multiple of 255
// This is because the max value of any of the 3 LEDs is 255, so it's
// an intuitive cutoff for the next color to start appearing.
// Gradients should also loop back to their starting color so there's no jumps in color.
uint32_t Rainbow(unsigned int i) {
if (i > 1529) return Rainbow(i % 1530);
if (i > 1274) return strand.Color(255, 0, 255 - (i % 255)); //violet -> red
if (i > 1019) return strand.Color((i % 255), 0, 255); //blue -> violet
if (i > 764) return strand.Color(0, 255 - (i % 255), 255); //aqua -> blue
if (i > 509) return strand.Color(0, 255, (i % 255)); //green -> aqua
if (i > 255) return strand.Color(255 - (i % 255), 255, 0); //yellow -> green
return strand.Color(255, i, 0); //red -> yellow
}
//////////</Helper Functions>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment