aboutsummaryrefslogtreecommitdiff
path: root/visualizer.c
diff options
context:
space:
mode:
authorFred Sundvik <fsundvik@gmail.com>2016-02-13 19:38:23 +0200
committerFred Sundvik <fsundvik@gmail.com>2016-02-13 19:38:23 +0200
commit9e58d022ba4320ce0917defe4b81c1c7b5de77bb (patch)
treeeb1d0bfa31d60af786d2f529729da9de119c83de /visualizer.c
parent01b955aa64766d51191a716d3a9d74d35f221b28 (diff)
downloadqmk_firmware-9e58d022ba4320ce0917defe4b81c1c7b5de77bb.tar.gz
qmk_firmware-9e58d022ba4320ce0917defe4b81c1c7b5de77bb.zip
Add visualizer
A generic visualizer that supports animations. There's a few predefined keyframe types included, and more can be added by the user.
Diffstat (limited to 'visualizer.c')
-rw-r--r--visualizer.c349
1 files changed, 349 insertions, 0 deletions
diff --git a/visualizer.c b/visualizer.c
new file mode 100644
index 000000000..2a92524e2
--- /dev/null
+++ b/visualizer.c
@@ -0,0 +1,349 @@
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 <string.h>
28
29#ifdef LCD_ENABLE
30#include "gfx.h"
31#endif
32
33#ifdef LCD_BACKLIGHT_ENABLE
34#include "lcd_backlight.h"
35#endif
36
37//#define DEBUG_VISUALIZER
38
39#ifdef DEBUG_VISUALIZER
40#include "debug.h"
41#else
42#include "nodebug.h"
43#endif
44
45
46static visualizer_keyboard_status_t current_status = {
47 .layer = 0xFFFFFFFF,
48 .default_layer = 0xFFFFFFFF,
49 .leds = 0xFFFFFFFF,
50};
51
52static bool same_status(visualizer_keyboard_status_t* status1, visualizer_keyboard_status_t* status2) {
53 return memcmp(status1, status2, sizeof(visualizer_keyboard_status_t)) == 0;
54}
55
56static event_source_t layer_changed_event;
57static bool visualizer_enabled = false;
58
59#define MAX_SIMULTANEOUS_ANIMATIONS 4
60static keyframe_animation_t* animations[MAX_SIMULTANEOUS_ANIMATIONS] = {};
61
62void start_keyframe_animation(keyframe_animation_t* animation) {
63 animation->current_frame = -1;
64 animation->time_left_in_frame = 0;
65 animation->need_update = true;
66 int free_index = -1;
67 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
68 if (animations[i] == animation) {
69 return;
70 }
71 if (free_index == -1 && animations[i] == NULL) {
72 free_index=i;
73 }
74 }
75 if (free_index!=-1) {
76 animations[free_index] = animation;
77 }
78}
79
80void stop_keyframe_animation(keyframe_animation_t* animation) {
81 animation->current_frame = animation->num_frames;
82 animation->time_left_in_frame = 0;
83 animation->need_update = true;
84 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
85 if (animations[i] == animation) {
86 animations[i] = NULL;
87 return;
88 }
89 }
90}
91
92static bool update_keyframe_animation(keyframe_animation_t* animation, visualizer_state_t* state, systime_t delta, systime_t* sleep_time) {
93 dprintf("Animation frame%d, left %d, delta %d\n", animation->current_frame,
94 animation->time_left_in_frame, delta);
95 if (animation->current_frame == animation->num_frames) {
96 animation->need_update = false;
97 return false;
98 }
99 if (animation->current_frame == -1) {
100 animation->current_frame = 0;
101 animation->time_left_in_frame = animation->frame_lengths[0];
102 animation->need_update = true;
103 } else {
104 animation->time_left_in_frame -= delta;
105 while (animation->time_left_in_frame <= 0) {
106 int left = animation->time_left_in_frame;
107 if (animation->need_update) {
108 animation->time_left_in_frame = 0;
109 (*animation->frame_functions[animation->current_frame])(animation, state);
110 }
111 animation->current_frame++;
112 animation->need_update = true;
113 if (animation->current_frame == animation->num_frames) {
114 if (animation->loop) {
115 animation->current_frame = 0;
116 }
117 else {
118 stop_keyframe_animation(animation);
119 return false;
120 }
121 }
122 delta = -left;
123 animation->time_left_in_frame = animation->frame_lengths[animation->current_frame];
124 animation->time_left_in_frame -= delta;
125 }
126 }
127 if (animation->need_update) {
128 animation->need_update = (*animation->frame_functions[animation->current_frame])(animation, state);
129 }
130
131 int wanted_sleep = animation->need_update ? 10 : animation->time_left_in_frame;
132 if ((unsigned)wanted_sleep < *sleep_time) {
133 *sleep_time = wanted_sleep;
134 }
135
136 return true;
137}
138
139bool keyframe_no_operation(keyframe_animation_t* animation, visualizer_state_t* state) {
140 (void)animation;
141 (void)state;
142 return false;
143}
144
145#ifdef LCD_BACKLIGHT_ENABLE
146bool keyframe_animate_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state) {
147 int frame_length = animation->frame_lengths[1];
148 int current_pos = frame_length - animation->time_left_in_frame;
149 uint8_t t_h = LCD_HUE(state->target_lcd_color);
150 uint8_t t_s = LCD_SAT(state->target_lcd_color);
151 uint8_t t_i = LCD_INT(state->target_lcd_color);
152 uint8_t p_h = LCD_HUE(state->prev_lcd_color);
153 uint8_t p_s = LCD_SAT(state->prev_lcd_color);
154 uint8_t p_i = LCD_INT(state->prev_lcd_color);
155
156 uint8_t d_h1 = t_h - p_h; //Modulo arithmetic since we want to wrap around
157 int d_h2 = t_h - p_h;
158 // Chose the shortest way around
159 int d_h = abs(d_h2) < d_h1 ? d_h2 : d_h1;
160 int d_s = t_s - p_s;
161 int d_i = t_i - p_i;
162
163 int hue = (d_h * current_pos) / frame_length;
164 int sat = (d_s * current_pos) / frame_length;
165 int intensity = (d_i * current_pos) / frame_length;
166 //dprintf("%X -> %X = %X\n", p_h, t_h, hue);
167 hue += p_h;
168 sat += p_s;
169 intensity += p_i;
170 state->current_lcd_color = LCD_COLOR(hue, sat, intensity);
171 lcd_backlight_color(
172 LCD_HUE(state->current_lcd_color),
173 LCD_SAT(state->current_lcd_color),
174 LCD_INT(state->current_lcd_color));
175
176 return true;
177}
178
179bool keyframe_set_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state) {
180 (void)animation;
181 state->prev_lcd_color = state->target_lcd_color;
182 state->current_lcd_color = state->target_lcd_color;
183 lcd_backlight_color(
184 LCD_HUE(state->current_lcd_color),
185 LCD_SAT(state->current_lcd_color),
186 LCD_INT(state->current_lcd_color));
187 return false;
188}
189#endif // LCD_BACKLIGHT_ENABLE
190
191#ifdef LCD_ENABLE
192bool keyframe_display_layer_text(keyframe_animation_t* animation, visualizer_state_t* state) {
193 (void)animation;
194 gdispClear(White);
195 gdispDrawString(0, 10, state->layer_text, state->font_dejavusansbold12, Black);
196 gdispFlush();
197 return false;
198}
199
200static void format_layer_bitmap_string(uint16_t default_layer, uint16_t layer, char* buffer) {
201 for (int i=0; i<16;i++)
202 {
203 uint32_t mask = (1u << i);
204 if (default_layer & mask) {
205 if (layer & mask) {
206 *buffer = 'B';
207 } else {
208 *buffer = 'D';
209 }
210 } else if (layer & mask) {
211 *buffer = '1';
212 } else {
213 *buffer = '0';
214 }
215 ++buffer;
216
217 if (i==3 || i==7 || i==11) {
218 *buffer = ' ';
219 ++buffer;
220 }
221 }
222 *buffer = 0;
223}
224
225bool keyframe_display_layer_bitmap(keyframe_animation_t* animation, visualizer_state_t* state) {
226 (void)animation;
227 const char* layer_help = "1=On D=Default B=Both";
228 char layer_buffer[16 + 4]; // 3 spaces and one null terminator
229 gdispClear(White);
230 gdispDrawString(0, 0, layer_help, state->font_fixed5x8, Black);
231 format_layer_bitmap_string(state->status.default_layer, state->status.layer, layer_buffer);
232 gdispDrawString(0, 10, layer_buffer, state->font_fixed5x8, Black);
233 format_layer_bitmap_string(state->status.default_layer >> 16, state->status.layer >> 16, layer_buffer);
234 gdispDrawString(0, 20, layer_buffer, state->font_fixed5x8, Black);
235 gdispFlush();
236 return false;
237}
238#endif // LCD_ENABLE
239
240bool user_visualizer_inited(keyframe_animation_t* animation, visualizer_state_t* state) {
241 (void)animation;
242 (void)state;
243 dprint("User visualizer inited\n");
244 visualizer_enabled = true;
245 return false;
246}
247
248// TODO: Optimize the stack size, this is probably way too big
249static THD_WORKING_AREA(visualizerThreadStack, 1024);
250static THD_FUNCTION(visualizerThread, arg) {
251 (void)arg;
252
253 event_listener_t event_listener;
254 chEvtRegister(&layer_changed_event, &event_listener, 0);
255
256 visualizer_state_t state = {
257 .status = {
258 .default_layer = 0xFFFFFFFF,
259 .layer = 0xFFFFFFFF,
260 .leds = 0xFFFFFFFF,
261 },
262
263 .current_lcd_color = 0,
264#ifdef LCD_ENABLE
265 .font_fixed5x8 = gdispOpenFont("fixed_5x8"),
266 .font_dejavusansbold12 = gdispOpenFont("DejaVuSansBold12")
267#endif
268 };
269 initialize_user_visualizer(&state);
270 state.prev_lcd_color = state.current_lcd_color;
271
272#ifdef LCD_BACKLIGHT_ENABLE
273 lcd_backlight_color(
274 LCD_HUE(state.current_lcd_color),
275 LCD_SAT(state.current_lcd_color),
276 LCD_INT(state.current_lcd_color));
277#endif
278
279 systime_t sleep_time = TIME_INFINITE;
280 systime_t current_time = chVTGetSystemTimeX();
281
282 while(true) {
283 systime_t new_time = chVTGetSystemTimeX();
284 systime_t delta = new_time - current_time;
285 current_time = new_time;
286 bool enabled = visualizer_enabled;
287 if (!same_status(&state.status, &current_status)) {
288 if (visualizer_enabled) {
289 state.status = current_status;
290 update_user_visualizer_state(&state);
291 state.prev_lcd_color = state.current_lcd_color;
292 }
293 }
294 sleep_time = TIME_INFINITE;
295 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
296 if (animations[i]) {
297 update_keyframe_animation(animations[i], &state, delta, &sleep_time);
298 }
299 }
300 if (enabled != visualizer_enabled) {
301 sleep_time = 0;
302 }
303
304 systime_t after_update = chVTGetSystemTimeX();
305 unsigned update_delta = after_update - current_time;
306 if (sleep_time != TIME_INFINITE) {
307 if (sleep_time > update_delta) {
308 sleep_time -= update_delta;
309 }
310 else {
311 sleep_time = 0;
312 }
313 }
314 dprintf("Update took %d, last delta %d, sleep_time %d\n", update_delta, delta, sleep_time);
315 chEvtWaitOneTimeout(EVENT_MASK(0), sleep_time);
316 }
317#ifdef LCD_ENABLE
318 gdispCloseFont(state.font_fixed5x8);
319 gdispCloseFont(state.font_dejavusansbold12);
320#endif
321}
322
323void visualizer_init(void) {
324 // We are using a low priority thread, the idea is to have it run only
325 // when the main thread is sleeping during the matrix scanning
326 chEvtObjectInit(&layer_changed_event);
327 (void)chThdCreateStatic(visualizerThreadStack, sizeof(visualizerThreadStack),
328 LOWPRIO, visualizerThread, NULL);
329}
330
331void visualizer_set_state(uint32_t default_state, uint32_t state, uint32_t leds) {
332 // Note that there's a small race condition here, the thread could read
333 // a state where one of these are set but not the other. But this should
334 // not really matter as it will be fixed during the next loop step.
335 // Alternatively a mutex could be used instead of the volatile variables
336 bool changed = false;
337 visualizer_keyboard_status_t new_status = {
338 .layer = state,
339 .default_layer = default_state,
340 .leds = leds,
341 };
342 if (!same_status(&current_status, &new_status)) {
343 changed = true;
344 }
345 current_status = new_status;
346 if (changed) {
347 chEvtBroadcast(&layer_changed_event);
348 }
349}