diff --git a/visualizer.c b/visualizer.c new file mode 100644 index 000000000..2a92524e2 --- /dev/null +++ b/visualizer.c @@ -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 + +#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;icurrent_frame = animation->num_frames; + animation->time_left_in_frame = 0; + animation->need_update = true; + for (int i=0;icurrent_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 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); + } +} diff --git a/visualizer.h b/visualizer.h new file mode 100644 index 000000000..b7b0a3a7d --- /dev/null +++ b/visualizer.h @@ -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 +#include +#include + +#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 */ diff --git a/visualizer.mk b/visualizer.mk index 8ffc1e4ac..ff4b61f08 100644 --- a/visualizer.mk +++ b/visualizer.mk @@ -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