Root/
Source at commit b99c81409f094dabf19a63aa748afcff2425e822 created 11 years 1 month ago. By Werner Almesberger, ubb-la/gui.c: add user-defined zero reference (set/unset with space) | |
---|---|
1 | /* |
2 | * gui.c - Graphical output for ubb-la |
3 | * |
4 | * Written 2013 by Werner Almesberger |
5 | * Copyright 2013 Werner Almesberger |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License as published by |
9 | * the Free Software Foundation; either version 2 of the License, or |
10 | * (at your option) any later version. |
11 | */ |
12 | |
13 | #include <stdarg.h> |
14 | #include <stdint.h> |
15 | #include <stdlib.h> |
16 | #include <alloca.h> |
17 | |
18 | #include "SDL.h" |
19 | #include "SDL_gfxPrimitives.h" |
20 | #include "SDL_gfxPrimitives_font.h" |
21 | |
22 | #include "gui.h" |
23 | |
24 | |
25 | #if 0 |
26 | #define DEBUG(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) |
27 | #else |
28 | #define DEBUG(fmt, ...) |
29 | #endif |
30 | |
31 | |
32 | #define XRES 320 /* canvas width */ |
33 | #define YRES 240 /* canvas height */ |
34 | |
35 | |
36 | /* ----- Colors ------------------------------------------------------------ */ |
37 | |
38 | |
39 | #define TEXT_RGBA 0xffffffff /* general text */ |
40 | #define UNIT_RGBA 0xb0e0ffff /* units */ |
41 | #define MAP_BUF_RGBA 0x808080ff /* buffer in the map */ |
42 | #define MAP_VIEW_RGBA 0xffffffff /* current view in the map */ |
43 | #define CENTER_RGBA 0x5080ffff /* center marker */ |
44 | #define USER_REF_RGBA 0x00ff40ff /* user reference marker */ |
45 | #define LEVEL_RGBA 0xffff00ff /* constant level or single change */ |
46 | #define BOUNCE_RGBA 0xff8080ff /* bouncing signal */ |
47 | #define LABEL_RGBA 0xffffffff /* channel label */ |
48 | #define DIV_RGBA 0x808080ff /* divisions */ |
49 | |
50 | |
51 | /* ----- Layout ------------------------------------------------------------ */ |
52 | |
53 | |
54 | #define XCENTER ((XRES+CH_XOFF)/2) |
55 | #define XWIDTH (XRES-CH_XOFF) |
56 | |
57 | #define MAP_BUF_Y0 2 |
58 | #define MAP_BUF_Y1 8 |
59 | #define MAP_VIEW_Y0 0 |
60 | #define MAP_VIEW_Y1 10 |
61 | |
62 | #define CENTER_W 8 |
63 | #define CENTER_Y0 20 |
64 | #define CENTER_Y1 (CENTER_Y0+4) |
65 | |
66 | #define USER_REF_W 8 |
67 | #define USER_REF_Y0 92 |
68 | #define USER_REF_Y1 (USER_REF_Y0+4) |
69 | |
70 | #define CH_XOFF 30 |
71 | #define CH_YOFF 30 |
72 | #define CH_SKIP 16 |
73 | #define CH_HEIGHT 8 |
74 | |
75 | #define DIV_X 32 |
76 | #define DIV_Y 6 |
77 | |
78 | #define MAX_ZOOM 3 |
79 | |
80 | #define UNIT_GAP 2 /* space between number and unit */ |
81 | |
82 | #define POS_T_X 182 |
83 | #define POS_SAMP_X 270 |
84 | #define POS_Y (MEAS_DIV_Y-8-3) |
85 | |
86 | #define MEAS_DIV_Y (FREQ_Y-3-1) |
87 | |
88 | #define FREQ_X 0 |
89 | #define FREQ_Y 222 |
90 | #define INTERVAL_X 0 |
91 | #define INTERVAL_Y (FREQ_Y+8+2) |
92 | #define DIV_SAMP_X (DIV_INT_X-8) |
93 | #define DIV_SAMP_Y FREQ_Y |
94 | #define DIV_INT_X 80 |
95 | #define DIV_INT_Y INTERVAL_Y |
96 | |
97 | #define USER_T_X POS_T_X |
98 | #define USER_T_Y FREQ_Y |
99 | #define USER_SAMP_X POS_SAMP_X |
100 | #define USER_SAMP_Y USER_T_Y |
101 | #define USER_FREQ_X POS_T_X |
102 | #define USER_FREQ_Y (USER_T_Y+8+2) |
103 | |
104 | |
105 | /* ----- Miscellaneous definitions ----------------------------------------- */ |
106 | |
107 | |
108 | #define REPEAT_DELAY_MS 300 |
109 | #define REPEAT_INTERVAL_MS 30 |
110 | |
111 | static SDL_Surface *surf; |
112 | static int user_ref = -1; |
113 | |
114 | |
115 | /* ----- SDL initialization and screen-wide functions ---------------------- */ |
116 | |
117 | |
118 | void gui_init(void) |
119 | { |
120 | if (SDL_Init(SDL_INIT_VIDEO) < 0) { |
121 | fprintf(stderr, "SDL_init: %s\n", SDL_GetError()); |
122 | exit(1); |
123 | } |
124 | atexit(SDL_Quit); |
125 | |
126 | surf = SDL_SetVideoMode(XRES, YRES, 0, SDL_SWSURFACE); |
127 | if (!surf) { |
128 | fprintf(stderr, "SDL_SetVideoMode: %s\n", SDL_GetError()); |
129 | exit(1); |
130 | } |
131 | |
132 | SDL_EnableKeyRepeat(REPEAT_DELAY_MS, REPEAT_INTERVAL_MS); |
133 | } |
134 | |
135 | |
136 | static void clear(void) |
137 | { |
138 | SDL_LockSurface(surf); |
139 | SDL_FillRect(surf, NULL, SDL_MapRGB(surf->format, 0, 0, 0)); |
140 | } |
141 | |
142 | |
143 | static void update(void) |
144 | { |
145 | SDL_UnlockSurface(surf); |
146 | SDL_UpdateRect(surf, 0, 0, 0, 0); |
147 | } |
148 | |
149 | |
150 | /* ----- Text output ------------------------------------------------------- */ |
151 | |
152 | |
153 | /* |
154 | * stringColor from SDL_gfx fails for some reason. SDL_ttf is just too much |
155 | * compatibility trouble. So we do our own. |
156 | */ |
157 | |
158 | static void textf(int x, int y, uint32_t color, const char *fmt, ...) |
159 | { |
160 | va_list ap; |
161 | char *tmp, *s; |
162 | uint8_t *p; |
163 | int n, ix, iy; |
164 | |
165 | va_start(ap, fmt); |
166 | n = vsnprintf(NULL, 0, fmt, ap); |
167 | va_end(ap); |
168 | tmp = alloca(n+1); |
169 | va_start(ap, fmt); |
170 | vsnprintf(tmp, n+1, fmt, ap); |
171 | va_end(ap); |
172 | |
173 | for (s = tmp; *s; s++) { |
174 | p = gfxPrimitivesFontdata+(*s << 3); |
175 | for (iy = 0; iy != 8; iy++) { |
176 | for (ix = 0; ix != 8; ix++) |
177 | if ((*p << ix) & 0x80) |
178 | pixelColor(surf, x+ix, y+iy, color); |
179 | p++; |
180 | } |
181 | x += 8; |
182 | } |
183 | } |
184 | |
185 | |
186 | /* ----- Map of buffer and view -------------------------------------------- */ |
187 | |
188 | |
189 | static void show_map(int skip, int nibbles, int s0, int s1) |
190 | { |
191 | int w = nibbles-skip; |
192 | int m = (nibbles-skip) >> 1; |
193 | int scale = 0; |
194 | int x0, x1; |
195 | |
196 | while (w >= XWIDTH/2) { |
197 | w >>= 1; |
198 | scale++; |
199 | } |
200 | boxColor(surf, XCENTER-(w >> 1), MAP_BUF_Y0, |
201 | XCENTER+(w >> 1), MAP_BUF_Y1, MAP_BUF_RGBA); |
202 | x0 = (s0-m+(XCENTER << scale)) >> scale; |
203 | x1 = x0+((s1-s0) >> scale); |
204 | rectangleColor(surf, x0, MAP_VIEW_Y0, x1, MAP_VIEW_Y1, MAP_VIEW_RGBA); |
205 | } |
206 | |
207 | |
208 | /* ----- Waveform elements ------------------------------------------------- */ |
209 | |
210 | |
211 | static inline int ch_y(int ch, int v) |
212 | { |
213 | return CH_YOFF+CH_SKIP*ch+(v ? 0 : CH_HEIGHT); |
214 | } |
215 | |
216 | |
217 | static void bounce(int x, int ch) |
218 | { |
219 | vlineColor(surf, x, ch_y(ch, 0), ch_y(ch, 1), BOUNCE_RGBA); |
220 | } |
221 | |
222 | |
223 | static void change(int x, int ch) |
224 | { |
225 | vlineColor(surf, x, ch_y(ch, 0), ch_y(ch, 1), LEVEL_RGBA); |
226 | } |
227 | |
228 | |
229 | static void level(int x0, int x1, int ch, int v) |
230 | { |
231 | if (x0 == x1) |
232 | pixelColor(surf, x0, ch_y(ch, v), LEVEL_RGBA); |
233 | else |
234 | hlineColor(surf, x0, x1, ch_y(ch, v), LEVEL_RGBA); |
235 | } |
236 | |
237 | |
238 | /* ----- Show (part of) a buffer ------------------------------------------- */ |
239 | |
240 | |
241 | static void show_buffer_zoom_in(const uint8_t *buf, int skip, int nibbles, |
242 | int x0, int x1) |
243 | { |
244 | int f = (x1-x0)/(nibbles-skip); |
245 | int x, i, j; |
246 | int last[4] = { -1, -1, -1, -1}; |
247 | uint8_t v, bit; |
248 | |
249 | DEBUG("in: %d-%d (%d) Sa; %d-%d (%d) pix; %d f (%d)\n", |
250 | skip, nibbles, nibbles-skip, x0, x1, x1-x0, f, f*(nibbles-skip)); |
251 | x = x0; |
252 | for (i = skip; i != nibbles; i++) { |
253 | v = (buf[i >> 1] >> 4*(~i & 1)) & 0xf; |
254 | for (j = 0; j != 4; j++) { |
255 | bit = (v >> j) & 1; |
256 | if (bit == last[j] || last[j] == -1) { |
257 | level(x, x+f-1, j, bit); |
258 | } else { |
259 | change(x, j); |
260 | level(x+1, x+f-1, j, bit); |
261 | } |
262 | last[j] = bit; |
263 | } |
264 | x += f; |
265 | } |
266 | } |
267 | |
268 | |
269 | static void show_buffer_zoom_out(const uint8_t *buf, int skip, int nibbles, |
270 | int x0, int x1) |
271 | { |
272 | int f = (nibbles-skip)/(x1-x0); |
273 | int x; |
274 | int i = skip, n, j; |
275 | int changes[4], last[4] = { -1, -1, -1, -1}, next[4]; |
276 | uint8_t v, bit; |
277 | |
278 | DEBUG("out: %d-%d (%d) Sa; %d-%d (%d) pix; %d f (%d)\n", |
279 | skip, nibbles, nibbles-skip, x0, x1, x1-x0, f, f*(x1-x0)); |
280 | for (x = x0; x != x1; x++) { |
281 | n = i+f; |
282 | for (j = 0; j != 4; j++) { |
283 | next[j] = last[j]; |
284 | changes[j] = 0; |
285 | } |
286 | while (i != n) { |
287 | v = (buf[i >> 1] >> 4*(~i & 1)) & 0xf; |
288 | for (j = 0; j != 4; j++) { |
289 | bit = (v >> j) & 1; |
290 | if (bit != next[j]) { |
291 | changes[j]++; |
292 | next[j] = bit; |
293 | } |
294 | } |
295 | i++; |
296 | } |
297 | for (j = 0; j != 4; j++) { |
298 | if (changes[j] > 1) |
299 | bounce(x, j); |
300 | else if (!changes[j] || last[j] == -1) |
301 | level(x, x, j, next[j]); |
302 | else |
303 | change(x, j); |
304 | last[j] = next[j]; |
305 | } |
306 | } |
307 | } |
308 | |
309 | |
310 | static void user_ref_marker(int x0, int x1, int x) |
311 | { |
312 | if (x < 0 || x > x1-x0) |
313 | return; |
314 | filledTrigonColor(surf, x0+x, USER_REF_Y0, |
315 | x0+x-USER_REF_W/2, USER_REF_Y1, |
316 | x0+x+USER_REF_W/2, USER_REF_Y1, USER_REF_RGBA); |
317 | } |
318 | |
319 | |
320 | static void show_buffer(const uint8_t *buf, int skip, int nibbles, |
321 | int x0, int x1, int zoom, int pos) |
322 | { |
323 | int xm, w, s, p0, p1; |
324 | int d, dp, ref; |
325 | |
326 | xm = (x0+x1) >> 1; |
327 | dp = pos-((nibbles-skip) >> 1); |
328 | ref = user_ref+skip; |
329 | DEBUG("show: %d-%d Sa; %d-%d pix; pos %d dp %d; xm %d xcenter %d\n", |
330 | skip, nibbles, x0, x1, pos, dp, xm, XCENTER); |
331 | if (zoom < 0) { |
332 | s = (x1-x0) << -zoom; |
333 | show_map(skip, nibbles, pos-s/2, pos+s/2); |
334 | w = (nibbles-skip) >> -zoom; |
335 | p0 = xm-(w >> 1)-(dp >> -zoom); |
336 | p1 = xm+((w+1) >> 1)-(dp >> -zoom); |
337 | if (p0 < x0) { |
338 | skip += (x0-p0) << -zoom; |
339 | p0 = x0; |
340 | } |
341 | if (p1 > x1) { |
342 | nibbles -= (p1-x1) << -zoom; |
343 | p1 = x1; |
344 | } |
345 | show_buffer_zoom_out(buf, skip, nibbles, p0, p1); |
346 | if (user_ref != -1) |
347 | user_ref_marker(p0, p1, (ref-skip) >> -zoom); |
348 | } else { |
349 | s = (x1-x0) >> zoom; |
350 | show_map(skip, nibbles, pos-s/2, pos+s/2); |
351 | w = (nibbles-skip) << zoom; |
352 | p0 = xm-(w >> 1)-(dp << zoom); |
353 | p1 = xm+((w+1) >> 1)-(dp << zoom); |
354 | if (p0 < x0) { |
355 | d = ((x0-p0)+(1 << zoom)-1) >> zoom; |
356 | skip += d; |
357 | p0 += d << zoom; |
358 | } |
359 | if (p1 > x1) { |
360 | d = ((p1-x1)+(1 << zoom)-1) >> zoom; |
361 | nibbles -= d; |
362 | p1 -= d << zoom; |
363 | } |
364 | show_buffer_zoom_in(buf, skip, nibbles, p0, p1); |
365 | if (user_ref != -1) |
366 | user_ref_marker(p0, p1, (ref-skip) << zoom); |
367 | } |
368 | } |
369 | |
370 | |
371 | /* ----- Display various settings ------------------------------------------ */ |
372 | |
373 | |
374 | static void si_text(int x, int y, double v, const char *unit, int digits) |
375 | { |
376 | const char *pfx; |
377 | |
378 | if (v >= 1e6) { |
379 | v /= 1e6; |
380 | pfx = "M"; |
381 | } else if (v >= 1e3) { |
382 | v /= 1e3; |
383 | pfx = "k"; |
384 | } else if (v >= 1) { |
385 | pfx = " "; |
386 | } else if (v >= 1e-3) { |
387 | v *= 1e3; |
388 | pfx = "m"; |
389 | } else if (v >= 1e-6) { |
390 | v *= 1e6; |
391 | pfx = "u"; |
392 | } else { |
393 | v *= 1e9; |
394 | pfx = "n"; |
395 | } |
396 | if (v >= 10 && digits == 3) |
397 | textf(x, y, TEXT_RGBA, "%3d", (int) (v+0.5)); |
398 | else if (v >= 100 && digits == 4) |
399 | textf(x, y, TEXT_RGBA, "%4d", (int) (v+0.5)); |
400 | else if (v >= 100) |
401 | textf(x, y, TEXT_RGBA, "%*.*f", digits, digits-4, v); |
402 | else if (v >= 10) |
403 | textf(x, y, TEXT_RGBA, "%*.*f", digits, digits-3, v); |
404 | else |
405 | textf(x, y, TEXT_RGBA, "%*.*f", digits, digits-2, v); |
406 | textf(x+digits*8+UNIT_GAP, y, UNIT_RGBA, "%s%s", pfx, unit); |
407 | } |
408 | |
409 | |
410 | static void show_freq(double freq, int zoom) |
411 | { |
412 | int div; |
413 | |
414 | si_text(FREQ_X, FREQ_Y, freq, "Sa/s", 3); |
415 | si_text(INTERVAL_X, INTERVAL_Y, 1/freq, "s/Sa", 3); |
416 | div = (DIV_X >> MAX_ZOOM) << (MAX_ZOOM-zoom); |
417 | textf(DIV_SAMP_X, DIV_SAMP_Y, TEXT_RGBA, "%4d", div); |
418 | textf(DIV_SAMP_X+4*8+UNIT_GAP, DIV_SAMP_Y, UNIT_RGBA, "Sa/div", div); |
419 | si_text(DIV_INT_X, DIV_INT_Y, div/freq, "s/div", 3); |
420 | } |
421 | |
422 | |
423 | static void show_position(double freq, int pos) |
424 | { |
425 | si_text(POS_T_X, POS_Y, pos/freq, "s", 7); |
426 | hlineColor(surf, 0, XRES-1, MEAS_DIV_Y, DIV_RGBA); |
427 | si_text(POS_T_X, POS_Y, pos/freq, "s", 7); |
428 | hlineColor(surf, 0, XRES-1, MEAS_DIV_Y, DIV_RGBA); |
429 | |
430 | textf(POS_SAMP_X, POS_Y, TEXT_RGBA, "%4d", pos); |
431 | textf(POS_SAMP_X+4*8+UNIT_GAP, POS_Y, UNIT_RGBA, "Sa", div); |
432 | } |
433 | |
434 | |
435 | static void show_user_ref(double freq, int pos, int user_ref) |
436 | { |
437 | si_text(USER_T_X, USER_T_Y, fabs(user_ref-pos)/freq, "s", 7); |
438 | |
439 | textf(USER_SAMP_X,USER_SAMP_Y, TEXT_RGBA, "%4d", |
440 | pos > user_ref ? pos-user_ref : user_ref-pos); |
441 | textf(USER_SAMP_X+4*8+UNIT_GAP, USER_SAMP_Y, UNIT_RGBA, "Sa", div); |
442 | |
443 | if (pos != user_ref) |
444 | si_text(USER_FREQ_X, USER_FREQ_Y, |
445 | freq/fabs(user_ref-pos), "Hz", 7); |
446 | } |
447 | |
448 | |
449 | /* ----- Show divisions ---------------------------------------------------- */ |
450 | |
451 | |
452 | static void show_divisions(void) |
453 | { |
454 | int n = XWIDTH/2/DIV_X; |
455 | int i; |
456 | |
457 | for (i = -n; i <= n; i++) |
458 | vlineColor(surf, XCENTER+i*DIV_X, |
459 | ch_y(0, 1)-DIV_Y, ch_y(3, 0)+DIV_Y, |
460 | i ? DIV_RGBA : CENTER_RGBA); |
461 | } |
462 | |
463 | |
464 | /* ----- Main event loop --------------------------------------------------- */ |
465 | |
466 | |
467 | static int pos_step(int zoom) |
468 | { |
469 | return 1 << (MAX_ZOOM-zoom+1); |
470 | } |
471 | |
472 | |
473 | void gui(const uint8_t *buf, int skip, int nibbles, double freq) |
474 | { |
475 | SDL_Event event; |
476 | int pos = (skip+nibbles) >> 1; |
477 | int zoom; /* < 0: zoom out; 0: 1 pixel = 1 sample; > 1: zoom in */ |
478 | int min_zoom = 0; |
479 | int i; |
480 | |
481 | while (XWIDTH < (nibbles-skip) >> -min_zoom) |
482 | min_zoom--; |
483 | zoom = min_zoom; |
484 | while (1) { |
485 | clear(); |
486 | for (i = 0; i != 4; i++) |
487 | textf(0, ch_y(i, 1), LABEL_RGBA, "CH%d", i); |
488 | show_divisions(); |
489 | filledTrigonColor(surf, XCENTER, CENTER_Y1, |
490 | XCENTER-CENTER_W/2, CENTER_Y0, |
491 | XCENTER+CENTER_W/2, CENTER_Y0, CENTER_RGBA); |
492 | show_buffer(buf, skip, nibbles, CH_XOFF, XRES, zoom, pos); |
493 | show_freq(freq, zoom); |
494 | show_position(freq, pos); |
495 | if (user_ref != -1) |
496 | show_user_ref(freq, pos, user_ref); |
497 | update(); |
498 | |
499 | while (1) { |
500 | SDL_WaitEvent(&event); |
501 | switch (event.type) { |
502 | case SDL_KEYDOWN: |
503 | switch (event.key.keysym.sym) { |
504 | case SDLK_UP: |
505 | if (zoom < MAX_ZOOM) |
506 | zoom++; |
507 | break; |
508 | case SDLK_DOWN: |
509 | if (zoom > min_zoom) |
510 | zoom--; |
511 | break; |
512 | case SDLK_LEFT: |
513 | pos -= pos_step(zoom); |
514 | if (pos < 0) |
515 | pos = 0; |
516 | break; |
517 | case SDLK_RIGHT: |
518 | pos += pos_step(zoom); |
519 | if (pos > nibbles-skip-1) |
520 | pos = nibbles-skip-1; |
521 | break; |
522 | case SDLK_SPACE: |
523 | if (pos == user_ref) |
524 | user_ref = -1; |
525 | else |
526 | user_ref = pos; |
527 | break; |
528 | case SDLK_RETURN: |
529 | case SDLK_q: |
530 | return; |
531 | default: |
532 | printf("%x\n", event.key.keysym.sym); |
533 | continue; |
534 | } |
535 | break; |
536 | case SDL_QUIT: |
537 | return; |
538 | default: |
539 | continue; |
540 | } |
541 | break; |
542 | } |
543 | } |
544 | } |
545 |
Branches:
master