linmodem/display.c

558 lines
13 KiB
C

/*
* X11 interface for linmodem
*
* Copyright (c) 2000 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*/
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>
#include <X11/keysym.h>
#include "lm.h"
#define NB_MODES 5
enum {
DISP_MODE_SAMPLE, /* display the brut samples */
DISP_MODE_ECHOCANCEL, /* display the echo cancellor info */
DISP_MODE_SAMPLESYNC, /* display the samples after freq normalize
& symbol recovery */
DISP_MODE_EQUALIZER, /* display the equalizer filter */
DISP_MODE_QAM, /* display the qam */
} disp_state;
char *mode_str[NB_MODES] = {
"Sample",
"Echo Cancel",
"Sample Sync",
"Equalizer",
"QAM",
};
#define QAM_SIZE 460
static void set_state(int state);
/**************************/
#define RGB(r, g, b) ((((r) >> 3) << 11) | (((g) >> 2) << 5) | ((b) >> 3))
Display *display;
Window window;
GC gc;
XFontStruct *xfont;
unsigned int fg, bg;
int minx, miny;
int font_xsize, font_ysize;
int lm_display_init(void)
{
XSizeHints hint;
int screen;
XVisualInfo vinfo;
int size_x = 640;
int size_y = 480;
XSetWindowAttributes xwa;
char *fontname = "fixed";
display = XOpenDisplay("");
if (display == NULL) {
return -1;
}
screen = DefaultScreen(display);
bg = RGB(0, 0, 0);
fg = RGB(255, 255, 255);
/* Fill in hint structure */
hint.x = 0;
hint.y = 0;
hint.width = size_x;
hint.height = size_y;
hint.flags = PPosition | PSize;
/* Make the window */
if (!XMatchVisualInfo(display, screen, 24, TrueColor, &vinfo)) {
printf("A 16 bit visual is need by this program\n");
return -1;
}
window = XCreateSimpleWindow(display,
DefaultRootWindow(display),
hint.x, hint.y,
hint.width, hint.height,
4, fg, bg);
/* Enable backing store */
xwa.backing_store = Always;
XChangeWindowAttributes(display, window, CWBackingStore, &xwa);
XSelectInput(display, window, StructureNotifyMask);
/* Tell other applications about this window */
XSetStandardProperties(display, window,
"linmodem", "linmodem",
None, NULL, 0, &hint);
/* Map window. */
XMapWindow(display, window);
/* Wait for map. */
while (1) {
XEvent xev;
XNextEvent(display, &xev);
if (xev.type == MapNotify && xev.xmap.event == window)
break;
}
XSelectInput(display, window, KeyPressMask
| ButtonPressMask);
gc = XCreateGC(display, window, 0, 0);
xfont = XLoadQueryFont (display, fontname);
if (!xfont) {
fprintf(stderr, "Could not load font '%s'\n", fontname);
return -1;
}
font_xsize = xfont->max_bounds.rbearing - xfont->min_bounds.lbearing;
font_ysize = xfont->max_bounds.ascent + xfont->max_bounds.descent;
set_state(DISP_MODE_SAMPLE);
return 0;
}
void lm_display_close(void)
{
XCloseDisplay(display);
}
void printf_at(int x, int y, char *fmt, ...)
{
va_list ap;
char buf[1024];
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
XSetFont(display, gc, xfont->fid);
XSetForeground(display, gc, bg);
XFillRectangle(display, window, gc,
x * font_xsize, y * font_ysize,
font_xsize * strlen(buf), font_ysize);
XSetForeground(display, gc, fg);
XDrawString(display, window, gc, x * font_xsize, (y + 1) * font_ysize - 1,
buf, strlen(buf));
va_end(ap);
}
/* draw a graph at (x1, y1, x1 + w, y1 + h). The points are computed
by calc_func */
#define DG_AUTOSCALE_YMIN 0x0001
#define DG_AUTOSCALE_YMAX 0x0002
void draw_graph(char *title,
int x1, int y1, int w, int h,
float xmin, float xmax, float ymin, float ymax,
int flags, float (*calc_func)(float))
{
int i, yy, xx;
float x, y;
int ly;
float tab[1024];
/* auto scale */
for(i=0;i<w;i++) {
x = ((float)i / (float)(w-1)) * (xmax - xmin) + xmin;
y = calc_func(x);
tab[i] = y;
if (i == 0) {
if (flags & DG_AUTOSCALE_YMIN)
ymin = y;
if (flags & DG_AUTOSCALE_YMAX)
ymax = y;
} else {
if (flags & DG_AUTOSCALE_YMIN && y < ymin)
ymin = y;
if (flags & DG_AUTOSCALE_YMAX && y > ymax)
ymax = y;
}
}
/* draw ! */
XSetForeground(display, gc, bg);
XFillRectangle(display, window, gc, x1, y1, w, h);
XSetForeground(display, gc, RGB(255, 0, 0));
XDrawRectangle(display, window, gc, x1, y1, w, h);
/* axis */
XSetForeground(display, gc, RGB(0, 255, 0));
if (xmin <= 0.0 && 0.0 <= xmax) {
xx = (int)rint(((0.0 - xmin) / (xmax - xmin)) * (w-1)) + x1;
XDrawLine(display, window, gc,
xx, y1, xx, y1 + h - 1);
}
if (ymin <= 0.0 && 0.0 <= ymax) {
yy = y1 + h - 1 - (int)rint(((0.0 - ymin) / (ymax - ymin)) * (h-1));
XDrawLine(display, window, gc,
x1, yy, x1 + w - 1, yy);
}
XSetForeground(display, gc, RGB(255, 255, 255));
ly = -1;
for(i=0;i<w;i++) {
y = tab[i];
if (y >= ymin && y <= ymax) {
yy = y1 + h - 1 - (int)rint(((y - ymin) / (ymax - ymin)) * (h-1));
if (ly >= 0) {
XDrawLine(display, window, gc,
i - 1, ly, i, yy);
} else {
XDrawPoint(display, window, gc,
i, yy);
}
ly = yy;
} else {
ly = -1;
}
}
/* title */
XSetForeground(display, gc, RGB(0, 255, 0));
printf_at((x1+font_xsize-1) / font_xsize,
(y1+font_ysize-1) / font_ysize,
"%s - ymin=%6.3e ymax=%6.3e", title, ymin, ymax);
}
/***************************************************/
/* utilities */
/***************************************************/
/* si and sq must be betwen -1.0 and 1.0 */
int nb_samples = 0;
void lm_dump_qam(float si, float sq)
{
int x, y;
if (disp_state != DISP_MODE_QAM)
return;
x = (int)(si * (QAM_SIZE/2)) + (QAM_SIZE/2);
y = (int)(sq * (QAM_SIZE/2)) + (QAM_SIZE/2);
if (x < 0 || x >= QAM_SIZE ||
y < 0 || y >= QAM_SIZE)
return;
XSetForeground(display, gc, RGB(255, 255, 255));
XDrawPoint(display, window, gc, x, y);
nb_samples++;
printf_at(minx, 1, "# samples: %d", nb_samples);
}
/* print samples */
#define NB_SAMPLES 512
float sample_mem[NB_CHANNELS][NB_SAMPLES];
int sample_pos[NB_CHANNELS];
float sample_hamming[NB_SAMPLES];
int sample_hamming_init = 0;
complex sample_fft[NB_SAMPLES];
int sample_channel;
float calc_sample(float x)
{
return sample_mem[sample_channel][(int)x];
}
float calc_sample_pow(float x)
{
complex *p = &sample_fft[(int)x];
return p->re * p->re + p->im * p->im;
}
void draw_samples(int channel)
{
int i;
sample_channel = channel;
draw_graph("Sample",
0, 0, QAM_SIZE, QAM_SIZE/2,
0.0, NB_SAMPLES - 1, 0.0, 0.0,
DG_AUTOSCALE_YMAX | DG_AUTOSCALE_YMIN,
calc_sample);
if (!sample_hamming_init) {
calc_hamming(sample_hamming, NB_SAMPLES);
sample_hamming_init = 1;
}
for(i=0;i<NB_SAMPLES;i++) {
sample_fft[i].re = sample_mem[channel][i] * sample_hamming[i];
sample_fft[i].im = 0;
}
fft_calc(sample_fft, NB_SAMPLES, 0);
draw_graph("Spectral power",
0, QAM_SIZE/2, QAM_SIZE, QAM_SIZE/2,
0.0, NB_SAMPLES/2 - 1, 0.0, 0.0,
DG_AUTOSCALE_YMAX, calc_sample_pow);
}
void lm_dump_sample(int channel, float val)
{
sample_mem[channel][sample_pos[channel]] = val;
if (++sample_pos[channel] == NB_SAMPLES) {
sample_pos[channel] = 0;
if ((disp_state == DISP_MODE_SAMPLE &&
channel == CHANNEL_SAMPLE) ||
(disp_state == DISP_MODE_SAMPLESYNC &&
channel == CHANNEL_SAMPLESYNC)) {
draw_samples(channel);
}
}
}
#if 0
int last_eye_y[2];
int last_eye_x[2];
/* print an eye diagram (each symbol is sampled at integer time
values). We print 1 centered period */
void lm_dump_eye(int channel, float time, float val)
{
int x, y;
if (disp_state != DISP_MODE_EYE)
return;
if (val < -1)
val = -1;
else if (val > 1)
val = 1;
y = (int)(val * (QAM_SIZE/2)) + (QAM_SIZE/2);
time += 0.5;
if (time >= 1.0)
time -= 1.0;
x = (int)(time * QAM_SIZE);
XSetForeground(display, gc, RGB(255, 255, 255));
if (x > last_eye_x[channel] && 0) {
XDrawLine(display, window, gc,
last_eye_x[channel], last_eye_y[channel], x, y);
} else {
XDrawPoint(display, window, gc, x, y);
}
last_eye_x[channel] = x;
last_eye_y[channel] = y;
}
#endif
/* print equalizer */
#define EQ_FFT_SIZE 144
static int eq_count = 0;
static s32 (*eq_filter)[2];
static int eq_norm;
static complex eq_fft[EQ_FFT_SIZE];
float calc_eq_re(float x)
{
return (float)eq_filter[(int)rint(x)][0] / (float)eq_norm;
}
float calc_eq_im(float x)
{
return (float)eq_filter[(int)rint(x)][1] / (float)eq_norm;
}
float calc_eq_pow(float x)
{
complex *p = &eq_fft[(int)x];
return p->re * p->re + p->im * p->im;
}
float calc_eq_phase(float x)
{
complex *p = &eq_fft[(int)x];
return atan2(p->im, p->re);
}
void lm_dump_equalizer(s32 eq_filter1[][2], int norm, int size)
{
int i;
if (disp_state != DISP_MODE_EQUALIZER)
return;
if (++eq_count == 1) {
eq_count = 0;
eq_filter = eq_filter1;
eq_norm = norm;
draw_graph("Eqz real",
0, 0, QAM_SIZE, QAM_SIZE/4,
0.0, size - 1, -1.5, 1.5,
0,
calc_eq_re);
draw_graph("Eqz imag",
0, QAM_SIZE/4, QAM_SIZE, QAM_SIZE/4,
0.0, size - 1, -1.5, 1.5,
0,
calc_eq_im);
for(i=0;i<EQ_FFT_SIZE;i++) {
if (i < size) {
eq_fft[i].re = eq_filter[i][0] / (float)norm;
eq_fft[i].im = eq_filter[i][1] / (float)norm;
} else {
eq_fft[i].re = eq_fft[i].im = 0;
}
}
if (EQ_FFT_SIZE == 144) {
complex eq_fft1[144];
slow_fft(eq_fft1, eq_fft, EQ_FFT_SIZE, 0);
for(i=0;i<EQ_FFT_SIZE;i++)
eq_fft[i] = eq_fft1[i];
} else {
fft_calc(eq_fft, EQ_FFT_SIZE, 0);
}
draw_graph("Eqz spec pow",
0, 2*QAM_SIZE/4, QAM_SIZE, QAM_SIZE/4,
0.0, EQ_FFT_SIZE - 1, 0.0, 0.0,
DG_AUTOSCALE_YMAX, calc_eq_pow);
draw_graph("Eqz spec phase",
0, 3*QAM_SIZE/4, QAM_SIZE, QAM_SIZE/4,
0.0, EQ_FFT_SIZE - 1, -M_PI, M_PI,
0, calc_eq_phase);
}
}
/* agc */
void lm_dump_agc(float gain)
{
printf_at(minx, 2, "AGC: %10.5f", gain);
}
void lm_dump_linesim_power(float tx_db, float rx_db, float noise_db)
{
printf_at(minx, 3, "TX: %6.2f dB SNR: %6.2f dB", tx_db, rx_db - noise_db);
printf_at(minx, 4, "RX: %6.2f dB N0: %6.2f dB", rx_db, noise_db);
}
static void set_state(int state)
{
int i;
minx = ((QAM_SIZE + font_xsize) / font_xsize) + 1;
miny = ((QAM_SIZE + font_ysize) / font_ysize);
disp_state = state;
XSetForeground(display, gc, bg);
XFillRectangle(display, window, gc, 0, 0, 640, 480);
XSetForeground(display, gc, RGB(255, 0, 0));
XDrawRectangle(display, window, gc, 0, 0, QAM_SIZE, QAM_SIZE);
switch(disp_state) {
case DISP_MODE_QAM:
XDrawLine(display, window, gc, 0, QAM_SIZE/2, QAM_SIZE-1, QAM_SIZE/2);
XDrawLine(display, window, gc, QAM_SIZE/2, 0, QAM_SIZE/2, QAM_SIZE-1);
break;
default:
break;
}
printf_at(minx, 0, "Mode: %s", mode_str[disp_state]);
for(i=0;i<NB_MODES;i++) {
printf_at(1 + 15 * i, miny, "F%d:%s", i + 1, mode_str[i]);
}
}
int lm_display_poll_event(void)
{
char buf[80];
XEvent xev;
KeySym keysym;
XComposeStatus status;
if (XPending(display) <= 0)
return 0;
XNextEvent(display, &xev);
switch(xev.type) {
case KeyPress:
XLookupString((XKeyEvent *) & xev, buf, 80, &keysym, &status);
switch(keysym) {
case XK_q:
return 1;
case XK_F1:
case XK_F2:
case XK_F3:
case XK_F4:
case XK_F5:
case XK_F6:
case XK_F7:
case XK_F8:
{
int mode;
mode = keysym - XK_F1;
if (mode < NB_MODES) {
set_state(mode);
}
}
break;
}
break;
}
return 0;
}