aboutsummaryrefslogtreecommitdiff
path: root/quantum/visualizer/visualizer.c
diff options
context:
space:
mode:
authorFred Sundvik <fsundvik@gmail.com>2016-07-06 20:15:45 +0300
committerFred Sundvik <fsundvik@gmail.com>2016-07-06 20:15:45 +0300
commit9f33a5593cc70dfb0885328061f1aa4b2c2fa386 (patch)
treec122041dcc3c4e2d01daf5995dad03939d16f55c /quantum/visualizer/visualizer.c
parent19f480992c015aec0a15dca43e740bad8b7834e6 (diff)
parentbde869aa7ec8601459bc63b9636081d21108d1be (diff)
downloadqmk_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.c481
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/*
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 "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
57static visualizer_keyboard_status_t current_status = {
58 .layer = 0xFFFFFFFF,
59 .default_layer = 0xFFFFFFFF,
60 .leds = 0xFFFFFFFF,
61 .suspended = false,
62};
63
64static 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
71static event_source_t layer_changed_event;
72static bool visualizer_enabled = false;
73
74#define MAX_SIMULTANEOUS_ANIMATIONS 4
75static keyframe_animation_t* animations[MAX_SIMULTANEOUS_ANIMATIONS] = {};
76
77#ifdef USE_SERIAL_LINK
78MASTER_TO_ALL_SLAVES_OBJECT(current_status, visualizer_keyboard_status_t);
79
80static remote_object_t* remote_objects[] = {
81 REMOTE_OBJECT(current_status),
82};
83
84#endif
85
86
87void 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
105void 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
117void 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
128static 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
175bool 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
182bool 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
215bool 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
228bool 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
236static 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
261bool 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
276bool 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
288bool 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
297bool 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
306static THD_WORKING_AREA(visualizerThreadStack, 1024);
307static 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, &current_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
402void 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
421void 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
438void 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(&current_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(&current_status, &new_status)) {
466 changed = true;
467 current_status = new_status;
468 }
469 }
470 update_status(changed);
471}
472
473void visualizer_suspend(void) {
474 current_status.suspended = true;
475 update_status(true);
476}
477
478void visualizer_resume(void) {
479 current_status.suspended = false;
480 update_status(true);
481}