From 2cf84a227e84f4b2e4cc8213b0fa9e2fd6be7907 Mon Sep 17 00:00:00 2001 From: Joshua Diamond Date: Tue, 29 Dec 2020 21:10:38 -0500 Subject: [PATCH 1/3] Refine twinkle to be smoother (use breathing curve) --- docs/feature_rgblight.md | 2 +- quantum/rgblight.c | 78 +++++++++++++++++++++++----------------- quantum/rgblight.h | 2 +- 3 files changed, 47 insertions(+), 35 deletions(-) diff --git a/docs/feature_rgblight.md b/docs/feature_rgblight.md index 762056b34320..93e4e0b4d588 100644 --- a/docs/feature_rgblight.md +++ b/docs/feature_rgblight.md @@ -139,7 +139,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 db725301cbd9..8018cf510f62 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 */ @@ -935,7 +938,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 @@ -976,8 +979,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 @@ -986,17 +988,24 @@ void rgblight_task(void) { # include # endif -__attribute__((weak)) const uint8_t RGBLED_BREATHING_INTERVALS[] PROGMEM = {30, 20, 10, 5}; - -void rgblight_effect_breathing(animation_status_t *anim) { - float val; - +static uint8_t breathe_calc(uint8_t pos) { // 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]); + return pgm_read_byte(&rgblight_effect_breathe_table[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)); + 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) { + uint8_t val = breathe_calc(anim->pos); rgblight_sethsv_noeeprom_old(rgblight_config.hue, rgblight_config.sat, val); anim->pos = (anim->pos + 1); } @@ -1248,48 +1257,51 @@ 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); 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 = ((uint16_t)255) * (breathe_calc((uint16_t)255 * t->life / t->max_life) - bottom) / (top - bottom); + c->v = ((uint16_t)rgblight_config.val * unscaled) >> 8; + } else if (rand() < ((uint16_t)RAND_MAX * RGBLIGHT_EFFECT_TWINKLE_PROBABILITY) * (128 + (uint16_t)rgblight_config.val / 2) >> 8) { // 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 c02fd4f37b64..3f0c22be34b6 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 From af26cb92c6fe14c4a428884c2300519361ab94e7 Mon Sep 17 00:00:00 2001 From: Joshua Diamond Date: Wed, 30 Dec 2020 12:00:32 -0500 Subject: [PATCH 2/3] tune more for firmware size --- quantum/rgblight.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/quantum/rgblight.c b/quantum/rgblight.c index 8018cf510f62..60f002ee3802 100644 --- a/quantum/rgblight.c +++ b/quantum/rgblight.c @@ -1275,6 +1275,8 @@ void rgblight_effect_twinkle(animation_status_t *anim) { 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; } + for (uint8_t i = 0; i < rgblight_ranges.effect_num_leds; i++) { TwinkleState *t = &(led_twinkle_state[i]); HSV * c = &(t->hsv); @@ -1291,7 +1293,7 @@ void rgblight_effect_twinkle(animation_status_t *anim) { } else if (t->life) { // This LED is already on, either brightening or dimming t->life--; - uint8_t unscaled = ((uint16_t)255) * (breathe_calc((uint16_t)255 * t->life / t->max_life) - bottom) / (top - bottom); + uint8_t unscaled = frac(breathe_calc(frac(t->life, t->max_life)) - bottom, top - bottom); c->v = ((uint16_t)rgblight_config.val * unscaled) >> 8; } else if (rand() < ((uint16_t)RAND_MAX * RGBLIGHT_EFFECT_TWINKLE_PROBABILITY) * (128 + (uint16_t)rgblight_config.val / 2) >> 8) { // This LED is off, but was randomly selected to start brightening From c687ebd61f18c58e9dd04e73d1912d865a63cd42 Mon Sep 17 00:00:00 2001 From: Joshua Diamond Date: Wed, 30 Dec 2020 12:25:16 -0500 Subject: [PATCH 3/3] fix bug when v=255 --- quantum/rgblight.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/quantum/rgblight.c b/quantum/rgblight.c index 60f002ee3802..12b8093d9a91 100644 --- a/quantum/rgblight.c +++ b/quantum/rgblight.c @@ -1276,6 +1276,7 @@ void rgblight_effect_twinkle(animation_status_t *anim) { 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]); @@ -1294,8 +1295,8 @@ void rgblight_effect_twinkle(animation_status_t *anim) { // This LED is already on, either brightening or dimming t->life--; uint8_t unscaled = frac(breathe_calc(frac(t->life, t->max_life)) - bottom, top - bottom); - c->v = ((uint16_t)rgblight_config.val * unscaled) >> 8; - } else if (rand() < ((uint16_t)RAND_MAX * RGBLIGHT_EFFECT_TWINKLE_PROBABILITY) * (128 + (uint16_t)rgblight_config.val / 2) >> 8) { + 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 if (random_color) { c->h = rand() % 0xFF;