//this sketch for simply record any fastled animation
//to rgb animation format for a4Tech Bloody B810R keyboard KeyDominator  

//Yaroslaw Turbin 20-04-2022
//https://twitter.com/ldir_ko
//https://www.reddit.com/user/ldirko/
//https://vk.com/ldirko
//https://www.youtube.com/c/ldirldir/


#include "FastLED.h"
 
// Matrix size
#define NUM_ROWS_PLANAR 6   //virtual buffer ROWS size 
#define NUM_COLS_PLANAR 29  //virtual buffer COLS size

#define NUM_LEDS (NUM_ROWS_PLANAR*NUM_COLS_PLANAR)  // 174 //for tests
#define NUM_LEDS_KEYB 104
#define SAFE_INDEX NUM_LEDS_KEYB

 
// LEDs pin
#define DATA_PIN 3   
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB 
 
// LED brightness
#define BRIGHTNESS 255
#define MAX_POWER_MILLIAMPS 700 
 
// Define the array of leds
CRGB leds[NUM_LEDS+1];
CRGB keybLeds [NUM_LEDS_KEYB+1];

void setup() {
  Serial.begin(115200);
  FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
  // FastLED.setMaxPowerInVoltsAndMilliamps(5, MAX_POWER_MILLIAMPS);
  FastLED.setBrightness(BRIGHTNESS);
}

void StartText(int frames) {    //header of animation file
  Serial.print("<?xml version=");   
  Serial.print((char)34);
  Serial.print("1.0");
  Serial.print((char)34);
  Serial.print(" encoding=");
  Serial.print((char)34);
  Serial.print("Unicode");
  Serial.print((char)34);
  Serial.println("?>");
  Serial.println("<Root>");
  Serial.println("<Description>ckAnimation:RGB Plasma by ldirko. wwww.twitter.com/ldir_ko www.youtube.com/c/ldirldir/ </Description>");
  Serial.println("<Time>0</Time>");
  Serial.println("<BackgroundColor>000000</BackgroundColor>");
  Serial.print("<FrameCount>");
  Serial.print(frames);
  Serial.println("</FrameCount>");
  Serial.println("");
}


void OneFrame (int FrameNumber, float DisplayTime ){ //parce one frame of leds values to serial output

  Serial.print("<Frame");
  Serial.print(FrameNumber, DEC);
  Serial.println(">");
  Serial.println("<ColorPicture>");
  
  for (int i = 0; i < NUM_LEDS_KEYB; i++) {
    byte r = keybLeds[i].b; // swap r and b for keyboard format
    byte g = keybLeds[i].g;
    byte b = keybLeds[i].r;

    if (r<16) Serial.print("0");   // add 0 before next digit if number <16 for full 2 HEX digit output
    Serial.print(r,HEX);
    if (g<16) Serial.print("0");   // add 0 before next digit if number <16 for full 2 HEX digit output
    Serial.print(g,HEX);
    if (b<16) Serial.print("0");   // add 0 before next digit if number <16 for full 2 HEX digit output
    Serial.print(b,HEX);
    if (i!=(NUM_LEDS-1)) Serial.print(", ");   //do not print comma after last led in frame
  }

  Serial.println("");
  Serial.print("</ColorPicture>");
  Serial.println("");
  Serial.print("<DisplayTime>");
  Serial.print(DisplayTime, 2);
  Serial.print("</DisplayTime>");
  Serial.println("");
  Serial.print("</Frame");
  Serial.print(FrameNumber);
  Serial.print(">");
  Serial.println("");
  Serial.println("");
}

void FinalText() {         //add </Root> in final
  Serial.println("</Root>");
}


byte PlanarTable[] = {   //  keyboard lookup map table. i made it by hand )))
  0, 104, 104, 1, 2, 3, 104, 4, 104, 5, 6, 7, 104, 8, 104, 9, 10, 104, 11, 12, 104, 104, 104, 104, 104, 104, 13, 14, 15,
  16, 17, 104, 18, 19, 20, 104, 21, 22, 23, 104, 24, 25, 104, 26, 27, 28, 104, 29, 104, 104, 30, 31, 32, 104, 33, 34, 35, 36,
  37, 104, 38, 39, 104, 40, 41, 42, 104, 43, 44, 45, 104, 46, 47, 104, 48, 49, 104, 50, 104, 51, 52, 53, 104, 54, 55, 56, 57,
  58, 104, 59, 104, 60, 61, 62, 104, 63, 64, 65, 104, 66, 67, 104, 68, 69, 104, 70, 104, 104, 104, 104, 104, 104, 71, 72, 73, 104,
  104, 74, 104, 75, 76, 104, 77, 78, 79, 104, 80, 81, 82, 104, 83, 84, 104, 104, 85, 104, 104, 104, 86, 104, 104, 87, 88, 89, 90,
  91, 104, 92, 93, 104, 104, 104, 104, 104, 94, 104, 104, 104, 104, 95, 96, 104, 97, 104, 98, 104, 99, 100, 101, 104, 102, 104, 103, 104
};

const uint8_t exp_gamma [256] PROGMEM = {
 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,   1,   1,   1,   1,   1,
 1,   2,   2,   2,   2,   2,   2,   2,   2,   2,   3,   3,   3,   3,   3,
 4,   4,   4,   4,   4,   5,   5,   5,   5,   5,   6,   6,   6,   7,   7,
 7,   7,   8,   8,   8,   9,   9,   9,   10,  10,  10,  11,  11,  12,  12,
 12,  13,  13,  14,  14,  14,  15,  15,  16,  16,  17,  17,  18,  18,  19,
 19,  20,  20,  21,  21,  22,  23,  23,  24,  24,  25,  26,  26,  27,  28,
 28,  29,  30,  30,  31,  32,  32,  33,  34,  35,  35,  36,  37,  38,  39,
 39,  40,  41,  42,  43,  44,  44,  45,  46,  47,  48,  49,  50,  51,  52,
 53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,  64,  65,  66,  67,
 68,  70,  71,  72,  73,  74,  75,  77,  78,  79,  80,  82,  83,  84,  85,
 87,  89,  91,  92,  93,  95,  96,  98,  99,  100, 101, 102, 105, 106, 108,
 109, 111, 112, 114, 115, 117, 118, 120, 121, 123, 125, 126, 128, 130, 131,
 133, 135, 136, 138, 140, 142, 143, 145, 147, 149, 151, 152, 154, 156, 158,
 160, 162, 164, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187,
 190, 192, 194, 196, 198, 200, 202, 204, 207, 209, 211, 213, 216, 218, 220,
 222, 225, 227, 229, 232, 234, 236, 239, 241, 244, 246, 249, 251, 253, 254, 255
};

void GammaCorrection(){   //gamma correction function 
 byte r,g,b;
  for (uint16_t i=0; i<NUM_LEDS; i++){
    r=leds[i].r;
    g=leds[i].g;
    b=leds[i].b;
    leds[i].r = pgm_read_byte(exp_gamma + r);
    leds[i].g = pgm_read_byte(exp_gamma + g);
    leds[i].b = pgm_read_byte(exp_gamma + b);
  }
}

void GammaCorrection_keyb(){   //gamma correction function 
 byte r,g,b;
  for (uint16_t i=0; i<NUM_LEDS_KEYB; i++){
    r=keybLeds[i].r;
    g=keybLeds[i].g;
    b=keybLeds[i].b;
    keybLeds[i].r = pgm_read_byte(exp_gamma + r);
    keybLeds[i].g = pgm_read_byte(exp_gamma + g);
    keybLeds[i].b = pgm_read_byte(exp_gamma + b);
  }
}

byte XY_PLANAR (byte x, byte y) {   //calculate XY keyboard index 
  return (PlanarTable[y*NUM_COLS_PLANAR+x]);
}

int XY(byte x, byte y) {
  return (y*NUM_COLS_PLANAR+x);
}

uint16_t globalframe = 0;

CRGB CalcPlasma (byte x, byte y, int a){
  CRGB color;
  color.b=sin8((x-8)*cos8((y+20)*8)/4+a);
  color.g=(sin8(x*24+a/3)+cos8(y*16+a/2))/2;
  color.r=sin8(cos8(x*16+a/3)+sin8(y*16+a/4)+a);
  return (color);   
}

void LedsRoutine (){     //calculate something here
 int  a = globalframe;
  
 for (int x = 0; x < NUM_COLS_PLANAR; x++) {
   for (int y = 0; y < NUM_ROWS_PLANAR; y++) {
     int index = XY(x, y);
     leds[index] = CalcPlasma(x,y,a);
     int keybIndex = XY_PLANAR(x, y);
     keybLeds[keybIndex]=leds[index];
    }
  }

  GammaCorrection(); 
  GammaCorrection_keyb();
  globalframe+=10;   //speed of effect
  FastLED.show();
}

void LedsRoutineBack (){     //calculate something here
 int  a = globalframe;
  
 for (int x = 0; x < NUM_COLS_PLANAR; x++) {
   for (int y = 0; y < NUM_ROWS_PLANAR; y++) {
     int index = XY(x, y);
     leds[index] = CalcPlasma(x,y,a);
     int keybIndex = XY_PLANAR(x, y);
     keybLeds[keybIndex]=leds[index];
    }
  }

  GammaCorrection(); 
  GammaCorrection_keyb();
  globalframe-=10;   //speed of effect
  FastLED.show();
}



void loop() {
  static byte init = 1;  //no need to change this
  static int frame = 1;  //no need to change this

  int HowManyFrames = 80;  // how many frames in animation. 80 frames is max in Keydominator!
   
  if (init) {StartText(HowManyFrames), init=0;} //add header. run once
  
  while (HowManyFrames>40){
    LedsRoutine (); //calc one animation frame 
    OneFrame(frame, 0.1);  //parse one animation frame. Minimal delay between frames in KeyDominator is 0.05
    frame++;
    HowManyFrames--;
  }
  
  while (HowManyFrames>0){  //ping-pong animation for loop anumation
    LedsRoutineBack (); //calc one animation frame 
    OneFrame(frame, 0.1);  //parse one animation frame. 0.1 - is minimal delay between frames in KeyDominator  
    frame++;
    HowManyFrames--;
  }

  FinalText();   //add final text. run once
  for(;;);  // stop here
}