// ArrayOfLedArrays - see https://github.com/FastLED/FastLED/wiki/Multiple-Controller-Examples for more info on
// using multiple controllers. In this example, we're going to set up three NEOPIXEL strips on three
// different pins, each strip getting its own CRGB array to be played with, only this time they're going
// to be all parts of an array of arrays.
#include <FastLED.h>
#define TOTAL_LEDS (2+10+3)
CRGB l_fxOne[TOTAL_LEDS];
CRGB l_fxTwo[TOTAL_LEDS];
CRGB l_onestrip[TOTAL_LEDS];
#define EYE_LEN 1
#define SIDES_LEN 5
#define TAIL_LEN 3
#define BRIGHTNESS 64
TBlendType currentBlending = LINEARBLEND;
boolean halfChance() {
return random8() > 128;
}
boolean lowChance() {
return random8() > 198;
}
const boolean one_strip_mode = false;
void FadeToBlack(CRGB* leds) {
for(int i = 0; i < TOTAL_LEDS; i++) {
leds[i].nscale8(250);
}
}
void setup() {
randomSeed(analogRead(A0));
random16_set_seed(analogRead(A0));
if(one_strip_mode) {
FastLED.addLeds<NEOPIXEL, 2>(l_onestrip, TOTAL_LEDS).setCorrection(TypicalLEDStrip);
} else {
CRGB* dest = l_onestrip;
FastLED.addLeds<NEOPIXEL, 8>(dest, EYE_LEN).setCorrection(TypicalLEDStrip);
dest += EYE_LEN;
FastLED.addLeds<NEOPIXEL, 9>(dest, EYE_LEN).setCorrection(TypicalLEDStrip);
dest += EYE_LEN;
FastLED.addLeds<NEOPIXEL, 10>(dest, SIDES_LEN).setCorrection(TypicalLEDStrip);
dest += SIDES_LEN;
FastLED.addLeds<NEOPIXEL, 11>(dest, SIDES_LEN).setCorrection(TypicalLEDStrip);
dest += SIDES_LEN;
FastLED.addLeds<NEOPIXEL, 13>(dest, TAIL_LEN).setCorrection(TypicalLEDStrip);
}
FastLED.setBrightness( BRIGHTNESS );
Serial.begin(115200); // Any baud rate should work
}
#define FRAMES_PER_SECOND 100
class FadeNumber {
public:
uint8_t Value;
private:
uint8_t mTarget;
uint8_t mStartValue;
uint16_t mTimeMs;
unsigned long mTransitionEndTime;
public:
bool IsDone;
FadeNumber(uint8_t startValue) {
Value = startValue;
IsDone = true;
}
void Transition(uint8_t target, uint16_t timeMs)
{
mTarget = target;
mStartValue = Value;
mTimeMs = timeMs;
mTransitionEndTime = millis() + mTimeMs;
IsDone = false;
}
void Update(boolean debug = false) {
if(IsDone) {
return;
}
uint8_t easeInValue = map((mTransitionEndTime - millis()), 0, mTimeMs, 255, 0);
uint8_t easeInOutput = ease8InOutQuad(easeInValue);
Value = lerp8by8(mStartValue, mTarget, easeInOutput);
if(debug)
{
char buffer[512];
sprintf(buffer, "mEaseInValue: %hu mEaseOutOutput: %hu Value: %hu Target %hu mStartValue %hu Time %d mTransitionEndTime %d Time %l", easeInValue, easeInOutput, Value, mTarget, mStartValue, mTimeMs, mTransitionEndTime, millis());
//Serial.println(buffer);
}
if(easeInOutput == 255) {
IsDone = true;
}
}
};
class Trigger {
unsigned long mTriggerTime;
public:
FadeNumber TriggerDelta;
Trigger(uint8_t triggerTime): TriggerDelta(triggerTime) {
mTriggerTime = millis() + TriggerDelta.Value;
}
boolean Update() {
if(millis() > mTriggerTime) {
mTriggerTime = millis() + TriggerDelta.Value;
return true;
}
TriggerDelta.Update();
return false;
}
};
//EFFECT #1 - Organic
class OrganicEffectState {
public:
Trigger HueUpdate;
FadeNumber ColorJump;
Trigger ColorIndexSpeed;
OrganicEffectState(int startIndex) : ColorJump(5), ColorIndexSpeed(100), HueUpdate(100) {
ColorIndex = startIndex;
UpdatePallete();
}
boolean IsReverse = true;
int ColorIndex = 0;
uint8_t Hue = 0;
CRGBPalette16 palleteSides;
CRGBPalette16 palleteEyes;
void UpdatePallete() {
CRGB colorA = CHSV(Hue, 255, 255);
CRGB colorB = CHSV(Hue + 128, 255, 255);
CRGB black = CRGB::Black;
palleteSides = CRGBPalette16(
colorA, black, colorB, black,
colorA, black, colorB, black,
colorA, black, colorB, black,
colorA, black, colorB, black);
palleteEyes = CRGBPalette16(
colorA, colorB, colorA, colorB,
colorA, colorB, colorA, colorB,
colorA, colorB, colorA, colorB,
colorA, colorB, colorA, colorB);
}
void Update() {
ColorJump.Update();
if(ColorIndexSpeed.Update()) {
ColorIndex += (IsReverse ? -1 : 1);
}
if(HueUpdate.Update()) {
Hue+=1;
UpdatePallete();
}
}
void TryChange() {
if(ColorIndexSpeed.TriggerDelta.IsDone && ColorJump.IsDone)
{
if(halfChance()) {
ColorIndexSpeed.TriggerDelta.Transition(20 + random8(150), 1500 + random16(10000));
}
if(halfChance())
{
ColorJump.Transition(1 + random8(10), 1500 + random16(10000));
}
}
if(lowChance()) {
IsReverse = !IsReverse;
}
}
};
class Pattern
{
public:
virtual void Render(CRGB* leds) = 0;
virtual void Update() = 0;
virtual void TryChange() = 0;
virtual void Enter() {
};
};
class OrganicEffect: public Pattern
{
OrganicEffectState s;
public:
OrganicEffect() : s(0) {
}
void Render(CRGB* leds) {
CRGB *dest = leds;
FillLEDsFromPaletteColors(dest, &s.palleteEyes, EYE_LEN,(s.ColorIndex / 4), 1, 96);
dest += EYE_LEN;
FillLEDsFromPaletteColors(dest, &s.palleteEyes, EYE_LEN,(s.ColorIndex / 4), 1, 96);
dest += EYE_LEN;
FillLEDsFromPaletteColors(dest, &s.palleteSides, SIDES_LEN, s.ColorIndex, s.ColorJump.Value, 128);
dest += SIDES_LEN;
FillLEDsFromPaletteColors(dest, &s.palleteSides, SIDES_LEN, s.ColorIndex + 32, s.ColorJump.Value, 128);
dest += SIDES_LEN;
FillLEDsFromPaletteColors(dest, &s.palleteEyes, TAIL_LEN, s.ColorIndex / 4, 3, 96);
}
void Update() {
s.Update();
}
void TryChange() {
s.TryChange();
}
private:
void FillLEDsFromPaletteColors( CRGB* colors, CRGBPalette16* pallete, int numLeds, uint8_t colorIndex, uint8_t colorJump, int brightness)
{
for( int i = 0; i < numLeds; i++) {
colors[i] = ColorFromPalette( *pallete, colorIndex, brightness, currentBlending);
colorIndex += colorJump;
}
}
};
class RainbowEffect: public Pattern
{
public:
Trigger HueUpdate;
uint8_t Hue = 0;
FadeNumber HueJump;
RainbowEffect() : HueUpdate(50), HueJump(5) {
}
void Render(CRGB* leds) {
fill_rainbow(leds, TOTAL_LEDS, Hue, HueJump.Value);
}
void Update() {
if(HueUpdate.Update()) {
Hue += 1;
}
}
void TryChange() {
if(halfChance()) {
HueJump.Transition(random8(20), random16(5000) + 3000);
}
}
};
class ColorStrobe: public Pattern
{
public:
Trigger MovePixel;
uint8_t lastPixel = 0;
FadeNumber HueColor;
uint8_t howManyPixels;
bool useAdd = true;
ColorStrobe() : MovePixel(200), HueColor(200) {
}
void Enter() {
HueColor.Value = random8();
howManyPixels = 1 + random8(2);
useAdd = halfChance();
}
uint8_t getPos(uint8_t index) {
const uint8_t jump = (TOTAL_LEDS / howManyPixels);
return (lastPixel + (jump * index)) % TOTAL_LEDS;
}
void Render(CRGB* leds) {
for(uint8_t i=0; i < howManyPixels; i++) {
leds[getPos(i)] = CHSV(HueColor.Value + i * 30, 255, 196);;
}
FadeToBlack(leds);
}
void Update() {
if(MovePixel.Update()) {
lastPixel ++;
lastPixel %= TOTAL_LEDS;
}
}
void TryChange() {
if(lowChance()) {
useAdd = halfChance();
}
if(halfChance()) {
HueColor.Transition(random8(), 1000 + random16(1500));
}
}
};
class GradientEffect: public Pattern
{
public:
Trigger HueMove;
uint8_t hueStart = 0;
FadeNumber HueJump;
bool use2Colors = false;
GradientEffect() : HueMove(100), HueJump(30) {
Enter();
}
void Render(CRGB* leds) {
if(use2Colors)
fill_gradient(leds, TOTAL_LEDS, CHSV(hueStart, 255, 196), CHSV(hueStart+HueJump.Value, 255, 196));
else
fill_gradient(leds, TOTAL_LEDS, CHSV(hueStart, 255, 196), CHSV(hueStart+HueJump.Value, 255, 196), CHSV(hueStart+hueStart+HueJump.Value*2, 255, 196));
}
void Enter() {
hueStart = random8(255);
use2Colors = random8() > 128;
}
void Update() {
HueJump.Update();
if(HueMove.Update()) {
hueStart++;
}
}
void TryChange() {
if(halfChance()){
HueJump.Transition(40 + random8(40), 1500 + random16(6000));
}
}
};
OrganicEffect OrganicFx;
RainbowEffect RainbowFx;
ColorStrobe StrobeFx;
GradientEffect GradientFx;
int currentPattern = 2;
#define TOTAL_PATTERNS 4
const Pattern* gPatterns[] = {&OrganicFx, &RainbowFx, &StrobeFx, &GradientFx};
Pattern* current = NULL;
Pattern* nextPattern = NULL;
FadeNumber TransitionState(0);
void loop() {
if(current == NULL) {
current = gPatterns[random8(TOTAL_PATTERNS)];
//current = gPatterns[currentPattern];
current->Enter();
}
current->Render(l_fxOne);
current->Update();
if(nextPattern) {
nextPattern->Render(l_fxTwo);
nextPattern->Update();
}
TransitionState.Update();
for(int i=0; i < TOTAL_LEDS; i++) {
if(nextPattern)
l_onestrip[i] = blend(l_fxOne[i], l_fxTwo[i], TransitionState.Value);
else
memcpy(l_onestrip, l_fxOne, sizeof(l_onestrip));
}
blur1d(l_onestrip, TOTAL_LEDS, 64);
FastLED.show();
if(TransitionState.Value == 255 && nextPattern != NULL) {
//Serial.println("Exit pattern!");
current = nextPattern;
nextPattern = NULL;
TransitionState.Value = 0;
memcpy(l_fxOne, l_fxTwo, sizeof(l_fxOne));
}
EVERY_N_SECONDS( 1 ) {
current->TryChange();
if(nextPattern)
nextPattern->TryChange();
}
EVERY_N_SECONDS( 6 ) {
if(nextPattern == NULL) {
//Serial.println("Set pattern!");
do{
uint8_t r = random8(TOTAL_PATTERNS);
//Serial.println(r);
nextPattern = gPatterns[r];
} while(nextPattern == current);
nextPattern->Enter();
TransitionState.Value = 0;
TransitionState.Transition(255, 3000 + random16(3000));
}
}
FastLED.delay(1000/FRAMES_PER_SECOND);
}