524 lines
14 KiB
C
524 lines
14 KiB
C
/*
|
|
* led_rgbprog.c
|
|
*
|
|
* Created on: Jul 27, 2024
|
|
* Author: true
|
|
*/
|
|
#include <stdint.h>
|
|
|
|
#include "config.h"
|
|
#include "hsv2rgb.h"
|
|
#include "led.h"
|
|
#include "rand.h"
|
|
|
|
|
|
// led programs
|
|
void led_rgb_0_rainbow(uint8_t preview, uint8_t tick);
|
|
void led_rgb_1_lite_then_fade(uint8_t preview, uint8_t tick);
|
|
void led_rgb_2_twinkle(uint8_t preview, uint8_t tick);
|
|
void led_rgb_3_alternate(uint8_t preview, uint8_t tick);
|
|
void led_rgb_4_typing(uint8_t preview, uint8_t tick);
|
|
|
|
void ((*led_rgbprog[5])(uint8_t, uint8_t)) = {
|
|
led_rgb_0_rainbow,
|
|
led_rgb_1_lite_then_fade,
|
|
led_rgb_2_twinkle,
|
|
led_rgb_3_alternate,
|
|
led_rgb_4_typing
|
|
};
|
|
|
|
static uint8_t prog_rgb[3];
|
|
|
|
|
|
|
|
/*
|
|
* always have to have unicorn piss
|
|
*
|
|
* button: angle (45 degree shifts for each press)
|
|
* knob: spacing
|
|
*/
|
|
static uint16_t rainbow_hue = 0;
|
|
|
|
// probably some way to compute this but I can't think of it right now
|
|
// might take more codespace than this 40-byte LUT anyway
|
|
// encoding: LED1-LED2 LED3-LED4 LED5-MAX
|
|
// LED6-LED7 LED8-LED9
|
|
static const uint8_t rainbow_angles[8][5] = {
|
|
{0x12, 0x34, 0x55,
|
|
0x01, 0x23}, // bottom left to top right
|
|
{0x01, 0x23, 0x44,
|
|
0x01, 0x23}, // left to right
|
|
{0x01, 0x23, 0x44,
|
|
0x12, 0x34}, // top left to bottom right
|
|
{0x00, 0x00, 0x02,
|
|
0x22, 0x22}, // top to bottom
|
|
{0x43, 0x21, 0x04,
|
|
0x32, 0x10}, // top right to bottom left
|
|
{0x43, 0x21, 0x04,
|
|
0x43, 0x21}, // right to left
|
|
{0x43, 0x21, 0x04,
|
|
0x32, 0x10}, // bottom right to top left
|
|
{0x22, 0x22, 0x22,
|
|
0x00, 0x00} // bottom to top
|
|
};
|
|
|
|
void led_rgb_0_rainbow(uint8_t preview, uint8_t tick)
|
|
{
|
|
uint8_t i, j; // iterator
|
|
uint8_t n; // led index, remapped
|
|
uint8_t idx; // led index being updated, 0-5
|
|
uint8_t max; // max number of iterations for this angle
|
|
uint8_t angle; // fill direction from LUT
|
|
|
|
uint16_t hoff;
|
|
|
|
// run at half framerate
|
|
if (tick & 1) {
|
|
// no matter what, this puke is getting updated
|
|
led_is_updated();
|
|
|
|
// setup
|
|
angle = userconf.ledprog_setting[0][0];
|
|
angle &= 0x7;
|
|
max = rainbow_angles[angle][2] & 0x7;
|
|
|
|
hoff = userconf.ledprog_setting[0][1];
|
|
hoff <<= 2;
|
|
hoff = (256*6) - hoff;
|
|
|
|
// process outputs
|
|
for (i = 0; i <= max; i++) {
|
|
// get the next puke
|
|
hsv2rgb_8b((rainbow_hue + (hoff * i)) % (256*6), 255, 255,
|
|
&prog_rgb[0], &prog_rgb[1], &prog_rgb[2]);
|
|
|
|
// apply to next set of LEDs
|
|
n = 0;
|
|
for (j = 0; j < 9; j++) {
|
|
idx = rainbow_angles[angle][n >> 1]; // get mask byte
|
|
if (!(n & 1)) idx >>= 4; // shift even numbered LED index over
|
|
idx &= 0x7; // mask lowest 3 bits
|
|
|
|
if (i == idx) { // is this LED part of this pattern at this index?
|
|
if (preview) { // are we in preview mode?
|
|
if (j != 0) { // is the first letter not the one selected?
|
|
if ((preview & 0x7f) == 0) {// is this preview selected?
|
|
if (j < 5) continue; // pass over any outputs that are not "TECH" letters
|
|
}
|
|
} else return; // preview is not selected, so we're done here
|
|
}
|
|
rgb[j][0] = prog_rgb[0]; // if so, copy the values
|
|
rgb[j][1] = prog_rgb[1];
|
|
rgb[j][2] = prog_rgb[2];
|
|
}
|
|
|
|
n++; // nybble index
|
|
if (n == 5) n++; // skip the 6th nybble; this indicates the max
|
|
}
|
|
}
|
|
|
|
rainbow_hue += 2;
|
|
rainbow_hue %= (256*6);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* LEDs are lit from left to right, which fade out as it passes
|
|
* then top word then bottom word light, then fade out,
|
|
* and program repeats.
|
|
* button: fade rate
|
|
* knob: hue
|
|
*/
|
|
|
|
#define LITE_INTER_DWELL 20 // time between characters
|
|
#define LITE_IDLE_DWELL (256+64) // time before changing direction
|
|
#define LITE_PERSIST 10 // how long to keep each LED at fullbrite
|
|
|
|
static uint8_t lite_direction = 0;
|
|
static uint8_t lite_idx = 0;
|
|
static uint16_t lite_timeout = 0;
|
|
static uint8_t lite_persist[9] = {0};
|
|
|
|
void led_rgb_1_lite_then_fade(uint8_t preview, uint8_t tick)
|
|
{
|
|
uint16_t i, j;
|
|
uint8_t start, end;
|
|
|
|
uint16_t hue = userconf.ledprog_setting[1][1];
|
|
uint8_t rate = userconf.ledprog_setting[1][0];
|
|
|
|
// user sets the color
|
|
i = 255;
|
|
if (hue >= 254) {
|
|
// force white
|
|
i = 0;
|
|
}
|
|
|
|
hsv2rgb_8b(hue * 6, i, 255, &prog_rgb[0], &prog_rgb[1], &prog_rgb[2]);
|
|
|
|
// make the rate adjustment a bit more intuitive
|
|
if (rate >= 254) rate = 255;
|
|
else rate >>= 2;
|
|
rate++;
|
|
|
|
// fade out anything that's already lit at user's rate
|
|
for (i = 0; i < 9; i++) {
|
|
if (lite_persist[i]) lite_persist[i]--;
|
|
else for (j = 0; j < 3; j++) {
|
|
if (rgb[i][j] > rate) rgb[i][j] -= rate; else rgb[i][j] = 0;
|
|
}
|
|
}
|
|
|
|
// light it up
|
|
if (!lite_timeout) {
|
|
if (!lite_direction) {
|
|
// left to right
|
|
// light RETRO
|
|
if (lite_idx < 5) {
|
|
rgb[lite_idx][0] = prog_rgb[0];
|
|
rgb[lite_idx][1] = prog_rgb[1];
|
|
rgb[lite_idx][2] = prog_rgb[2];
|
|
lite_persist[lite_idx] = LITE_PERSIST;
|
|
}
|
|
|
|
// light TECH
|
|
if (lite_idx < 4) {
|
|
rgb[lite_idx + 5][0] = prog_rgb[0];
|
|
rgb[lite_idx + 5][1] = prog_rgb[1];
|
|
rgb[lite_idx + 5][2] = prog_rgb[2];
|
|
lite_persist[lite_idx + 5] = LITE_PERSIST;
|
|
}
|
|
} else {
|
|
// up to down
|
|
if (lite_idx < 2) {
|
|
if (!lite_idx) {
|
|
start = 0;
|
|
end = 5;
|
|
} else {
|
|
start = 5;
|
|
end = 9;
|
|
}
|
|
|
|
for (i = start; i < end; i++) {
|
|
rgb[i][0] = prog_rgb[0];
|
|
rgb[i][1] = prog_rgb[1];
|
|
rgb[i][2] = prog_rgb[2];
|
|
lite_persist[i] = LITE_PERSIST;
|
|
}
|
|
}
|
|
}
|
|
|
|
// next light
|
|
lite_idx++;
|
|
|
|
end = lite_direction ? 2 : 5;
|
|
|
|
// after idle phase, start over in next direction
|
|
if (lite_idx > end) {
|
|
lite_idx = 0;
|
|
lite_direction++;
|
|
lite_direction &= 1;
|
|
}
|
|
|
|
lite_timeout = lite_direction ? LITE_INTER_DWELL*2 : LITE_INTER_DWELL;
|
|
|
|
// at idle phase?
|
|
if (lite_idx == end) {
|
|
lite_timeout = LITE_IDLE_DWELL;
|
|
}
|
|
|
|
led_is_updated();
|
|
}
|
|
|
|
lite_timeout--;
|
|
}
|
|
|
|
/*
|
|
* LEDs are on at some idle level, and random letters will
|
|
* twinkle by intensifying and shifting hue slightly by a
|
|
* random small amount.
|
|
* button: saturation
|
|
* knob: flashing intensity
|
|
*/
|
|
|
|
#define TWINKLE_PERSIST 3;
|
|
#define TWINKLE_RGB_VAL 255
|
|
uint8_t twinkle_set[9] = {0};
|
|
|
|
void led_rgb_2_twinkle(uint8_t preview, uint8_t tick)
|
|
{
|
|
uint8_t i, j;
|
|
uint32_t rnd;
|
|
uint8_t sat;
|
|
|
|
// set
|
|
if ((tick & 3) == 0) {
|
|
rnd = prng_get8();
|
|
if (rnd < userconf.ledprog_setting[2][1]) {
|
|
// yup, we're doing it
|
|
rnd = prng_get16();
|
|
rnd *= 1536;
|
|
rnd >>= 16;
|
|
|
|
sat = userconf.ledprog_setting[2][0];
|
|
if (sat >= 128) sat = 128 - (sat - 128);
|
|
sat <<= 1;
|
|
|
|
hsv2rgb_8b(rnd, sat, TWINKLE_RGB_VAL, &prog_rgb[0], &prog_rgb[1], &prog_rgb[2]);
|
|
|
|
rnd = prng_get16() & 0xfff;
|
|
rnd /= 455;
|
|
|
|
rgb[rnd][0] = prog_rgb[0];
|
|
rgb[rnd][1] = prog_rgb[1];
|
|
rgb[rnd][2] = prog_rgb[2];
|
|
twinkle_set[rnd] = TWINKLE_PERSIST;
|
|
}
|
|
}
|
|
|
|
// decay
|
|
for (i = 0; i < 9; i++) {
|
|
twinkle_set[i]--;
|
|
for (j = 0; j < 2; j++) {
|
|
if (twinkle_set[i]) rgb[i][j]--;
|
|
else rgb[i][j] >>= 1;
|
|
}
|
|
}
|
|
|
|
led_is_updated();
|
|
}
|
|
|
|
/*
|
|
* alternates flashing the words between two colors.
|
|
* button: offset degrees (360/16)
|
|
* knob: hue (alternate hue will be either 90 or 180 degrees offset)
|
|
*/
|
|
|
|
|
|
void led_rgb_3_alternate(uint8_t preview, uint8_t tick)
|
|
{
|
|
uint8_t i, j;
|
|
|
|
uint16_t hue = userconf.ledprog_setting[3][1] * 6;
|
|
uint16_t offset = (userconf.ledprog_setting[3][0] & 0xf) * 16 * 6;
|
|
|
|
uint8_t is_flashing = 0;
|
|
|
|
if (userconf.cursor_color == CONF_CURSOR_OFF) {
|
|
// flash at ~2Hz
|
|
tick >>= 5;
|
|
if (tick & 1) is_flashing = 1;
|
|
} else {
|
|
// match cursor
|
|
if (cursor[userconf.cursor_color]) {
|
|
is_flashing = 1;
|
|
}
|
|
}
|
|
|
|
// don't need to update flashing state. this is handled by the cursor
|
|
|
|
// set hues
|
|
for (i = 0; i < 2; i++) {
|
|
hsv2rgb_8b(hue, 255, 255, &prog_rgb[0], &prog_rgb[1], &prog_rgb[2]);
|
|
hue += offset;
|
|
hue %= 1536;
|
|
|
|
if (is_flashing) {
|
|
// set TECH
|
|
for (j = 5; j < 9; j++) {
|
|
rgb[j][0] = prog_rgb[0];
|
|
rgb[j][1] = prog_rgb[1];
|
|
rgb[j][2] = prog_rgb[2];
|
|
}
|
|
} else {
|
|
// set RETRO
|
|
for (j = 0; j < 5; j++) {
|
|
rgb[j][0] = prog_rgb[0];
|
|
rgb[j][1] = prog_rgb[1];
|
|
rgb[j][2] = prog_rgb[2];
|
|
}
|
|
}
|
|
|
|
is_flashing++;
|
|
is_flashing &= 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* a green, orange or white phosphor sort of "typing,"
|
|
* with quick but not instant fade-in and flashing of
|
|
* an active character.
|
|
* the only program that interferes with the cursor.
|
|
* after "typing" and filling, the characters fade then
|
|
* the program repeats.
|
|
*
|
|
* no user config for this program
|
|
*/
|
|
|
|
#define TYPING_CHAR_DELAY_MIN 12 // about 1/10 of a second
|
|
#define TYPING_CHAR_DELAY_MAX 500 // just shy of 4 seconds
|
|
|
|
static const uint8_t idle_glow[3] = {12, 8, 8};
|
|
static const uint8_t typing[3][3] = {
|
|
{255, 240, 240}, // white
|
|
{ 0, 240, 0}, // green
|
|
{255, 96, 12}, // orange
|
|
};
|
|
|
|
static uint8_t typing_idx;
|
|
static uint16_t typing_char_delay;
|
|
|
|
static uint8_t typing_flash_state;
|
|
static uint8_t typing_flash_delay;
|
|
|
|
static uint8_t typing_fadeout;
|
|
|
|
|
|
void led_rgb_4_typing(uint8_t preview, uint8_t tick)
|
|
{
|
|
uint8_t i, j;
|
|
uint8_t w;
|
|
uint16_t s;
|
|
uint8_t color;
|
|
|
|
// match cursor color (well, approximate, or go green)
|
|
color = userconf.cursor_color;
|
|
if (color == CONF_CURSOR_OFF) color = CONF_CURSOR_GREEN;
|
|
|
|
// set flash
|
|
if (!typing_flash_delay) {
|
|
typing_flash_state++;
|
|
typing_flash_state &= 1;
|
|
|
|
typing_flash_delay = userconf.cursor_flash;
|
|
}
|
|
typing_flash_delay--;
|
|
|
|
// set rgb colors as appropriate
|
|
for (i = 0; i < 9; i++) {
|
|
if (i < typing_idx || ((i == typing_idx) && (typing_flash_state))) {
|
|
// these are on
|
|
rgb[i][0] = typing[color][0];
|
|
rgb[i][1] = typing[color][1];
|
|
rgb[i][2] = typing[color][2];
|
|
}
|
|
|
|
if (i > typing_idx || ((i == typing_idx) && (!typing_flash_state))) {
|
|
// these are idle
|
|
rgb[i][0] = idle_glow[0];
|
|
rgb[i][1] = idle_glow[1];
|
|
rgb[i][2] = idle_glow[2];
|
|
}
|
|
|
|
// flashing cursor fadeout
|
|
if (i == typing_idx && typing_flash_state) {
|
|
if (typing_flash_delay <= 8) {
|
|
w = 8 - typing_flash_delay;
|
|
rgb[i][0] >>= w; if (rgb[i][0] < idle_glow[0]) rgb[i][0] = idle_glow[0];
|
|
rgb[i][1] >>= w; if (rgb[i][1] < idle_glow[1]) rgb[i][1] = idle_glow[1];
|
|
rgb[i][2] >>= w; if (rgb[i][2] < idle_glow[2]) rgb[i][2] = idle_glow[2];
|
|
}
|
|
}
|
|
}
|
|
|
|
// set cursor as appropriate
|
|
for (i = 0; i < 2; i++) {
|
|
cursor[i] = 0;
|
|
}
|
|
|
|
// at the cursor
|
|
if (typing_idx >= 9) {
|
|
cursor[color] = typing_flash_state ? 240 : 12;
|
|
if (typing_flash_delay <= 8) {
|
|
w = 8 - typing_flash_delay;
|
|
cursor[color] >>= w;
|
|
if (cursor[color] < 12) cursor[color] = 12;
|
|
}
|
|
}
|
|
|
|
// decay all the characters
|
|
if (typing_idx == 10) {
|
|
if (typing_fadeout >= 3) typing_fadeout--;
|
|
else typing_fadeout = 0;
|
|
|
|
for (i = 0; i < 9; i++) {
|
|
for (j = 0; j < 2; j++) {
|
|
s = rgb[i][j];
|
|
s *= typing_fadeout;
|
|
rgb[i][j] = s >> 8;
|
|
}
|
|
}
|
|
|
|
s = cursor[color];
|
|
s *= typing_fadeout;
|
|
cursor[color] = s >> 8;
|
|
}
|
|
|
|
// are we on the next character?
|
|
if (!(--typing_char_delay)) {
|
|
// next character
|
|
typing_idx++;
|
|
|
|
if (typing_idx == 9) {
|
|
// at the cursor, we wait two max periods
|
|
typing_char_delay = TYPING_CHAR_DELAY_MAX << 1;
|
|
} else if (typing_idx == 10) {
|
|
// at the end, we wait half of one max period
|
|
typing_char_delay = TYPING_CHAR_DELAY_MAX >> 1;
|
|
} else {
|
|
typing_char_delay = prng_scale16(TYPING_CHAR_DELAY_MIN, TYPING_CHAR_DELAY_MAX);
|
|
}
|
|
|
|
if (typing_idx > 10) typing_idx = 0;
|
|
|
|
// reset cursor fade (used in last step)
|
|
typing_fadeout = 0xff;
|
|
|
|
// reset flash state and delay to reset cursor flash
|
|
// comment this code to keep persistent cursor rate
|
|
typing_flash_state = 0;
|
|
typing_flash_delay = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* initialize settings for programs before they run
|
|
* this can be run when changing programs to set them
|
|
* to a known state before starting over
|
|
*/
|
|
void led_rgb_firstrun()
|
|
{
|
|
uint8_t i;
|
|
|
|
for (i = 0; i < 9; i++) {
|
|
rgb[i][0] = 0;
|
|
rgb[i][1] = 0;
|
|
rgb[i][2] = 0;
|
|
}
|
|
|
|
// program 0: rainbow
|
|
// nothing to set
|
|
|
|
// program 1: lite then fade
|
|
lite_direction = 0;
|
|
lite_idx = 0;
|
|
lite_timeout = 0;
|
|
for (i = 0; i < 9; i++) {
|
|
lite_persist[i] = 0;
|
|
}
|
|
|
|
// program 2: twinkle
|
|
for (i = 0; i < 9; i++) twinkle_set[i] = 0;
|
|
|
|
// program 3: alternating
|
|
// nothing to set
|
|
|
|
// program 4: typing
|
|
typing_idx = 0;
|
|
typing_char_delay = prng_scale16(TYPING_CHAR_DELAY_MIN, TYPING_CHAR_DELAY_MAX);
|
|
|
|
typing_flash_state = 0;
|
|
typing_flash_delay = userconf.cursor_flash;
|
|
}
|