From 2fe7e221ec9e412cc008aa5c03eaf27e35ff62c6 Mon Sep 17 00:00:00 2001
From: Joshua Diamond <josh@windowoffire.com>
Date: Sat, 9 May 2020 04:56:16 -0400
Subject: New RGB Lighting effect: Twinkle (#8887)

* Add twinkle RGB Lighting effect

* 2nd twinkle algo - double-buffering

* Further refinement: Per-LED twinkle

* Add documentation for Twinkle RBG Lighting mode

* Bias twinkle saturation closer to the set value

* Fix whitespace
---
 quantum/rgblight.c       | 65 +++++++++++++++++++++++++++++++++++++++++++++++-
 quantum/rgblight.h       | 20 ++++++++++++++-
 quantum/rgblight_modes.h |  8 ++++++
 3 files changed, 91 insertions(+), 2 deletions(-)

(limited to 'quantum')

diff --git a/quantum/rgblight.c b/quantum/rgblight.c
index 33326fa868..8d01bcf0e6 100644
--- a/quantum/rgblight.c
+++ b/quantum/rgblight.c
@@ -15,6 +15,7 @@
  */
 #include <math.h>
 #include <string.h>
+#include <stdlib.h>
 #ifdef __AVR__
 #    include <avr/eeprom.h>
 #    include <avr/interrupt.h>
@@ -561,7 +562,7 @@ void rgblight_sethsv_at(uint8_t hue, uint8_t sat, uint8_t val, uint8_t index) {
     rgblight_setrgb_at(tmp_led.r, tmp_led.g, tmp_led.b, index);
 }
 
-#if defined(RGBLIGHT_EFFECT_BREATHING) || defined(RGBLIGHT_EFFECT_RAINBOW_MOOD) || defined(RGBLIGHT_EFFECT_RAINBOW_SWIRL) || defined(RGBLIGHT_EFFECT_SNAKE) || defined(RGBLIGHT_EFFECT_KNIGHT)
+#if defined(RGBLIGHT_EFFECT_BREATHING) || defined(RGBLIGHT_EFFECT_RAINBOW_MOOD) || defined(RGBLIGHT_EFFECT_RAINBOW_SWIRL) || defined(RGBLIGHT_EFFECT_SNAKE) || defined(RGBLIGHT_EFFECT_KNIGHT) || defined(RGBLIGHT_EFFECT_TWINKLE)
 
 static uint8_t get_interval_time(const uint8_t *default_interval_address, uint8_t velocikey_min, uint8_t velocikey_max) {
     return
@@ -904,6 +905,12 @@ void rgblight_task(void) {
             interval_time = 500;
             effect_func   = (effect_func_t)rgblight_effect_alternating;
         }
+#    endif
+#    ifdef RGBLIGHT_EFFECT_TWINKLE
+        else if (rgblight_status.base_mode == RGBLIGHT_MODE_TWINKLE) {
+            interval_time = get_interval_time(&RGBLED_TWINKLE_INTERVALS[delta % 3], 5, 50);
+            effect_func   = (effect_func_t)rgblight_effect_twinkle;
+        }
 #    endif
         if (animation_status.restart) {
             animation_status.restart    = false;
@@ -1189,3 +1196,59 @@ void rgblight_effect_alternating(animation_status_t *anim) {
     anim->pos = (anim->pos + 1) % 2;
 }
 #endif
+
+#ifdef RGBLIGHT_EFFECT_TWINKLE
+__attribute__((weak)) const uint8_t RGBLED_TWINKLE_INTERVALS[] PROGMEM = {50, 25, 10};
+
+typedef struct PACKED {
+  HSV hsv;
+  uint8_t life;
+  bool up;
+} TwinkleState;
+
+static TwinkleState led_twinkle_state[RGBLED_NUM];
+
+void rgblight_effect_twinkle(animation_status_t *anim) {
+
+    bool random_color = anim->delta / 3;
+    bool restart = anim->pos == 0;
+    anim->pos = 1;
+
+    for (uint8_t i = 0; i < rgblight_ranges.effect_num_leds; i++) {
+        TwinkleState *t = &(led_twinkle_state[i]);
+        HSV *c = &(t->hsv);
+        if (restart) {
+            // Restart
+            t->life = 0;
+            t->hsv.v = 0;
+        } else if (t->life) {
+            // This LED is already on, either brightening or dimming
+            t->life--;
+            uint8_t on = t->up ? RGBLIGHT_EFFECT_TWINKLE_LIFE - t->life : t->life;
+            c->v = (uint16_t) rgblight_config.val * on / RGBLIGHT_EFFECT_TWINKLE_LIFE;
+            if (t->life == 0 && t->up) {
+                t->up = false;
+                t->life = RGBLIGHT_EFFECT_TWINKLE_LIFE;
+            }
+            if (!random_color) {
+                c->h = rgblight_config.hue;
+                c->s = rgblight_config.sat;
+            }
+        } else if (rand() < RAND_MAX * RGBLIGHT_EFFECT_TWINKLE_PROBABILITY) {
+            // This LED is off, but was randomly selected to start brightening
+            c->h = random_color ? rand() % 0xFF : rgblight_config.hue;
+            c->s = random_color ? (rand() % (rgblight_config.sat / 2)) + (rgblight_config.sat / 2) : rgblight_config.sat;
+            c->v = 0;
+            t->life = RGBLIGHT_EFFECT_TWINKLE_LIFE;
+            t->up = true;
+        } else {
+            // This LED is off, and was NOT selected to start brightening
+        }
+
+        LED_TYPE *ledp = led + i + rgblight_ranges.effect_start_pos;
+        sethsv(c->h, c->s, c->v, ledp);
+    }
+
+    rgblight_set();
+}
+#endif
diff --git a/quantum/rgblight.h b/quantum/rgblight.h
index e060737283..7de9c3f3de 100644
--- a/quantum/rgblight.h
+++ b/quantum/rgblight.h
@@ -59,6 +59,12 @@
 |       34        | RGBLIGHT_MODE_STATIC_GRADIENT + 9 |
 |       35        | RGBLIGHT_MODE_RGB_TEST            |
 |       36        | RGBLIGHT_MODE_ALTERNATING         |
+|       37        | RGBLIGHT_MODE_TWINKLE             |
+|       38        | RGBLIGHT_MODE_TWINKLE + 1         |
+|       39        | RGBLIGHT_MODE_TWINKLE + 2         |
+|       40        | RGBLIGHT_MODE_TWINKLE + 3         |
+|       41        | RGBLIGHT_MODE_TWINKLE + 4         |
+|       42        | RGBLIGHT_MODE_TWINKLE + 5         |
 |-----------------|-----------------------------------|
  *****/
 
@@ -73,6 +79,7 @@
 #    define RGBLIGHT_EFFECT_STATIC_GRADIENT
 #    define RGBLIGHT_EFFECT_RGB_TEST
 #    define RGBLIGHT_EFFECT_ALTERNATING
+#    define RGBLIGHT_EFFECT_TWINKLE
 #endif
 
 #ifdef RGBLIGHT_STATIC_PATTERNS
@@ -89,7 +96,8 @@
   || defined(RGBLIGHT_EFFECT_KNIGHT)        \
   || defined(RGBLIGHT_EFFECT_CHRISTMAS)     \
   || defined(RGBLIGHT_EFFECT_RGB_TEST)      \
-  || defined(RGBLIGHT_EFFECT_ALTERNATING)
+  || defined(RGBLIGHT_EFFECT_ALTERNATING)   \
+  || defined(RGBLIGHT_EFFECT_TWINKLE)
 #    define RGBLIGHT_USE_TIMER
 #endif
 
@@ -141,6 +149,14 @@ enum RGBLIGHT_EFFECT_MODE {
 #        define RGBLIGHT_EFFECT_CHRISTMAS_STEP 2
 #    endif
 
+#    ifndef RGBLIGHT_EFFECT_TWINKLE_LIFE
+#        define RGBLIGHT_EFFECT_TWINKLE_LIFE 75
+#    endif
+
+#    ifndef RGBLIGHT_EFFECT_TWINKLE_PROBABILITY
+#        define RGBLIGHT_EFFECT_TWINKLE_PROBABILITY 1/127
+#    endif
+
 #    ifndef RGBLIGHT_HUE_STEP
 #        define RGBLIGHT_HUE_STEP 8
 #    endif
@@ -208,6 +224,7 @@ extern const uint8_t  RGBLED_RAINBOW_SWIRL_INTERVALS[3] PROGMEM;
 extern const uint8_t  RGBLED_SNAKE_INTERVALS[3] PROGMEM;
 extern const uint8_t  RGBLED_KNIGHT_INTERVALS[3] PROGMEM;
 extern const uint16_t RGBLED_RGBTEST_INTERVALS[1] PROGMEM;
+extern const uint8_t  RGBLED_TWINKLE_INTERVALS[3] PROGMEM;
 extern bool           is_rgblight_initialized;
 
 // Should stay in sycn with rgb matrix config as we reuse eeprom storage for both (for now)
@@ -398,6 +415,7 @@ void rgblight_effect_knight(animation_status_t *anim);
 void rgblight_effect_christmas(animation_status_t *anim);
 void rgblight_effect_rgbtest(animation_status_t *anim);
 void rgblight_effect_alternating(animation_status_t *anim);
+void rgblight_effect_twinkle(animation_status_t *anim);
 
 #    endif
 
diff --git a/quantum/rgblight_modes.h b/quantum/rgblight_modes.h
index 40c9ce4980..7abdb87bc6 100644
--- a/quantum/rgblight_modes.h
+++ b/quantum/rgblight_modes.h
@@ -53,6 +53,14 @@ _RGBM_SINGLE_DYNAMIC(RGB_TEST)
 #    ifdef RGBLIGHT_EFFECT_ALTERNATING
 _RGBM_SINGLE_DYNAMIC(ALTERNATING)
 #    endif
+#    ifdef RGBLIGHT_EFFECT_TWINKLE
+_RGBM_MULTI_DYNAMIC(TWINKLE)
+_RGBM_TMP_DYNAMIC(twinkle_38, TWINKLE)
+_RGBM_TMP_DYNAMIC(twinkle_39, TWINKLE)
+_RGBM_TMP_DYNAMIC(twinkle_40, TWINKLE)
+_RGBM_TMP_DYNAMIC(twinkle_41, TWINKLE)
+_RGBM_TMP_DYNAMIC(TWINKLE_end, TWINKLE)
+#    endif
 ////  Add a new mode here.
 // #ifdef RGBLIGHT_EFFECT_<name>
 //    _RGBM_<SINGLE|MULTI>_<STATIC|DYNAMIC>( <name> )
-- 
cgit v1.2.3