/* * led_rgbprog.c * * Created on: Jul 27, 2024 * Author: true */ #include #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; }