4 Copyright (c) 2016 Fred Sundvik
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 #include "visualizer.h"
34 #ifdef LCD_BACKLIGHT_ENABLE
35 #include "lcd_backlight.h"
38 //#define DEBUG_VISUALIZER
40 #ifdef DEBUG_VISUALIZER
46 #ifdef USE_SERIAL_LINK
47 #include "serial_link/protocol/transport.h"
48 #include "serial_link/system/serial_link.h"
51 // Define this in config.h
52 #ifndef VISUALIZER_THREAD_PRIORITY
53 #define "Visualizer thread priority not defined"
57 static visualizer_keyboard_status_t current_status = {
59 .default_layer = 0xFFFFFFFF,
64 static bool same_status(visualizer_keyboard_status_t* status1, visualizer_keyboard_status_t* status2) {
65 return status1->layer == status2->layer &&
66 status1->default_layer == status2->default_layer &&
67 status1->leds == status2->leds &&
68 status1->suspended == status2->suspended;
71 static event_source_t layer_changed_event;
72 static bool visualizer_enabled = false;
74 #define MAX_SIMULTANEOUS_ANIMATIONS 4
75 static keyframe_animation_t* animations[MAX_SIMULTANEOUS_ANIMATIONS] = {};
77 #ifdef USE_SERIAL_LINK
78 MASTER_TO_ALL_SLAVES_OBJECT(current_status, visualizer_keyboard_status_t);
80 static remote_object_t* remote_objects[] = {
81 REMOTE_OBJECT(current_status),
87 void start_keyframe_animation(keyframe_animation_t* animation) {
88 animation->current_frame = -1;
89 animation->time_left_in_frame = 0;
90 animation->need_update = true;
92 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
93 if (animations[i] == animation) {
96 if (free_index == -1 && animations[i] == NULL) {
100 if (free_index!=-1) {
101 animations[free_index] = animation;
105 void stop_keyframe_animation(keyframe_animation_t* animation) {
106 animation->current_frame = animation->num_frames;
107 animation->time_left_in_frame = 0;
108 animation->need_update = true;
109 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
110 if (animations[i] == animation) {
111 animations[i] = NULL;
117 void stop_all_keyframe_animations(void) {
118 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
120 animations[i]->current_frame = animations[i]->num_frames;
121 animations[i]->time_left_in_frame = 0;
122 animations[i]->need_update = true;
123 animations[i] = NULL;
128 static bool update_keyframe_animation(keyframe_animation_t* animation, visualizer_state_t* state, systime_t delta, systime_t* sleep_time) {
129 dprintf("Animation frame%d, left %d, delta %d\n", animation->current_frame,
130 animation->time_left_in_frame, delta);
131 if (animation->current_frame == animation->num_frames) {
132 animation->need_update = false;
135 if (animation->current_frame == -1) {
136 animation->current_frame = 0;
137 animation->time_left_in_frame = animation->frame_lengths[0];
138 animation->need_update = true;
140 animation->time_left_in_frame -= delta;
141 while (animation->time_left_in_frame <= 0) {
142 int left = animation->time_left_in_frame;
143 if (animation->need_update) {
144 animation->time_left_in_frame = 0;
145 (*animation->frame_functions[animation->current_frame])(animation, state);
147 animation->current_frame++;
148 animation->need_update = true;
149 if (animation->current_frame == animation->num_frames) {
150 if (animation->loop) {
151 animation->current_frame = 0;
154 stop_keyframe_animation(animation);
159 animation->time_left_in_frame = animation->frame_lengths[animation->current_frame];
160 animation->time_left_in_frame -= delta;
163 if (animation->need_update) {
164 animation->need_update = (*animation->frame_functions[animation->current_frame])(animation, state);
167 int wanted_sleep = animation->need_update ? 10 : animation->time_left_in_frame;
168 if ((unsigned)wanted_sleep < *sleep_time) {
169 *sleep_time = wanted_sleep;
175 bool keyframe_no_operation(keyframe_animation_t* animation, visualizer_state_t* state) {
181 #ifdef LCD_BACKLIGHT_ENABLE
182 bool keyframe_animate_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state) {
183 int frame_length = animation->frame_lengths[animation->current_frame];
184 int current_pos = frame_length - animation->time_left_in_frame;
185 uint8_t t_h = LCD_HUE(state->target_lcd_color);
186 uint8_t t_s = LCD_SAT(state->target_lcd_color);
187 uint8_t t_i = LCD_INT(state->target_lcd_color);
188 uint8_t p_h = LCD_HUE(state->prev_lcd_color);
189 uint8_t p_s = LCD_SAT(state->prev_lcd_color);
190 uint8_t p_i = LCD_INT(state->prev_lcd_color);
192 uint8_t d_h1 = t_h - p_h; //Modulo arithmetic since we want to wrap around
193 int d_h2 = t_h - p_h;
194 // Chose the shortest way around
195 int d_h = abs(d_h2) < d_h1 ? d_h2 : d_h1;
199 int hue = (d_h * current_pos) / frame_length;
200 int sat = (d_s * current_pos) / frame_length;
201 int intensity = (d_i * current_pos) / frame_length;
202 //dprintf("%X -> %X = %X\n", p_h, t_h, hue);
206 state->current_lcd_color = LCD_COLOR(hue, sat, intensity);
208 LCD_HUE(state->current_lcd_color),
209 LCD_SAT(state->current_lcd_color),
210 LCD_INT(state->current_lcd_color));
215 bool keyframe_set_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state) {
217 state->prev_lcd_color = state->target_lcd_color;
218 state->current_lcd_color = state->target_lcd_color;
220 LCD_HUE(state->current_lcd_color),
221 LCD_SAT(state->current_lcd_color),
222 LCD_INT(state->current_lcd_color));
225 #endif // LCD_BACKLIGHT_ENABLE
228 bool keyframe_display_layer_text(keyframe_animation_t* animation, visualizer_state_t* state) {
231 gdispDrawString(0, 10, state->layer_text, state->font_dejavusansbold12, Black);
236 static void format_layer_bitmap_string(uint16_t default_layer, uint16_t layer, char* buffer) {
237 for (int i=0; i<16;i++)
239 uint32_t mask = (1u << i);
240 if (default_layer & mask) {
246 } else if (layer & mask) {
253 if (i==3 || i==7 || i==11) {
261 bool keyframe_display_layer_bitmap(keyframe_animation_t* animation, visualizer_state_t* state) {
263 const char* layer_help = "1=On D=Default B=Both";
264 char layer_buffer[16 + 4]; // 3 spaces and one null terminator
266 gdispDrawString(0, 0, layer_help, state->font_fixed5x8, Black);
267 format_layer_bitmap_string(state->status.default_layer, state->status.layer, layer_buffer);
268 gdispDrawString(0, 10, layer_buffer, state->font_fixed5x8, Black);
269 format_layer_bitmap_string(state->status.default_layer >> 16, state->status.layer >> 16, layer_buffer);
270 gdispDrawString(0, 20, layer_buffer, state->font_fixed5x8, Black);
276 bool keyframe_disable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state) {
280 gdispSetPowerMode(powerOff);
282 #ifdef LCD_BACKLIGHT_ENABLE
283 lcd_backlight_hal_color(0, 0, 0);
288 bool keyframe_enable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state) {
292 gdispSetPowerMode(powerOn);
297 bool enable_visualization(keyframe_animation_t* animation, visualizer_state_t* state) {
300 dprint("User visualizer inited\n");
301 visualizer_enabled = true;
305 // TODO: Optimize the stack size, this is probably way too big
306 static THD_WORKING_AREA(visualizerThreadStack, 1024);
307 static THD_FUNCTION(visualizerThread, arg) {
310 event_listener_t event_listener;
311 chEvtRegister(&layer_changed_event, &event_listener, 0);
313 visualizer_keyboard_status_t initial_status = {
314 .default_layer = 0xFFFFFFFF,
320 visualizer_state_t state = {
321 .status = initial_status,
322 .current_lcd_color = 0,
324 .font_fixed5x8 = gdispOpenFont("fixed_5x8"),
325 .font_dejavusansbold12 = gdispOpenFont("DejaVuSansBold12")
328 initialize_user_visualizer(&state);
329 state.prev_lcd_color = state.current_lcd_color;
331 #ifdef LCD_BACKLIGHT_ENABLE
333 LCD_HUE(state.current_lcd_color),
334 LCD_SAT(state.current_lcd_color),
335 LCD_INT(state.current_lcd_color));
338 systime_t sleep_time = TIME_INFINITE;
339 systime_t current_time = chVTGetSystemTimeX();
342 systime_t new_time = chVTGetSystemTimeX();
343 systime_t delta = new_time - current_time;
344 current_time = new_time;
345 bool enabled = visualizer_enabled;
346 if (!same_status(&state.status, ¤t_status)) {
347 if (visualizer_enabled) {
348 if (current_status.suspended) {
349 stop_all_keyframe_animations();
350 visualizer_enabled = false;
351 state.status = current_status;
352 user_visualizer_suspend(&state);
355 state.status = current_status;
356 update_user_visualizer_state(&state);
358 state.prev_lcd_color = state.current_lcd_color;
361 if (!enabled && state.status.suspended && current_status.suspended == false) {
362 // Setting the status to the initial status will force an update
363 // when the visualizer is enabled again
364 state.status = initial_status;
365 state.status.suspended = false;
366 stop_all_keyframe_animations();
367 user_visualizer_resume(&state);
368 state.prev_lcd_color = state.current_lcd_color;
370 sleep_time = TIME_INFINITE;
371 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
373 update_keyframe_animation(animations[i], &state, delta, &sleep_time);
376 // The animation can enable the visualizer
377 // And we might need to update the state when that happens
379 if (enabled != visualizer_enabled) {
383 systime_t after_update = chVTGetSystemTimeX();
384 unsigned update_delta = after_update - current_time;
385 if (sleep_time != TIME_INFINITE) {
386 if (sleep_time > update_delta) {
387 sleep_time -= update_delta;
393 dprintf("Update took %d, last delta %d, sleep_time %d\n", update_delta, delta, sleep_time);
394 chEvtWaitOneTimeout(EVENT_MASK(0), sleep_time);
397 gdispCloseFont(state.font_fixed5x8);
398 gdispCloseFont(state.font_dejavusansbold12);
402 void visualizer_init(void) {
407 #ifdef LCD_BACKLIGHT_ENABLE
408 lcd_backlight_init();
411 #ifdef USE_SERIAL_LINK
412 add_remote_objects(remote_objects, sizeof(remote_objects) / sizeof(remote_object_t*) );
414 // We are using a low priority thread, the idea is to have it run only
415 // when the main thread is sleeping during the matrix scanning
416 chEvtObjectInit(&layer_changed_event);
417 (void)chThdCreateStatic(visualizerThreadStack, sizeof(visualizerThreadStack),
418 VISUALIZER_THREAD_PRIORITY, visualizerThread, NULL);
421 void update_status(bool changed) {
423 chEvtBroadcast(&layer_changed_event);
425 #ifdef USE_SERIAL_LINK
426 static systime_t last_update = 0;
427 systime_t current_update = chVTGetSystemTimeX();
428 systime_t delta = current_update - last_update;
429 if (changed || delta > MS2ST(10)) {
430 last_update = current_update;
431 visualizer_keyboard_status_t* r = begin_write_current_status();
433 end_write_current_status();
438 void visualizer_update(uint32_t default_state, uint32_t state, uint32_t leds) {
439 // Note that there's a small race condition here, the thread could read
440 // a state where one of these are set but not the other. But this should
441 // not really matter as it will be fixed during the next loop step.
442 // Alternatively a mutex could be used instead of the volatile variables
444 bool changed = false;
445 #ifdef USE_SERIAL_LINK
446 if (is_serial_link_connected ()) {
447 visualizer_keyboard_status_t* new_status = read_current_status();
449 if (!same_status(¤t_status, new_status)) {
451 current_status = *new_status;
459 visualizer_keyboard_status_t new_status = {
461 .default_layer = default_state,
463 .suspended = current_status.suspended,
465 if (!same_status(¤t_status, &new_status)) {
467 current_status = new_status;
470 update_status(changed);
473 void visualizer_suspend(void) {
474 current_status.suspended = true;
478 void visualizer_resume(void) {
479 current_status.suspended = false;