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 VISUALIZER_USER_DATA_SIZE
264 visualizer_state_t state = {
265 .status = initial_status,
266 .current_lcd_color = 0,
268 .font_fixed5x8 = gdispOpenFont("fixed_5x8"),
269 .font_dejavusansbold12 = gdispOpenFont("DejaVuSansBold12")
272 initialize_user_visualizer(&state);
273 state.prev_lcd_color = state.current_lcd_color;
275 #ifdef LCD_BACKLIGHT_ENABLE
277 LCD_HUE(state.current_lcd_color),
278 LCD_SAT(state.current_lcd_color),
279 LCD_INT(state.current_lcd_color));
282 systemticks_t sleep_time = TIME_INFINITE;
283 systemticks_t current_time = gfxSystemTicks();
284 bool force_update = true;
287 systemticks_t new_time = gfxSystemTicks();
288 systemticks_t delta = new_time - current_time;
289 current_time = new_time;
290 bool enabled = visualizer_enabled;
291 if (force_update || !same_status(&state.status, ¤t_status)) {
292 force_update = false;
294 if(current_status.backlight_level != state.status.backlight_level) {
295 if (current_status.backlight_level != 0) {
296 gdispGSetPowerMode(LED_DISPLAY, powerOn);
297 uint16_t percent = (uint16_t)current_status.backlight_level * 100 / BACKLIGHT_LEVELS;
298 gdispGSetBacklight(LED_DISPLAY, percent);
301 gdispGSetPowerMode(LED_DISPLAY, powerOff);
305 if (visualizer_enabled) {
306 if (current_status.suspended) {
307 stop_all_keyframe_animations();
308 visualizer_enabled = false;
309 state.status = current_status;
310 user_visualizer_suspend(&state);
313 visualizer_keyboard_status_t prev_status = state.status;
314 state.status = current_status;
315 update_user_visualizer_state(&state, &prev_status);
317 state.prev_lcd_color = state.current_lcd_color;
320 if (!enabled && state.status.suspended && current_status.suspended == false) {
321 // Setting the status to the initial status will force an update
322 // when the visualizer is enabled again
323 state.status = initial_status;
324 state.status.suspended = false;
325 stop_all_keyframe_animations();
326 user_visualizer_resume(&state);
327 state.prev_lcd_color = state.current_lcd_color;
329 sleep_time = TIME_INFINITE;
330 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
332 update_keyframe_animation(animations[i], &state, delta, &sleep_time);
335 #ifdef BACKLIGHT_ENABLE
336 gdispGFlush(LED_DISPLAY);
340 gdispGFlush(LCD_DISPLAY);
346 // Enable the visualizer when the startup or the suspend animation has finished
347 if (!visualizer_enabled && state.status.suspended == false && get_num_running_animations() == 0) {
348 visualizer_enabled = true;
353 systemticks_t after_update = gfxSystemTicks();
354 unsigned update_delta = after_update - current_time;
355 if (sleep_time != TIME_INFINITE) {
356 if (sleep_time > update_delta) {
357 sleep_time -= update_delta;
363 dprintf("Update took %d, last delta %d, sleep_time %d\n", update_delta, delta, sleep_time);
364 #ifdef PROTOCOL_CHIBIOS
365 // The gEventWait function really takes milliseconds, even if the documentation says ticks.
366 // Unfortunately there's no generic ugfx conversion from system time to milliseconds,
367 // so let's do it in a platform dependent way.
369 // On windows the system ticks is the same as milliseconds anyway
370 if (sleep_time != TIME_INFINITE) {
371 sleep_time = ST2MS(sleep_time);
374 geventEventWait(&event_listener, sleep_time);
377 gdispCloseFont(state.font_fixed5x8);
378 gdispCloseFont(state.font_dejavusansbold12);
384 void visualizer_init(void) {
387 #ifdef LCD_BACKLIGHT_ENABLE
388 lcd_backlight_init();
391 #ifdef SERIAL_LINK_ENABLE
392 add_remote_objects(remote_objects, sizeof(remote_objects) / sizeof(remote_object_t*) );
396 LCD_DISPLAY = get_lcd_display();
399 #ifdef BACKLIGHT_ENABLE
400 LED_DISPLAY = get_led_display();
403 // We are using a low priority thread, the idea is to have it run only
404 // when the main thread is sleeping during the matrix scanning
405 gfxThreadCreate(visualizerThreadStack, sizeof(visualizerThreadStack),
406 VISUALIZER_THREAD_PRIORITY, visualizerThread, NULL);
409 void update_status(bool changed) {
411 GSourceListener* listener = geventGetSourceListener((GSourceHandle)¤t_status, NULL);
413 geventSendEvent(listener);
416 #ifdef SERIAL_LINK_ENABLE
417 static systime_t last_update = 0;
418 systime_t current_update = chVTGetSystemTimeX();
419 systime_t delta = current_update - last_update;
420 if (changed || delta > MS2ST(10)) {
421 last_update = current_update;
422 visualizer_keyboard_status_t* r = begin_write_current_status();
424 end_write_current_status();
429 uint8_t visualizer_get_mods() {
430 uint8_t mods = get_mods();
432 #ifndef NO_ACTION_ONESHOT
433 if (!has_oneshot_mods_timed_out()) {
434 mods |= get_oneshot_mods();
440 #ifdef VISUALIZER_USER_DATA_SIZE
441 void visualizer_set_user_data(void* u) {
442 memcpy(user_data, u, VISUALIZER_USER_DATA_SIZE);
446 void visualizer_update(uint32_t default_state, uint32_t state, uint8_t mods, uint32_t leds) {
447 // Note that there's a small race condition here, the thread could read
448 // a state where one of these are set but not the other. But this should
449 // not really matter as it will be fixed during the next loop step.
450 // Alternatively a mutex could be used instead of the volatile variables
452 bool changed = false;
453 #ifdef SERIAL_LINK_ENABLE
454 if (is_serial_link_connected ()) {
455 visualizer_keyboard_status_t* new_status = read_current_status();
457 if (!same_status(¤t_status, new_status)) {
459 current_status = *new_status;
467 visualizer_keyboard_status_t new_status = {
469 .default_layer = default_state,
472 #ifdef BACKLIGHT_ENABLE
473 .backlight_level = current_status.backlight_level,
475 .suspended = current_status.suspended,
477 #ifdef VISUALIZER_USER_DATA_SIZE
478 memcpy(new_status.user_data, user_data, VISUALIZER_USER_DATA_SIZE);
480 if (!same_status(¤t_status, &new_status)) {
482 current_status = new_status;
485 update_status(changed);
488 void visualizer_suspend(void) {
489 current_status.suspended = true;
493 void visualizer_resume(void) {
494 current_status.suspended = false;
498 #ifdef BACKLIGHT_ENABLE
499 void backlight_set(uint8_t level) {
500 current_status.backlight_level = level;