Here's the entire sketch
// --------------------------------
// -- VU Meter - Scott's version --
// --------------------------------
#include <FastLED.h>
#include <EEPROM.h>
#include <JC_Button.h>
#include <Conceptinetics.h>
# define LEFT_OUT_PIN 6 // Left channel data out pin to LEDs [6]
# define RIGHT_OUT_PIN 5 // Right channel data out pin to LEDs [5]
# define LEFT_IN_PIN A5 // Left aux in signal [A5]
# define RIGHT_IN_PIN A4 // Right aux in signal [A4]
# define BTN_PIN 3 // Push button on this pin [3]
# define DEBOUNCE_MS 20 // Number of ms to debounce the button [20]
# define LONG_PRESS 500 // Number of ms to hold the button to count as long press [500]
# define N_PIXELS 18 // Number of pixels in each string [24]
# define MAX_MILLIAMPS 500 // Maximum current to draw [500]
# define COLOR_ORDER GRB // Colour order of LED strip [GRB]
# define LED_TYPE WS2812B // LED string type [WS2812B]
# define DC_OFFSET 0 // DC offset in aux signal [0]
# define NOISE 20 // Noise/hum/interference in aux signal [10]
# define SAMPLES 60 // Length of buffer for dynamic level adjustment [60]
# define TOP (N_PIXELS + 2) // Allow dot to go slightly off scale [(N_PIXELS + 2)]
# define PEAK_FALL 20 // Rate of peak falling dot [20]
# define N_PIXELS_HALF (N_PIXELS / 2)
# define PATTERN_TIME 10 // Seconds to show eaach pattern on auto [10]
# define STEREO true // If true, L&R channels are independent. If false, both L&R outputs display same data from L audio channel [false]
uint8_t volCountLeft = 0; // Frame counter for storing past volume data
int volLeft[SAMPLES]; // Collection of prior volume samples
int lvlLeft = 0; // Current "dampened" audio level
int minLvlAvgLeft = 0; // For dynamic adjustment of graph low & high
int maxLvlAvgLeft = 512;
uint8_t volCountRight = 0; // Frame counter for storing past volume data
int volRight[SAMPLES]; // Collection of prior volume samples
int lvlRight = 0; // Current "dampened" audio level
int minLvlAvgRight = 0; // For dynamic adjustment of graph low & high
int maxLvlAvgRight = 512;
CRGB ledsLeft[N_PIXELS];
CRGB ledsRight[N_PIXELS];
uint8_t myhue = 0;
void vu4(bool is_centered, uint8_t channel);
void vu5(bool is_centered, uint8_t channel);
void vu6(bool is_centered, uint8_t channel);
void vu7(bool show_background);
void vu8();
//void vu9();
void vu10();
void balls();
void fire();
void juggle();
void ripple(boolean show_background);
void sinelon();
void twinkle();
void rainbow(uint8_t rate);
// --------------------
// --- Button Stuff ---
// --------------------
uint8_t state = 0;
int buttonPushCounter = 0;
bool autoChangeVisuals = false;
Button modeBtn(BTN_PIN, DEBOUNCE_MS);
void incrementButtonPushCounter() {
buttonPushCounter = ++buttonPushCounter %17;
EEPROM.write(1, buttonPushCounter);
}
void setup() {
delay(1000); // power-up safety delay
FastLED.addLeds < LED_TYPE, LEFT_OUT_PIN, COLOR_ORDER > (ledsLeft, N_PIXELS).setCorrection(TypicalLEDStrip);
FastLED.addLeds < LED_TYPE, RIGHT_OUT_PIN, COLOR_ORDER > (ledsRight, N_PIXELS).setCorrection(TypicalLEDStrip);
//FastLED.setBrightness(BRIGHTNESS);
FastLED.setMaxPowerInVoltsAndMilliamps(5, MAX_MILLIAMPS);
modeBtn.begin();
Serial.begin(57600);
buttonPushCounter = (int)EEPROM.read(1); // load previous setting
buttonPushCounter = 0;
Serial.print("Starting pattern ");
Serial.println(buttonPushCounter);
}
void loop() {
// Read button
modeBtn.read();
switch (state) {
case 0:
if (modeBtn.wasReleased()) {
Serial.print("Short press, pattern ");
Serial.println(buttonPushCounter);
incrementButtonPushCounter();
autoChangeVisuals = false;
}
else if (modeBtn.pressedFor(LONG_PRESS))
state = 1;
break;
case 1:
if (modeBtn.wasReleased()) {
state = 0;
Serial.print("Long press, auto, pattern ");
Serial.println(buttonPushCounter);
autoChangeVisuals = true;
}
break;
}
// Switch pattern if on auto
if(autoChangeVisuals){
EVERY_N_SECONDS(PATTERN_TIME) {
incrementButtonPushCounter();
Serial.print("Auto, pattern ");
Serial.println(buttonPushCounter);
}
}
// Run selected pattern
switch (buttonPushCounter) {
case 0:
vu4(false, 0);
if (STEREO) vu4(false, 1);
else copyLeftToRight();
break;
case 1:
vu4(true, 0);
if (STEREO) vu4(true, 1);
else copyLeftToRight();
break;
case 2:
vu5(false, 0);
if (STEREO) vu5(false, 1);
else copyLeftToRight();
break;
case 3:
vu5(true, 0);
if (STEREO) vu5(true, 1);
else copyLeftToRight();
break;
case 4:
vu6(false, 0);
if (STEREO) vu6(false, 1);
else copyLeftToRight();
break;
case 5:
vu7(true);
copyLeftToRight();
break;
case 6:
vu8();
copyLeftToRight();
break;
case 7:
//vu9();
break;
case 8:
vu10();
break;
case 9:
vu7(false);
copyLeftToRight();
break;
case 10:
twinkle();
break;
case 11:
sinelon();
break;
case 12:
balls();
break;
case 13:
juggle();
break;
case 14:
fire();
break;
case 15:
ripple(false);
break;
case 16:
rainbow(10);
break;
}
}
// ------------------
// -- VU functions --
// ------------------
uint16_t auxReading(uint8_t channel) {
int n = 0;
uint16_t height = 0;
if(channel == 0) {
int n = analogRead(LEFT_IN_PIN); // Raw reading from left line in
n = abs(n - 512 - DC_OFFSET); // Center on zero
n = (n <= NOISE) ? 0 : (n - NOISE); // Remove noise/hum
lvlLeft = ((lvlLeft * 7) + n) >> 3; // "Dampened" reading else looks twitchy (>>3 is divide by 8)
volLeft[volCountLeft] = n; // Save sample for dynamic leveling
volCountLeft = ++volCountLeft % SAMPLES;
// Calculate bar height based on dynamic min/max levels (fixed point):
height = TOP * (lvlLeft - minLvlAvgLeft) / (long)(maxLvlAvgLeft - minLvlAvgLeft);
}
else {
int n = analogRead(RIGHT_IN_PIN); // Raw reading from mic
n = abs(n - 512 - DC_OFFSET); // Center on zero
n = (n <= NOISE) ? 0 : (n - NOISE); // Remove noise/hum
lvlRight = ((lvlRight * 7) + n) >> 3; // "Dampened" reading (else looks twitchy)
volRight[volCountRight] = n; // Save sample for dynamic leveling
volCountRight = ++volCountRight % SAMPLES;
// Calculate bar height based on dynamic min/max levels (fixed point):
height = TOP * (lvlRight - minLvlAvgRight) / (long)(maxLvlAvgRight - minLvlAvgRight);
}
// Calculate bar height based on dynamic min/max levels (fixed point):
height = constrain(height, 0, TOP);
return height;
}
void copyLeftToRight() {
for (uint8_t i = 0; i < N_PIXELS; i++) {
ledsRight[i] = ledsLeft[i];
}
}
/*
* Function for dropping the peak
*/
uint8_t peakLeft, peakRight;
void dropPeak(uint8_t channel) {
static uint8_t dotCountLeft, dotCountRight;
if(channel == 0) {
if(++dotCountLeft >= PEAK_FALL) { //fall rate
if(peakLeft > 0) peakLeft--;
dotCountLeft = 0;
}
} else {
if(++dotCountRight >= PEAK_FALL) { //fall rate
if(peakRight > 0) peakRight--;
dotCountRight = 0;
}
}
}
/*
* Function for averaging the sample readings
*/
void averageReadings(uint8_t channel) {
uint16_t minLvl, maxLvl;
// minLvl and maxLvl indicate the volume range over prior frames, used
// for vertically scaling the output graph (so it looks interesting
// regardless of volume level). If they're 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 (this
// also lets the graph go to zero when no sound is playing):
if(channel == 0) {
minLvl = maxLvl = volLeft[0];
for (int i = 1; i < SAMPLES; i++) {
if (volLeft[i] < minLvl) minLvl = volLeft[i];
else if (volLeft[i] > maxLvl) maxLvl = volLeft[i];
}
if ((maxLvl - minLvl) < TOP) maxLvl = minLvl + TOP;
minLvlAvgLeft = (minLvlAvgLeft * 63 + minLvl) >> 6; // Dampen min/max levels
maxLvlAvgLeft = (maxLvlAvgLeft * 63 + maxLvl) >> 6; // (fake rolling average)
}
else {
minLvl = maxLvl = volRight[0];
for (int i = 1; i < SAMPLES; i++) {
if (volRight[i] < minLvl) minLvl = volRight[i];
else if (volRight[i] > maxLvl) maxLvl = volRight[i];
}
if ((maxLvl - minLvl) < TOP) maxLvl = minLvl + TOP;
minLvlAvgRight = (minLvlAvgRight * 63 + minLvl) >> 6; // Dampen min/max levels
maxLvlAvgRight = (maxLvlAvgRight * 63 + maxLvl) >> 6; // (fake rolling average)
}
}
/*
* Standby: Three balls bouncing under gravity
*/
void balls() {
const float H0 = 0.5;
const int NUM_BALLS = 3;
static float h[NUM_BALLS]; // An array of heights
static float vImpact0 = sqrt(-2 * -9.81 * H0); // Impact velocity of the ball when it hits the ground if "dropped" from the top of the strip
static float vImpact[NUM_BALLS]; // As time goes on the impact velocity will change, so make an array to store those values
static float tCycle[NUM_BALLS]; // The time since the last time the ball struck the ground
static int pos[NUM_BALLS]; // The integer position of the dot on the strip (LED index)
static long tLast[NUM_BALLS]; // The clock time of the last ground strike
static float COR[NUM_BALLS]; // Coefficient of Restitution (bounce damping)
static bool fireFirstRun = true;
if (fireFirstRun) {
for (int i = 0; i < NUM_BALLS; i++) { // Initialize variables
tLast[i] = millis();
h[i] = H0;
pos[i] = 0; // Balls start on the ground
vImpact[i] = vImpact0; // And "pop" up at vImpact0
tCycle[i] = 0;
COR[i] = 0.90 - float(i) / pow(NUM_BALLS, 2);
}
fireFirstRun = false;
}
for (int i = 0; i < NUM_BALLS; i++) {
tCycle[i] = millis() - tLast[i]; // Calculate the time since the last time the ball was on the ground
// A little kinematics equation calculates positon as a function of time, acceleration (gravity) and intial velocity
h[i] = 0.5 * -9.81 * pow(tCycle[i] / 1000, 2.0) + vImpact[i] * tCycle[i] / 1000;
if (h[i] < 0) {
h[i] = 0; // If the ball crossed the threshold of the "ground," put it back on the ground
vImpact[i] = COR[i] * vImpact[i]; // and recalculate its new upward velocity as it's old velocity * COR
tLast[i] = millis();
if (vImpact[i] < 0.01) vImpact[i] = vImpact0; // If the ball is barely moving, "pop" it back up at vImpact0
}
pos[i] = round(h[i] * (N_PIXELS - 1) / H0); // Map "h" to a "pos" integer index position on the LED strip
}
//Choose color of LEDs, then the "pos" LED on
for (int i = 0; i < NUM_BALLS; i++) ledsLeft[pos[i]] = CHSV(uint8_t(i * 40), 255, 255);
// Copy left LED array into right LED array
for (uint8_t i = 0; i < N_PIXELS; i++) {
ledsRight[i] = ledsLeft[i];
}
FastLED.show();
//Then off for the next loop around
for (int i = 0; i < NUM_BALLS; i++) {
ledsLeft[pos[i]] = CRGB::Black;
ledsRight[pos[i]] = CRGB::Black;
}
}
/*
* Standby: Looks like fire! Both channels are seperate and random
*/
void fire() {
const uint8_t COOLING = 55;
const uint8_t SPARKING = 50;
// Array of temperature readings at each simulation cell
static byte heatLeft[N_PIXELS];
static byte heatRight[N_PIXELS];
// Step 1. Cool down every cell a little
for (int i = 0; i < N_PIXELS; i++) {
heatLeft[i] = qsub8(heatLeft[i], random8(0, ((COOLING * 10) / N_PIXELS) + 2));
}
// Step 2. Heat from each cell drifts 'up' and diffuses a little
for (int k = N_PIXELS - 1; k >= 2; k--) {
heatLeft[k] = (heatLeft[k - 1] + heatLeft[k - 2] + heatLeft[k - 2]) / 3;
}
// Step 3. Randomly ignite new 'sparks' of heat near the bottom
if (random8() < SPARKING) {
int y = random8(7);
heatLeft[y] = qadd8(heatLeft[y], random8(160, 255));
}
// Step 4. Map from heat cells to LED colors
for (int j = 0; j < N_PIXELS; j++) {
// Scale the heat value from 0-255 down to 0-240
// for best results with color palettes.
byte colorindex = scale8(heatLeft[j], 240);
CRGB color = ColorFromPalette(CRGBPalette16(CRGB::Black, CRGB::Red, CRGB::Yellow, CRGB::White), colorindex);
int pixelnumber = j;
ledsLeft[pixelnumber] = color;
}
// Now do it all again for the right channel
// Step 1. Cool down every cell a little
for (int i = 0; i < N_PIXELS; i++) {
heatRight[i] = qsub8(heatRight[i], random8(0, ((COOLING * 10) / N_PIXELS) + 2));
}
// Step 2. Heat from each cell drifts 'up' and diffuses a little
for (int k = N_PIXELS - 1; k >= 2; k--) {
heatRight[k] = (heatRight[k - 1] + heatRight[k - 2] + heatRight[k - 2]) / 3;
}
// Step 3. Randomly ignite new 'sparks' of heat near the bottom
if (random8() < SPARKING) {
int y = random8(7);
heatRight[y] = qadd8(heatRight[y], random8(160, 255));
}
// Step 4. Map from heat cells to LED colors
for (int j = 0; j < N_PIXELS; j++) {
// Scale the heat value from 0-255 down to 0-240
// for best results with color palettes.
byte colorindex = scale8(heatRight[j], 240);
CRGB color = ColorFromPalette(CRGBPalette16(CRGB::Black, CRGB::Red, CRGB::Yellow, CRGB::White), colorindex);
int pixelnumber = j;
ledsRight[pixelnumber] = color;
}
FastLED.show();
}
/*
* Standby: Several colored dots, weaving in and out of sync with each other
*/
const uint8_t FADE_RATE = 2; // How long should the trails be. Very low value = longer trails.
void juggle() {
const uint8_t NUM_DOTS = 4; // Number of dots in use.
const uint8_t HUE_INC = 16; // Incremental change in hue between each dot.
static uint8_t thishue = 0; // Starting hue.
static uint8_t curhue = 0;
const uint8_t basebeat = 5;
curhue = thishue; // Reset the hue values?
fadeToBlackBy(ledsLeft, N_PIXELS, FADE_RATE);
for (int i = 0; i < NUM_DOTS; i++) {
ledsLeft[beatsin16(basebeat + i + NUM_DOTS, 0, N_PIXELS - 1)] |= CHSV(curhue, 255, 255);
curhue += HUE_INC;
}
// Copy left LED array into right LED array
copyLeftToRight();
FastLED.show();
}
/*
* Standby: Rainbow cycling around in clockwise direction
*/
void rainbow(uint8_t rate) {
static uint8_t leftHue = 0;
static uint8_t rightHue = 0;
fill_rainbow(ledsLeft, N_PIXELS, leftHue, 7);
fill_rainbow(ledsRight, N_PIXELS, rightHue, 7);
EVERY_N_MILLISECONDS(20) {
leftHue = (leftHue + rate) % 255;
rightHue = (rightHue - rate) % 255;
}
FastLED.show();
}
/*
* Standby: Ripple with or without background
*/
void ripple(boolean show_background) {
const float RIPPLE_FADE_RATE = 0.80;
const uint8_t MAX_STEPS = 16;
static uint8_t rippleColor = 0;
static uint8_t rippleCenter = 0;
static int rippleStep = -1;
// -- fill background --
EVERY_N_MILLISECONDS(10) {
myhue++;
}
if (show_background) {
fill_solid(ledsLeft, N_PIXELS, CHSV(myhue, 255, 128));
} else {
fill_solid(ledsLeft, N_PIXELS, CRGB::Black);
}
EVERY_N_MILLISECONDS(50) {
// -- do ripple --
if (rippleStep == -1) {
rippleCenter = random(N_PIXELS);
rippleColor = myhue + 128;
rippleStep = 0;
}
if (rippleStep == 0) {
ledsLeft[rippleCenter] = CHSV(rippleColor, 255, 255);
rippleStep++;
} else {
if (rippleStep < MAX_STEPS) {
ledsLeft[wrap(rippleCenter + rippleStep)] = CHSV(rippleColor, 255, pow(RIPPLE_FADE_RATE, rippleStep) * 255);
ledsLeft[wrap(rippleCenter - rippleStep)] = CHSV(rippleColor, 255, pow(RIPPLE_FADE_RATE, rippleStep) * 255);
if (rippleStep > 3) {
ledsLeft[wrap(rippleCenter + rippleStep - 3)] = CHSV(rippleColor, 255, pow(RIPPLE_FADE_RATE, rippleStep - 2) * 255);
ledsLeft[wrap(rippleCenter - rippleStep + 3)] = CHSV(rippleColor, 255, pow(RIPPLE_FADE_RATE, rippleStep - 2) * 255);
}
rippleStep++;
} else {
rippleStep = -1;
}
}
}
// Copy left LED array into right LED array
copyLeftToRight();
FastLED.show();
delay(50);
}
int wrap(int rippleStep) {
if (rippleStep < 0) return N_PIXELS + rippleStep;
if (rippleStep > N_PIXELS - 1) return rippleStep - N_PIXELS;
return rippleStep;
}
/*
* Standby: A colored dot sweeping back and forth, with fading trails
*/
void sinelon() {
const uint8_t THIS_BEAT = 23;
const uint8_t THAT_BEAT = 28;
const uint8_t THIS_FADE = 2; // How quickly does it fade? Lower = slower fade rate.
fadeToBlackBy(ledsLeft, N_PIXELS, THIS_FADE);
int pos1 = beatsin16(THIS_BEAT, 0, N_PIXELS - 1);
int pos2 = beatsin16(THAT_BEAT, 0, N_PIXELS - 1);
ledsLeft[(pos1 + pos2) / 2] += CHSV(myhue, 255, 255);
EVERY_N_MILLISECONDS(10) {
myhue++;
}
// Copy left LED array into right LED array
copyLeftToRight();
FastLED.show();
}
/*
* Standby: Twinkling lights of random colours
*/
void twinkle() {
if (random(25) == 1) {
uint16_t i = random(N_PIXELS);
ledsLeft[i] = CRGB(random(256), random(256), random(256));
}
fadeToBlackBy(ledsLeft, N_PIXELS, FADE_RATE);
if (random(25) == 1) {
uint16_t i = random(N_PIXELS);
ledsRight[i] = CRGB(random(256), random(256), random(256));
}
fadeToBlackBy(ledsRight, N_PIXELS, FADE_RATE);
FastLED.show();
delay(10);
}
void vu4(bool is_centered, uint8_t channel) {
CRGB* leds;
uint8_t i = 0;
uint8_t *peak; // Pointer variable declaration
uint16_t height = auxReading(channel);
if(channel == 0) {
leds = ledsLeft; // Store address of peakLeft in peak, then use *peak to
peak = &peakLeft; // access the value of that address
}
else {
leds = ledsRight;
peak = &peakRight;
}
// Draw vu meter part
fill_solid(leds, N_PIXELS, CRGB::Black);
if(is_centered) {
// Fill with colour gradient
fill_gradient(leds, N_PIXELS_HALF , CHSV(96, 255, 255), N_PIXELS - 1, CHSV(224, 255, 255),SHORTEST_HUES);
fill_gradient(leds, N_PIXELS_HALF-1, CHSV(96, 255, 255), 0, CHSV(224, 255, 255),LONGEST_HUES);
// Black out ends
for (i = 0; i < N_PIXELS; i++) {
uint8_t numBlack = (N_PIXELS - constrain(height, 0, N_PIXELS-1)) / 2;
if(i <= numBlack -1 || i >= N_PIXELS - numBlack) leds[i] = CRGB::Black;
}
// Draw peak dot
if(height/2 > *peak)
*peak = height/2; // Keep 'peak' dot at top
if(*peak > 0 && *peak <= N_PIXELS_HALF-1) {
leds[N_PIXELS_HALF + *peak] = CHSV(rainbowHue2(*peak, N_PIXELS_HALF),255,255);
leds[N_PIXELS_HALF - 1 - *peak] = CHSV(rainbowHue2(*peak, N_PIXELS_HALF),255,255);
}
}
else {
// Fill with color gradient
fill_gradient(leds, 0, CHSV(96, 255, 255), N_PIXELS - 1, CHSV(224, 255, 255),SHORTEST_HUES);
//Black out end
for (i = 0; i < N_PIXELS; i++) {
if(i >= height) leds[i] = CRGB::Black;
}
// Draw peak dot
if(height > *peak)
*peak = height; // Keep 'peak' dot at top
if(*peak > 0 && *peak <= N_PIXELS-1)
leds[*peak] = CHSV(rainbowHue2(*peak, N_PIXELS), 255, 255); // Set peak colour correctly
}
dropPeak(channel);
averageReadings(channel);
FastLED.show();
}
uint8_t rainbowHue2(uint8_t pixel, uint8_t num_pixels) {
uint8_t hue = 96 - pixel * (145 / num_pixels);
return hue;
}
/*
* VU: Old-skool green and red from bottom or middle
*/
void vu5(bool is_centered, uint8_t channel) {
CRGB* leds;
uint8_t i = 0;
uint8_t *peak; // Pointer variable declaration
uint16_t height = auxReading(channel);
if(channel == 0) {
leds = ledsLeft; // Store address of peakLeft in peak, then use *peak to
peak = &peakLeft; // access the value of that address
}
else {
leds = ledsRight;
peak = &peakRight;
}
if (height > *peak)
*peak = height; // Keep 'peak' dot at top
if(is_centered) {
// Color pixels based on old school green / red
for (uint8_t i = 0; i < N_PIXELS_HALF; i++) {
if (i >= height) {
// Pixels greater than peak, no light
leds[N_PIXELS_HALF - i - 1] = CRGB::Black;
leds[N_PIXELS_HALF + i] = CRGB::Black;
} else {
if (i > N_PIXELS_HALF - (N_PIXELS_HALF / 3)){
leds[N_PIXELS_HALF - i - 1] = CRGB::Red;
leds[N_PIXELS_HALF + i] = CRGB::Red;
}
else {
leds[N_PIXELS_HALF - i - 1] = CRGB::Green;
leds[N_PIXELS_HALF + i] = CRGB::Green;
}
}
}
// Draw peak dot
if (*peak > 0 && *peak <= N_PIXELS_HALF - 1) {
if (*peak > N_PIXELS_HALF - (N_PIXELS_HALF / 3)){
leds[N_PIXELS_HALF - *peak - 1] = CRGB::Red;
leds[N_PIXELS_HALF + *peak] = CRGB::Red;
} else {
leds[N_PIXELS_HALF - *peak - 1] = CRGB::Green;
leds[N_PIXELS_HALF + *peak] = CRGB::Green;
}
}
} else {
// Color pixels based on old school green/red vu
for (uint8_t i = 0; i < N_PIXELS; i++) {
if (i >= height) leds[i] = CRGB::Black;
else if (i > N_PIXELS - (N_PIXELS / 3)) leds[i] = CRGB::Red;
else leds[i] = CRGB::Green;
}
// Draw peak dot
if (*peak > 0 && *peak <= N_PIXELS - 1)
if (*peak > N_PIXELS - (N_PIXELS / 3)) leds[*peak] = CRGB::Red;
else leds[*peak] = CRGB::Green;
}
dropPeak(channel);
averageReadings(channel);
FastLED.show();
}
/*
* VU: Rainbow from bottom or middle with hue cycling
*/
void vu6(bool is_centered, uint8_t channel) {
const uint8_t SPEED = 10;
static uint8_t hueOffset = 30;
CRGB* leds;
uint8_t i = 0;
uint8_t *peak; // Pointer variable declaration
uint16_t height = auxReading(channel);
if(channel == 0) {
leds = ledsLeft; // Store address of peakLeft in peak, then use *peak to
peak = &peakLeft; // access the value of that address
}
else {
leds = ledsRight;
peak = &peakRight;
}
if(height > *peak)
*peak = height; // Keep 'peak' dot at top
EVERY_N_MILLISECONDS(SPEED) {hueOffset++;}
if(is_centered) {
// Color pixels based on rainbow gradient
for (uint8_t i = 0; i < N_PIXELS_HALF; i++) {
if (i >= height) {
leds[N_PIXELS_HALF - i - 1] = CRGB::Black;
leds[N_PIXELS_HALF + i] = CRGB::Black;
} else {
leds[N_PIXELS_HALF - i - 1] = CHSV(hueOffset + (10 * i),255,255);
leds[N_PIXELS_HALF + i] = CHSV(hueOffset + (10 * i),255,255);
}
}
// Draw peak dot
if (*peak > 0 && *peak <= N_PIXELS_HALF - 1) {
leds[N_PIXELS_HALF - *peak - 1] = CHSV(hueOffset,255,255);
leds[N_PIXELS_HALF + *peak] = CHSV(hueOffset,255,255);
}
}
else {
// Color pixels based on rainbow gradient
for (uint8_t i = 0; i < N_PIXELS; i++) {
if (i >= height) {
leds[i] = CRGB::Black;
} else {
leds[i] = CHSV(hueOffset + (10 * i),255,255);
}
}
// Draw peak dot
if (*peak > 0 && *peak <= N_PIXELS - 1)
leds[*peak] = CHSV(hueOffset, 255, 255);
}
dropPeak(channel);
averageReadings(channel);
FastLED.show();
}
/*
* VU: Ripple (mono) with or without background
*/
int rippleStep = -1;
const float RIPPLE_FADE_RATE = 0.80;
int peakspersec = 0;
int peakcount = 0;
unsigned int sampleavg;
uint8_t rippleHue = 0;
uint8_t bgcol = 0;
void vu7(boolean show_background) {
EVERY_N_MILLISECONDS(1000) {
peakspersec = peakcount; // Count the peaks per second. This value will become the foreground rippleHue.
peakcount = 0; // Reset the counter every second.
}
soundmems();
EVERY_N_MILLISECONDS(20) {
ripple3(show_background);
}
FastLED.show();
}
void soundmems() { // Rolling average counter - means we don't have to go through an array each time.
static int samplecount;
static unsigned long samplesum;
static unsigned long oldtime;
unsigned long newtime = millis();
unsigned int sample = abs(analogRead(LEFT_IN_PIN) -512);
samplesum = samplesum + sample - volLeft[samplecount]; // Add the new sample and remove the oldest sample in the array
sampleavg = samplesum / SAMPLES; // Get an average
volLeft[samplecount] = sample; // Update oldest sample in the array with new sample
samplecount = (samplecount + 1) % SAMPLES; // Update the counter for the array
if ((sample > (sampleavg + 50)) && (newtime > (oldtime + 100))) { // Check for a peak, which is 50 > the average, but wait at least 100ms for another.
rippleStep = -1;
peakcount++;
oldtime = newtime;
}
}
void ripple3(bool show_background) {
const uint8_t MAX_STEPS = 16;
static int center = 0;
if(show_background) {
for (int i = 0; i < N_PIXELS; i++) {
ledsLeft[i] = CHSV(bgcol, 255, sampleavg * 2); // Set the background colour.
}
} else {
fadeToBlackBy(ledsLeft, N_PIXELS, 64);
}
switch (rippleStep) {
case -1: // Initialize ripple variables.
center = random(N_PIXELS);
rippleHue = (peakspersec * 10) % 255; // More peaks/s = higher the hue colour.
rippleStep = 0;
bgcol = bgcol + 8;
break;
case 0:
ledsLeft[center] = CHSV(rippleHue, 255, 255); // Display the first pixel of the ripple.
rippleStep++;
break;
case MAX_STEPS: // At the end of the ripples.
break;
default: // Middle of the ripples.
ledsLeft[(center + rippleStep + N_PIXELS) % N_PIXELS] += CHSV(rippleHue, 255, 255 / rippleStep * 2); // Simple wrap from Marc Miller.
ledsLeft[(center - rippleStep + N_PIXELS) % N_PIXELS] += CHSV(rippleHue, 255, 255 / rippleStep * 2);
rippleStep++; // Next step.
break;
}
}
/*
* VU: Hue cycling, three bars (shatter, mono)
*/
void vu8() {
const uint8_t DRAW_MAX = 30;
const uint8_t SEGMENTS = 3;
static int origin = 0;
static uint8_t scroll_color = 0;
static int last_intensity = 0;
static int intensity_max = 0;
static int origin_at_flip = 0;
static CHSV draw[DRAW_MAX];
static bool growing = false;
static bool fall_from_left = true;
int intensity = auxReading(0);
//// Update Origin ////
// detect peak change and save origin at curve vertex
if (growing && intensity < last_intensity) {
growing = false;
intensity_max = last_intensity;
fall_from_left = !fall_from_left;
origin_at_flip = origin;
} else if (intensity > last_intensity) {
growing = true;
origin_at_flip = origin;
}
last_intensity = intensity;
// adjust origin if falling
if (!growing) {
if (fall_from_left) {
origin = origin_at_flip + ((intensity_max - intensity) / 2);
} else {
origin = origin_at_flip - ((intensity_max - intensity) / 2);
}
// correct for origin out of bounds
if (origin < 0) {
origin = DRAW_MAX - abs(origin);
} else if (origin > DRAW_MAX - 1) {
origin = origin - DRAW_MAX - 1;
}
}
//// Assign draw values ///
// draw amplitue as 1/2 intensity both directions from origin
int min_lit = origin - (intensity / 2);
int max_lit = origin + (intensity / 2);
if (min_lit < 0) {
min_lit = min_lit + DRAW_MAX;
}
if (max_lit >= DRAW_MAX) {
max_lit = max_lit - DRAW_MAX;
}
for (int i=0; i < DRAW_MAX; i++) {
// if i is within origin +/- 1/2 intensity
if (
(min_lit < max_lit && min_lit < i && i < max_lit) // range is within bounds and i is within range
|| (min_lit > max_lit && (i > min_lit || i < max_lit)) // range wraps out of bounds and i is within that wrap
) {
draw[i] = CHSV(scroll_color,255,255);
} else {
draw[i] = CHSV(scroll_color,0,0);
}
}
//// Write Segmented ////
int seg_len = N_PIXELS / SEGMENTS;
for (int s = 0; s < SEGMENTS; s++) {
for (int i = 0; i < seg_len; i++) {
ledsLeft[i + (s*seg_len)] = draw[map(i, 0, seg_len, 0, DRAW_MAX)];
}
}
FastLED.show();
averageReadings(0);
EVERY_N_MILLISECONDS(20) {
scroll_color = ++scroll_color % 255;
}
}
/*
* VU: Palette blending demo
*/
CRGBPalette16 currentPalette;
CRGBPalette16 targetPalette;
void vu10() {
EVERY_N_SECONDS(5) { // Change the target palette to a random one every 5 seconds.
static uint8_t baseC = random8(); // You can use this as a baseline colour if you want similar hues in the next line.
for (int i = 0; i < 16; i++) {
targetPalette[i] = CHSV(random8(), 255, 255);
}
}
EVERY_N_MILLISECONDS(100) {
uint8_t maxChanges = 24;
nblendPaletteTowardPalette(currentPalette, targetPalette, maxChanges); // AWESOME palette blending capability.
}
EVERY_N_MILLISECONDS(20) { // FastLED based non-blocking delay to update/display the sequence.
soundtun();
FastLED.show();
}
}
void soundtun() {
int sampleLeft = abs(analogRead(LEFT_IN_PIN) - 512 - DC_OFFSET);
CRGB newcolourLeft = ColorFromPalette(currentPalette, constrain(sampleLeft, 0, 255), constrain(sampleLeft, 0, 255), LINEARBLEND);
nblend(ledsLeft[0], newcolourLeft, 128);
for (int i = N_PIXELS - 1; i > 0; i--) {
ledsLeft[i] = ledsLeft[i - 1];
}
if (STEREO) {
int sampleRight = abs(analogRead(RIGHT_IN_PIN) - 512 - DC_OFFSET);
CRGB newcolourRight = ColorFromPalette(currentPalette, constrain(sampleRight, 0, 255), constrain(sampleRight, 0, 255), LINEARBLEND);
nblend(ledsRight[0], newcolourRight, 128);
for (int i = N_PIXELS - 1; i > 0; i--) {
ledsRight[i] = ledsRight[i + 1];
}
}
else {
copyLeftToRight();
}
}