Эх сурвалжийг харах

Add visualizer

A generic visualizer that supports animations. There's a few
predefined keyframe types included, and more can be added by the
user.
Fred Sundvik 9 жил өмнө
parent
commit
9e58d022ba
3 өөрчлөгдсөн 472 нэмэгдсэн , 1 устгасан
  1. 349 0
      visualizer.c
  2. 120 0
      visualizer.h
  3. 3 1
      visualizer.mk

+ 349 - 0
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 <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, &current_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(&current_status, &new_status)) {
+        changed = true;
+    }
+    current_status = new_status;
+    if (changed) {
+        chEvtBroadcast(&layer_changed_event);
+    }
+}

+ 120 - 0
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 <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 */

+ 3 - 1
visualizer.mk

@@ -23,11 +23,13 @@
 GFXLIB = $(VISUALIZER_DIR)/ugfx
 GFXLIB = $(VISUALIZER_DIR)/ugfx
 ifdef LCD_ENABLE
 ifdef LCD_ENABLE
 include $(GFXLIB)/gfx.mk
 include $(GFXLIB)/gfx.mk
+OPT_DEFS += -DLCD_ENABLE
 endif
 endif
-SRC += $(GFXSRC)
+SRC += $(GFXSRC) $(VISUALIZER_DIR)/visualizer.c
 INC += $(GFXINC) $(VISUALIZER_DIR)
 INC += $(GFXINC) $(VISUALIZER_DIR)
 
 
 ifdef LCD_BACKLIGHT_ENABLE
 ifdef LCD_BACKLIGHT_ENABLE
 SRC += $(VISUALIZER_DIR)/lcd_backlight.c
 SRC += $(VISUALIZER_DIR)/lcd_backlight.c
 SRC += lcd_backlight_hal.c
 SRC += lcd_backlight_hal.c
+OPT_DEFS += -DLCD_BACKLIGHT_ENABLE
 endif
 endif