mirror of
https://github.com/Keychron/qmk_firmware.git
synced 2024-12-28 03:59:33 +06:00
Add visualizer
A generic visualizer that supports animations. There's a few predefined keyframe types included, and more can be added by the user.
This commit is contained in:
parent
01b955aa64
commit
9e58d022ba
349
visualizer.c
Normal file
349
visualizer.c
Normal file
@ -0,0 +1,349 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Fred Sundvik
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "visualizer.h"
|
||||
#include "ch.h"
|
||||
#include <string.h>
|
||||
|
||||
#ifdef LCD_ENABLE
|
||||
#include "gfx.h"
|
||||
#endif
|
||||
|
||||
#ifdef LCD_BACKLIGHT_ENABLE
|
||||
#include "lcd_backlight.h"
|
||||
#endif
|
||||
|
||||
//#define DEBUG_VISUALIZER
|
||||
|
||||
#ifdef DEBUG_VISUALIZER
|
||||
#include "debug.h"
|
||||
#else
|
||||
#include "nodebug.h"
|
||||
#endif
|
||||
|
||||
|
||||
static visualizer_keyboard_status_t current_status = {
|
||||
.layer = 0xFFFFFFFF,
|
||||
.default_layer = 0xFFFFFFFF,
|
||||
.leds = 0xFFFFFFFF,
|
||||
};
|
||||
|
||||
static bool same_status(visualizer_keyboard_status_t* status1, visualizer_keyboard_status_t* status2) {
|
||||
return memcmp(status1, status2, sizeof(visualizer_keyboard_status_t)) == 0;
|
||||
}
|
||||
|
||||
static event_source_t layer_changed_event;
|
||||
static bool visualizer_enabled = false;
|
||||
|
||||
#define MAX_SIMULTANEOUS_ANIMATIONS 4
|
||||
static keyframe_animation_t* animations[MAX_SIMULTANEOUS_ANIMATIONS] = {};
|
||||
|
||||
void start_keyframe_animation(keyframe_animation_t* animation) {
|
||||
animation->current_frame = -1;
|
||||
animation->time_left_in_frame = 0;
|
||||
animation->need_update = true;
|
||||
int free_index = -1;
|
||||
for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
|
||||
if (animations[i] == animation) {
|
||||
return;
|
||||
}
|
||||
if (free_index == -1 && animations[i] == NULL) {
|
||||
free_index=i;
|
||||
}
|
||||
}
|
||||
if (free_index!=-1) {
|
||||
animations[free_index] = animation;
|
||||
}
|
||||
}
|
||||
|
||||
void stop_keyframe_animation(keyframe_animation_t* animation) {
|
||||
animation->current_frame = animation->num_frames;
|
||||
animation->time_left_in_frame = 0;
|
||||
animation->need_update = true;
|
||||
for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
|
||||
if (animations[i] == animation) {
|
||||
animations[i] = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool update_keyframe_animation(keyframe_animation_t* animation, visualizer_state_t* state, systime_t delta, systime_t* sleep_time) {
|
||||
dprintf("Animation frame%d, left %d, delta %d\n", animation->current_frame,
|
||||
animation->time_left_in_frame, delta);
|
||||
if (animation->current_frame == animation->num_frames) {
|
||||
animation->need_update = false;
|
||||
return false;
|
||||
}
|
||||
if (animation->current_frame == -1) {
|
||||
animation->current_frame = 0;
|
||||
animation->time_left_in_frame = animation->frame_lengths[0];
|
||||
animation->need_update = true;
|
||||
} else {
|
||||
animation->time_left_in_frame -= delta;
|
||||
while (animation->time_left_in_frame <= 0) {
|
||||
int left = animation->time_left_in_frame;
|
||||
if (animation->need_update) {
|
||||
animation->time_left_in_frame = 0;
|
||||
(*animation->frame_functions[animation->current_frame])(animation, state);
|
||||
}
|
||||
animation->current_frame++;
|
||||
animation->need_update = true;
|
||||
if (animation->current_frame == animation->num_frames) {
|
||||
if (animation->loop) {
|
||||
animation->current_frame = 0;
|
||||
}
|
||||
else {
|
||||
stop_keyframe_animation(animation);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
delta = -left;
|
||||
animation->time_left_in_frame = animation->frame_lengths[animation->current_frame];
|
||||
animation->time_left_in_frame -= delta;
|
||||
}
|
||||
}
|
||||
if (animation->need_update) {
|
||||
animation->need_update = (*animation->frame_functions[animation->current_frame])(animation, state);
|
||||
}
|
||||
|
||||
int wanted_sleep = animation->need_update ? 10 : animation->time_left_in_frame;
|
||||
if ((unsigned)wanted_sleep < *sleep_time) {
|
||||
*sleep_time = wanted_sleep;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool keyframe_no_operation(keyframe_animation_t* animation, visualizer_state_t* state) {
|
||||
(void)animation;
|
||||
(void)state;
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef LCD_BACKLIGHT_ENABLE
|
||||
bool keyframe_animate_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state) {
|
||||
int frame_length = animation->frame_lengths[1];
|
||||
int current_pos = frame_length - animation->time_left_in_frame;
|
||||
uint8_t t_h = LCD_HUE(state->target_lcd_color);
|
||||
uint8_t t_s = LCD_SAT(state->target_lcd_color);
|
||||
uint8_t t_i = LCD_INT(state->target_lcd_color);
|
||||
uint8_t p_h = LCD_HUE(state->prev_lcd_color);
|
||||
uint8_t p_s = LCD_SAT(state->prev_lcd_color);
|
||||
uint8_t p_i = LCD_INT(state->prev_lcd_color);
|
||||
|
||||
uint8_t d_h1 = t_h - p_h; //Modulo arithmetic since we want to wrap around
|
||||
int d_h2 = t_h - p_h;
|
||||
// Chose the shortest way around
|
||||
int d_h = abs(d_h2) < d_h1 ? d_h2 : d_h1;
|
||||
int d_s = t_s - p_s;
|
||||
int d_i = t_i - p_i;
|
||||
|
||||
int hue = (d_h * current_pos) / frame_length;
|
||||
int sat = (d_s * current_pos) / frame_length;
|
||||
int intensity = (d_i * current_pos) / frame_length;
|
||||
//dprintf("%X -> %X = %X\n", p_h, t_h, hue);
|
||||
hue += p_h;
|
||||
sat += p_s;
|
||||
intensity += p_i;
|
||||
state->current_lcd_color = LCD_COLOR(hue, sat, intensity);
|
||||
lcd_backlight_color(
|
||||
LCD_HUE(state->current_lcd_color),
|
||||
LCD_SAT(state->current_lcd_color),
|
||||
LCD_INT(state->current_lcd_color));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool keyframe_set_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state) {
|
||||
(void)animation;
|
||||
state->prev_lcd_color = state->target_lcd_color;
|
||||
state->current_lcd_color = state->target_lcd_color;
|
||||
lcd_backlight_color(
|
||||
LCD_HUE(state->current_lcd_color),
|
||||
LCD_SAT(state->current_lcd_color),
|
||||
LCD_INT(state->current_lcd_color));
|
||||
return false;
|
||||
}
|
||||
#endif // LCD_BACKLIGHT_ENABLE
|
||||
|
||||
#ifdef LCD_ENABLE
|
||||
bool keyframe_display_layer_text(keyframe_animation_t* animation, visualizer_state_t* state) {
|
||||
(void)animation;
|
||||
gdispClear(White);
|
||||
gdispDrawString(0, 10, state->layer_text, state->font_dejavusansbold12, Black);
|
||||
gdispFlush();
|
||||
return false;
|
||||
}
|
||||
|
||||
static void format_layer_bitmap_string(uint16_t default_layer, uint16_t layer, char* buffer) {
|
||||
for (int i=0; i<16;i++)
|
||||
{
|
||||
uint32_t mask = (1u << i);
|
||||
if (default_layer & mask) {
|
||||
if (layer & mask) {
|
||||
*buffer = 'B';
|
||||
} else {
|
||||
*buffer = 'D';
|
||||
}
|
||||
} else if (layer & mask) {
|
||||
*buffer = '1';
|
||||
} else {
|
||||
*buffer = '0';
|
||||
}
|
||||
++buffer;
|
||||
|
||||
if (i==3 || i==7 || i==11) {
|
||||
*buffer = ' ';
|
||||
++buffer;
|
||||
}
|
||||
}
|
||||
*buffer = 0;
|
||||
}
|
||||
|
||||
bool keyframe_display_layer_bitmap(keyframe_animation_t* animation, visualizer_state_t* state) {
|
||||
(void)animation;
|
||||
const char* layer_help = "1=On D=Default B=Both";
|
||||
char layer_buffer[16 + 4]; // 3 spaces and one null terminator
|
||||
gdispClear(White);
|
||||
gdispDrawString(0, 0, layer_help, state->font_fixed5x8, Black);
|
||||
format_layer_bitmap_string(state->status.default_layer, state->status.layer, layer_buffer);
|
||||
gdispDrawString(0, 10, layer_buffer, state->font_fixed5x8, Black);
|
||||
format_layer_bitmap_string(state->status.default_layer >> 16, state->status.layer >> 16, layer_buffer);
|
||||
gdispDrawString(0, 20, layer_buffer, state->font_fixed5x8, Black);
|
||||
gdispFlush();
|
||||
return false;
|
||||
}
|
||||
#endif // LCD_ENABLE
|
||||
|
||||
bool user_visualizer_inited(keyframe_animation_t* animation, visualizer_state_t* state) {
|
||||
(void)animation;
|
||||
(void)state;
|
||||
dprint("User visualizer inited\n");
|
||||
visualizer_enabled = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Optimize the stack size, this is probably way too big
|
||||
static THD_WORKING_AREA(visualizerThreadStack, 1024);
|
||||
static THD_FUNCTION(visualizerThread, arg) {
|
||||
(void)arg;
|
||||
|
||||
event_listener_t event_listener;
|
||||
chEvtRegister(&layer_changed_event, &event_listener, 0);
|
||||
|
||||
visualizer_state_t state = {
|
||||
.status = {
|
||||
.default_layer = 0xFFFFFFFF,
|
||||
.layer = 0xFFFFFFFF,
|
||||
.leds = 0xFFFFFFFF,
|
||||
},
|
||||
|
||||
.current_lcd_color = 0,
|
||||
#ifdef LCD_ENABLE
|
||||
.font_fixed5x8 = gdispOpenFont("fixed_5x8"),
|
||||
.font_dejavusansbold12 = gdispOpenFont("DejaVuSansBold12")
|
||||
#endif
|
||||
};
|
||||
initialize_user_visualizer(&state);
|
||||
state.prev_lcd_color = state.current_lcd_color;
|
||||
|
||||
#ifdef LCD_BACKLIGHT_ENABLE
|
||||
lcd_backlight_color(
|
||||
LCD_HUE(state.current_lcd_color),
|
||||
LCD_SAT(state.current_lcd_color),
|
||||
LCD_INT(state.current_lcd_color));
|
||||
#endif
|
||||
|
||||
systime_t sleep_time = TIME_INFINITE;
|
||||
systime_t current_time = chVTGetSystemTimeX();
|
||||
|
||||
while(true) {
|
||||
systime_t new_time = chVTGetSystemTimeX();
|
||||
systime_t delta = new_time - current_time;
|
||||
current_time = new_time;
|
||||
bool enabled = visualizer_enabled;
|
||||
if (!same_status(&state.status, ¤t_status)) {
|
||||
if (visualizer_enabled) {
|
||||
state.status = current_status;
|
||||
update_user_visualizer_state(&state);
|
||||
state.prev_lcd_color = state.current_lcd_color;
|
||||
}
|
||||
}
|
||||
sleep_time = TIME_INFINITE;
|
||||
for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
|
||||
if (animations[i]) {
|
||||
update_keyframe_animation(animations[i], &state, delta, &sleep_time);
|
||||
}
|
||||
}
|
||||
if (enabled != visualizer_enabled) {
|
||||
sleep_time = 0;
|
||||
}
|
||||
|
||||
systime_t after_update = chVTGetSystemTimeX();
|
||||
unsigned update_delta = after_update - current_time;
|
||||
if (sleep_time != TIME_INFINITE) {
|
||||
if (sleep_time > update_delta) {
|
||||
sleep_time -= update_delta;
|
||||
}
|
||||
else {
|
||||
sleep_time = 0;
|
||||
}
|
||||
}
|
||||
dprintf("Update took %d, last delta %d, sleep_time %d\n", update_delta, delta, sleep_time);
|
||||
chEvtWaitOneTimeout(EVENT_MASK(0), sleep_time);
|
||||
}
|
||||
#ifdef LCD_ENABLE
|
||||
gdispCloseFont(state.font_fixed5x8);
|
||||
gdispCloseFont(state.font_dejavusansbold12);
|
||||
#endif
|
||||
}
|
||||
|
||||
void visualizer_init(void) {
|
||||
// We are using a low priority thread, the idea is to have it run only
|
||||
// when the main thread is sleeping during the matrix scanning
|
||||
chEvtObjectInit(&layer_changed_event);
|
||||
(void)chThdCreateStatic(visualizerThreadStack, sizeof(visualizerThreadStack),
|
||||
LOWPRIO, visualizerThread, NULL);
|
||||
}
|
||||
|
||||
void visualizer_set_state(uint32_t default_state, uint32_t state, uint32_t leds) {
|
||||
// Note that there's a small race condition here, the thread could read
|
||||
// a state where one of these are set but not the other. But this should
|
||||
// not really matter as it will be fixed during the next loop step.
|
||||
// Alternatively a mutex could be used instead of the volatile variables
|
||||
bool changed = false;
|
||||
visualizer_keyboard_status_t new_status = {
|
||||
.layer = state,
|
||||
.default_layer = default_state,
|
||||
.leds = leds,
|
||||
};
|
||||
if (!same_status(¤t_status, &new_status)) {
|
||||
changed = true;
|
||||
}
|
||||
current_status = new_status;
|
||||
if (changed) {
|
||||
chEvtBroadcast(&layer_changed_event);
|
||||
}
|
||||
}
|
120
visualizer.h
Normal file
120
visualizer.h
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Fred Sundvik
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef VISUALIZER_H
|
||||
#define VISUALIZER_H
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef LCD_ENABLE
|
||||
#include "gfx.h"
|
||||
#endif
|
||||
|
||||
#ifdef LCD_BACKLIGHT_ENABLE
|
||||
#include "lcd_backlight.h"
|
||||
#endif
|
||||
|
||||
// This need to be called once at the start
|
||||
void visualizer_init(void);
|
||||
// This should be called before every matrix scan
|
||||
void visualizer_set_state(uint32_t default_state, uint32_t state, uint32_t leds);
|
||||
|
||||
// If you need support for more than 8 keyframes per animation, you can change this
|
||||
#define MAX_VISUALIZER_KEY_FRAMES 8
|
||||
|
||||
struct keyframe_animation_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t layer;
|
||||
uint32_t default_layer;
|
||||
uint32_t leds; // See led.h for available statuses
|
||||
} visualizer_keyboard_status_t;
|
||||
|
||||
// The state struct is used by the various keyframe functions
|
||||
// It's also used for setting the LCD color and layer text
|
||||
// from the user customized code
|
||||
typedef struct visualizer_state_t {
|
||||
// The user code should primarily be modifying these
|
||||
uint32_t target_lcd_color;
|
||||
const char* layer_text;
|
||||
|
||||
// The user visualizer(and animation functions) can read these
|
||||
visualizer_keyboard_status_t status;
|
||||
|
||||
// These are used by the animation functions
|
||||
uint32_t current_lcd_color;
|
||||
uint32_t prev_lcd_color;
|
||||
#ifdef LCD_ENABLE
|
||||
font_t font_fixed5x8;
|
||||
font_t font_dejavusansbold12;
|
||||
#endif
|
||||
} visualizer_state_t;
|
||||
|
||||
// Any custom keyframe function should have this signature
|
||||
// return true to get continuous updates, otherwise you will only get one
|
||||
// update per frame
|
||||
typedef bool (*frame_func)(struct keyframe_animation_t*, visualizer_state_t*);
|
||||
|
||||
// Represents a keyframe animation, so fields are internal to the system
|
||||
// while others are meant to be initialized by the user code
|
||||
typedef struct keyframe_animation_t {
|
||||
// These should be initialized
|
||||
int num_frames;
|
||||
bool loop;
|
||||
int frame_lengths[MAX_VISUALIZER_KEY_FRAMES];
|
||||
frame_func frame_functions[MAX_VISUALIZER_KEY_FRAMES];
|
||||
|
||||
// Used internally by the system, and can also be read by
|
||||
// keyframe update functions
|
||||
int current_frame;
|
||||
int time_left_in_frame;
|
||||
bool need_update;
|
||||
|
||||
} keyframe_animation_t;
|
||||
|
||||
void start_keyframe_animation(keyframe_animation_t* animation);
|
||||
void stop_keyframe_animation(keyframe_animation_t* animation);
|
||||
|
||||
// Some predefined keyframe functions that can be used by the user code
|
||||
// Does nothing, useful for adding delays
|
||||
bool keyframe_no_operation(keyframe_animation_t* animation, visualizer_state_t* state);
|
||||
// Animates the LCD backlight color between the current color and the target color (of the state)
|
||||
bool keyframe_animate_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state);
|
||||
// Sets the backlight color to the target color
|
||||
bool keyframe_set_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state);
|
||||
// Displays the layer text centered vertically on the screen
|
||||
bool keyframe_display_layer_text(keyframe_animation_t* animation, visualizer_state_t* state);
|
||||
// Displays a bitmap (0/1) of all the currently active layers
|
||||
bool keyframe_display_layer_bitmap(keyframe_animation_t* animation, visualizer_state_t* state);
|
||||
// Call this once, when the initial animation has finished, alternatively you can call it
|
||||
// directly from the initalize_user_visualizer function (the animation can be null)
|
||||
bool user_visualizer_inited(keyframe_animation_t* animation, visualizer_state_t* state);
|
||||
|
||||
// These two functions have to be implemented by the user
|
||||
void initialize_user_visualizer(visualizer_state_t* state);
|
||||
void update_user_visualizer_state(visualizer_state_t* state);
|
||||
|
||||
|
||||
#endif /* VISUALIZER_H */
|
@ -23,11 +23,13 @@
|
||||
GFXLIB = $(VISUALIZER_DIR)/ugfx
|
||||
ifdef LCD_ENABLE
|
||||
include $(GFXLIB)/gfx.mk
|
||||
OPT_DEFS += -DLCD_ENABLE
|
||||
endif
|
||||
SRC += $(GFXSRC)
|
||||
SRC += $(GFXSRC) $(VISUALIZER_DIR)/visualizer.c
|
||||
INC += $(GFXINC) $(VISUALIZER_DIR)
|
||||
|
||||
ifdef LCD_BACKLIGHT_ENABLE
|
||||
SRC += $(VISUALIZER_DIR)/lcd_backlight.c
|
||||
SRC += lcd_backlight_hal.c
|
||||
OPT_DEFS += -DLCD_BACKLIGHT_ENABLE
|
||||
endif
|
||||
|
Loading…
Reference in New Issue
Block a user