/* * gui.c - Graphical output for ubb-la * * Written 2013 by Werner Almesberger * Copyright 2013 Werner Almesberger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include "SDL.h" #include "SDL_gfxPrimitives.h" #include "SDL_gfxPrimitives_font.h" #include "gui.h" #if 0 #define DEBUG(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) #else #define DEBUG(fmt, ...) #endif #define XRES 320 /* canvas width */ #define YRES 240 /* canvas height */ /* ----- Colors ------------------------------------------------------------ */ #define TEXT_RGBA 0xffffffff /* general text */ #define UNIT_RGBA 0xb0e0ffff /* units */ #define MAP_BUF_RGBA 0x808080ff /* buffer in the map */ #define MAP_VIEW_RGBA 0xffffffff /* current view in the map */ #define CENTER_RGBA 0x5080ffff /* center marker */ #define USER_REF_RGBA 0x00ff40ff /* user reference marker */ #define LEVEL_RGBA 0xffff00ff /* constant level or single change */ #define BOUNCE_RGBA 0xff8080ff /* bouncing signal */ #define LABEL_RGBA 0xffffffff /* channel label */ #define DIV_RGBA 0x808080ff /* divisions */ /* ----- Layout ------------------------------------------------------------ */ #define XCENTER ((XRES+CH_XOFF)/2) #define XWIDTH (XRES-CH_XOFF) #define MAP_BUF_Y0 2 #define MAP_BUF_Y1 8 #define MAP_VIEW_Y0 0 #define MAP_VIEW_Y1 10 #define CENTER_W 8 #define CENTER_Y0 20 #define CENTER_Y1 (CENTER_Y0+4) #define USER_REF_W 8 #define USER_REF_Y0 92 #define USER_REF_Y1 (USER_REF_Y0+4) #define CH_XOFF 30 #define CH_YOFF 30 #define CH_SKIP 16 #define CH_HEIGHT 8 #define DIV_X 32 #define DIV_Y 6 #define MAX_ZOOM 3 #define UNIT_GAP 2 /* space between number and unit */ #define POS_T_X 182 #define POS_SAMP_X 270 #define POS_Y (MEAS_DIV_Y-8-3) #define MEAS_DIV_Y (FREQ_Y-3-1) #define FREQ_X 0 #define FREQ_Y 222 #define INTERVAL_X 0 #define INTERVAL_Y (FREQ_Y+8+2) #define DIV_SAMP_X (DIV_INT_X-8) #define DIV_SAMP_Y FREQ_Y #define DIV_INT_X 80 #define DIV_INT_Y INTERVAL_Y #define USER_T_X POS_T_X #define USER_T_Y FREQ_Y #define USER_SAMP_X POS_SAMP_X #define USER_SAMP_Y USER_T_Y #define USER_FREQ_X POS_T_X #define USER_FREQ_Y (USER_T_Y+8+2) /* ----- Miscellaneous definitions ----------------------------------------- */ #define REPEAT_DELAY_MS 300 #define REPEAT_INTERVAL_MS 30 static SDL_Surface *surf; static int user_ref = -1; /* ----- Helper functions -------------------------------------------------- */ static inline uint8_t get_sample(const uint8_t *buf, unsigned i) { return (buf[i >> 1] >> 4*(~i & 1)) & 0xf; } /* ----- SDL initialization and screen-wide functions ---------------------- */ void gui_init(void) { if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "SDL_init: %s\n", SDL_GetError()); exit(1); } atexit(SDL_Quit); surf = SDL_SetVideoMode(XRES, YRES, 0, SDL_SWSURFACE); if (!surf) { fprintf(stderr, "SDL_SetVideoMode: %s\n", SDL_GetError()); exit(1); } SDL_EnableKeyRepeat(REPEAT_DELAY_MS, REPEAT_INTERVAL_MS); #ifdef BEN SDL_ShowCursor(SDL_DISABLE); #endif } static void clear(void) { SDL_LockSurface(surf); SDL_FillRect(surf, NULL, SDL_MapRGB(surf->format, 0, 0, 0)); } static void update(void) { SDL_UnlockSurface(surf); SDL_UpdateRect(surf, 0, 0, 0, 0); } /* ----- Text output ------------------------------------------------------- */ /* * stringColor from SDL_gfx fails for some reason. SDL_ttf is just too much * compatibility trouble. So we do our own. */ static void textf(int x, int y, uint32_t color, const char *fmt, ...) { va_list ap; char *tmp, *s; uint8_t *p; int n, ix, iy; va_start(ap, fmt); n = vsnprintf(NULL, 0, fmt, ap); va_end(ap); tmp = alloca(n+1); va_start(ap, fmt); vsnprintf(tmp, n+1, fmt, ap); va_end(ap); for (s = tmp; *s; s++) { p = gfxPrimitivesFontdata+(*s << 3); for (iy = 0; iy != 8; iy++) { for (ix = 0; ix != 8; ix++) if ((*p << ix) & 0x80) pixelColor(surf, x+ix, y+iy, color); p++; } x += 8; } } /* ----- Map of buffer and view -------------------------------------------- */ static void show_map(int skip, int nibbles, int s0, int s1) { int w = nibbles-skip; int m = (nibbles-skip) >> 1; int scale = 0; int x0, x1; while (w >= XWIDTH/2) { w >>= 1; scale++; } boxColor(surf, XCENTER-(w >> 1), MAP_BUF_Y0, XCENTER+(w >> 1), MAP_BUF_Y1, MAP_BUF_RGBA); x0 = (s0-m+(XCENTER << scale)) >> scale; x1 = x0+((s1-s0) >> scale); rectangleColor(surf, x0, MAP_VIEW_Y0, x1, MAP_VIEW_Y1, MAP_VIEW_RGBA); } /* ----- Waveform elements ------------------------------------------------- */ static inline int ch_y(int ch, int v) { return CH_YOFF+CH_SKIP*ch+(v ? 0 : CH_HEIGHT); } static void bounce(int x, int ch) { vlineColor(surf, x, ch_y(ch, 0), ch_y(ch, 1), BOUNCE_RGBA); } static void change(int x, int ch) { vlineColor(surf, x, ch_y(ch, 0), ch_y(ch, 1), LEVEL_RGBA); } static void level(int x0, int x1, int ch, int v) { if (x0 == x1) pixelColor(surf, x0, ch_y(ch, v), LEVEL_RGBA); else hlineColor(surf, x0, x1, ch_y(ch, v), LEVEL_RGBA); } /* ----- Show (part of) a buffer ------------------------------------------- */ static void show_buffer_zoom_in(const uint8_t *buf, int skip, int nibbles, int x0, int x1) { int f = (x1-x0)/(nibbles-skip); int x, i, j; int last[4] = { -1, -1, -1, -1}; uint8_t v, bit; DEBUG("in: %d-%d (%d) Sa; %d-%d (%d) pix; %d f (%d)\n", skip, nibbles, nibbles-skip, x0, x1, x1-x0, f, f*(nibbles-skip)); x = x0; for (i = skip; i != nibbles; i++) { v = get_sample(buf, i); for (j = 0; j != 4; j++) { bit = (v >> j) & 1; if (bit == last[j] || last[j] == -1) { level(x, x+f-1, j, bit); } else { change(x, j); level(x+1, x+f-1, j, bit); } last[j] = bit; } x += f; } } static void show_buffer_zoom_out(const uint8_t *buf, int skip, int nibbles, int x0, int x1) { int f = (nibbles-skip)/(x1-x0); int x; int i = skip, n, j; int changes[4], last[4] = { -1, -1, -1, -1}, next[4]; uint8_t v, bit; DEBUG("out: %d-%d (%d) Sa; %d-%d (%d) pix; %d f (%d)\n", skip, nibbles, nibbles-skip, x0, x1, x1-x0, f, f*(x1-x0)); for (x = x0; x != x1; x++) { n = i+f; for (j = 0; j != 4; j++) { next[j] = last[j]; changes[j] = 0; } while (i != n) { v = get_sample(buf, i); for (j = 0; j != 4; j++) { bit = (v >> j) & 1; if (bit != next[j]) { changes[j]++; next[j] = bit; } } i++; } for (j = 0; j != 4; j++) { if (changes[j] > 1) bounce(x, j); else if (!changes[j] || last[j] == -1) level(x, x, j, next[j]); else change(x, j); last[j] = next[j]; } } } static void user_ref_marker(int x0, int x1, int x) { if (x < 0 || x > x1-x0) return; filledTrigonColor(surf, x0+x, USER_REF_Y0, x0+x-USER_REF_W/2, USER_REF_Y1, x0+x+USER_REF_W/2, USER_REF_Y1, USER_REF_RGBA); } static void show_buffer(const uint8_t *buf, int skip, int nibbles, int x0, int x1, int zoom, int pos) { int xm, w, s, p0, p1; int d, dp, ref; xm = (x0+x1) >> 1; dp = pos-((nibbles-skip) >> 1); ref = user_ref+skip; DEBUG("show: %d-%d Sa; %d-%d pix; pos %d dp %d; xm %d xcenter %d\n", skip, nibbles, x0, x1, pos, dp, xm, XCENTER); if (zoom < 0) { s = (x1-x0) << -zoom; show_map(skip, nibbles, pos-s/2, pos+s/2); w = (nibbles-skip) >> -zoom; p0 = xm-(w >> 1)-(dp >> -zoom); p1 = xm+((w+1) >> 1)-(dp >> -zoom); if (p0 < x0) { skip += (x0-p0) << -zoom; p0 = x0; } if (p1 > x1) { nibbles -= (p1-x1) << -zoom; p1 = x1; } show_buffer_zoom_out(buf, skip, nibbles, p0, p1); if (user_ref != -1) user_ref_marker(p0, p1, (ref-skip) >> -zoom); } else { s = (x1-x0) >> zoom; show_map(skip, nibbles, pos-s/2, pos+s/2); w = (nibbles-skip) << zoom; p0 = xm-(w >> 1)-(dp << zoom); p1 = xm+((w+1) >> 1)-(dp << zoom); if (p0 < x0) { d = ((x0-p0)+(1 << zoom)-1) >> zoom; skip += d; p0 += d << zoom; } if (p1 > x1) { d = ((p1-x1)+(1 << zoom)-1) >> zoom; nibbles -= d; p1 -= d << zoom; } show_buffer_zoom_in(buf, skip, nibbles, p0, p1); if (user_ref != -1) user_ref_marker(p0, p1, (ref-skip) << zoom); } } /* ----- Display various settings ------------------------------------------ */ static void si_text(int x, int y, double v, const char *unit, int digits) { const char *pfx; if (v >= 1e6) { v /= 1e6; pfx = "M"; } else if (v >= 1e3) { v /= 1e3; pfx = "k"; } else if (v >= 1) { pfx = " "; } else if (v >= 1e-3) { v *= 1e3; pfx = "m"; } else if (v >= 1e-6) { v *= 1e6; pfx = "u"; } else { v *= 1e9; pfx = "n"; } if (v >= 10 && digits == 3) textf(x, y, TEXT_RGBA, "%3d", (int) (v+0.5)); else if (v >= 100 && digits == 4) textf(x, y, TEXT_RGBA, "%4d", (int) (v+0.5)); else if (digits == 7) textf(x, y, TEXT_RGBA, "%*.*f", digits, 3, v); else if (v >= 100) textf(x, y, TEXT_RGBA, "%*.*f", digits, digits-4, v); else if (v >= 10) textf(x, y, TEXT_RGBA, "%*.*f", digits, digits-3, v); else textf(x, y, TEXT_RGBA, "%*.*f", digits, digits-2, v); textf(x+digits*8+UNIT_GAP, y, UNIT_RGBA, "%s%s", pfx, unit); } static void show_freq(double freq, int zoom) { int div; si_text(FREQ_X, FREQ_Y, freq, "Sa/s", 3); si_text(INTERVAL_X, INTERVAL_Y, 1/freq, "s/Sa", 3); div = (DIV_X >> MAX_ZOOM) << (MAX_ZOOM-zoom); textf(DIV_SAMP_X, DIV_SAMP_Y, TEXT_RGBA, "%4d", div); textf(DIV_SAMP_X+4*8+UNIT_GAP, DIV_SAMP_Y, UNIT_RGBA, "Sa/div", div); si_text(DIV_INT_X, DIV_INT_Y, div/freq, "s/div", 3); } static void show_position(double freq, int pos) { si_text(POS_T_X, POS_Y, pos/freq, "s", 7); hlineColor(surf, 0, XRES-1, MEAS_DIV_Y, DIV_RGBA); si_text(POS_T_X, POS_Y, pos/freq, "s", 7); hlineColor(surf, 0, XRES-1, MEAS_DIV_Y, DIV_RGBA); textf(POS_SAMP_X, POS_Y, TEXT_RGBA, "%4d", pos); textf(POS_SAMP_X+4*8+UNIT_GAP, POS_Y, UNIT_RGBA, "Sa", div); } static void show_user_ref(double freq, int pos, int user_ref) { si_text(USER_T_X, USER_T_Y, fabs(user_ref-pos)/freq, "s", 7); textf(USER_SAMP_X,USER_SAMP_Y, TEXT_RGBA, "%4d", pos > user_ref ? pos-user_ref : user_ref-pos); textf(USER_SAMP_X+4*8+UNIT_GAP, USER_SAMP_Y, UNIT_RGBA, "Sa", div); if (pos != user_ref) si_text(USER_FREQ_X, USER_FREQ_Y, freq/fabs(user_ref-pos), "Hz", 7); } /* ----- Show divisions ---------------------------------------------------- */ static void show_divisions(void) { int n = XWIDTH/2/DIV_X; int i; for (i = -n; i <= n; i++) vlineColor(surf, XCENTER+i*DIV_X, ch_y(0, 1)-DIV_Y, ch_y(3, 0)+DIV_Y, i ? DIV_RGBA : CENTER_RGBA); } /* ----- Main event loop --------------------------------------------------- */ static int pos_step(int zoom) { return zoom == MAX_ZOOM ? 1 : 1 << (MAX_ZOOM-zoom+1); } void gui(const uint8_t *buf, int skip, int nibbles, double freq) { SDL_Event event; int pos = (skip+nibbles) >> 1; int zoom; /* < 0: zoom out; 0: 1 pixel = 1 sample; > 1: zoom in */ int min_zoom = 0; int i; while (XWIDTH < (nibbles-skip) >> -min_zoom) min_zoom--; zoom = min_zoom; while (1) { clear(); for (i = 0; i != 4; i++) textf(0, ch_y(i, 1), LABEL_RGBA, "CH%d", i); show_divisions(); filledTrigonColor(surf, XCENTER, CENTER_Y1, XCENTER-CENTER_W/2, CENTER_Y0, XCENTER+CENTER_W/2, CENTER_Y0, CENTER_RGBA); show_buffer(buf, skip, nibbles, CH_XOFF, XRES, zoom, pos); show_freq(freq, zoom); show_position(freq, pos); if (user_ref != -1) show_user_ref(freq, pos, user_ref); update(); while (1) { SDL_WaitEvent(&event); switch (event.type) { case SDL_KEYDOWN: switch (event.key.keysym.sym) { case SDLK_UP: if (zoom < MAX_ZOOM) zoom++; break; case SDLK_DOWN: if (zoom > min_zoom) zoom--; break; case SDLK_LEFT: pos -= pos_step(zoom); if (pos < 0) pos = 0; break; case SDLK_RIGHT: pos += pos_step(zoom); if (pos > nibbles-skip-1) pos = nibbles-skip-1; break; case SDLK_SPACE: if (pos == user_ref) user_ref = -1; else user_ref = pos; break; case SDLK_RETURN: case SDLK_q: return; default: printf("%x\n", event.key.keysym.sym); continue; } break; case SDL_QUIT: return; default: continue; } break; } } }