334 lines
8.7 KiB
C
334 lines
8.7 KiB
C
/*
|
|
* led_anim.c
|
|
*
|
|
* Copyright (C) 2023 Sylvain Munaut <tnt@246tNt.com>
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "led_anim.h"
|
|
#include "utils.h"
|
|
|
|
#define N_LEDS 42
|
|
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Helper: Candle effect */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
/* Sequences from half life, designed to run at 10 fps */
|
|
static const char * const candles[] = {
|
|
"mmmmmaaaaammmmmaaaaaabcdefgabcdefg",
|
|
"mmmaaaabcdefgmmmmaaaammmaamm",
|
|
"mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa",
|
|
};
|
|
|
|
struct candle_state {
|
|
uint8_t sel; /* Which candle ? */
|
|
uint8_t len; /* Sequence lenght */
|
|
uint8_t spd; /* Speed of sequence (64 = Nominal) */
|
|
uint16_t pha; /* Current phase (10.6 format) */
|
|
};
|
|
|
|
static void
|
|
candle_init(struct candle_state *c)
|
|
{
|
|
c->sel = rand16() % 3;
|
|
c->len = strlen(candles[c->sel]);
|
|
c->spd = 48 + (rand16() % 40);
|
|
c->pha = rand16() % (c->len << 6);
|
|
}
|
|
|
|
static uint8_t
|
|
candle_exec(struct candle_state *c, uint8_t spd)
|
|
{
|
|
int m;
|
|
|
|
/* Advance */
|
|
/* We're called at 30 fps, but anim string nominal is 10 fps */
|
|
/* Also we need to modulate both by our internal speed factor
|
|
* and the one provided in argument */
|
|
c->pha += (85 * spd * c->spd) >> 14;
|
|
|
|
/* Wrap */
|
|
m = (int)c->len << 6;
|
|
while (c->pha >= m)
|
|
c->pha -= m;
|
|
|
|
/* Return current state */
|
|
return candles[c->sel][c->pha >> 6] - 'a';
|
|
}
|
|
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Anim: Sparkle */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
struct la_sparkle_state {
|
|
uint16_t time;
|
|
bool candle;
|
|
struct {
|
|
uint16_t raw;
|
|
struct candle_state cdl;
|
|
} leds[N_LEDS];
|
|
};
|
|
|
|
static void
|
|
la_sparkle_init_common(void *state, bool candle)
|
|
{
|
|
struct la_sparkle_state *s = state;
|
|
|
|
s->time = 0;
|
|
s->candle = candle;
|
|
|
|
for (int i=0; i<N_LEDS; i++) {
|
|
s->leds[i].raw = 0;
|
|
candle_init(&s->leds[i].cdl);
|
|
}
|
|
}
|
|
|
|
static void
|
|
la_sparkle1_init(void *state)
|
|
{
|
|
la_sparkle_init_common(state, true);
|
|
}
|
|
|
|
static void
|
|
la_sparkle2_init(void *state)
|
|
{
|
|
la_sparkle_init_common(state, false);
|
|
}
|
|
|
|
static void
|
|
la_sparkle_render(void *state, uint16_t *leds,
|
|
struct led_anim_config *conf, uint32_t frame)
|
|
{
|
|
struct la_sparkle_state *s = state;
|
|
bool has_ob = false;
|
|
int i;
|
|
|
|
/* Decrease lightness of all leds */
|
|
for (i=0; i<N_LEDS; i++)
|
|
{
|
|
uint16_t dec;
|
|
|
|
/* Base */
|
|
dec = 2 * conf->speed;
|
|
|
|
/* Fast decrease for high intensity leds */
|
|
if (s->leds[i].raw > 40000)
|
|
dec += ((uint32_t)(s->leds[i].raw - 40000) * conf->speed) >> 10;
|
|
|
|
/* Decrement with low saturation to 0 */
|
|
if ((s->leds[i].raw < dec) || (s->leds[i].raw < 500))
|
|
s->leds[i].raw = 0;
|
|
else
|
|
s->leds[i].raw -= dec;
|
|
|
|
/* Check if any led is over bright */
|
|
if (s->leds[i].raw > 45000)
|
|
has_ob = true;
|
|
}
|
|
|
|
/* Every ~0.25s we make a led brighter. If there is none already,
|
|
* make it overbright, else just normal */
|
|
s->time += conf->speed;
|
|
if (s->time > 512) {
|
|
s->time -= 512;
|
|
while (s->leds[i = (rand16() % N_LEDS)].raw > 10000);
|
|
s->leds[i].raw = has_ob ? 35000 : 65535;
|
|
}
|
|
|
|
/* Final lightness is raw + candle flicker if it's < 6000 */
|
|
for (i=0; i<N_LEDS; i++)
|
|
{
|
|
/* Base value */
|
|
uint16_t r = s->leds[i].raw;
|
|
|
|
/* Add candle flicker */
|
|
if (s->candle && (r < 6000))
|
|
r += (550 * candle_exec(&s->leds[i].cdl, conf->speed));
|
|
|
|
/* Final assign */
|
|
leds[i] = ((uint32_t)r * conf->dim) >> 8;
|
|
}
|
|
}
|
|
|
|
static const struct led_anim la_sparkle1 = {
|
|
.init = la_sparkle1_init,
|
|
.render = la_sparkle_render,
|
|
};
|
|
|
|
static const struct led_anim la_sparkle2 = {
|
|
.init = la_sparkle2_init,
|
|
.render = la_sparkle_render,
|
|
};
|
|
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Anim: Candle */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
struct la_candle_state {
|
|
struct candle_state cdl[N_LEDS];
|
|
};
|
|
|
|
static void
|
|
la_candle_init(void *state)
|
|
{
|
|
struct la_candle_state *s = state;
|
|
|
|
for (int i=0; i<N_LEDS; i++)
|
|
candle_init(&s->cdl[i]);
|
|
}
|
|
|
|
static void
|
|
la_candle_render(void *state, uint16_t *leds,
|
|
struct led_anim_config *conf, uint32_t frame)
|
|
{
|
|
struct la_candle_state *s = state;
|
|
|
|
for (int i=0; i<N_LEDS; i++)
|
|
leds[i] = (3750 * conf->dim * candle_exec(&s->cdl[i], conf->speed)) >> 8;
|
|
}
|
|
|
|
static const struct led_anim la_candle = {
|
|
.init = la_candle_init,
|
|
.render = la_candle_render,
|
|
};
|
|
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Anim: Breathe */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
static const uint8_t breathe[256] = {
|
|
2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7,
|
|
8, 8, 9, 10, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 22, 23,
|
|
25, 26, 28, 30, 32, 34, 36, 38, 41, 43, 46, 48, 51, 54, 57, 60,
|
|
63, 67, 70, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 114, 118, 123,
|
|
127, 132, 137, 141, 146, 151, 156, 160, 165, 170, 174, 179, 184, 188, 193, 197,
|
|
201, 206, 210, 214, 217, 221, 225, 228, 231, 234, 237, 240, 242, 245, 247, 249,
|
|
250, 252, 253, 254, 254, 255, 255, 255, 255, 255, 254, 254, 253, 253, 252, 251,
|
|
250, 249, 248, 247, 246, 245, 243, 242, 240, 238, 237, 235, 233, 231, 229, 227,
|
|
224, 222, 220, 217, 215, 213, 210, 207, 205, 202, 199, 196, 194, 191, 188, 185,
|
|
182, 179, 176, 173, 170, 167, 164, 161, 158, 155, 152, 149, 146, 143, 140, 137,
|
|
134, 131, 128, 125, 122, 119, 116, 113, 110, 108, 105, 102, 99, 97, 94, 91,
|
|
89, 86, 84, 81, 79, 77, 74, 72, 70, 68, 65, 63, 61, 59, 57, 55,
|
|
53, 52, 50, 48, 46, 45, 43, 41, 40, 38, 37, 36, 34, 33, 32, 30,
|
|
29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 17, 16, 15,
|
|
14, 14, 13, 12, 12, 11, 11, 10, 10, 9, 9, 8, 8, 8, 7, 7,
|
|
6, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 3
|
|
};
|
|
|
|
struct la_breathe_state {
|
|
int n_ob;
|
|
struct {
|
|
uint8_t spd; /* Speed (64=Nominal) */
|
|
uint16_t pha; /* Phase (8.8 FP) */
|
|
uint8_t brg; /* Brightness (128=Nominal, 184=OverBright) */
|
|
bool ob;
|
|
} leds[N_LEDS];
|
|
};
|
|
|
|
static void
|
|
la_breathe_init(void *state)
|
|
{
|
|
struct la_breathe_state *s = state;
|
|
|
|
s->n_ob = 0;
|
|
|
|
for (int i=0; i<N_LEDS; i++) {
|
|
s->leds[i].spd = 48 + (rand16() % 40);
|
|
s->leds[i].pha = rand16();
|
|
s->leds[i].brg = 64 + (rand16() & 63);
|
|
s->leds[i].ob = false;
|
|
}
|
|
}
|
|
|
|
static void
|
|
la_breathe_render(void *state, uint16_t *leds,
|
|
struct led_anim_config *conf, uint32_t frame)
|
|
{
|
|
struct la_breathe_state *s = state;
|
|
|
|
for (int i=0; i<N_LEDS; i++) {
|
|
/* Advance */
|
|
uint16_t pha = s->leds[i].pha + ((28 * s->leds[i].spd * conf->speed) >> 8);
|
|
|
|
/* Wrap around */
|
|
if (pha < s->leds[i].pha) {
|
|
/* Pick new speed */
|
|
s->leds[i].spd = 48 + (rand16() % 40);
|
|
|
|
/* New brightness, possibly OverBright */
|
|
if (!s->leds[i].ob && (s->n_ob < 2) && (rand16() & 1)) {
|
|
s->leds[i].brg = 152 + (rand16() & 31);
|
|
s->leds[i].ob = true;
|
|
s->n_ob++;
|
|
} else {
|
|
s->leds[i].brg = 64 + (rand16() & 63);
|
|
}
|
|
}
|
|
|
|
s->leds[i].pha = pha;
|
|
|
|
/* Early OverBright release */
|
|
/* After pha >= 40000, we're not overbright anyway, so
|
|
* allow another led to use it early */
|
|
if (s->leds[i].ob && (s->leds[i].pha > 40000)) {
|
|
s->leds[i].ob = false;
|
|
s->n_ob--;
|
|
}
|
|
|
|
/* Render */
|
|
leds[i] = (350UL * conf->dim * s->leds[i].brg * breathe[s->leds[i].pha >> 8]) >> 16;
|
|
}
|
|
}
|
|
|
|
static const struct led_anim la_breathe = {
|
|
.init = la_breathe_init,
|
|
.render = la_breathe_render,
|
|
};
|
|
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Anim: Constant */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
static void
|
|
la_const_init(void *state)
|
|
{
|
|
/* Nothing to do */
|
|
}
|
|
|
|
static void
|
|
la_const_render(void *state, uint16_t *leds,
|
|
struct led_anim_config *conf, uint32_t frame)
|
|
{
|
|
for (int i=0; i<N_LEDS; i++)
|
|
leds[i] = conf->dim * 192;
|
|
}
|
|
|
|
static const struct led_anim la_const = {
|
|
.init = la_const_init,
|
|
.render = la_const_render,
|
|
};
|
|
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Anim list */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
const struct led_anim * const all_anims[] = {
|
|
&la_sparkle1,
|
|
&la_sparkle2,
|
|
&la_candle,
|
|
&la_breathe,
|
|
&la_const,
|
|
NULL /* guard */
|
|
};
|