aboutsummaryrefslogtreecommitdiff
path: root/quantum/visualizer/visualizer.c
diff options
context:
space:
mode:
authorJack Humbert <jack.humb@gmail.com>2016-07-06 15:02:51 -0400
committerGitHub <noreply@github.com>2016-07-06 15:02:51 -0400
commit5baaf871bbdd6667625860b0daef7fe9a91e08db (patch)
treee02fdb7bb85a16de027c6c1946817e96d5304ab3 /quantum/visualizer/visualizer.c
parent19f480992c015aec0a15dca43e740bad8b7834e6 (diff)
parent6c296557909501b71fe344ce379e74094cf77c8e (diff)
downloadqmk_firmware-5baaf871bbdd6667625860b0daef7fe9a91e08db.tar.gz
qmk_firmware-5baaf871bbdd6667625860b0daef7fe9a91e08db.zip
Merge pull request #488 from fredizzimo/add_visualizer
Add visualizer library
Diffstat (limited to 'quantum/visualizer/visualizer.c')
-rw-r--r--quantum/visualizer/visualizer.c549
1 files changed, 549 insertions, 0 deletions
diff --git a/quantum/visualizer/visualizer.c b/quantum/visualizer/visualizer.c
new file mode 100644
index 000000000..c24073405
--- /dev/null
+++ b/quantum/visualizer/visualizer.c
@@ -0,0 +1,549 @@
1/*
2The MIT License (MIT)
3
4Copyright (c) 2016 Fred Sundvik
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22SOFTWARE.
23*/
24
25#include "visualizer.h"
26#include "config.h"
27#include <string.h>
28#ifdef PROTOCOL_CHIBIOS
29#include "ch.h"
30#endif
31
32#ifdef LCD_ENABLE
33#include "gfx.h"
34#endif
35
36#ifdef LCD_BACKLIGHT_ENABLE
37#include "lcd_backlight.h"
38#endif
39
40//#define DEBUG_VISUALIZER
41
42#ifdef DEBUG_VISUALIZER
43#include "debug.h"
44#else
45#include "nodebug.h"
46#endif
47
48#ifdef USE_SERIAL_LINK
49#include "serial_link/protocol/transport.h"
50#include "serial_link/system/serial_link.h"
51#endif
52
53// Define this in config.h
54#ifndef VISUALIZER_THREAD_PRIORITY
55#define "Visualizer thread priority not defined"
56#endif
57
58
59static visualizer_keyboard_status_t current_status = {
60 .layer = 0xFFFFFFFF,
61 .default_layer = 0xFFFFFFFF,
62 .leds = 0xFFFFFFFF,
63 .suspended = false,
64};
65
66static 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;
71}
72
73static bool visualizer_enabled = false;
74
75#define MAX_SIMULTANEOUS_ANIMATIONS 4
76static keyframe_animation_t* animations[MAX_SIMULTANEOUS_ANIMATIONS] = {};
77
78#ifdef USE_SERIAL_LINK
79MASTER_TO_ALL_SLAVES_OBJECT(current_status, visualizer_keyboard_status_t);
80
81static remote_object_t* remote_objects[] = {
82 REMOTE_OBJECT(current_status),
83};
84
85#endif
86
87GDisplay* LCD_DISPLAY = 0;
88GDisplay* LED_DISPLAY = 0;
89
90__attribute__((weak))
91GDisplay* get_lcd_display(void) {
92 return gdispGetDisplay(0);
93}
94
95__attribute__((weak))
96GDisplay* get_led_display(void) {
97 return gdispGetDisplay(1);
98}
99
100void start_keyframe_animation(keyframe_animation_t* animation) {
101 animation->current_frame = -1;
102 animation->time_left_in_frame = 0;
103 animation->need_update = true;
104 int free_index = -1;
105 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
106 if (animations[i] == animation) {
107 return;
108 }
109 if (free_index == -1 && animations[i] == NULL) {
110 free_index=i;
111 }
112 }
113 if (free_index!=-1) {
114 animations[free_index] = animation;
115 }
116}
117
118void 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;
127 return;
128 }
129 }
130}
131
132void stop_all_keyframe_animations(void) {
133 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
134 if (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;
141 }
142 }
143}
144
145static 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;
151 return false;
152 }
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;
158 } else {
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;
167 }
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;
174 }
175 else {
176 stop_keyframe_animation(animation);
177 return false;
178 }
179 }
180 delta = -left;
181 animation->time_left_in_frame = animation->frame_lengths[animation->current_frame];
182 animation->time_left_in_frame -= delta;
183 }
184 }
185 if (animation->need_update) {
186 animation->need_update = (*animation->frame_functions[animation->current_frame])(animation, state);
187 animation->first_update_of_frame = false;
188 }
189
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;
193 }
194
195 return true;
196}
197
198void 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) {
201 next_frame = 0;
202 }
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);
211}
212
213bool keyframe_no_operation(keyframe_animation_t* animation, visualizer_state_t* state) {
214 (void)animation;
215 (void)state;
216 return false;
217}
218
219#ifdef LCD_BACKLIGHT_ENABLE
220bool 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);
229
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;
234 int d_s = t_s - p_s;
235 int d_i = t_i - p_i;
236
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);
241 hue += p_h;
242 sat += p_s;
243 intensity += p_i;
244 state->current_lcd_color = LCD_COLOR(hue, sat, intensity);
245 lcd_backlight_color(
246 LCD_HUE(state->current_lcd_color),
247 LCD_SAT(state->current_lcd_color),
248 LCD_INT(state->current_lcd_color));
249
250 return true;
251}
252
253bool keyframe_set_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state) {
254 (void)animation;
255 state->prev_lcd_color = state->target_lcd_color;
256 state->current_lcd_color = state->target_lcd_color;
257 lcd_backlight_color(
258 LCD_HUE(state->current_lcd_color),
259 LCD_SAT(state->current_lcd_color),
260 LCD_INT(state->current_lcd_color));
261 return false;
262}
263#endif // LCD_BACKLIGHT_ENABLE
264
265#ifdef LCD_ENABLE
266bool keyframe_display_layer_text(keyframe_animation_t* animation, visualizer_state_t* state) {
267 (void)animation;
268 gdispClear(White);
269 gdispDrawString(0, 10, state->layer_text, state->font_dejavusansbold12, Black);
270 gdispFlush();
271 return false;
272}
273
274static void format_layer_bitmap_string(uint16_t default_layer, uint16_t layer, char* buffer) {
275 for (int i=0; i<16;i++)
276 {
277 uint32_t mask = (1u << i);
278 if (default_layer & mask) {
279 if (layer & mask) {
280 *buffer = 'B';
281 } else {
282 *buffer = 'D';
283 }
284 } else if (layer & mask) {
285 *buffer = '1';
286 } else {
287 *buffer = '0';
288 }
289 ++buffer;
290
291 if (i==3 || i==7 || i==11) {
292 *buffer = ' ';
293 ++buffer;
294 }
295 }
296 *buffer = 0;
297}
298
299bool keyframe_display_layer_bitmap(keyframe_animation_t* animation, visualizer_state_t* state) {
300 (void)animation;
301 const char* layer_help = "1=On D=Default B=Both";
302 char layer_buffer[16 + 4]; // 3 spaces and one null terminator
303 gdispClear(White);
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);
309 gdispFlush();
310 return false;
311}
312#endif // LCD_ENABLE
313
314bool keyframe_disable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state) {
315 (void)animation;
316 (void)state;
317#ifdef LCD_ENABLE
318 gdispSetPowerMode(powerOff);
319#endif
320#ifdef LCD_BACKLIGHT_ENABLE
321 lcd_backlight_hal_color(0, 0, 0);
322#endif
323 return false;
324}
325
326bool keyframe_enable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state) {
327 (void)animation;
328 (void)state;
329#ifdef LCD_ENABLE
330 gdispSetPowerMode(powerOn);
331#endif
332 return false;
333}
334
335bool enable_visualization(keyframe_animation_t* animation, visualizer_state_t* state) {
336 (void)animation;
337 (void)state;
338 dprint("User visualizer inited\n");
339 visualizer_enabled = true;
340 return false;
341}
342
343// TODO: Optimize the stack size, this is probably way too big
344static DECLARE_THREAD_STACK(visualizerThreadStack, 1024);
345static DECLARE_THREAD_FUNCTION(visualizerThread, arg) {
346 (void)arg;
347
348 GListener event_listener;
349 geventListenerInit(&event_listener);
350 geventAttachSource(&event_listener, (GSourceHandle)&current_status, 0);
351
352 visualizer_keyboard_status_t initial_status = {
353 .default_layer = 0xFFFFFFFF,
354 .layer = 0xFFFFFFFF,
355 .leds = 0xFFFFFFFF,
356 .suspended = false,
357 };
358
359 visualizer_state_t state = {
360 .status = initial_status,
361 .current_lcd_color = 0,
362#ifdef LCD_ENABLE
363 .font_fixed5x8 = gdispOpenFont("fixed_5x8"),
364 .font_dejavusansbold12 = gdispOpenFont("DejaVuSansBold12")
365#endif
366 };
367 initialize_user_visualizer(&state);
368 state.prev_lcd_color = state.current_lcd_color;
369
370#ifdef LCD_BACKLIGHT_ENABLE
371 lcd_backlight_color(
372 LCD_HUE(state.current_lcd_color),
373 LCD_SAT(state.current_lcd_color),
374 LCD_INT(state.current_lcd_color));
375#endif
376
377 systemticks_t sleep_time = TIME_INFINITE;
378 systemticks_t current_time = gfxSystemTicks();
379
380 while(true) {
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, &current_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);
392 }
393 else {
394 state.status = current_status;
395 update_user_visualizer_state(&state);
396 }
397 state.prev_lcd_color = state.current_lcd_color;
398 }
399 }
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;
408 }
409 sleep_time = TIME_INFINITE;
410 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
411 if (animations[i]) {
412 update_keyframe_animation(animations[i], &state, delta, &sleep_time);
413 }
414 }
415#ifdef LED_ENABLE
416 gdispGFlush(LED_DISPLAY);
417#endif
418
419#ifdef EMULATOR
420 draw_emulator();
421#endif
422 // The animation can enable the visualizer
423 // And we might need to update the state when that happens
424 // so don't sleep
425 if (enabled != visualizer_enabled) {
426 sleep_time = 0;
427 }
428
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;
434 }
435 else {
436 sleep_time = 0;
437 }
438 }
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.
444
445 // On windows the system ticks is the same as milliseconds anyway
446 if (sleep_time != TIME_INFINITE) {
447 sleep_time = ST2MS(sleep_time);
448 }
449#endif
450 geventEventWait(&event_listener, sleep_time);
451 }
452#ifdef LCD_ENABLE
453 gdispCloseFont(state.font_fixed5x8);
454 gdispCloseFont(state.font_dejavusansbold12);
455#endif
456
457 return 0;
458}
459
460void visualizer_init(void) {
461#ifdef LCD_ENABLE
462 gfxInit();
463#endif
464
465#ifdef LCD_BACKLIGHT_ENABLE
466 lcd_backlight_init();
467#endif
468
469#ifdef USE_SERIAL_LINK
470 add_remote_objects(remote_objects, sizeof(remote_objects) / sizeof(remote_object_t*) );
471#endif
472
473#ifdef LCD_ENABLE
474 LCD_DISPLAY = get_lcd_display();
475#endif
476#ifdef LED_ENABLE
477 LED_DISPLAY = get_led_display();
478#endif
479
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);
484}
485
486void update_status(bool changed) {
487 if (changed) {
488 GSourceListener* listener = geventGetSourceListener((GSourceHandle)&current_status, NULL);
489 if (listener) {
490 geventSendEvent(listener);
491 }
492 }
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();
500 *r = current_status;
501 end_write_current_status();
502 }
503#endif
504}
505
506void 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
511
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();
516 if (new_status) {
517 if (!same_status(&current_status, new_status)) {
518 changed = true;
519 current_status = *new_status;
520 }
521 }
522 }
523 else {
524#else
525 {
526#endif
527 visualizer_keyboard_status_t new_status = {
528 .layer = state,
529 .default_layer = default_state,
530 .leds = leds,
531 .suspended = current_status.suspended,
532 };
533 if (!same_status(&current_status, &new_status)) {
534 changed = true;
535 current_status = new_status;
536 }
537 }
538 update_status(changed);
539}
540
541void visualizer_suspend(void) {
542 current_status.suspended = true;
543 update_status(true);
544}
545
546void visualizer_resume(void) {
547 current_status.suspended = false;
548 update_status(true);
549}