#ifndef LEANSDR_GUI_H #define LEANSDR_GUI_H #include #include "framework.h" namespace leansdr { ////////////////////////////////////////////////////////////////////// // GUI blocks ////////////////////////////////////////////////////////////////////// #ifdef GUI #include #include #include static const int DEFAULT_GUI_DECIMATION = 64; struct gfx { Display *display; int screen; int w, h; Window window; GC gc; Pixmap dbuf; gfx(scheduler *sch, const char *name) { window_placement *wp; for ( wp=sch->windows; wp && wp->name; ++wp ) if ( ! strcmp(wp->name, name) ) break; if ( wp && wp->name ) init(wp->name, wp->x, wp->y, wp->w, wp->h); else { fprintf(stderr, "No placement hints for window '%s'\n", name); init(name, -1, -1, 320, 240); } } gfx(const char *name, int _x, int _y, int _w, int _h) { init(name, _x, _y, _w, _h); } void init(const char *name, int _x, int _y, int _w, int _h) { buttons = 0; clicks = 0; mmoved = false; w = _w; h = _h; display = XOpenDisplay(getenv("DISPLAY")); if ( ! display ) fatal("display"); screen = DefaultScreen(display); XSetWindowAttributes xswa; xswa.event_mask = (ExposureMask| StructureNotifyMask| ButtonPressMask| ButtonReleaseMask| KeyPressMask| KeyReleaseMask| PointerMotionMask); xswa.background_pixel = BlackPixel(display, screen); window = XCreateWindow(display, DefaultRootWindow(display), 100,100, w,h, 10, CopyFromParent,InputOutput, CopyFromParent, CWEventMask|CWBackPixel, &xswa); if ( !window ) fatal("window"); XStoreName(display, window, name); XMapWindow(display, window); if ( _x>=0 && _y>=0 ) XMoveWindow(display, window, _x, _y); dbuf = XCreatePixmap(display, window, w, h, DefaultDepth(display,screen)); gc = XCreateGC(display, dbuf, 0, NULL); if ( ! gc ) fatal("gc"); } void clear() { setfg(0, 0, 0); XFillRectangle(display, dbuf, gc, 0, 0, w, h); } void show() { XCopyArea(display, dbuf, window, gc, 0, 0, w, h, 0, 0); } void sync() { XSync(display, False); } void events() { XEvent ev; while ( XCheckWindowEvent(display, window, -1, &ev) ) { switch ( ev.type ) { case ButtonPress: { int b = ev.xbutton.button; buttons |= 1< struct cscope : runnable { T xymin, xymax; unsigned long decimation; unsigned long pixels_per_frame; cstln_lut<256> **cstln; // Optional ptr to optional constellation cscope(scheduler *sch, pipebuf< complex > &_in, T _xymin, T _xymax, const char *_name=NULL) : runnable(sch, _name?_name:_in.name), xymin(_xymin), xymax(_xymax), decimation(DEFAULT_GUI_DECIMATION), pixels_per_frame(1024), cstln(NULL), in(_in), phase(0), g(sch, name) { } void run() { while ( in.readable() >= pixels_per_frame ) { if ( ! phase ) { draw_begin(); g.setfg(0, 255, 0); complex *p = in.rd(), *pend = p+pixels_per_frame; for ( ; pre-xymin)/(xymax-xymin), g.h - g.h*(p->im-xymin)/(xymax-xymin)); if ( cstln && (*cstln) ) { // Plot constellation points g.setfg(255, 255, 255); for ( int i=0; i<(*cstln)->nsymbols; ++i ) { complex *p = &(*cstln)->symbols[i]; int x = g.w*(p->re-xymin)/(xymax-xymin); int y = g.h - g.h*(p->im-xymin)/(xymax-xymin); for ( int d=-2; d<=2; ++d ) { g.point(x+d, y); g.point(x, y+d); } } } g.show(); g.sync(); } in.read(pixels_per_frame); if ( ++phase >= decimation ) phase = 0; } } //private: pipereader< complex > in; unsigned long phase; gfx g; void draw_begin() { g.clear(); g.setfg(0, 255, 0); g.line(g.w/2,0, g.w/2, g.h); g.line(0,g.h/2, g.w,g.h/2); } }; template struct wavescope : runnable { T ymin, ymax; unsigned long decimation; wavescope(scheduler *sch, pipebuf &_in, T _ymin, T _ymax, const char *_name=NULL) : runnable(sch, _name?_name:_in.name), in(_in), ymin(_ymin), ymax(_ymax), decimation(DEFAULT_GUI_DECIMATION), g(sch, name), phase(0), x(0) { g.clear(); } void run() { while ( in.readable() >= g.w ) { if ( ! phase ) plot(in.rd(), g.w); in.read(g.w); if ( ++phase >= decimation ) phase = 0; } } void plot(T *p, int count) { T *pend = p + count; g.clear(); g.setfg(0, 255, 0); for ( int x=0; p in; int phase; gfx g; int x; }; template struct slowmultiscope : runnable { struct chanspec { pipebuf *in; const char *name, *format; unsigned char rgb[3]; float scale; float ymin, ymax; enum flag { DEFAULT = 0, ASYNC = 1, // Read whatever is available COUNT = 2, // Display number of items read instead of value SUM = 4, // Display sum of values LINE = 8, // Connect points WRAP = 16, // Modulo min..max DISABLED = 32, // Ignore channel } flags; }; unsigned long samples_per_pixel; float sample_freq; // Sample rate in Hz (used for cursor operations) slowmultiscope(scheduler *sch, const chanspec *specs, int nspecs, const char *_name) : runnable(sch, _name?_name:"slowmultiscope"), samples_per_pixel(1), sample_freq(1), g(sch, name), t(0), x(0), total_samples(0) { chans = new channel[nspecs]; nchans = 0; for ( int i=0; i(*specs[i].in); chans[nchans].accum = 0; ++nchans; } g.clear(); } void run() { // Read up to one pixel worth of data unsigned long count = samples_per_pixel; for ( channel *c=chans; cspec.flags&chanspec::ASYNC) ) count = min(count, c->in->readable()); for ( int n=count; n--; ) { for ( channel *c=chans; cspec.flags & chanspec::ASYNC ) // For async channels, read any and all available data. nr = c->in->readable(); else nr = 1; g.setfg(c->spec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]); int y = -1; while ( nr-- ) { float v = *c->in->rd() * c->spec.scale; if ( c->spec.flags & chanspec::COUNT ) ++c->accum; else if ( c->spec.flags & chanspec::SUM ) c->accum += v; else { c->print_val = v; float nv = (v-c->spec.ymin) / (c->spec.ymax-c->spec.ymin); if ( c->spec.flags & chanspec::WRAP ) nv = nv - floor(nv); y = g.h - g.h*nv; } c->in->read(1); } // Display count/sum channels only when the cursor is about to move. if ( (c->spec.flags&(chanspec::COUNT|chanspec::SUM)) && t+1 >= samples_per_pixel ) { T v = c->accum; y = g.h-1 - g.h*(v-c->spec.ymin)/(c->spec.ymax-c->spec.ymin); c->accum = 0; c->print_val = v; } if ( y >= 0 ) { if ( c->spec.flags & chanspec::LINE ) { if ( x ) g.line(x-1, c->prev_y, x, y); c->prev_y = y; } else g.point(x, y); } } g.show(); // Print instantatenous values as text for ( int i=0; ispec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]); char text[256]; sprintf(text, c->spec.format, c->print_val); g.transient_text(5, 20+16*i, text); } run_gui(); if ( ++t >= samples_per_pixel ) { t = 0; ++x; if ( x >= g.w ) x = 0; g.setfg(0, 0, 0); g.line(x, 0, x, g.h-1); } run_gui(); g.sync(); } total_samples += count; } void run_gui() { g.events(); // Print cursor time float ct = g.mx * samples_per_pixel / sample_freq; float tt = total_samples / sample_freq; char text[256]; sprintf(text, "%.3f / %.3f s", ct, tt); g.setfg(255, 255, 255); g.transient_text(g.w*3/4, 20, text); } private: int nchans; struct channel { chanspec spec; pipereader *in; float accum; int prev_y; float print_val; } *chans; gfx g; unsigned long t; int x; int total_samples; }; template struct spectrumscope : runnable { T ymax; float amax; unsigned long size; unsigned long decimation; spectrumscope(scheduler *sch, pipebuf< complex > & _in, T _max, const char *_name=NULL) : runnable(sch, _name?_name:_in.name), ymax(_max), amax(_max), size(4096), decimation(DEFAULT_GUI_DECIMATION), in(_in), phase(0), g(sch, name), fft(NULL) { } void run() { while ( in.readable() >= size ) { if ( ! phase ) do_fft(in.rd()); in.read(size); if ( ++phase >= decimation ) phase = 0; } } private: pipereader< complex > in; int phase; gfx g; cfft_engine *fft; void do_fft(complex *input) { draw_begin(); if ( !fft || fft->n!=size ) { if ( fft ) delete fft; fft = new cfft_engine(size); } complex *pin=input, *pend=pin+size; complex data[size], *pout=data; g.setfg(255, 0, 0); for ( int x=0; pinre = (float)pin->re; pout->im = (float)pin->im; // g.point(x, g.h/2-pout->re*g.h/2/ymax); } fft->inplace(data, true); g.setfg(0, 255, 0); for ( int i=0; i v = data[i];; float y = hypot(v.re, v.im); g.line(x, g.h-1, x, g.h-1-y*g.h/amax); } if ( g.buttons ) { char s[256]; float f = 2.4e6 * (g.mx-g.w/2) / g.w; sprintf(s, "%f", f); g.text(16, 16, s); } g.show(); g.sync(); } void draw_begin() { g.clear(); g.setfg(255, 255, 255); g.line(g.w/2,0, g.w/2,g.h); } }; template struct rfscope : runnable { unsigned long size; unsigned long decimation; float Fs; // Sampling freq for display (Hz) float Fc; // Center freq for display (Hz) int ncursors; float *cursors; // Cursor frequencies float hzoom; // Horizontal zoom factor float db0, dbrange; // Vertical range db0 .. db0+dbrange float bw; // Smoothing bandwidth rfscope(scheduler *sch, pipebuf< complex > & _in, const char *_name=NULL) : runnable(sch, _name?_name:_in.name), size(4096), decimation(DEFAULT_GUI_DECIMATION), Fs(1), Fc(0), ncursors(0), hzoom(1), db0(-25), dbrange(50), bw(0.05), in(_in), phase(0), g(sch, name), fft(NULL), filtered(NULL) { } void run() { while ( in.readable() >= size ) { if ( ! phase ) do_fft(in.rd()); in.read(size); if ( ++phase >= decimation ) phase = 0; } } private: pipereader< complex > in; int phase; gfx g; cfft_engine *fft; float *filtered; void do_fft(complex *input) { g.events(); draw_begin(); if ( !fft || fft->n!=size ) { if ( fft ) delete fft; fft = new cfft_engine(size); } // Convert to complex and transform complex *pin=input, *pend=pin+size; complex data[size], *pout=data; for ( int x=0; pinre = (float)pin->re; pout->im = (float)pin->im; } fft->inplace(data, true); float amp2[size]; for ( int i=0; i &v = data[i];; amp2[i] = (v.re*v.re + v.im*v.im)*size; } if ( ! filtered ) { filtered = new float[size]; for ( int i=0; i struct genscope : runnable { struct render { int x, y; char dir; // 'h'orizontal or 'v'ertical }; struct chanspec { pipebuf *in; // NULL if disabled render r; }; genscope(scheduler *sch, chanspec *specs, int _nchans, const char *_name=NULL) : runnable(sch, _name?_name:"genscope"), nchans(_nchans), g(sch, name) { chans = new channel[nchans]; for ( int i=0; i(*specs[i].in); } } g.clear(); gettimeofday(&tv, NULL); } struct channel { chanspec spec; pipereader *in; } *chans; int nchans; struct timeval tv; void run() { g.setfg(0, 255, 0); for ( channel *pc=chans; pcin ) continue; int n = pc->in->readable(); T last = pc->in->rd()[n-1]; pc->in->read(n); int dx = pc->spec.r.dir=='h' ? last : 0; int dy = pc->spec.r.dir=='v' ? last : 0; dx /= 3; dy /= 3; g.line(pc->spec.r.x-dx, pc->spec.r.y-dy, pc->spec.r.x+dx, pc->spec.r.y+dy); char txt[16]; sprintf(txt, "%d", (int)last); g.text(pc->spec.r.x+5, pc->spec.r.y-2, txt); } struct timeval newtv; gettimeofday(&newtv, NULL); int dt = (newtv.tv_sec-tv.tv_sec)*1000 + (newtv.tv_usec-tv.tv_usec)/1000; if ( dt > 100 ) { fprintf(stderr, "#"); g.show(); g.sync(); g.clear(); tv = newtv; } } private: gfx g; }; #endif // GUI } // namespace #endif // LEANSDR_GUI_H