/*
**********************************************************************
* Neopixel Christmas animation by ericBcreator
* Designed to be used with an Arduino UNO, Nano or compatible device.
**********************************************************************
* Last update 20191208 by ericBcreator
*   - added support for 2 rings
*   - added offset support for setting the pixel 0
*   - added circle in and -out animations
*   - other enhancements
*
* This code is free for personal use, not for commercial purposes.
* Please leave this header intact.
*
* contact: [email protected]
**********************************************************************
*/ 
// See https://www.hackster.io/ericBcreator/neopixel-christmas-color-animation-4e94d9

//
// includes
//

#include <Adafruit_NeoPixel.h>

// Sine and Gamma tables from https://learn.adafruit.com/sipping-power-with-neopixels?view=all

// This bizarre construct isn't Arduino code in the conventional sense.
// It exploits features of GCC's preprocessor to generate a PROGMEM
// table (in flash memory) holding an 8-bit unsigned sine wave (0-255).
// R code: floor(sin(0:255/128*pi) * 127.5+.5)
// __COUNTER__ is special: https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html

const int _SBASE_ = __COUNTER__ + 1; // Index of 1st __COUNTER__ ref below
#define _S1_ (sin((__COUNTER__ - _SBASE_) / 128.0 * M_PI) + 1.0) * 127.5 + 0.5,
#define _S2_ _S1_ _S1_ _S1_ _S1_ _S1_ _S1_ _S1_ _S1_ // Expands to 8 items
#define _S3_ _S2_ _S2_ _S2_ _S2_ _S2_ _S2_ _S2_ _S2_ // Expands to 64 items
const uint8_t PROGMEM sineTable[] = { _S3_ _S3_ _S3_ _S3_ }; // 256 items

// Similar to above, but for an 8-bit gamma-correction table.
// R code: floor((0:255/255)**2.6*255) 
#define _GAMMA_ 2.6
const int _GBASE_ = __COUNTER__ + 1; // Index of 1st __COUNTER__ ref below
#define _G1_ pow((__COUNTER__ - _GBASE_) / 255.0, _GAMMA_) * 255.0 + 0.5,
#define _G2_ _G1_ _G1_ _G1_ _G1_ _G1_ _G1_ _G1_ _G1_ // Expands to 8 items
#define _G3_ _G2_ _G2_ _G2_ _G2_ _G2_ _G2_ _G2_ _G2_ // Expands to 64 items
const uint8_t PROGMEM gammaTable[] = { _G3_ _G3_ _G3_ _G3_ }; // 256 items

//
// debug settings
//

#define DEBUG                                 // print debug messages
//#define DEBUGVERBOSE                          // print calculated delay for fly in and out
//#define DEBUGCHECKPIXEL0                      // visual check which pixel is at 0 (after MAINPIXELOFFSET), sets it off after the startup fade in

#ifdef DEBUG
  #define DEBUGPRINT(x)   Serial.print(x)
  #define DEBUGPRINTLN(x) Serial.println(x)
#else
  #define DEBUGPRINT(x)
  #define DEBUGPRINTLN(x)
#endif

//
// definitions
//

#define SIGNALPIN           6                   // @EB-setup the digital output pin

// definition for 2 24 LED rings
//#define NUMOFPIXELS        48                   // @EB-setup the number of pixels
//#define TWOSTRIPS                               // @EB-setup define when using 2 LED strips
//#define MAINPIXELOFFSET     0                   // @EB-setup offset for pixel 0
//#define STRIP2OFFSET        1                   // @EB-setup offset for the 2nd strip (only works with 2 strips)

// definition for 1 60 LED ring
#define NUMOFPIXELS        20                   // @EB-setup the number of pixels
#define MAINPIXELOFFSET     0                   // @EB-setup offset for pixel 0

//
// settings
//

const int colorWheel = 0;                       // 0 red-green, 
                                                // 1 red-green-yellow, 
                                                // 2 red-green-white, 
                                                // 3 red-green-yellow-red-green-white
                                                // 4 red-green-blue 
                                                // 5 red-green-blue-red-green-yellow
const int maxColorWheel = 5;
const bool loopColorWheel = true;               // if true, loop through the color wheels
const int multiplier = 3;                       // multiplier for the number of loops of the animations

int startFadeDelay = 50;
int maxBrightness = 255;                         // 30 is about 
int ledBrightness = maxBrightness;

int fadeDelay = 750;
int fadeHoldDelay = 5000;

int flyDelay = 5;
int flyStep = 1;
float flyDelayFactor = .8;

int circleDelay = 30;

int slowLoopDelay = 500;
int slowLoopCount = 25 * multiplier;

int fastLoopDelay = 60;
int fastLoopCount = 100 * multiplier;

int walkDelay = 500;
int walkCount = 5 * multiplier;

int flashDelay = 200;
int flashCount = 10 * multiplier / 2;

//
// setup variables
//

int pixelOffset = 0;
int colorRange = 0;
int currentColorWheel = colorWheel;
bool mainLoopBit = false;

#ifdef TWOSTRIPS
  int middlePixel = NUMOFPIXELS / 2;
#endif

uint32_t stripColor[NUMOFPIXELS + 6];

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMOFPIXELS, SIGNALPIN, NEO_GRB + NEO_KHZ800); // grb

//
// setup
//

void setup() {
  #ifdef DEBUGPRINT
    Serial.begin(115200);
    DEBUGPRINTLN("Setup");
 //   DEBUGPRINTLN(__FILE_NAME__);
    DEBUGPRINTLN(__TIMESTAMP__);
  #endif
  pixels.begin();
  fadeIn();

  #ifdef DEBUGCHECKPIXEL0
    setOneLed(0, 0);
    #ifdef TWOSTRIPS
      setOneLed(middlePixel ,0);
    #endif
    pixels.show();
    delay(7500);
  #endif
}

//
// main loop
//

void loop() {  
  DEBUGPRINTLN("Main loop");
  slowLoop();
  fastLoop();
  walk();
  flash();
  
  #ifdef TWOSTRIPS
    circleOut();
    circleIn();
    circleOut();
  #else
    if (mainLoopBit)
      circleOut();
    else
      flyOut();
  #endif

  if (loopColorWheel) {
    pixelOffset = 0;
    currentColorWheel++;
    if (currentColorWheel > maxColorWheel)
      currentColorWheel = 0;
    DEBUGPRINTLN("  Colorwheel set to " + String(currentColorWheel));
    setStripColors();
  }

  #ifdef TWOSTRIPS
    circleIn();
  #else
    if (mainLoopBit)
      circleIn();
    else
      flyIn();
  #endif

  mainLoopBit = !mainLoopBit;
}

//
// functions
//

void fadeIn() {
  DEBUGPRINTLN("Fade in");
  for (int i = 0; i <= maxBrightness; i += 2) {  
    ledBrightness = i;
    setStripColors();
    setPixelColors();
    delay(startFadeDelay);
  }
}

void slowLoop() {
  DEBUGPRINTLN("  Anim: Slow loop");
  for (int i = 0; i < slowLoopCount; i++) {
    setPixelColors();
    pixelOffset++;
    pixelOffset = pixelOffset % (colorRange + 1);
    delay(slowLoopDelay);
  }
}

void fastLoop() {
  DEBUGPRINTLN("  Anim: Fast loop");
  for (int i = 0; i < fastLoopCount; i++) {
    setPixelColors();
    pixelOffset++;
    pixelOffset = pixelOffset % (colorRange + 1);
    delay(fastLoopDelay);
  }
}

void flyOut() {
  int i, j, delayTime;
  DEBUGPRINTLN("  Anim: Fly out");
  
  for (i = 0; i < NUMOFPIXELS; i++) {
    for (j = i; j > 0; j = j - flyStep) {
      setOneLed(j, stripColor[(i + pixelOffset) % NUMOFPIXELS]);
      pixels.show();
      
      delayTime = flyDelay - ((i * flyDelay * flyDelayFactor) / NUMOFPIXELS);
      #ifdef DEBUGVERBOSE
        DEBUGPRINT(delayTime);
        DEBUGPRINT("  ");
      #endif
      delay(delayTime);
      setOneLed(j, 0);
    }
  }
  
  #ifdef DEBUGVERBOSE
    DEBUGPRINTLN();
  #endif
  pixels.show();
}

void flyIn() {
  int i, j, delayTime;
  DEBUGPRINTLN("  Anim: Fly in");
  
  for (i = 0; i < NUMOFPIXELS; i++) {
    for (j = NUMOFPIXELS - i % flyStep; j > i; j = j - flyStep) {
      if (j >= 0) {
        setOneLed((j - 1), stripColor[(i + pixelOffset) % NUMOFPIXELS]);        
        pixels.show();
        
        delayTime = flyDelay - (((NUMOFPIXELS - i) * flyDelay * flyDelayFactor) / NUMOFPIXELS);
        #ifdef DEBUGVERBOSE
          DEBUGPRINT(delayTime);
          DEBUGPRINT("  ");
        #endif
        delay(delayTime);
        setOneLed((j - 1), 0);
      }
    }
    setOneLed((i - 1), stripColor[(i + pixelOffset) % NUMOFPIXELS]);
  }

  #ifdef DEBUGVERBOSE
    DEBUGPRINTLN();
  #endif
  pixels.show();
}

void circleOut() {
  int i;
  DEBUGPRINTLN("  Anim: Circle out");
  
  for (i = 0; i < NUMOFPIXELS; i++) {
    setOneLed(i, 0);
    pixels.show();
    delay(circleDelay);
  }
}

void circleIn() {
  int i;
  DEBUGPRINTLN("  Anim: Circle in");
  
  for (i = 0; i < NUMOFPIXELS; i++) {
    setOneLed(i, stripColor[(i + pixelOffset) % NUMOFPIXELS]);
    pixels.show();
    delay(circleDelay);
  }
}

void flash() {
  DEBUGPRINTLN("  Anim: Flash");
  
  for (int i = 0; i < flashCount; i++) {
    ledBrightness = maxBrightness * .2;
    setStripColors();
    setPixelColors();
    delay(flashDelay);

    ledBrightness = maxBrightness * .7;
    setStripColors();
    setPixelColors();
    delay(flashDelay);
  }
  
  ledBrightness = maxBrightness;
  setStripColors();
}

void walk() { 
  DEBUGPRINTLN("  Anim: Walk");
  
  int i, j, k, pixelCol;
  uint32_t pixelColor;

  for (k = 0; k < walkCount; k++) {
    for (j = colorRange; j >= 0; j--) {
      for (i = 0; i < NUMOFPIXELS; i++) {
        if ((i % (colorRange + 1)) == j) {
          pixelCol = i + pixelOffset;
          if (pixelCol >= NUMOFPIXELS)
            pixelCol -= colorRange + 1; 
          pixelColor = stripColor[pixelCol];
        } else
          pixelColor = 0;
        setOneLed(i, pixelColor);
      }
      pixels.show();
      delay(walkDelay);
    }
  }
}

//
// function for settings colors
//

void setStripColors() {  
  switch (currentColorWheel) {
    case 0: setStripColorsRG();     break;
    case 1: setStripColorsRGY();    break;
    case 2: setStripColorsRGW();    break;
    case 3: setStripColorsRGYRGW(); break;
    case 4: setStripColorsRGB();    break;
    case 5: setStripColorsRGBRGY(); break;
  }
}

void setStripColorsRG() {
  int r, g, b;
  colorRange = 1;
  
  r = ledBrightness;
  g = ledBrightness;
  b = 0;

  for (int i = 0; i < NUMOFPIXELS; i += 2) {
    stripColor[i] = pixels.Color(r, 0, 0);    
    stripColor[i+1] = pixels.Color(0, g, 0);
  }
}

void setStripColorsRGY() {
  int r, g, gy, b;
  colorRange = 2;
  
  r = ledBrightness;
  g = ledBrightness;  
  gy = (ledBrightness * 215 / 255);
  b = 0;

  for (int i = 0; i < NUMOFPIXELS ; i += 3) {
    stripColor[i] = pixels.Color(r, 0, 0);    
    stripColor[i+1] = pixels.Color(0, g, 0);
    stripColor[i+2] = pixels.Color(r, gy, 0);
  }
}

void setStripColorsRGW() {
  int r, g, bw;
  colorRange = 2;
  
  r = ledBrightness;
  g = ledBrightness;
  bw = ledBrightness / 3;

  for (int i = 0; i < NUMOFPIXELS ; i += 3) {
    stripColor[i] = pixels.Color(r, 0, 0);    
    stripColor[i+1] = pixels.Color(0, g, 0);
    stripColor[i+2] = pixels.Color(r, g, bw);
  }
}

void setStripColorsRGYRGW() {
  int r, g, gy, bw;
  colorRange = 5;
  
  r = ledBrightness;
  g = ledBrightness;  
  gy = (ledBrightness * 215 / 255);
  bw = ledBrightness / 3;

  for (int i = 0; i < NUMOFPIXELS ; i += 6) {
    stripColor[i] =  pixels.Color(r, 0, 0);    
    stripColor[i+1] = pixels.Color(0, g, 0);
    stripColor[i+2] = pixels.Color(r, gy, 0);
    stripColor[i+3] = pixels.Color(r, 0, 0);    
    stripColor[i+4] = pixels.Color(0, g, 0);
    stripColor[i+5] = pixels.Color(r, g, bw);
  }
}

void setStripColorsRGB() {
  int r, g, b;
  colorRange = 2;
  
  r = ledBrightness;
  g = ledBrightness;  
  b = ledBrightness;

  for (int i = 0; i < NUMOFPIXELS ; i += 3) {
    stripColor[i] = pixels.Color(r, 0, 0);    
    stripColor[i+1] = pixels.Color(0, g, 0);
    stripColor[i+2] = pixels.Color(0, 0, b);
  }
}

void setStripColorsRGBRGY() {
  int r, g, gy, b;
  colorRange = 5;
  
  r = ledBrightness;
  g = ledBrightness;  
  gy = (ledBrightness * 215 / 255);
  b = ledBrightness;

  for (int i = 0; i < NUMOFPIXELS ; i += 6) {
    stripColor[i] =  pixels.Color(r, 0, 0);    
    stripColor[i+1] = pixels.Color(0, g, 0);
    stripColor[i+2] = pixels.Color(r, gy, 0);
    stripColor[i+3] = pixels.Color(r, 0, 0);    
    stripColor[i+4] = pixels.Color(0, g, 0);
    stripColor[i+5] = pixels.Color(0, 0, b);
  }
}

void setPixelColors() {
  int pixelCol;
  
  for (int i = 0; i < NUMOFPIXELS; i++) {
    pixelCol = i + pixelOffset;
    if (pixelCol >= NUMOFPIXELS)
      pixelCol -= colorRange + 1;
      
    setOneLed(i, stripColor[pixelCol]);
  }
  pixels.show();
}

void setOneLed(int pixel, uint32_t color) {
  int targetPixel;

  #ifdef TWOSTRIPS
    if (pixel < middlePixel) {
      targetPixel = (pixel + MAINPIXELOFFSET) % middlePixel;
      if (targetPixel < 0)
        targetPixel += middlePixel;

      pixels.setPixelColor(targetPixel, color);  
    } else {
      targetPixel = (NUMOFPIXELS - pixel - MAINPIXELOFFSET - STRIP2OFFSET) % middlePixel;
      if (targetPixel < 0)
        targetPixel += middlePixel;
        
      pixels.setPixelColor((middlePixel + targetPixel), color);  
    }
  #else
    targetPixel = (pixel + MAINPIXELOFFSET) % NUMOFPIXELS;
    if (targetPixel < 0)
      targetPixel += NUMOFPIXELS;

    pixels.setPixelColor(targetPixel, color);
  #endif
}