From 6e8adeeaacd8cdc83422494e3d525caeded6fe9e Mon Sep 17 00:00:00 2001
From: Joshua Diamond <josh@windowoffire.com>
Date: Mon, 11 Jan 2021 02:04:42 -0500
Subject: [PATCH] Refine twinkle to be smoother (use breathing curve) (#11350)

* Refine twinkle to be smoother (use breathing curve)

* tune more for firmware size

* fix bug when v=255

~ drashna approved ~
---
 docs/feature_rgblight.md |  2 +-
 quantum/rgblight.c       | 83 ++++++++++++++++++++++++----------------
 quantum/rgblight.h       |  2 +-
 3 files changed, 51 insertions(+), 36 deletions(-)

diff --git a/docs/feature_rgblight.md b/docs/feature_rgblight.md
index 08d0e9531e..f24a4f570c 100644
--- a/docs/feature_rgblight.md
+++ b/docs/feature_rgblight.md
@@ -148,7 +148,7 @@ The following options are used to tweak the various animations:
 |`RGBLIGHT_EFFECT_KNIGHT_OFFSET`     |`0`          |The number of LEDs to start the "Knight" animation from the start of the strip by              |
 |`RGBLIGHT_RAINBOW_SWIRL_RANGE`      |`255`        |Range adjustment for the rainbow swirl effect to get different swirls                          |
 |`RGBLIGHT_EFFECT_SNAKE_LENGTH`      |`4`          |The number of LEDs to light up for the "Snake" animation                                       |
-|`RGBLIGHT_EFFECT_TWINKLE_LIFE`      |`75`         |Adjusts how quickly each LED brightens and dims when twinkling (in animation steps)            |
+|`RGBLIGHT_EFFECT_TWINKLE_LIFE`      |`200`        |Adjusts how quickly each LED brightens and dims when twinkling (in animation steps)            |
 |`RGBLIGHT_EFFECT_TWINKLE_PROBABILITY`|`1/127`     |Adjusts how likely each LED is to twinkle (on each animation step)                             |
 
 ### Example Usage to Reduce Memory Footprint
diff --git a/quantum/rgblight.c b/quantum/rgblight.c
index d277029e4b..59b3f40264 100644
--- a/quantum/rgblight.c
+++ b/quantum/rgblight.c
@@ -42,6 +42,9 @@
 #ifndef MIN
 #    define MIN(a, b) (((a) < (b)) ? (a) : (b))
 #endif
+#ifndef MAX
+#    define MAX(a, b) (((a) > (b)) ? (a) : (b))
+#endif
 
 #ifdef RGBLIGHT_SPLIT
 /* for split keyboard */
@@ -933,7 +936,7 @@ void rgblight_task(void) {
 #    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);
+            interval_time = get_interval_time(&RGBLED_TWINKLE_INTERVALS[delta % 3], 5, 30);
             effect_func   = (effect_func_t)rgblight_effect_twinkle;
         }
 #    endif
@@ -975,8 +978,7 @@ void rgblight_task(void) {
 
 #endif /* RGBLIGHT_USE_TIMER */
 
-// Effects
-#ifdef RGBLIGHT_EFFECT_BREATHING
+#if defined(RGBLIGHT_EFFECT_BREATHING) || defined(RGBLIGHT_EFFECT_TWINKLE)
 
 #    ifndef RGBLIGHT_EFFECT_BREATHE_CENTER
 #        ifndef RGBLIGHT_BREATHE_TABLE_SIZE
@@ -985,17 +987,24 @@ void rgblight_task(void) {
 #        include <rgblight_breathe_table.h>
 #    endif
 
+static uint8_t breathe_calc(uint8_t pos) {
+    // http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/
+#    ifdef RGBLIGHT_EFFECT_BREATHE_TABLE
+    return pgm_read_byte(&rgblight_effect_breathe_table[pos / table_scale]);
+#    else
+    return (exp(sin((pos / 255.0) * M_PI)) - RGBLIGHT_EFFECT_BREATHE_CENTER / M_E) * (RGBLIGHT_EFFECT_BREATHE_MAX / (M_E - 1 / M_E));
+#    endif
+}
+
+#endif
+
+// Effects
+#ifdef RGBLIGHT_EFFECT_BREATHING
+
 __attribute__((weak)) const uint8_t RGBLED_BREATHING_INTERVALS[] PROGMEM = {30, 20, 10, 5};
 
 void rgblight_effect_breathing(animation_status_t *anim) {
-    float val;
-
-    // http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/
-#    ifdef RGBLIGHT_EFFECT_BREATHE_TABLE
-    val = pgm_read_byte(&rgblight_effect_breathe_table[anim->pos / table_scale]);
-#    else
-    val = (exp(sin((anim->pos / 255.0) * M_PI)) - RGBLIGHT_EFFECT_BREATHE_CENTER / M_E) * (RGBLIGHT_EFFECT_BREATHE_MAX / (M_E - 1 / M_E));
-#    endif
+    uint8_t val = breathe_calc(anim->pos);
     rgblight_sethsv_noeeprom_old(rgblight_config.hue, rgblight_config.sat, val);
     anim->pos = (anim->pos + 1);
 }
@@ -1247,48 +1256,54 @@ void rgblight_effect_alternating(animation_status_t *anim) {
 #endif
 
 #ifdef RGBLIGHT_EFFECT_TWINKLE
-__attribute__((weak)) const uint8_t RGBLED_TWINKLE_INTERVALS[] PROGMEM = {50, 25, 10};
+__attribute__((weak)) const uint8_t RGBLED_TWINKLE_INTERVALS[] PROGMEM = {30, 15, 5};
 
 typedef struct PACKED {
     HSV     hsv;
     uint8_t life;
-    bool    up;
+    uint8_t max_life;
 } 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;
+    const bool random_color = anim->delta / 3;
+    const bool restart      = anim->pos == 0;
+    anim->pos               = 1;
+
+    const uint8_t bottom = breathe_calc(0);
+    const uint8_t top    = breathe_calc(127);
+
+    uint8_t frac(uint8_t n, uint8_t d) { return (uint16_t)255 * n / d; }
+    uint8_t scale(uint16_t v, uint8_t scale) { return (v * scale) >> 8; }
 
     for (uint8_t i = 0; i < rgblight_ranges.effect_num_leds; i++) {
         TwinkleState *t = &(led_twinkle_state[i]);
         HSV *         c = &(t->hsv);
+
+        if (!random_color) {
+            c->h = rgblight_config.hue;
+            c->s = rgblight_config.sat;
+        }
+
         if (restart) {
             // Restart
-            t->life  = 0;
-            t->hsv.v = 0;
+            t->life = 0;
+            c->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) {
+            uint8_t unscaled = frac(breathe_calc(frac(t->life, t->max_life)) - bottom, top - bottom);
+            c->v             = scale(rgblight_config.val, unscaled);
+        } else if (rand() < scale((uint16_t)RAND_MAX * RGBLIGHT_EFFECT_TWINKLE_PROBABILITY, 127 + rgblight_config.val / 2)) {
             // 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;
+            if (random_color) {
+                c->h = rand() % 0xFF;
+                c->s = (rand() % (rgblight_config.sat / 2)) + (rgblight_config.sat / 2);
+            }
+            c->v        = 0;
+            t->max_life = MAX(20, MIN(RGBLIGHT_EFFECT_TWINKLE_LIFE, rgblight_config.val));
+            t->life     = t->max_life;
         } else {
             // This LED is off, and was NOT selected to start brightening
         }
diff --git a/quantum/rgblight.h b/quantum/rgblight.h
index c02fd4f37b..3f0c22be34 100644
--- a/quantum/rgblight.h
+++ b/quantum/rgblight.h
@@ -150,7 +150,7 @@ enum RGBLIGHT_EFFECT_MODE {
 #    endif
 
 #    ifndef RGBLIGHT_EFFECT_TWINKLE_LIFE
-#        define RGBLIGHT_EFFECT_TWINKLE_LIFE 75
+#        define RGBLIGHT_EFFECT_TWINKLE_LIFE 200
 #    endif
 
 #    ifndef RGBLIGHT_EFFECT_TWINKLE_PROBABILITY