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"
28 #ifdef PROTOCOL_CHIBIOS
36 #ifdef LCD_BACKLIGHT_ENABLE
37 #include "lcd_backlight.h"
40 //#define DEBUG_VISUALIZER
42 #ifdef DEBUG_VISUALIZER
48 #ifdef USE_SERIAL_LINK
49 #include "serial_link/protocol/transport.h"
50 #include "serial_link/system/serial_link.h"
53 // Define this in config.h
54 #ifndef VISUALIZER_THREAD_PRIORITY
55 #define "Visualizer thread priority not defined"
59 static visualizer_keyboard_status_t current_status = {
61 .default_layer = 0xFFFFFFFF,
66 static bool same_status(visualizer_keyboard_status_t* status1, visualizer_keyboard_status_t* status2) {
67 return status1->layer == status2->layer &&
68 status1->default_layer == status2->default_layer &&
69 status1->leds == status2->leds &&
70 status1->suspended == status2->suspended;
73 static bool visualizer_enabled = false;
75 #define MAX_SIMULTANEOUS_ANIMATIONS 4
76 static keyframe_animation_t* animations[MAX_SIMULTANEOUS_ANIMATIONS] = {};
78 #ifdef USE_SERIAL_LINK
79 MASTER_TO_ALL_SLAVES_OBJECT(current_status, visualizer_keyboard_status_t);
81 static remote_object_t* remote_objects[] = {
82 REMOTE_OBJECT(current_status),
87 GDisplay* LCD_DISPLAY = 0;
88 GDisplay* LED_DISPLAY = 0;
91 GDisplay* get_lcd_display(void) {
92 return gdispGetDisplay(0);
96 GDisplay* get_led_display(void) {
97 return gdispGetDisplay(1);
100 void start_keyframe_animation(keyframe_animation_t* animation) {
101 animation->current_frame = -1;
102 animation->time_left_in_frame = 0;
103 animation->need_update = true;
105 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
106 if (animations[i] == animation) {
109 if (free_index == -1 && animations[i] == NULL) {
113 if (free_index!=-1) {
114 animations[free_index] = animation;
118 void stop_keyframe_animation(keyframe_animation_t* animation) {
119 animation->current_frame = animation->num_frames;
120 animation->time_left_in_frame = 0;
121 animation->need_update = true;
122 animation->first_update_of_frame = false;
123 animation->last_update_of_frame = false;
124 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
125 if (animations[i] == animation) {
126 animations[i] = NULL;
132 void stop_all_keyframe_animations(void) {
133 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
135 animations[i]->current_frame = animations[i]->num_frames;
136 animations[i]->time_left_in_frame = 0;
137 animations[i]->need_update = true;
138 animations[i]->first_update_of_frame = false;
139 animations[i]->last_update_of_frame = false;
140 animations[i] = NULL;
145 static bool update_keyframe_animation(keyframe_animation_t* animation, visualizer_state_t* state, systemticks_t delta, systemticks_t* sleep_time) {
146 // TODO: Clean up this messy code
147 dprintf("Animation frame%d, left %d, delta %d\n", animation->current_frame,
148 animation->time_left_in_frame, delta);
149 if (animation->current_frame == animation->num_frames) {
150 animation->need_update = false;
153 if (animation->current_frame == -1) {
154 animation->current_frame = 0;
155 animation->time_left_in_frame = animation->frame_lengths[0];
156 animation->need_update = true;
157 animation->first_update_of_frame = true;
159 animation->time_left_in_frame -= delta;
160 while (animation->time_left_in_frame <= 0) {
161 int left = animation->time_left_in_frame;
162 if (animation->need_update) {
163 animation->time_left_in_frame = 0;
164 animation->last_update_of_frame = true;
165 (*animation->frame_functions[animation->current_frame])(animation, state);
166 animation->last_update_of_frame = false;
168 animation->current_frame++;
169 animation->need_update = true;
170 animation->first_update_of_frame = true;
171 if (animation->current_frame == animation->num_frames) {
172 if (animation->loop) {
173 animation->current_frame = 0;
176 stop_keyframe_animation(animation);
181 animation->time_left_in_frame = animation->frame_lengths[animation->current_frame];
182 animation->time_left_in_frame -= delta;
185 if (animation->need_update) {
186 animation->need_update = (*animation->frame_functions[animation->current_frame])(animation, state);
187 animation->first_update_of_frame = false;
190 systemticks_t wanted_sleep = animation->need_update ? gfxMillisecondsToTicks(10) : (unsigned)animation->time_left_in_frame;
191 if (wanted_sleep < *sleep_time) {
192 *sleep_time = wanted_sleep;
198 void run_next_keyframe(keyframe_animation_t* animation, visualizer_state_t* state) {
199 int next_frame = animation->current_frame + 1;
200 if (next_frame == animation->num_frames) {
203 keyframe_animation_t temp_animation = *animation;
204 temp_animation.current_frame = next_frame;
205 temp_animation.time_left_in_frame = animation->frame_lengths[next_frame];
206 temp_animation.first_update_of_frame = true;
207 temp_animation.last_update_of_frame = false;
208 temp_animation.need_update = false;
209 visualizer_state_t temp_state = *state;
210 (*temp_animation.frame_functions[next_frame])(&temp_animation, &temp_state);
213 bool keyframe_no_operation(keyframe_animation_t* animation, visualizer_state_t* state) {
219 #ifdef LCD_BACKLIGHT_ENABLE
220 bool keyframe_animate_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state) {
221 int frame_length = animation->frame_lengths[animation->current_frame];
222 int current_pos = frame_length - animation->time_left_in_frame;
223 uint8_t t_h = LCD_HUE(state->target_lcd_color);
224 uint8_t t_s = LCD_SAT(state->target_lcd_color);
225 uint8_t t_i = LCD_INT(state->target_lcd_color);
226 uint8_t p_h = LCD_HUE(state->prev_lcd_color);
227 uint8_t p_s = LCD_SAT(state->prev_lcd_color);
228 uint8_t p_i = LCD_INT(state->prev_lcd_color);
230 uint8_t d_h1 = t_h - p_h; //Modulo arithmetic since we want to wrap around
231 int d_h2 = t_h - p_h;
232 // Chose the shortest way around
233 int d_h = abs(d_h2) < d_h1 ? d_h2 : d_h1;
237 int hue = (d_h * current_pos) / frame_length;
238 int sat = (d_s * current_pos) / frame_length;
239 int intensity = (d_i * current_pos) / frame_length;
240 //dprintf("%X -> %X = %X\n", p_h, t_h, hue);
244 state->current_lcd_color = LCD_COLOR(hue, sat, intensity);
246 LCD_HUE(state->current_lcd_color),
247 LCD_SAT(state->current_lcd_color),
248 LCD_INT(state->current_lcd_color));
253 bool keyframe_set_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state) {
255 state->prev_lcd_color = state->target_lcd_color;
256 state->current_lcd_color = state->target_lcd_color;
258 LCD_HUE(state->current_lcd_color),
259 LCD_SAT(state->current_lcd_color),
260 LCD_INT(state->current_lcd_color));
263 #endif // LCD_BACKLIGHT_ENABLE
266 bool keyframe_display_layer_text(keyframe_animation_t* animation, visualizer_state_t* state) {
269 gdispDrawString(0, 10, state->layer_text, state->font_dejavusansbold12, Black);
274 static void format_layer_bitmap_string(uint16_t default_layer, uint16_t layer, char* buffer) {
275 for (int i=0; i<16;i++)
277 uint32_t mask = (1u << i);
278 if (default_layer & mask) {
284 } else if (layer & mask) {
291 if (i==3 || i==7 || i==11) {
299 bool keyframe_display_layer_bitmap(keyframe_animation_t* animation, visualizer_state_t* state) {
301 const char* layer_help = "1=On D=Default B=Both";
302 char layer_buffer[16 + 4]; // 3 spaces and one null terminator
304 gdispDrawString(0, 0, layer_help, state->font_fixed5x8, Black);
305 format_layer_bitmap_string(state->status.default_layer, state->status.layer, layer_buffer);
306 gdispDrawString(0, 10, layer_buffer, state->font_fixed5x8, Black);
307 format_layer_bitmap_string(state->status.default_layer >> 16, state->status.layer >> 16, layer_buffer);
308 gdispDrawString(0, 20, layer_buffer, state->font_fixed5x8, Black);
314 bool keyframe_disable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state) {
318 gdispSetPowerMode(powerOff);
320 #ifdef LCD_BACKLIGHT_ENABLE
321 lcd_backlight_hal_color(0, 0, 0);
326 bool keyframe_enable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state) {
330 gdispSetPowerMode(powerOn);
335 bool enable_visualization(keyframe_animation_t* animation, visualizer_state_t* state) {
338 dprint("User visualizer inited\n");
339 visualizer_enabled = true;
343 // TODO: Optimize the stack size, this is probably way too big
344 static DECLARE_THREAD_STACK(visualizerThreadStack, 1024);
345 static DECLARE_THREAD_FUNCTION(visualizerThread, arg) {
348 GListener event_listener;
349 geventListenerInit(&event_listener);
350 geventAttachSource(&event_listener, (GSourceHandle)¤t_status, 0);
352 visualizer_keyboard_status_t initial_status = {
353 .default_layer = 0xFFFFFFFF,
359 visualizer_state_t state = {
360 .status = initial_status,
361 .current_lcd_color = 0,
363 .font_fixed5x8 = gdispOpenFont("fixed_5x8"),
364 .font_dejavusansbold12 = gdispOpenFont("DejaVuSansBold12")
367 initialize_user_visualizer(&state);
368 state.prev_lcd_color = state.current_lcd_color;
370 #ifdef LCD_BACKLIGHT_ENABLE
372 LCD_HUE(state.current_lcd_color),
373 LCD_SAT(state.current_lcd_color),
374 LCD_INT(state.current_lcd_color));
377 systemticks_t sleep_time = TIME_INFINITE;
378 systemticks_t current_time = gfxSystemTicks();
381 systemticks_t new_time = gfxSystemTicks();
382 systemticks_t delta = new_time - current_time;
383 current_time = new_time;
384 bool enabled = visualizer_enabled;
385 if (!same_status(&state.status, ¤t_status)) {
386 if (visualizer_enabled) {
387 if (current_status.suspended) {
388 stop_all_keyframe_animations();
389 visualizer_enabled = false;
390 state.status = current_status;
391 user_visualizer_suspend(&state);
394 state.status = current_status;
395 update_user_visualizer_state(&state);
397 state.prev_lcd_color = state.current_lcd_color;
400 if (!enabled && state.status.suspended && current_status.suspended == false) {
401 // Setting the status to the initial status will force an update
402 // when the visualizer is enabled again
403 state.status = initial_status;
404 state.status.suspended = false;
405 stop_all_keyframe_animations();
406 user_visualizer_resume(&state);
407 state.prev_lcd_color = state.current_lcd_color;
409 sleep_time = TIME_INFINITE;
410 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
412 update_keyframe_animation(animations[i], &state, delta, &sleep_time);
416 gdispGFlush(LED_DISPLAY);
422 // The animation can enable the visualizer
423 // And we might need to update the state when that happens
425 if (enabled != visualizer_enabled) {
429 systemticks_t after_update = gfxSystemTicks();
430 unsigned update_delta = after_update - current_time;
431 if (sleep_time != TIME_INFINITE) {
432 if (sleep_time > update_delta) {
433 sleep_time -= update_delta;
439 dprintf("Update took %d, last delta %d, sleep_time %d\n", update_delta, delta, sleep_time);
440 #ifdef PROTOCOL_CHIBIOS
441 // The gEventWait function really takes milliseconds, even if the documentation says ticks.
442 // Unfortunately there's no generic ugfx conversion from system time to milliseconds,
443 // so let's do it in a platform dependent way.
445 // On windows the system ticks is the same as milliseconds anyway
446 if (sleep_time != TIME_INFINITE) {
447 sleep_time = ST2MS(sleep_time);
450 geventEventWait(&event_listener, sleep_time);
453 gdispCloseFont(state.font_fixed5x8);
454 gdispCloseFont(state.font_dejavusansbold12);
460 void visualizer_init(void) {
465 #ifdef LCD_BACKLIGHT_ENABLE
466 lcd_backlight_init();
469 #ifdef USE_SERIAL_LINK
470 add_remote_objects(remote_objects, sizeof(remote_objects) / sizeof(remote_object_t*) );
474 LCD_DISPLAY = get_lcd_display();
477 LED_DISPLAY = get_led_display();
480 // We are using a low priority thread, the idea is to have it run only
481 // when the main thread is sleeping during the matrix scanning
482 gfxThreadCreate(visualizerThreadStack, sizeof(visualizerThreadStack),
483 VISUALIZER_THREAD_PRIORITY, visualizerThread, NULL);
486 void update_status(bool changed) {
488 GSourceListener* listener = geventGetSourceListener((GSourceHandle)¤t_status, NULL);
490 geventSendEvent(listener);
493 #ifdef USE_SERIAL_LINK
494 static systime_t last_update = 0;
495 systime_t current_update = chVTGetSystemTimeX();
496 systime_t delta = current_update - last_update;
497 if (changed || delta > MS2ST(10)) {
498 last_update = current_update;
499 visualizer_keyboard_status_t* r = begin_write_current_status();
501 end_write_current_status();
506 void visualizer_update(uint32_t default_state, uint32_t state, uint32_t leds) {
507 // Note that there's a small race condition here, the thread could read
508 // a state where one of these are set but not the other. But this should
509 // not really matter as it will be fixed during the next loop step.
510 // Alternatively a mutex could be used instead of the volatile variables
512 bool changed = false;
513 #ifdef USE_SERIAL_LINK
514 if (is_serial_link_connected ()) {
515 visualizer_keyboard_status_t* new_status = read_current_status();
517 if (!same_status(¤t_status, new_status)) {
519 current_status = *new_status;
527 visualizer_keyboard_status_t new_status = {
529 .default_layer = default_state,
531 .suspended = current_status.suspended,
533 if (!same_status(¤t_status, &new_status)) {
535 current_status = new_status;
538 update_status(changed);
541 void visualizer_suspend(void) {
542 current_status.suspended = true;
546 void visualizer_resume(void) {
547 current_status.suspended = false;