diff options
author | Fred Sundvik <fsundvik@gmail.com> | 2016-07-06 20:15:45 +0300 |
---|---|---|
committer | Fred Sundvik <fsundvik@gmail.com> | 2016-07-06 20:15:45 +0300 |
commit | 9f33a5593cc70dfb0885328061f1aa4b2c2fa386 (patch) | |
tree | c122041dcc3c4e2d01daf5995dad03939d16f55c /quantum/visualizer/visualizer.c | |
parent | 19f480992c015aec0a15dca43e740bad8b7834e6 (diff) | |
parent | bde869aa7ec8601459bc63b9636081d21108d1be (diff) | |
download | qmk_firmware-9f33a5593cc70dfb0885328061f1aa4b2c2fa386.tar.gz qmk_firmware-9f33a5593cc70dfb0885328061f1aa4b2c2fa386.zip |
Add 'quantum/visualizer/' from commit 'bde869aa7ec8601459bc63b9636081d21108d1be'
git-subtree-dir: quantum/visualizer
git-subtree-mainline: 19f480992c015aec0a15dca43e740bad8b7834e6
git-subtree-split: bde869aa7ec8601459bc63b9636081d21108d1be
Diffstat (limited to 'quantum/visualizer/visualizer.c')
-rw-r--r-- | quantum/visualizer/visualizer.c | 481 |
1 files changed, 481 insertions, 0 deletions
diff --git a/quantum/visualizer/visualizer.c b/quantum/visualizer/visualizer.c new file mode 100644 index 000000000..605be3059 --- /dev/null +++ b/quantum/visualizer/visualizer.c | |||
@@ -0,0 +1,481 @@ | |||
1 | /* | ||
2 | The MIT License (MIT) | ||
3 | |||
4 | Copyright (c) 2016 Fred Sundvik | ||
5 | |||
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: | ||
12 | |||
13 | The above copyright notice and this permission notice shall be included in all | ||
14 | copies or substantial portions of the Software. | ||
15 | |||
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 | ||
22 | SOFTWARE. | ||
23 | */ | ||
24 | |||
25 | #include "visualizer.h" | ||
26 | #include "ch.h" | ||
27 | #include "config.h" | ||
28 | #include <string.h> | ||
29 | |||
30 | #ifdef LCD_ENABLE | ||
31 | #include "gfx.h" | ||
32 | #endif | ||
33 | |||
34 | #ifdef LCD_BACKLIGHT_ENABLE | ||
35 | #include "lcd_backlight.h" | ||
36 | #endif | ||
37 | |||
38 | //#define DEBUG_VISUALIZER | ||
39 | |||
40 | #ifdef DEBUG_VISUALIZER | ||
41 | #include "debug.h" | ||
42 | #else | ||
43 | #include "nodebug.h" | ||
44 | #endif | ||
45 | |||
46 | #ifdef USE_SERIAL_LINK | ||
47 | #include "serial_link/protocol/transport.h" | ||
48 | #include "serial_link/system/serial_link.h" | ||
49 | #endif | ||
50 | |||
51 | // Define this in config.h | ||
52 | #ifndef VISUALIZER_THREAD_PRIORITY | ||
53 | #define "Visualizer thread priority not defined" | ||
54 | #endif | ||
55 | |||
56 | |||
57 | static visualizer_keyboard_status_t current_status = { | ||
58 | .layer = 0xFFFFFFFF, | ||
59 | .default_layer = 0xFFFFFFFF, | ||
60 | .leds = 0xFFFFFFFF, | ||
61 | .suspended = false, | ||
62 | }; | ||
63 | |||
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; | ||
69 | } | ||
70 | |||
71 | static event_source_t layer_changed_event; | ||
72 | static bool visualizer_enabled = false; | ||
73 | |||
74 | #define MAX_SIMULTANEOUS_ANIMATIONS 4 | ||
75 | static keyframe_animation_t* animations[MAX_SIMULTANEOUS_ANIMATIONS] = {}; | ||
76 | |||
77 | #ifdef USE_SERIAL_LINK | ||
78 | MASTER_TO_ALL_SLAVES_OBJECT(current_status, visualizer_keyboard_status_t); | ||
79 | |||
80 | static remote_object_t* remote_objects[] = { | ||
81 | REMOTE_OBJECT(current_status), | ||
82 | }; | ||
83 | |||
84 | #endif | ||
85 | |||
86 | |||
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; | ||
91 | int free_index = -1; | ||
92 | for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) { | ||
93 | if (animations[i] == animation) { | ||
94 | return; | ||
95 | } | ||
96 | if (free_index == -1 && animations[i] == NULL) { | ||
97 | free_index=i; | ||
98 | } | ||
99 | } | ||
100 | if (free_index!=-1) { | ||
101 | animations[free_index] = animation; | ||
102 | } | ||
103 | } | ||
104 | |||
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; | ||
112 | return; | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | |||
117 | void stop_all_keyframe_animations(void) { | ||
118 | for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) { | ||
119 | if (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; | ||
124 | } | ||
125 | } | ||
126 | } | ||
127 | |||
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; | ||
133 | return false; | ||
134 | } | ||
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; | ||
139 | } else { | ||
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); | ||
146 | } | ||
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; | ||
152 | } | ||
153 | else { | ||
154 | stop_keyframe_animation(animation); | ||
155 | return false; | ||
156 | } | ||
157 | } | ||
158 | delta = -left; | ||
159 | animation->time_left_in_frame = animation->frame_lengths[animation->current_frame]; | ||
160 | animation->time_left_in_frame -= delta; | ||
161 | } | ||
162 | } | ||
163 | if (animation->need_update) { | ||
164 | animation->need_update = (*animation->frame_functions[animation->current_frame])(animation, state); | ||
165 | } | ||
166 | |||
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; | ||
170 | } | ||
171 | |||
172 | return true; | ||
173 | } | ||
174 | |||
175 | bool keyframe_no_operation(keyframe_animation_t* animation, visualizer_state_t* state) { | ||
176 | (void)animation; | ||
177 | (void)state; | ||
178 | return false; | ||
179 | } | ||
180 | |||
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); | ||
191 | |||
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; | ||
196 | int d_s = t_s - p_s; | ||
197 | int d_i = t_i - p_i; | ||
198 | |||
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); | ||
203 | hue += p_h; | ||
204 | sat += p_s; | ||
205 | intensity += p_i; | ||
206 | state->current_lcd_color = LCD_COLOR(hue, sat, intensity); | ||
207 | lcd_backlight_color( | ||
208 | LCD_HUE(state->current_lcd_color), | ||
209 | LCD_SAT(state->current_lcd_color), | ||
210 | LCD_INT(state->current_lcd_color)); | ||
211 | |||
212 | return true; | ||
213 | } | ||
214 | |||
215 | bool keyframe_set_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state) { | ||
216 | (void)animation; | ||
217 | state->prev_lcd_color = state->target_lcd_color; | ||
218 | state->current_lcd_color = state->target_lcd_color; | ||
219 | lcd_backlight_color( | ||
220 | LCD_HUE(state->current_lcd_color), | ||
221 | LCD_SAT(state->current_lcd_color), | ||
222 | LCD_INT(state->current_lcd_color)); | ||
223 | return false; | ||
224 | } | ||
225 | #endif // LCD_BACKLIGHT_ENABLE | ||
226 | |||
227 | #ifdef LCD_ENABLE | ||
228 | bool keyframe_display_layer_text(keyframe_animation_t* animation, visualizer_state_t* state) { | ||
229 | (void)animation; | ||
230 | gdispClear(White); | ||
231 | gdispDrawString(0, 10, state->layer_text, state->font_dejavusansbold12, Black); | ||
232 | gdispFlush(); | ||
233 | return false; | ||
234 | } | ||
235 | |||
236 | static void format_layer_bitmap_string(uint16_t default_layer, uint16_t layer, char* buffer) { | ||
237 | for (int i=0; i<16;i++) | ||
238 | { | ||
239 | uint32_t mask = (1u << i); | ||
240 | if (default_layer & mask) { | ||
241 | if (layer & mask) { | ||
242 | *buffer = 'B'; | ||
243 | } else { | ||
244 | *buffer = 'D'; | ||
245 | } | ||
246 | } else if (layer & mask) { | ||
247 | *buffer = '1'; | ||
248 | } else { | ||
249 | *buffer = '0'; | ||
250 | } | ||
251 | ++buffer; | ||
252 | |||
253 | if (i==3 || i==7 || i==11) { | ||
254 | *buffer = ' '; | ||
255 | ++buffer; | ||
256 | } | ||
257 | } | ||
258 | *buffer = 0; | ||
259 | } | ||
260 | |||
261 | bool keyframe_display_layer_bitmap(keyframe_animation_t* animation, visualizer_state_t* state) { | ||
262 | (void)animation; | ||
263 | const char* layer_help = "1=On D=Default B=Both"; | ||
264 | char layer_buffer[16 + 4]; // 3 spaces and one null terminator | ||
265 | gdispClear(White); | ||
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); | ||
271 | gdispFlush(); | ||
272 | return false; | ||
273 | } | ||
274 | #endif // LCD_ENABLE | ||
275 | |||
276 | bool keyframe_disable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state) { | ||
277 | (void)animation; | ||
278 | (void)state; | ||
279 | #ifdef LCD_ENABLE | ||
280 | gdispSetPowerMode(powerOff); | ||
281 | #endif | ||
282 | #ifdef LCD_BACKLIGHT_ENABLE | ||
283 | lcd_backlight_hal_color(0, 0, 0); | ||
284 | #endif | ||
285 | return false; | ||
286 | } | ||
287 | |||
288 | bool keyframe_enable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state) { | ||
289 | (void)animation; | ||
290 | (void)state; | ||
291 | #ifdef LCD_ENABLE | ||
292 | gdispSetPowerMode(powerOn); | ||
293 | #endif | ||
294 | return false; | ||
295 | } | ||
296 | |||
297 | bool enable_visualization(keyframe_animation_t* animation, visualizer_state_t* state) { | ||
298 | (void)animation; | ||
299 | (void)state; | ||
300 | dprint("User visualizer inited\n"); | ||
301 | visualizer_enabled = true; | ||
302 | return false; | ||
303 | } | ||
304 | |||
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) { | ||
308 | (void)arg; | ||
309 | |||
310 | event_listener_t event_listener; | ||
311 | chEvtRegister(&layer_changed_event, &event_listener, 0); | ||
312 | |||
313 | visualizer_keyboard_status_t initial_status = { | ||
314 | .default_layer = 0xFFFFFFFF, | ||
315 | .layer = 0xFFFFFFFF, | ||
316 | .leds = 0xFFFFFFFF, | ||
317 | .suspended = false, | ||
318 | }; | ||
319 | |||
320 | visualizer_state_t state = { | ||
321 | .status = initial_status, | ||
322 | .current_lcd_color = 0, | ||
323 | #ifdef LCD_ENABLE | ||
324 | .font_fixed5x8 = gdispOpenFont("fixed_5x8"), | ||
325 | .font_dejavusansbold12 = gdispOpenFont("DejaVuSansBold12") | ||
326 | #endif | ||
327 | }; | ||
328 | initialize_user_visualizer(&state); | ||
329 | state.prev_lcd_color = state.current_lcd_color; | ||
330 | |||
331 | #ifdef LCD_BACKLIGHT_ENABLE | ||
332 | lcd_backlight_color( | ||
333 | LCD_HUE(state.current_lcd_color), | ||
334 | LCD_SAT(state.current_lcd_color), | ||
335 | LCD_INT(state.current_lcd_color)); | ||
336 | #endif | ||
337 | |||
338 | systime_t sleep_time = TIME_INFINITE; | ||
339 | systime_t current_time = chVTGetSystemTimeX(); | ||
340 | |||
341 | while(true) { | ||
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); | ||
353 | } | ||
354 | else { | ||
355 | state.status = current_status; | ||
356 | update_user_visualizer_state(&state); | ||
357 | } | ||
358 | state.prev_lcd_color = state.current_lcd_color; | ||
359 | } | ||
360 | } | ||
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; | ||
369 | } | ||
370 | sleep_time = TIME_INFINITE; | ||
371 | for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) { | ||
372 | if (animations[i]) { | ||
373 | update_keyframe_animation(animations[i], &state, delta, &sleep_time); | ||
374 | } | ||
375 | } | ||
376 | // The animation can enable the visualizer | ||
377 | // And we might need to update the state when that happens | ||
378 | // so don't sleep | ||
379 | if (enabled != visualizer_enabled) { | ||
380 | sleep_time = 0; | ||
381 | } | ||
382 | |||
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; | ||
388 | } | ||
389 | else { | ||
390 | sleep_time = 0; | ||
391 | } | ||
392 | } | ||
393 | dprintf("Update took %d, last delta %d, sleep_time %d\n", update_delta, delta, sleep_time); | ||
394 | chEvtWaitOneTimeout(EVENT_MASK(0), sleep_time); | ||
395 | } | ||
396 | #ifdef LCD_ENABLE | ||
397 | gdispCloseFont(state.font_fixed5x8); | ||
398 | gdispCloseFont(state.font_dejavusansbold12); | ||
399 | #endif | ||
400 | } | ||
401 | |||
402 | void visualizer_init(void) { | ||
403 | #ifdef LCD_ENABLE | ||
404 | gfxInit(); | ||
405 | #endif | ||
406 | |||
407 | #ifdef LCD_BACKLIGHT_ENABLE | ||
408 | lcd_backlight_init(); | ||
409 | #endif | ||
410 | |||
411 | #ifdef USE_SERIAL_LINK | ||
412 | add_remote_objects(remote_objects, sizeof(remote_objects) / sizeof(remote_object_t*) ); | ||
413 | #endif | ||
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); | ||
419 | } | ||
420 | |||
421 | void update_status(bool changed) { | ||
422 | if (changed) { | ||
423 | chEvtBroadcast(&layer_changed_event); | ||
424 | } | ||
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(); | ||
432 | *r = current_status; | ||
433 | end_write_current_status(); | ||
434 | } | ||
435 | #endif | ||
436 | } | ||
437 | |||
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 | ||
443 | |||
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(); | ||
448 | if (new_status) { | ||
449 | if (!same_status(¤t_status, new_status)) { | ||
450 | changed = true; | ||
451 | current_status = *new_status; | ||
452 | } | ||
453 | } | ||
454 | } | ||
455 | else { | ||
456 | #else | ||
457 | { | ||
458 | #endif | ||
459 | visualizer_keyboard_status_t new_status = { | ||
460 | .layer = state, | ||
461 | .default_layer = default_state, | ||
462 | .leds = leds, | ||
463 | .suspended = current_status.suspended, | ||
464 | }; | ||
465 | if (!same_status(¤t_status, &new_status)) { | ||
466 | changed = true; | ||
467 | current_status = new_status; | ||
468 | } | ||
469 | } | ||
470 | update_status(changed); | ||
471 | } | ||
472 | |||
473 | void visualizer_suspend(void) { | ||
474 | current_status.suspended = true; | ||
475 | update_status(true); | ||
476 | } | ||
477 | |||
478 | void visualizer_resume(void) { | ||
479 | current_status.suspended = false; | ||
480 | update_status(true); | ||
481 | } | ||