]> git.donarmstrong.com Git - qmk_firmware.git/blob - quantum/visualizer/visualizer.c
Add 'quantum/visualizer/' from commit 'bde869aa7ec8601459bc63b9636081d21108d1be'
[qmk_firmware.git] / quantum / visualizer / visualizer.c
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, &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
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(&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
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 }