dc32-retro-tech-addon/firmware/retro_tech_fw/user/src/led_rgbprog.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;
}