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
26 #include "visualizer.h"
28 #ifdef PROTOCOL_CHIBIOS
34 #ifdef LCD_BACKLIGHT_ENABLE
35 #include "lcd_backlight.h"
38 //#define DEBUG_VISUALIZER
40 #ifdef DEBUG_VISUALIZER
46 #ifdef SERIAL_LINK_ENABLE
47 #include "serial_link/protocol/transport.h"
48 #include "serial_link/system/serial_link.h"
51 #include "action_util.h"
53 // Define this in config.h
54 #ifndef VISUALIZER_THREAD_PRIORITY
55 // The visualizer needs gfx thread priorities
56 #define VISUALIZER_THREAD_PRIORITY (NORMAL_PRIORITY - 2)
59 static visualizer_keyboard_status_t current_status = {
61 .default_layer = 0xFFFFFFFF,
63 #ifdef BACKLIGHT_ENABLE
68 #ifdef VISUALIZER_USER_DATA_SIZE
73 static bool same_status(visualizer_keyboard_status_t* status1, visualizer_keyboard_status_t* status2) {
74 return status1->layer == status2->layer &&
75 status1->default_layer == status2->default_layer &&
76 status1->mods == status2->mods &&
77 status1->leds == status2->leds &&
78 status1->suspended == status2->suspended
79 #ifdef BACKLIGHT_ENABLE
80 && status1->backlight_level == status2->backlight_level
82 #ifdef VISUALIZER_USER_DATA_SIZE
83 && memcmp(status1->user_data, status2->user_data, VISUALIZER_USER_DATA_SIZE) == 0
88 static bool visualizer_enabled = false;
90 #ifdef VISUALIZER_USER_DATA_SIZE
91 static uint8_t user_data[VISUALIZER_USER_DATA_SIZE];
94 #define MAX_SIMULTANEOUS_ANIMATIONS 4
95 static keyframe_animation_t* animations[MAX_SIMULTANEOUS_ANIMATIONS] = {};
97 #ifdef SERIAL_LINK_ENABLE
98 MASTER_TO_ALL_SLAVES_OBJECT(current_status, visualizer_keyboard_status_t);
100 static remote_object_t* remote_objects[] = {
101 REMOTE_OBJECT(current_status),
106 GDisplay* LCD_DISPLAY = 0;
107 GDisplay* LED_DISPLAY = 0;
109 #ifdef LCD_DISPLAY_NUMBER
110 __attribute__((weak))
111 GDisplay* get_lcd_display(void) {
112 return gdispGetDisplay(LCD_DISPLAY_NUMBER);
116 #ifdef LED_DISPLAY_NUMBER
117 __attribute__((weak))
118 GDisplay* get_led_display(void) {
119 return gdispGetDisplay(LED_DISPLAY_NUMBER);
123 void start_keyframe_animation(keyframe_animation_t* animation) {
124 animation->current_frame = -1;
125 animation->time_left_in_frame = 0;
126 animation->need_update = true;
128 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
129 if (animations[i] == animation) {
132 if (free_index == -1 && animations[i] == NULL) {
136 if (free_index!=-1) {
137 animations[free_index] = animation;
141 void stop_keyframe_animation(keyframe_animation_t* animation) {
142 animation->current_frame = animation->num_frames;
143 animation->time_left_in_frame = 0;
144 animation->need_update = true;
145 animation->first_update_of_frame = false;
146 animation->last_update_of_frame = false;
147 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
148 if (animations[i] == animation) {
149 animations[i] = NULL;
155 void stop_all_keyframe_animations(void) {
156 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
158 animations[i]->current_frame = animations[i]->num_frames;
159 animations[i]->time_left_in_frame = 0;
160 animations[i]->need_update = true;
161 animations[i]->first_update_of_frame = false;
162 animations[i]->last_update_of_frame = false;
163 animations[i] = NULL;
168 static uint8_t get_num_running_animations(void) {
170 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
171 count += animations[i] ? 1 : 0;
176 static bool update_keyframe_animation(keyframe_animation_t* animation, visualizer_state_t* state, systemticks_t delta, systemticks_t* sleep_time) {
177 // TODO: Clean up this messy code
178 dprintf("Animation frame%d, left %d, delta %d\n", animation->current_frame,
179 animation->time_left_in_frame, delta);
180 if (animation->current_frame == animation->num_frames) {
181 animation->need_update = false;
184 if (animation->current_frame == -1) {
185 animation->current_frame = 0;
186 animation->time_left_in_frame = animation->frame_lengths[0];
187 animation->need_update = true;
188 animation->first_update_of_frame = true;
190 animation->time_left_in_frame -= delta;
191 while (animation->time_left_in_frame <= 0) {
192 int left = animation->time_left_in_frame;
193 if (animation->need_update) {
194 animation->time_left_in_frame = 0;
195 animation->last_update_of_frame = true;
196 (*animation->frame_functions[animation->current_frame])(animation, state);
197 animation->last_update_of_frame = false;
199 animation->current_frame++;
200 animation->need_update = true;
201 animation->first_update_of_frame = true;
202 if (animation->current_frame == animation->num_frames) {
203 if (animation->loop) {
204 animation->current_frame = 0;
207 stop_keyframe_animation(animation);
212 animation->time_left_in_frame = animation->frame_lengths[animation->current_frame];
213 animation->time_left_in_frame -= delta;
216 if (animation->need_update) {
217 animation->need_update = (*animation->frame_functions[animation->current_frame])(animation, state);
218 animation->first_update_of_frame = false;
221 systemticks_t wanted_sleep = animation->need_update ? gfxMillisecondsToTicks(10) : (unsigned)animation->time_left_in_frame;
222 if (wanted_sleep < *sleep_time) {
223 *sleep_time = wanted_sleep;
229 void run_next_keyframe(keyframe_animation_t* animation, visualizer_state_t* state) {
230 int next_frame = animation->current_frame + 1;
231 if (next_frame == animation->num_frames) {
234 keyframe_animation_t temp_animation = *animation;
235 temp_animation.current_frame = next_frame;
236 temp_animation.time_left_in_frame = animation->frame_lengths[next_frame];
237 temp_animation.first_update_of_frame = true;
238 temp_animation.last_update_of_frame = false;
239 temp_animation.need_update = false;
240 visualizer_state_t temp_state = *state;
241 (*temp_animation.frame_functions[next_frame])(&temp_animation, &temp_state);
244 // TODO: Optimize the stack size, this is probably way too big
245 static DECLARE_THREAD_STACK(visualizerThreadStack, 1024);
246 static DECLARE_THREAD_FUNCTION(visualizerThread, arg) {
249 GListener event_listener;
250 geventListenerInit(&event_listener);
251 geventAttachSource(&event_listener, (GSourceHandle)¤t_status, 0);
253 visualizer_keyboard_status_t initial_status = {
254 .default_layer = 0xFFFFFFFF,
259 #ifdef BACKLIGHT_ENABLE
260 .backlight_level = 0,
262 #ifdef VISUALIZER_USER_DATA_SIZE
267 visualizer_state_t state = {
268 .status = initial_status,
269 .current_lcd_color = 0,
271 .font_fixed5x8 = gdispOpenFont("fixed_5x8"),
272 .font_dejavusansbold12 = gdispOpenFont("DejaVuSansBold12")
275 initialize_user_visualizer(&state);
276 state.prev_lcd_color = state.current_lcd_color;
278 #ifdef LCD_BACKLIGHT_ENABLE
280 LCD_HUE(state.current_lcd_color),
281 LCD_SAT(state.current_lcd_color),
282 LCD_INT(state.current_lcd_color));
285 systemticks_t sleep_time = TIME_INFINITE;
286 systemticks_t current_time = gfxSystemTicks();
287 bool force_update = true;
290 systemticks_t new_time = gfxSystemTicks();
291 systemticks_t delta = new_time - current_time;
292 current_time = new_time;
293 bool enabled = visualizer_enabled;
294 if (force_update || !same_status(&state.status, ¤t_status)) {
295 force_update = false;
297 if(current_status.backlight_level != state.status.backlight_level) {
298 if (current_status.backlight_level != 0) {
299 gdispGSetPowerMode(LED_DISPLAY, powerOn);
300 uint16_t percent = (uint16_t)current_status.backlight_level * 100 / BACKLIGHT_LEVELS;
301 gdispGSetBacklight(LED_DISPLAY, percent);
304 gdispGSetPowerMode(LED_DISPLAY, powerOff);
306 state.status.backlight_level = current_status.backlight_level;
309 if (visualizer_enabled) {
310 if (current_status.suspended) {
311 stop_all_keyframe_animations();
312 visualizer_enabled = false;
313 state.status = current_status;
314 user_visualizer_suspend(&state);
317 visualizer_keyboard_status_t prev_status = state.status;
318 state.status = current_status;
319 update_user_visualizer_state(&state, &prev_status);
321 state.prev_lcd_color = state.current_lcd_color;
324 if (!enabled && state.status.suspended && current_status.suspended == false) {
325 // Setting the status to the initial status will force an update
326 // when the visualizer is enabled again
327 state.status = initial_status;
328 state.status.suspended = false;
329 stop_all_keyframe_animations();
330 user_visualizer_resume(&state);
331 state.prev_lcd_color = state.current_lcd_color;
333 sleep_time = TIME_INFINITE;
334 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
336 update_keyframe_animation(animations[i], &state, delta, &sleep_time);
339 #ifdef BACKLIGHT_ENABLE
340 gdispGFlush(LED_DISPLAY);
344 gdispGFlush(LCD_DISPLAY);
350 // Enable the visualizer when the startup or the suspend animation has finished
351 if (!visualizer_enabled && state.status.suspended == false && get_num_running_animations() == 0) {
352 visualizer_enabled = true;
357 systemticks_t after_update = gfxSystemTicks();
358 unsigned update_delta = after_update - current_time;
359 if (sleep_time != TIME_INFINITE) {
360 if (sleep_time > update_delta) {
361 sleep_time -= update_delta;
367 dprintf("Update took %d, last delta %d, sleep_time %d\n", update_delta, delta, sleep_time);
368 #ifdef PROTOCOL_CHIBIOS
369 // The gEventWait function really takes milliseconds, even if the documentation says ticks.
370 // Unfortunately there's no generic ugfx conversion from system time to milliseconds,
371 // so let's do it in a platform dependent way.
373 // On windows the system ticks is the same as milliseconds anyway
374 if (sleep_time != TIME_INFINITE) {
375 sleep_time = ST2MS(sleep_time);
378 geventEventWait(&event_listener, sleep_time);
381 gdispCloseFont(state.font_fixed5x8);
382 gdispCloseFont(state.font_dejavusansbold12);
388 void visualizer_init(void) {
391 #ifdef LCD_BACKLIGHT_ENABLE
392 lcd_backlight_init();
395 #ifdef SERIAL_LINK_ENABLE
396 add_remote_objects(remote_objects, sizeof(remote_objects) / sizeof(remote_object_t*) );
400 LCD_DISPLAY = get_lcd_display();
403 #ifdef BACKLIGHT_ENABLE
404 LED_DISPLAY = get_led_display();
407 // We are using a low priority thread, the idea is to have it run only
408 // when the main thread is sleeping during the matrix scanning
409 gfxThreadCreate(visualizerThreadStack, sizeof(visualizerThreadStack),
410 VISUALIZER_THREAD_PRIORITY, visualizerThread, NULL);
413 void update_status(bool changed) {
415 GSourceListener* listener = geventGetSourceListener((GSourceHandle)¤t_status, NULL);
417 geventSendEvent(listener);
420 #ifdef SERIAL_LINK_ENABLE
421 static systime_t last_update = 0;
422 systime_t current_update = chVTGetSystemTimeX();
423 systime_t delta = current_update - last_update;
424 if (changed || delta > MS2ST(10)) {
425 last_update = current_update;
426 visualizer_keyboard_status_t* r = begin_write_current_status();
428 end_write_current_status();
433 uint8_t visualizer_get_mods() {
434 uint8_t mods = get_mods();
436 #ifndef NO_ACTION_ONESHOT
437 if (!has_oneshot_mods_timed_out()) {
438 mods |= get_oneshot_mods();
444 #ifdef VISUALIZER_USER_DATA_SIZE
445 void visualizer_set_user_data(void* u) {
446 memcpy(user_data, u, VISUALIZER_USER_DATA_SIZE);
450 void visualizer_update(uint32_t default_state, uint32_t state, uint8_t mods, uint32_t leds) {
451 // Note that there's a small race condition here, the thread could read
452 // a state where one of these are set but not the other. But this should
453 // not really matter as it will be fixed during the next loop step.
454 // Alternatively a mutex could be used instead of the volatile variables
456 bool changed = false;
457 #ifdef SERIAL_LINK_ENABLE
458 if (is_serial_link_connected ()) {
459 visualizer_keyboard_status_t* new_status = read_current_status();
461 if (!same_status(¤t_status, new_status)) {
463 current_status = *new_status;
471 visualizer_keyboard_status_t new_status = {
473 .default_layer = default_state,
476 #ifdef BACKLIGHT_ENABLE
477 .backlight_level = current_status.backlight_level,
479 .suspended = current_status.suspended,
481 #ifdef VISUALIZER_USER_DATA_SIZE
482 memcpy(new_status.user_data, user_data, VISUALIZER_USER_DATA_SIZE);
484 if (!same_status(¤t_status, &new_status)) {
486 current_status = new_status;
489 update_status(changed);
492 void visualizer_suspend(void) {
493 current_status.suspended = true;
497 void visualizer_resume(void) {
498 current_status.suspended = false;
502 #ifdef BACKLIGHT_ENABLE
503 void backlight_set(uint8_t level) {
504 current_status.backlight_level = level;