--- /dev/null
--- /dev/null
++/*
++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 "led_test.h"
++#include "gfx.h"
++#include "math.h"
++
++#define CROSSFADE_TIME 1000
++#define GRADIENT_TIME 3000
++
++keyframe_animation_t led_test_animation = {
++ .num_frames = 14,
++ .loop = true,
++ .frame_lengths = {
++ gfxMillisecondsToTicks(1000), // fade in
++ gfxMillisecondsToTicks(1000), // no op (leds on)
++ gfxMillisecondsToTicks(1000), // fade out
++ gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade
++ gfxMillisecondsToTicks(GRADIENT_TIME), // left to rigt (outside in)
++ gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade
++ gfxMillisecondsToTicks(GRADIENT_TIME), // top_to_bottom
++ 0, // mirror leds
++ gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade
++ gfxMillisecondsToTicks(GRADIENT_TIME), // left_to_right (mirrored, so inside out)
++ gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade
++ gfxMillisecondsToTicks(GRADIENT_TIME), // top_to_bottom
++ 0, // normal leds
++ gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade
++
++ },
++ .frame_functions = {
++ keyframe_fade_in_all_leds,
++ keyframe_no_operation,
++ keyframe_fade_out_all_leds,
++ keyframe_led_crossfade,
++ keyframe_led_left_to_right_gradient,
++ keyframe_led_crossfade,
++ keyframe_led_top_to_bottom_gradient,
++ keyframe_mirror_led_orientation,
++ keyframe_led_crossfade,
++ keyframe_led_left_to_right_gradient,
++ keyframe_led_crossfade,
++ keyframe_led_top_to_bottom_gradient,
++ keyframe_normal_led_orientation,
++ keyframe_led_crossfade,
++ },
++};
++
++static uint8_t fade_led_color(keyframe_animation_t* animation, int from, int to) {
++ int frame_length = animation->frame_lengths[animation->current_frame];
++ int current_pos = frame_length - animation->time_left_in_frame;
++ int delta = to - from;
++ int luma = (delta * current_pos) / frame_length;
++ luma += from;
++ return luma;
++}
++
++static void keyframe_fade_all_leds_from_to(keyframe_animation_t* animation, uint8_t from, uint8_t to) {
++ uint8_t luma = fade_led_color(animation, from, to);
++ color_t color = LUMA2COLOR(luma);
++ gdispGClear(LED_DISPLAY, color);
++}
++
++// TODO: Should be customizable per keyboard
++#define NUM_ROWS 7
++#define NUM_COLS 7
++
++static uint8_t crossfade_start_frame[NUM_ROWS][NUM_COLS];
++static uint8_t crossfade_end_frame[NUM_ROWS][NUM_COLS];
++
++static uint8_t compute_gradient_color(float t, float index, float num) {
++ const float two_pi = 2.0f * PI;
++ float normalized_index = (1.0f - index / (num - 1)) * two_pi;
++ float x = t * two_pi + normalized_index;
++ float v = 0.5 * (cosf(x) + 1.0f);
++ return (uint8_t)(255.0f * v);
++}
++
++bool keyframe_fade_in_all_leds(keyframe_animation_t* animation, visualizer_state_t* state) {
++ (void)state;
++ keyframe_fade_all_leds_from_to(animation, 0, 255);
++ return true;
++}
++
++bool keyframe_fade_out_all_leds(keyframe_animation_t* animation, visualizer_state_t* state) {
++ (void)state;
++ keyframe_fade_all_leds_from_to(animation, 255, 0);
++ return true;
++}
++
++bool keyframe_led_left_to_right_gradient(keyframe_animation_t* animation, visualizer_state_t* state) {
++ (void)state;
++ float frame_length = animation->frame_lengths[animation->current_frame];
++ float current_pos = frame_length - animation->time_left_in_frame;
++ float t = current_pos / frame_length;
++ for (int i=0; i< NUM_COLS; i++) {
++ uint8_t color = compute_gradient_color(t, i, NUM_COLS);
++ gdispGDrawLine(LED_DISPLAY, i, 0, i, NUM_ROWS - 1, LUMA2COLOR(color));
++ }
++ return true;
++}
++
++bool keyframe_led_top_to_bottom_gradient(keyframe_animation_t* animation, visualizer_state_t* state) {
++ (void)state;
++ float frame_length = animation->frame_lengths[animation->current_frame];
++ float current_pos = frame_length - animation->time_left_in_frame;
++ float t = current_pos / frame_length;
++ for (int i=0; i< NUM_ROWS; i++) {
++ uint8_t color = compute_gradient_color(t, i, NUM_ROWS);
++ gdispGDrawLine(LED_DISPLAY, 0, i, NUM_COLS - 1, i, LUMA2COLOR(color));
++ }
++ return true;
++}
++
++static void copy_current_led_state(uint8_t* dest) {
++ for (int i=0;i<NUM_ROWS;i++) {
++ for (int j=0;j<NUM_COLS;j++) {
++ dest[i*NUM_COLS + j] = gdispGGetPixelColor(LED_DISPLAY, j, i);
++ }
++ }
++}
++bool keyframe_led_crossfade(keyframe_animation_t* animation, visualizer_state_t* state) {
++ (void)state;
++ if (animation->first_update_of_frame) {
++ copy_current_led_state(&crossfade_start_frame[0][0]);
++ run_next_keyframe(animation, state);
++ copy_current_led_state(&crossfade_end_frame[0][0]);
++ }
++ for (int i=0;i<NUM_ROWS;i++) {
++ for (int j=0;j<NUM_COLS;j++) {
++ color_t color = LUMA2COLOR(fade_led_color(animation, crossfade_start_frame[i][j], crossfade_end_frame[i][j]));
++ gdispGDrawPixel(LED_DISPLAY, j, i, color);
++ }
++ }
++ return true;
++}
++
++bool keyframe_mirror_led_orientation(keyframe_animation_t* animation, visualizer_state_t* state) {
++ (void)state;
++ (void)animation;
++ gdispGSetOrientation(LED_DISPLAY, GDISP_ROTATE_180);
++ return false;
++}
++
++bool keyframe_normal_led_orientation(keyframe_animation_t* animation, visualizer_state_t* state) {
++ (void)state;
++ (void)animation;
++ gdispGSetOrientation(LED_DISPLAY, GDISP_ROTATE_0);
++ return false;
++}
--- /dev/null
--- /dev/null
++/*
++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 TMK_VISUALIZER_LED_TEST_H_
++#define TMK_VISUALIZER_LED_TEST_H_
++
++#include "visualizer.h"
++
++bool keyframe_fade_in_all_leds(keyframe_animation_t* animation, visualizer_state_t* state);
++bool keyframe_fade_out_all_leds(keyframe_animation_t* animation, visualizer_state_t* state);
++bool keyframe_led_left_to_right_gradient(keyframe_animation_t* animation, visualizer_state_t* state);
++bool keyframe_led_top_to_bottom_gradient(keyframe_animation_t* animation, visualizer_state_t* state);
++bool keyframe_led_crossfade(keyframe_animation_t* animation, visualizer_state_t* state);
++bool keyframe_mirror_led_orientation(keyframe_animation_t* animation, visualizer_state_t* state);
++bool keyframe_normal_led_orientation(keyframe_animation_t* animation, visualizer_state_t* state);
++
++extern keyframe_animation_t led_test_animation;
++
++
++#endif /* TMK_VISUALIZER_LED_TEST_H_ */
--- /dev/null
- #include "ch.h"
+/*
+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"
- static event_source_t layer_changed_event;
+#include "config.h"
+#include <string.h>
++#ifdef PROTOCOL_CHIBIOS
++#include "ch.h"
++#endif
+
+#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
+
+#ifdef USE_SERIAL_LINK
+#include "serial_link/protocol/transport.h"
+#include "serial_link/system/serial_link.h"
+#endif
+
+// Define this in config.h
+#ifndef VISUALIZER_THREAD_PRIORITY
+#define "Visualizer thread priority not defined"
+#endif
+
+
+static visualizer_keyboard_status_t current_status = {
+ .layer = 0xFFFFFFFF,
+ .default_layer = 0xFFFFFFFF,
+ .leds = 0xFFFFFFFF,
+ .suspended = false,
+};
+
+static bool same_status(visualizer_keyboard_status_t* status1, visualizer_keyboard_status_t* status2) {
+ return status1->layer == status2->layer &&
+ status1->default_layer == status2->default_layer &&
+ status1->leds == status2->leds &&
+ status1->suspended == status2->suspended;
+}
+
- static bool update_keyframe_animation(keyframe_animation_t* animation, visualizer_state_t* state, systime_t delta, systime_t* sleep_time) {
+static bool visualizer_enabled = false;
+
+#define MAX_SIMULTANEOUS_ANIMATIONS 4
+static keyframe_animation_t* animations[MAX_SIMULTANEOUS_ANIMATIONS] = {};
+
+#ifdef USE_SERIAL_LINK
+MASTER_TO_ALL_SLAVES_OBJECT(current_status, visualizer_keyboard_status_t);
+
+static remote_object_t* remote_objects[] = {
+ REMOTE_OBJECT(current_status),
+};
+
+#endif
+
++GDisplay* LCD_DISPLAY = 0;
++GDisplay* LED_DISPLAY = 0;
++
++__attribute__((weak))
++GDisplay* get_lcd_display(void) {
++ return gdispGetDisplay(0);
++}
++
++__attribute__((weak))
++GDisplay* get_led_display(void) {
++ return gdispGetDisplay(1);
++}
+
+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;
++ animation->first_update_of_frame = false;
++ animation->last_update_of_frame = false;
+ for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
+ if (animations[i] == animation) {
+ animations[i] = NULL;
+ return;
+ }
+ }
+}
+
+void stop_all_keyframe_animations(void) {
+ for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
+ if (animations[i]) {
+ animations[i]->current_frame = animations[i]->num_frames;
+ animations[i]->time_left_in_frame = 0;
+ animations[i]->need_update = true;
++ animations[i]->first_update_of_frame = false;
++ animations[i]->last_update_of_frame = false;
+ animations[i] = NULL;
+ }
+ }
+}
+
- int wanted_sleep = animation->need_update ? 10 : animation->time_left_in_frame;
- if ((unsigned)wanted_sleep < *sleep_time) {
++static bool update_keyframe_animation(keyframe_animation_t* animation, visualizer_state_t* state, systemticks_t delta, systemticks_t* sleep_time) {
++ // TODO: Clean up this messy code
+ 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;
++ animation->first_update_of_frame = 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->last_update_of_frame = true;
+ (*animation->frame_functions[animation->current_frame])(animation, state);
++ animation->last_update_of_frame = false;
+ }
+ animation->current_frame++;
+ animation->need_update = true;
++ animation->first_update_of_frame = 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);
++ animation->first_update_of_frame = false;
+ }
+
- static THD_WORKING_AREA(visualizerThreadStack, 1024);
- static THD_FUNCTION(visualizerThread, arg) {
++ systemticks_t wanted_sleep = animation->need_update ? gfxMillisecondsToTicks(10) : (unsigned)animation->time_left_in_frame;
++ if (wanted_sleep < *sleep_time) {
+ *sleep_time = wanted_sleep;
+ }
+
+ return true;
+}
+
++void run_next_keyframe(keyframe_animation_t* animation, visualizer_state_t* state) {
++ int next_frame = animation->current_frame + 1;
++ if (next_frame == animation->num_frames) {
++ next_frame = 0;
++ }
++ keyframe_animation_t temp_animation = *animation;
++ temp_animation.current_frame = next_frame;
++ temp_animation.time_left_in_frame = animation->frame_lengths[next_frame];
++ temp_animation.first_update_of_frame = true;
++ temp_animation.last_update_of_frame = false;
++ temp_animation.need_update = false;
++ visualizer_state_t temp_state = *state;
++ (*temp_animation.frame_functions[next_frame])(&temp_animation, &temp_state);
++}
++
+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[animation->current_frame];
+ 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 keyframe_disable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state) {
+ (void)animation;
+ (void)state;
+#ifdef LCD_ENABLE
+ gdispSetPowerMode(powerOff);
+#endif
+#ifdef LCD_BACKLIGHT_ENABLE
+ lcd_backlight_hal_color(0, 0, 0);
+#endif
+ return false;
+}
+
+bool keyframe_enable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state) {
+ (void)animation;
+ (void)state;
+#ifdef LCD_ENABLE
+ gdispSetPowerMode(powerOn);
+#endif
+ return false;
+}
+
+bool enable_visualization(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
- event_listener_t event_listener;
- chEvtRegister(&layer_changed_event, &event_listener, 0);
++static DECLARE_THREAD_STACK(visualizerThreadStack, 1024);
++static DECLARE_THREAD_FUNCTION(visualizerThread, arg) {
+ (void)arg;
+
- systime_t sleep_time = TIME_INFINITE;
- systime_t current_time = chVTGetSystemTimeX();
++ GListener event_listener;
++ geventListenerInit(&event_listener);
++ geventAttachSource(&event_listener, (GSourceHandle)¤t_status, 0);
+
+ visualizer_keyboard_status_t initial_status = {
+ .default_layer = 0xFFFFFFFF,
+ .layer = 0xFFFFFFFF,
+ .leds = 0xFFFFFFFF,
+ .suspended = false,
+ };
+
+ visualizer_state_t state = {
+ .status = initial_status,
+ .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 new_time = chVTGetSystemTimeX();
- systime_t delta = new_time - current_time;
++ systemticks_t sleep_time = TIME_INFINITE;
++ systemticks_t current_time = gfxSystemTicks();
+
+ while(true) {
- systime_t after_update = chVTGetSystemTimeX();
++ systemticks_t new_time = gfxSystemTicks();
++ systemticks_t delta = new_time - current_time;
+ current_time = new_time;
+ bool enabled = visualizer_enabled;
+ if (!same_status(&state.status, ¤t_status)) {
+ if (visualizer_enabled) {
+ if (current_status.suspended) {
+ stop_all_keyframe_animations();
+ visualizer_enabled = false;
+ state.status = current_status;
+ user_visualizer_suspend(&state);
+ }
+ else {
+ state.status = current_status;
+ update_user_visualizer_state(&state);
+ }
+ state.prev_lcd_color = state.current_lcd_color;
+ }
+ }
+ if (!enabled && state.status.suspended && current_status.suspended == false) {
+ // Setting the status to the initial status will force an update
+ // when the visualizer is enabled again
+ state.status = initial_status;
+ state.status.suspended = false;
+ stop_all_keyframe_animations();
+ user_visualizer_resume(&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);
+ }
+ }
++#ifdef LED_ENABLE
++ gdispGFlush(LED_DISPLAY);
++#endif
++
++#ifdef EMULATOR
++ draw_emulator();
++#endif
+ // The animation can enable the visualizer
+ // And we might need to update the state when that happens
+ // so don't sleep
+ if (enabled != visualizer_enabled) {
+ sleep_time = 0;
+ }
+
- chEvtWaitOneTimeout(EVENT_MASK(0), sleep_time);
++ systemticks_t after_update = gfxSystemTicks();
+ 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);
- chEvtObjectInit(&layer_changed_event);
- (void)chThdCreateStatic(visualizerThreadStack, sizeof(visualizerThreadStack),
++#ifdef PROTOCOL_CHIBIOS
++ // The gEventWait function really takes milliseconds, even if the documentation says ticks.
++ // Unfortunately there's no generic ugfx conversion from system time to milliseconds,
++ // so let's do it in a platform dependent way.
++
++ // On windows the system ticks is the same as milliseconds anyway
++ if (sleep_time != TIME_INFINITE) {
++ sleep_time = ST2MS(sleep_time);
++ }
++#endif
++ geventEventWait(&event_listener, sleep_time);
+ }
+#ifdef LCD_ENABLE
+ gdispCloseFont(state.font_fixed5x8);
+ gdispCloseFont(state.font_dejavusansbold12);
+#endif
++
++ return 0;
+}
+
+void visualizer_init(void) {
+#ifdef LCD_ENABLE
+ gfxInit();
+#endif
+
+#ifdef LCD_BACKLIGHT_ENABLE
+ lcd_backlight_init();
+#endif
+
+#ifdef USE_SERIAL_LINK
+ add_remote_objects(remote_objects, sizeof(remote_objects) / sizeof(remote_object_t*) );
+#endif
++
++#ifdef LCD_ENABLE
++ LCD_DISPLAY = get_lcd_display();
++#endif
++#ifdef LED_ENABLE
++ LED_DISPLAY = get_led_display();
++#endif
++
+ // 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
- chEvtBroadcast(&layer_changed_event);
++ gfxThreadCreate(visualizerThreadStack, sizeof(visualizerThreadStack),
+ VISUALIZER_THREAD_PRIORITY, visualizerThread, NULL);
+}
+
+void update_status(bool changed) {
+ if (changed) {
++ GSourceListener* listener = geventGetSourceListener((GSourceHandle)¤t_status, NULL);
++ if (listener) {
++ geventSendEvent(listener);
++ }
+ }
+#ifdef USE_SERIAL_LINK
+ static systime_t last_update = 0;
+ systime_t current_update = chVTGetSystemTimeX();
+ systime_t delta = current_update - last_update;
+ if (changed || delta > MS2ST(10)) {
+ last_update = current_update;
+ visualizer_keyboard_status_t* r = begin_write_current_status();
+ *r = current_status;
+ end_write_current_status();
+ }
+#endif
+}
+
+void visualizer_update(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;
+#ifdef USE_SERIAL_LINK
+ if (is_serial_link_connected ()) {
+ visualizer_keyboard_status_t* new_status = read_current_status();
+ if (new_status) {
+ if (!same_status(¤t_status, new_status)) {
+ changed = true;
+ current_status = *new_status;
+ }
+ }
+ }
+ else {
+#else
+ {
+#endif
+ visualizer_keyboard_status_t new_status = {
+ .layer = state,
+ .default_layer = default_state,
+ .leds = leds,
+ .suspended = current_status.suspended,
+ };
+ if (!same_status(¤t_status, &new_status)) {
+ changed = true;
+ current_status = new_status;
+ }
+ }
+ update_status(changed);
+}
+
+void visualizer_suspend(void) {
+ current_status.suspended = true;
+ update_status(true);
+}
+
+void visualizer_resume(void) {
+ current_status.suspended = false;
+ update_status(true);
+}
--- /dev/null
- // If you need support for more than 8 keyframes per animation, you can change this
- #define MAX_VISUALIZER_KEY_FRAMES 8
+/*
+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 at every matrix scan
+void visualizer_update(uint32_t default_state, uint32_t state, uint32_t leds);
+// This should be called when the keyboard goes to suspend state
+void visualizer_suspend(void);
+// This should be called when the keyboard wakes up from suspend state
+void visualizer_resume(void);
+
- // These two functions have to be implemented by the user
++// These functions are week, so they can be overridden by the keyboard
++// if needed
++GDisplay* get_lcd_display(void);
++GDisplay* get_led_display(void);
++
++// For emulator builds, this function need to be implemented
++#ifdef EMULATOR
++void draw_emulator(void);
++#endif
++
++// If you need support for more than 16 keyframes per animation, you can change this
++#define MAX_VISUALIZER_KEY_FRAMES 16
+
+struct keyframe_animation_t;
+
+typedef struct {
+ uint32_t layer;
+ uint32_t default_layer;
+ uint32_t leds; // See led.h for available statuses
+ bool suspended;
+} 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 first_update_of_frame;
++ bool last_update_of_frame;
+ bool need_update;
+
+} keyframe_animation_t;
+
++extern GDisplay* LCD_DISPLAY;
++extern GDisplay* LED_DISPLAY;
++
+void start_keyframe_animation(keyframe_animation_t* animation);
+void stop_keyframe_animation(keyframe_animation_t* animation);
++// This runs the next keyframe, but does not update the animation state
++// Useful for crossfades for example
++void run_next_keyframe(keyframe_animation_t* animation, visualizer_state_t* state);
+
+// 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);
+
+bool keyframe_disable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state);
+bool keyframe_enable_lcd_and_backlight(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 enable_visualization(keyframe_animation_t* animation, visualizer_state_t* state);
+
++// These 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);
+void user_visualizer_suspend(visualizer_state_t* state);
+void user_visualizer_resume(visualizer_state_t* state);
+
+
+#endif /* VISUALIZER_H */
--- /dev/null
- include $(GFXLIB)/gfx.mk
+# 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.
+
+GFXLIB = $(VISUALIZER_DIR)/ugfx
++SRC += $(VISUALIZER_DIR)/visualizer.c
++UINCDIR += $(GFXINC) $(VISUALIZER_DIR)
++
+ifdef LCD_ENABLE
- SRC += $(GFXSRC) $(VISUALIZER_DIR)/visualizer.c
- UINCDIR += $(GFXINC) $(VISUALIZER_DIR)
+UDEFS += -DLCD_ENABLE
+ULIBS += -lm
++USE_UGFX = yes
+endif
- SRC += $(VISUALIZER_USER)
+
+ifdef LCD_BACKLIGHT_ENABLE
+SRC += $(VISUALIZER_DIR)/lcd_backlight.c
++ifndef EMULATOR
+SRC += lcd_backlight_hal.c
++endif
+UDEFS += -DLCD_BACKLIGHT_ENABLE
+endif
+
++ifdef LED_ENABLE
++SRC += $(VISUALIZER_DIR)/led_test.c
++UDEFS += -DLED_ENABLE
++USE_UGFX = yes
++endif
++
++ifdef USE_UGFX
++include $(GFXLIB)/gfx.mk
++SRC += $(GFXSRC)
++UDEFS += $(patsubst %,-D%,$(patsubst -D%,%,$(GFXDEFS)))
++ULIBS += $(patsubst %,-l%,$(patsubst -l%,%,$(GFXLIBS)))
++endif
++
+ifndef VISUALIZER_USER
+VISUALIZER_USER = visualizer_user.c
+endif
++SRC += $(VISUALIZER_USER)
++
++ifdef EMULATOR
++UINCDIR += $(TMK_DIR)/common
++endif