SDRAngel  4.11.5
Developer docs for <a href="https://github.com/f4exb/sdrangel">SDRangel<\a>, an Open Source Qt5 / OpenGL 3.0+ SDR and signal analyzer frontend to various hardware.
gui.h
Go to the documentation of this file.
1 // This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
2 // See the toplevel README for more information.
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 
17 #ifndef LEANSDR_GUI_H
18 #define LEANSDR_GUI_H
19 
20 #include <sys/time.h>
21 
22 #include "framework.h"
23 
24 namespace leansdr
25 {
26 
28 // GUI blocks
30 
31 #ifdef GUI
32 
33 #include <X11/X.h>
34 #include <X11/Xlib.h>
35 #include <X11/Xutil.h>
36 
37 static const int DEFAULT_GUI_DECIMATION = 64;
38 
39 struct gfx
40 {
41  Display *display;
42  int screen;
43  int w, h;
44  Window window;
45  GC gc;
46  Pixmap dbuf;
47  gfx(scheduler *sch, const char *name)
48  {
49  window_placement *wp;
50  for (wp = sch->windows; wp && wp->name; ++wp)
51  if (!strcmp(wp->name, name))
52  break;
53  if (wp && wp->name)
54  init(wp->name, wp->x, wp->y, wp->w, wp->h);
55  else
56  {
57  fprintf(stderr, "No placement hints for window '%s'\n", name);
58  init(name, -1, -1, 320, 240);
59  }
60  }
61  gfx(const char *name, int _x, int _y, int _w, int _h)
62  {
63  init(name, _x, _y, _w, _h);
64  }
65  void init(const char *name, int _x, int _y, int _w, int _h)
66  {
67  buttons = 0;
68  clicks = 0;
69  mmoved = false;
70  w = _w;
71  h = _h;
72  display = XOpenDisplay(getenv("DISPLAY"));
73  if (!display)
74  fatal("display");
75  screen = DefaultScreen(display);
76  XSetWindowAttributes xswa;
77  xswa.event_mask = (ExposureMask |
78  StructureNotifyMask |
79  ButtonPressMask |
80  ButtonReleaseMask |
81  KeyPressMask |
82  KeyReleaseMask |
83  PointerMotionMask);
84  xswa.background_pixel = BlackPixel(display, screen);
85  window = XCreateWindow(display, DefaultRootWindow(display),
86  100, 100, w, h, 10, CopyFromParent, InputOutput,
87  CopyFromParent, CWEventMask | CWBackPixel,
88  &xswa);
89  if (!window)
90  fatal("window");
91  XStoreName(display, window, name);
92  XMapWindow(display, window);
93  if (_x >= 0 && _y >= 0)
94  XMoveWindow(display, window, _x, _y);
95  dbuf = XCreatePixmap(display, window, w, h, DefaultDepth(display, screen));
96  gc = XCreateGC(display, dbuf, 0, NULL);
97  if (!gc)
98  fatal("gc");
99  }
100  void clear()
101  {
102  setfg(0, 0, 0);
103  XFillRectangle(display, dbuf, gc, 0, 0, w, h);
104  }
105  void show()
106  {
107  XCopyArea(display, dbuf, window, gc, 0, 0, w, h, 0, 0);
108  }
109  void sync()
110  {
111  XSync(display, False);
112  }
113  void events()
114  {
115  XEvent ev;
116  while (XCheckWindowEvent(display, window, -1, &ev))
117  {
118  switch (ev.type)
119  {
120  case ButtonPress:
121  {
122  int b = ev.xbutton.button;
123  buttons |= 1 << b;
124  clicks |= 1 << b;
125  mx = ev.xbutton.x;
126  my = ev.xbutton.y;
127  break;
128  }
129  case ButtonRelease:
130  {
131  int b = ev.xbutton.button;
132  buttons &= ~(1 << b);
133  mx = ev.xbutton.x;
134  my = ev.xbutton.y;
135  break;
136  }
137  case MotionNotify:
138  mx = ev.xbutton.x;
139  my = ev.xbutton.y;
140  mmoved = true;
141  break;
142  }
143  }
144  }
145  void setfg(unsigned char r, unsigned char g, unsigned char b)
146  {
147  XColor c;
148  c.red = r << 8;
149  c.green = g << 8;
150  c.blue = b << 8;
151  c.flags = DoRed | DoGreen | DoBlue;
152  if (!XAllocColor(display, DefaultColormap(display, screen), &c))
153  fatal("color");
154  XSetForeground(display, gc, c.pixel);
155  }
156  void point(int x, int y)
157  {
158  XDrawPoint(display, dbuf, gc, x, y);
159  }
160  void line(int x0, int y0, int x1, int y1)
161  {
162  XDrawLine(display, dbuf, gc, x0, y0, x1, y1);
163  }
164  void text(int x, int y, const char *s)
165  {
166  XDrawString(display, dbuf, gc, x, y, s, strlen(s));
167  }
168  void transient_text(int x, int y, const char *s)
169  {
170  XDrawString(display, window, gc, x, y, s, strlen(s));
171  }
172  int buttons; // Mask of button states (2|4|8)
173  int clicks; // Same, accumulated (must be cleared by owner)
174  int mx, my; // Cursor position
175  bool mmoved; // Pointer moved (must be cleared by owner)
176 };
177 
178 template <typename T>
179 struct cscope : runnable
180 {
181  T xymin, xymax;
182  unsigned long decimation;
183  unsigned long pixels_per_frame;
184  cstln_base **cstln; // Optional ptr to optional constellation
185  cscope(scheduler *sch, pipebuf<complex<T>> &_in, T _xymin, T _xymax,
186  const char *_name = NULL)
187  : runnable(sch, _name ? _name : _in.name),
188  xymin(_xymin), xymax(_xymax),
189  decimation(DEFAULT_GUI_DECIMATION), pixels_per_frame(1024),
190  cstln(NULL),
191  in(_in), phase(0), g(sch, name)
192  {
193  }
194  void run()
195  {
196  while (in.readable() >= pixels_per_frame)
197  {
198  if (!phase)
199  {
200  draw_begin();
201  g.setfg(0, 255, 0);
202  complex<T> *p = in.rd(), *pend = p + pixels_per_frame;
203  for (; p < pend; ++p)
204  g.point(g.w * (p->re - xymin) / (xymax - xymin),
205  g.h - g.h * (p->im - xymin) / (xymax - xymin));
206  if (cstln && (*cstln))
207  {
208  // Plot constellation points
209  g.setfg(255, 255, 255);
210  for (int i = 0; i < (*cstln)->nsymbols; ++i)
211  {
212  complex<signed char> *p = &(*cstln)->symbols[i];
213  int x = g.w * (p->re - xymin) / (xymax - xymin);
214  int y = g.h - g.h * (p->im - xymin) / (xymax - xymin);
215  for (int d = -2; d <= 2; ++d)
216  {
217  g.point(x + d, y);
218  g.point(x, y + d);
219  }
220  }
221  }
222  g.show();
223  g.sync();
224  }
225  in.read(pixels_per_frame);
226  if (++phase >= decimation)
227  phase = 0;
228  }
229  }
230  //private:
231  pipereader<complex<T>> in;
232  unsigned long phase;
233  gfx g;
234  void draw_begin()
235  {
236  g.clear();
237  g.setfg(0, 255, 0);
238  g.line(g.w / 2, 0, g.w / 2, g.h);
239  g.line(0, g.h / 2, g.w, g.h / 2);
240  }
241 };
242 
243 template <typename T>
244 struct wavescope : runnable
245 {
246  T ymin, ymax;
247  unsigned long decimation;
248  float hgrid;
249  wavescope(scheduler *sch, pipebuf<T> &_in,
250  T _ymin, T _ymax, const char *_name = NULL)
251  : runnable(sch, _name ? _name : _in.name),
252  ymin(_ymin), ymax(_ymax),
253  decimation(DEFAULT_GUI_DECIMATION), hgrid(0),
254  in(_in),
255  phase(0), g(sch, name), x(0)
256  {
257  g.clear();
258  }
259  void run()
260  {
261  while (in.readable() >= g.w)
262  {
263  if (!phase)
264  plot(in.rd(), g.w);
265  in.read(g.w);
266  if (++phase >= decimation)
267  phase = 0;
268  }
269  }
270  void plot(T *p, int count)
271  {
272  T *pend = p + count;
273  g.clear();
274  g.setfg(128, 128, 0);
275  g.line(0, g.h / 2, g.w - 1, g.h / 2);
276  if (hgrid)
277  {
278  for (float x = 0; x < g.w; x += hgrid)
279  g.line(x, 0, x, g.h - 1);
280  }
281  g.setfg(0, 255, 0);
282  for (int x = 0; p < pend; ++x, ++p)
283  {
284  T v = *p;
285  g.point(x, g.h - 1 - (g.h - 1) * (v - ymin) / (ymax - ymin));
286  }
287  g.show();
288  g.sync();
289  }
290 
291  private:
292  pipereader<T> in;
293  int phase;
294  gfx g;
295  int x;
296 };
297 
298 template <typename T>
299 struct slowmultiscope : runnable
300 {
301  struct chanspec
302  {
303  pipebuf<T> *in;
304  const char *name, *format;
305  unsigned char rgb[3];
306  float scale;
307  float ymin, ymax;
308  enum flag
309  {
310  DEFAULT = 0,
311  ASYNC = 1, // Read whatever is available
312  COUNT = 2, // Display number of items read instead of value
313  SUM = 4, // Display sum of values
314  LINE = 8, // Connect points
315  WRAP = 16, // Modulo min..max
316  DISABLED = 32, // Ignore channel
317  } flags;
318  };
319  unsigned long samples_per_pixel;
320  float sample_freq; // Sample rate in Hz (used for cursor operations)
321  slowmultiscope(scheduler *sch, const chanspec *specs, int nspecs,
322  const char *_name)
323  : runnable(sch, _name ? _name : "slowmultiscope"),
324  samples_per_pixel(1), sample_freq(1),
325  g(sch, name), t(0), x(0), total_samples(0)
326  {
327  chans = new channel[nspecs];
328  nchans = 0;
329  for (int i = 0; i < nspecs; ++i)
330  {
331  if (specs[i].flags & chanspec::DISABLED)
332  continue;
333  chans[nchans].spec = specs[i];
334  chans[nchans].in = new pipereader<T>(*specs[i].in);
335  chans[nchans].accum = 0;
336  ++nchans;
337  }
338  g.clear();
339  }
340 
341  void run()
342  {
343  // Asynchronous channels: Read all available data
344  for (channel *c = chans; c < chans + nchans; ++c)
345  {
346  if (c->spec.flags & chanspec::ASYNC)
347  run_channel(c, c->in->readable());
348  }
349  // Synchronous channels: Read up to one pixel worth of data
350  // between display syncs.
351  unsigned long count = samples_per_pixel;
352  for (channel *c = chans; c < chans + nchans; ++c)
353  if (!(c->spec.flags & chanspec::ASYNC))
354  count = min(count, c->in->readable());
355  for (int n = count; n--;)
356  {
357  for (channel *c = chans; c < chans + nchans; ++c)
358  if (!(c->spec.flags & chanspec::ASYNC))
359  run_channel(c, 1);
360  }
361  t += count;
362  g.show();
363  // Print instantatenous values as text
364  for (int i = 0; i < nchans; ++i)
365  {
366  channel *c = &chans[i];
367  g.setfg(c->spec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]);
368  char text[256];
369  sprintf(text, c->spec.format, c->print_val);
370  g.transient_text(5, 20 + 16 * i, text);
371  }
372  run_gui();
373  if (t >= samples_per_pixel)
374  {
375  t = 0;
376  ++x;
377  if (x >= g.w)
378  x = 0;
379  g.setfg(0, 0, 0);
380  g.line(x, 0, x, g.h - 1);
381  }
382  run_gui();
383  g.sync();
384  total_samples += count;
385  }
386 
387  private:
388  int nchans;
389  struct channel
390  {
391  chanspec spec;
392  pipereader<T> *in;
393  float accum;
394  int prev_y;
395  float print_val;
396  } * chans;
397  void run_channel(channel *c, int nr)
398  {
399  g.setfg(c->spec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]);
400  int y = -1;
401  while (nr--)
402  {
403  float v = *c->in->rd() * c->spec.scale;
404  if (c->spec.flags & chanspec::COUNT)
405  ++c->accum;
406  else if (c->spec.flags & chanspec::SUM)
407  c->accum += v;
408  else
409  {
410  c->print_val = v;
411  float nv = (v - c->spec.ymin) / (c->spec.ymax - c->spec.ymin);
412  if (c->spec.flags & chanspec::WRAP)
413  nv = nv - floor(nv);
414  y = g.h - g.h * nv;
415  }
416  c->in->read(1);
417  }
418  // Display count/sum channels only when the cursor is about to move.
419  if ((c->spec.flags & (chanspec::COUNT | chanspec::SUM)) &&
420  t + 1 >= samples_per_pixel)
421  {
422  T v = c->accum;
423  y = g.h - 1 - g.h * (v - c->spec.ymin) / (c->spec.ymax - c->spec.ymin);
424  c->accum = 0;
425  c->print_val = v;
426  }
427  if (y >= 0)
428  {
429  if (c->spec.flags & chanspec::LINE)
430  {
431  if (x)
432  g.line(x - 1, c->prev_y, x, y);
433  c->prev_y = y;
434  }
435  else
436  g.point(x, y);
437  }
438  }
439  void run_gui()
440  {
441  g.events();
442  // Print cursor time
443  float ct = g.mx * samples_per_pixel / sample_freq;
444  float tt = total_samples / sample_freq;
445  char text[256];
446  sprintf(text, "%.3f / %.3f s", ct, tt);
447  g.setfg(255, 255, 255);
448  g.transient_text(g.w * 3 / 4, 20, text);
449  }
450  gfx g;
451  unsigned long t; // Time for synchronous channels
452  int x;
453  int total_samples;
454 };
455 
456 template <typename T>
457 struct spectrumscope : runnable
458 {
459  unsigned long size;
460  float amax;
461  unsigned long decimation;
462  spectrumscope(scheduler *sch, pipebuf<complex<T>> &_in,
463  T _max, const char *_name = NULL)
464  : runnable(sch, _name ? _name : _in.name),
465  size(4096), amax(_max / sqrtf(size)),
466  decimation(DEFAULT_GUI_DECIMATION),
467  in(_in),
468  nmarkers(0),
469  phase(0), g(sch, name), fft(NULL)
470  {
471  }
472  void mark_freq(float f)
473  {
474  if (nmarkers == MAX_MARKERS)
475  fail("Too many markers");
476  markers[nmarkers++] = f;
477  }
478  void run()
479  {
480  while (in.readable() >= size)
481  {
482  if (!phase)
483  do_fft(in.rd());
484  in.read(size);
485  if (++phase >= decimation)
486  phase = 0;
487  }
488  }
489 
490  private:
491  pipereader<complex<T>> in;
492  static const int MAX_MARKERS = 4;
493  float markers[MAX_MARKERS];
494  int nmarkers;
495  int phase;
496  gfx g;
497  cfft_engine<float> *fft;
498  void do_fft(complex<T> *input)
499  {
500  draw_begin();
501  if (!fft || fft->n != size)
502  {
503  if (fft)
504  delete fft;
505  fft = new cfft_engine<float>(size);
506  }
507  complex<T> *pin = input, *pend = pin + size;
508  complex<float> data[size], *pout = data;
509  g.setfg(255, 0, 0);
510  for (int x = 0; pin < pend; ++pin, ++pout, ++x)
511  {
512  pout->re = (float)pin->re;
513  pout->im = (float)pin->im;
514  // g.point(x, g.h/2-pout->re*g.h/2/ymax);
515  }
516  fft->inplace(data, true);
517  g.setfg(0, 255, 0);
518  for (int i = 0; i < size; ++i)
519  {
520  int x = ((i < size / 2) ? i + size / 2 : i - size / 2) * g.w / size;
521  complex<float> v = data[i];
522  ;
523  float y = hypot(v.re, v.im);
524  g.line(x, g.h - 1, x, g.h - 1 - y * g.h / amax);
525  }
526  if (g.buttons)
527  {
528  char s[256];
529  float f = 2.4e6 * (g.mx - g.w / 2) / g.w;
530  sprintf(s, "%f", f);
531  g.text(16, 16, s);
532  }
533  g.show();
534  g.sync();
535  }
536  void draw_begin()
537  {
538  g.clear();
539  g.setfg(255, 255, 255);
540  g.line(g.w / 2, 0, g.w / 2, g.h);
541  g.setfg(255, 0, 0);
542  for (int i = 0; i < nmarkers; ++i)
543  {
544  int x = g.w * (0.5 + markers[i]);
545  g.line(x, 0, x, g.h);
546  }
547  }
548 };
549 
550 template <typename T>
551 struct rfscope : runnable
552 {
553  unsigned long size;
554  unsigned long decimation;
555  float Fs; // Sampling freq for display (Hz)
556  float Fc; // Center freq for display (Hz)
557  int ncursors;
558  float *cursors; // Cursor frequencies
559  float hzoom; // Horizontal zoom factor
560  float db0, dbrange; // Vertical range db0 .. db0+dbrange
561  float bw; // Smoothing bandwidth
562  rfscope(scheduler *sch, pipebuf<complex<T>> &_in,
563  const char *_name = NULL)
564  : runnable(sch, _name ? _name : _in.name),
565  size(4096), decimation(DEFAULT_GUI_DECIMATION),
566  Fs(1), Fc(0), ncursors(0), hzoom(1),
567  db0(-25), dbrange(50), bw(0.05),
568  in(_in), phase(0), g(sch, name), fft(NULL), filtered(NULL)
569  {
570  }
571  void run()
572  {
573  while (in.readable() >= size)
574  {
575  if (!phase)
576  do_fft(in.rd());
577  in.read(size);
578  if (++phase >= decimation)
579  phase = 0;
580  }
581  }
582 
583  private:
584  pipereader<complex<T>> in;
585  int phase;
586  gfx g;
587  cfft_engine<float> *fft;
588  float *filtered;
589 
590  void do_fft(complex<T> *input)
591  {
592  g.events();
593  draw_begin();
594  if (!fft || fft->n != size)
595  {
596  if (fft)
597  delete fft;
598  fft = new cfft_engine<float>(size);
599  }
600  // Convert to complex<float> and transform
601  complex<T> *pin = input, *pend = pin + size;
602  complex<float> data[size], *pout = data;
603  for (int x = 0; pin < pend; ++pin, ++pout, ++x)
604  {
605  pout->re = (float)pin->re;
606  pout->im = (float)pin->im;
607  }
608  fft->inplace(data, true);
609  float amp2[size];
610  for (int i = 0; i < size; ++i)
611  {
612  complex<float> &v = data[i];
613  ;
614  amp2[i] = (v.re * v.re + v.im * v.im) * size;
615  }
616  if (!filtered)
617  {
618  filtered = new float[size];
619  for (int i = 0; i < size; ++i)
620  filtered[i] = amp2[i];
621  }
622  float bwcomp = 1 - bw;
623  g.setfg(0, 255, 0);
624  for (int i = 0; i < size; ++i)
625  {
626  filtered[i] = amp2[i] * bw + filtered[i] * bwcomp;
627  float db = filtered[i] ? 10 * logf(filtered[i]) / logf(10) : db0;
628  int is = (i < size / 2) ? i : i - size;
629  int x = g.w / 2 + is * hzoom * g.w / size;
630  int y = g.h - 1 - (db - db0) * g.h / dbrange;
631  g.line(x, g.h - 1, x, y);
632  }
633  if (g.buttons)
634  {
635  char s[256];
636  float freq = Fc + Fs * (g.mx - g.w / 2) / g.w / hzoom;
637  float val = db0 + (float)((g.h - 1) - g.my) * dbrange / g.h;
638  sprintf(s, "%f.3 Hz %f.2 dB", freq, val);
639  g.setfg(255, 255, 255);
640  g.text(16, 16, s);
641  }
642  // Draw cursors
643  g.setfg(255, 255, 0);
644  for (int i = 0; i < ncursors; ++i)
645  {
646  int x = g.w / 2 + (cursors[i] - Fc) * hzoom * g.w / Fs;
647  g.line(x, 0, x, g.h - 1);
648  }
649  g.show();
650  g.sync();
651  }
652  void draw_begin()
653  {
654  g.clear();
655  // dB scale
656  g.setfg(64, 64, 64);
657  for (float db = floorf(db0); db < db0 + dbrange; ++db)
658  {
659  int y = g.h - 1 - (db - db0) * g.h / dbrange;
660  g.line(0, y, g.w - 1, y);
661  }
662  // DC line
663  g.setfg(255, 255, 255);
664  g.line(g.w / 2, 0, g.w / 2, g.h);
665  }
666 };
667 
668 template <typename T>
669 struct genscope : runnable
670 {
671  struct render
672  {
673  int x, y;
674  char dir; // 'h'orizontal or 'v'ertical
675  };
676  struct chanspec
677  {
678  pipebuf<T> *in; // NULL if disabled
679  render r;
680  };
681  genscope(scheduler *sch, chanspec *specs, int _nchans,
682  const char *_name = NULL)
683  : runnable(sch, _name ? _name : "genscope"),
684  nchans(_nchans),
685  g(sch, name)
686  {
687  chans = new channel[nchans];
688  for (int i = 0; i < nchans; ++i)
689  {
690  if (!specs[i].in)
691  {
692  chans[i].in = NULL;
693  }
694  else
695  {
696  chans[i].spec = specs[i];
697  chans[i].in = new pipereader<T>(*specs[i].in);
698  }
699  }
700  g.clear();
701  gettimeofday(&tv, NULL);
702  }
703  struct channel
704  {
705  chanspec spec;
706  pipereader<T> *in;
707  } * chans;
708  int nchans;
709  struct timeval tv;
710  void run()
711  {
712  g.setfg(0, 255, 0);
713  for (channel *pc = chans; pc < chans + nchans; ++pc)
714  {
715  if (!pc->in)
716  continue;
717  int n = pc->in->readable();
718  T last = pc->in->rd()[n - 1];
719  pc->in->read(n);
720  int dx = pc->spec.r.dir == 'h' ? last : 0;
721  int dy = pc->spec.r.dir == 'v' ? last : 0;
722  dx /= 3;
723  dy /= 3;
724  g.line(pc->spec.r.x - dx, pc->spec.r.y - dy,
725  pc->spec.r.x + dx, pc->spec.r.y + dy);
726  char txt[16];
727  sprintf(txt, "%d", (int)last);
728  g.text(pc->spec.r.x + 5, pc->spec.r.y - 2, txt);
729  }
730  struct timeval newtv;
731  gettimeofday(&newtv, NULL);
732  int dt = (newtv.tv_sec - tv.tv_sec) * 1000 + (newtv.tv_usec - tv.tv_usec) / 1000;
733  if (dt > 100)
734  {
735  fprintf(stderr, "#");
736  g.show();
737  g.sync();
738  g.clear();
739  tv = newtv;
740  }
741  }
742 
743  private:
744  gfx g;
745 };
746 
747 #endif // GUI
748 
749 } // namespace leansdr
750 
751 #endif // LEANSDR_GUI_H
int decimation(float Fin, float Fout)
Definition: datvdemod.h:66
Fixed< IntType, IntBits > floor(Fixed< IntType, IntBits > const &x)
Definition: fixed.h:2301
void fail(const char *s)
Definition: framework.cpp:11
int32_t i
Definition: decimators.h:244
void fatal(const char *s)
Definition: framework.cpp:6
T min(const T &x, const T &y)
Definition: framework.h:440