mirror of
git://projects.qi-hardware.com/ben-blinkenlights.git
synced 2024-11-27 16:57:31 +02:00
9cc032816e
This permits precise interval/frequency calculation with the user-defined reference.
545 lines
12 KiB
C
545 lines
12 KiB
C
/*
|
|
* 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 <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <alloca.h>
|
|
|
|
#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;
|
|
|
|
|
|
/* ----- 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);
|
|
}
|
|
|
|
|
|
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 = (buf[i >> 1] >> 4*(~i & 1)) & 0xf;
|
|
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 = (buf[i >> 1] >> 4*(~i & 1)) & 0xf;
|
|
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 (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;
|
|
}
|
|
}
|
|
}
|