kopia lustrzana https://github.com/f4exb/sdrangel
				
				
				
			
		
			
				
	
	
		
			754 wiersze
		
	
	
		
			21 KiB
		
	
	
	
		
			C++
		
	
	
			
		
		
	
	
			754 wiersze
		
	
	
		
			21 KiB
		
	
	
	
		
			C++
		
	
	
// Copyright (C) 2018-2019, 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
 | 
						|
 | 
						|
// This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
 | 
						|
// See the toplevel README for more information.
 | 
						|
//
 | 
						|
// 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 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// This program is distributed in the hope that it will be useful,
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
// GNU General Public License for more details.
 | 
						|
//
 | 
						|
// You should have received a copy of the GNU General Public License
 | 
						|
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
#ifndef LEANSDR_GUI_H
 | 
						|
#define LEANSDR_GUI_H
 | 
						|
 | 
						|
#include <sys/time.h>
 | 
						|
 | 
						|
#include "framework.h"
 | 
						|
 | 
						|
namespace leansdr
 | 
						|
{
 | 
						|
 | 
						|
//////////////////////////////////////////////////////////////////////
 | 
						|
// GUI blocks
 | 
						|
//////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
#ifdef GUI
 | 
						|
 | 
						|
#include <X11/X.h>
 | 
						|
#include <X11/Xlib.h>
 | 
						|
#include <X11/Xutil.h>
 | 
						|
 | 
						|
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 << b;
 | 
						|
                clicks |= 1 << b;
 | 
						|
                mx = ev.xbutton.x;
 | 
						|
                my = ev.xbutton.y;
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            case ButtonRelease:
 | 
						|
            {
 | 
						|
                int b = ev.xbutton.button;
 | 
						|
                buttons &= ~(1 << b);
 | 
						|
                mx = ev.xbutton.x;
 | 
						|
                my = ev.xbutton.y;
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            case MotionNotify:
 | 
						|
                mx = ev.xbutton.x;
 | 
						|
                my = ev.xbutton.y;
 | 
						|
                mmoved = true;
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    void setfg(unsigned char r, unsigned char g, unsigned char b)
 | 
						|
    {
 | 
						|
        XColor c;
 | 
						|
        c.red = r << 8;
 | 
						|
        c.green = g << 8;
 | 
						|
        c.blue = b << 8;
 | 
						|
        c.flags = DoRed | DoGreen | DoBlue;
 | 
						|
        if (!XAllocColor(display, DefaultColormap(display, screen), &c))
 | 
						|
            fatal("color");
 | 
						|
        XSetForeground(display, gc, c.pixel);
 | 
						|
    }
 | 
						|
    void point(int x, int y)
 | 
						|
    {
 | 
						|
        XDrawPoint(display, dbuf, gc, x, y);
 | 
						|
    }
 | 
						|
    void line(int x0, int y0, int x1, int y1)
 | 
						|
    {
 | 
						|
        XDrawLine(display, dbuf, gc, x0, y0, x1, y1);
 | 
						|
    }
 | 
						|
    void text(int x, int y, const char *s)
 | 
						|
    {
 | 
						|
        XDrawString(display, dbuf, gc, x, y, s, strlen(s));
 | 
						|
    }
 | 
						|
    void transient_text(int x, int y, const char *s)
 | 
						|
    {
 | 
						|
        XDrawString(display, window, gc, x, y, s, strlen(s));
 | 
						|
    }
 | 
						|
    int buttons; // Mask of button states (2|4|8)
 | 
						|
    int clicks;  // Same, accumulated (must be cleared by owner)
 | 
						|
    int mx, my;  // Cursor position
 | 
						|
    bool mmoved; // Pointer moved (must be cleared by owner)
 | 
						|
};
 | 
						|
 | 
						|
template <typename T>
 | 
						|
struct cscope : runnable
 | 
						|
{
 | 
						|
    T xymin, xymax;
 | 
						|
    unsigned long decimation;
 | 
						|
    unsigned long pixels_per_frame;
 | 
						|
    cstln_base **cstln; // Optional ptr to optional constellation
 | 
						|
    cscope(scheduler *sch, pipebuf<std::complex<T>> &_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);
 | 
						|
                std::complex<T> *p = in.rd(), *pend = p + pixels_per_frame;
 | 
						|
                for (; p < pend; ++p)
 | 
						|
                    g.point(g.w * (p->re - 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)
 | 
						|
                    {
 | 
						|
                        std::complex<signed char> *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<std::complex<T>> 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 <typename T>
 | 
						|
struct wavescope : runnable
 | 
						|
{
 | 
						|
    T ymin, ymax;
 | 
						|
    unsigned long decimation;
 | 
						|
    float hgrid;
 | 
						|
    wavescope(scheduler *sch, pipebuf<T> &_in,
 | 
						|
              T _ymin, T _ymax, const char *_name = NULL)
 | 
						|
        : runnable(sch, _name ? _name : _in.name),
 | 
						|
          ymin(_ymin), ymax(_ymax),
 | 
						|
          decimation(DEFAULT_GUI_DECIMATION), hgrid(0),
 | 
						|
          in(_in),
 | 
						|
          phase(0), g(sch, name), 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(128, 128, 0);
 | 
						|
        g.line(0, g.h / 2, g.w - 1, g.h / 2);
 | 
						|
        if (hgrid)
 | 
						|
        {
 | 
						|
            for (float x = 0; x < g.w; x += hgrid)
 | 
						|
                g.line(x, 0, x, g.h - 1);
 | 
						|
        }
 | 
						|
        g.setfg(0, 255, 0);
 | 
						|
        for (int x = 0; p < pend; ++x, ++p)
 | 
						|
        {
 | 
						|
            T v = *p;
 | 
						|
            g.point(x, g.h - 1 - (g.h - 1) * (v - ymin) / (ymax - ymin));
 | 
						|
        }
 | 
						|
        g.show();
 | 
						|
        g.sync();
 | 
						|
    }
 | 
						|
 | 
						|
  private:
 | 
						|
    pipereader<T> in;
 | 
						|
    int phase;
 | 
						|
    gfx g;
 | 
						|
    int x;
 | 
						|
};
 | 
						|
 | 
						|
template <typename T>
 | 
						|
struct slowmultiscope : runnable
 | 
						|
{
 | 
						|
    struct chanspec
 | 
						|
    {
 | 
						|
        pipebuf<T> *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 < nspecs; ++i)
 | 
						|
        {
 | 
						|
            if (specs[i].flags & chanspec::DISABLED)
 | 
						|
                continue;
 | 
						|
            chans[nchans].spec = specs[i];
 | 
						|
            chans[nchans].in = new pipereader<T>(*specs[i].in);
 | 
						|
            chans[nchans].accum = 0;
 | 
						|
            ++nchans;
 | 
						|
        }
 | 
						|
        g.clear();
 | 
						|
    }
 | 
						|
 | 
						|
    void run()
 | 
						|
    {
 | 
						|
        // Asynchronous channels: Read all available data
 | 
						|
        for (channel *c = chans; c < chans + nchans; ++c)
 | 
						|
        {
 | 
						|
            if (c->spec.flags & chanspec::ASYNC)
 | 
						|
                run_channel(c, c->in->readable());
 | 
						|
        }
 | 
						|
        // Synchronous channels: Read up to one pixel worth of data
 | 
						|
        // between display syncs.
 | 
						|
        unsigned long count = samples_per_pixel;
 | 
						|
        for (channel *c = chans; c < chans + nchans; ++c)
 | 
						|
            if (!(c->spec.flags & chanspec::ASYNC))
 | 
						|
                count = min(count, c->in->readable());
 | 
						|
        for (int n = count; n--;)
 | 
						|
        {
 | 
						|
            for (channel *c = chans; c < chans + nchans; ++c)
 | 
						|
                if (!(c->spec.flags & chanspec::ASYNC))
 | 
						|
                    run_channel(c, 1);
 | 
						|
        }
 | 
						|
        t += count;
 | 
						|
        g.show();
 | 
						|
        // Print instantatenous values as text
 | 
						|
        for (int i = 0; i < nchans; ++i)
 | 
						|
        {
 | 
						|
            channel *c = &chans[i];
 | 
						|
            g.setfg(c->spec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]);
 | 
						|
            char text[256];
 | 
						|
            snprintf(text, sizeof(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;
 | 
						|
    }
 | 
						|
 | 
						|
  private:
 | 
						|
    int nchans;
 | 
						|
    struct channel
 | 
						|
    {
 | 
						|
        chanspec spec;
 | 
						|
        pipereader<T> *in;
 | 
						|
        float accum;
 | 
						|
        int prev_y;
 | 
						|
        float print_val;
 | 
						|
    } * chans;
 | 
						|
    void run_channel(channel *c, int nr)
 | 
						|
    {
 | 
						|
        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);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    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];
 | 
						|
        snprintf(text, sizeof(text), "%.3f / %.3f s", ct, tt);
 | 
						|
        g.setfg(255, 255, 255);
 | 
						|
        g.transient_text(g.w * 3 / 4, 20, text);
 | 
						|
    }
 | 
						|
    gfx g;
 | 
						|
    unsigned long t; // Time for synchronous channels
 | 
						|
    int x;
 | 
						|
    int total_samples;
 | 
						|
};
 | 
						|
 | 
						|
template <typename T>
 | 
						|
struct spectrumscope : runnable
 | 
						|
{
 | 
						|
    unsigned long size;
 | 
						|
    float amax;
 | 
						|
    unsigned long decimation;
 | 
						|
    spectrumscope(scheduler *sch, pipebuf<std::complex<T>> &_in,
 | 
						|
                  T _max, const char *_name = NULL)
 | 
						|
        : runnable(sch, _name ? _name : _in.name),
 | 
						|
          size(4096), amax(_max / sqrtf(size)),
 | 
						|
          decimation(DEFAULT_GUI_DECIMATION),
 | 
						|
          in(_in),
 | 
						|
          nmarkers(0),
 | 
						|
          phase(0), g(sch, name), fft(NULL)
 | 
						|
    {
 | 
						|
    }
 | 
						|
    void mark_freq(float f)
 | 
						|
    {
 | 
						|
        if (nmarkers == MAX_MARKERS)
 | 
						|
            fail("Too many markers");
 | 
						|
        markers[nmarkers++] = f;
 | 
						|
    }
 | 
						|
    void run()
 | 
						|
    {
 | 
						|
        while (in.readable() >= size)
 | 
						|
        {
 | 
						|
            if (!phase)
 | 
						|
                do_fft(in.rd());
 | 
						|
            in.read(size);
 | 
						|
            if (++phase >= decimation)
 | 
						|
                phase = 0;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
  private:
 | 
						|
    pipereader<std::complex<T>> in;
 | 
						|
    static const int MAX_MARKERS = 4;
 | 
						|
    float markers[MAX_MARKERS];
 | 
						|
    int nmarkers;
 | 
						|
    int phase;
 | 
						|
    gfx g;
 | 
						|
    cfft_engine<float> *fft;
 | 
						|
    void do_fft(std::complex<T> *input)
 | 
						|
    {
 | 
						|
        draw_begin();
 | 
						|
        if (!fft || fft->n != size)
 | 
						|
        {
 | 
						|
            if (fft)
 | 
						|
                delete fft;
 | 
						|
            fft = new cfft_engine<float>(size);
 | 
						|
        }
 | 
						|
        std::complex<T> *pin = input, *pend = pin + size;
 | 
						|
        std::complex<float> data[size], *pout = data;
 | 
						|
        g.setfg(255, 0, 0);
 | 
						|
        for (int x = 0; pin < pend; ++pin, ++pout, ++x)
 | 
						|
        {
 | 
						|
            pout->re = (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 < size; ++i)
 | 
						|
        {
 | 
						|
            int x = ((i < size / 2) ? i + size / 2 : i - size / 2) * g.w / size;
 | 
						|
            std::complex<float> v = data[i];
 | 
						|
            ;
 | 
						|
            float y = hypot(v.real(), v.imag());
 | 
						|
            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;
 | 
						|
            snprintf(s, sizeof(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);
 | 
						|
        g.setfg(255, 0, 0);
 | 
						|
        for (int i = 0; i < nmarkers; ++i)
 | 
						|
        {
 | 
						|
            int x = g.w * (0.5 + markers[i]);
 | 
						|
            g.line(x, 0, x, g.h);
 | 
						|
        }
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
template <typename T>
 | 
						|
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<std::complex<T>> &_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<std::complex<T>> in;
 | 
						|
    int phase;
 | 
						|
    gfx g;
 | 
						|
    cfft_engine<float> *fft;
 | 
						|
    float *filtered;
 | 
						|
 | 
						|
    void do_fft(std::complex<T> *input)
 | 
						|
    {
 | 
						|
        g.events();
 | 
						|
        draw_begin();
 | 
						|
        if (!fft || fft->n != size)
 | 
						|
        {
 | 
						|
            if (fft)
 | 
						|
                delete fft;
 | 
						|
            fft = new cfft_engine<float>(size);
 | 
						|
        }
 | 
						|
        // Convert to std::complex<float> and transform
 | 
						|
        std::complex<T> *pin = input, *pend = pin + size;
 | 
						|
        std::complex<float> data[size], *pout = data;
 | 
						|
        for (int x = 0; pin < pend; ++pin, ++pout, ++x)
 | 
						|
        {
 | 
						|
            pout->re = (float)pin->re;
 | 
						|
            pout->im = (float)pin->im;
 | 
						|
        }
 | 
						|
        fft->inplace(data, true);
 | 
						|
        float amp2[size];
 | 
						|
        for (int i = 0; i < size; ++i)
 | 
						|
        {
 | 
						|
            std::complex<float> &v = data[i];
 | 
						|
            ;
 | 
						|
            amp2[i] = (v.real() * v.real() + v.imag() * v.imag()) * size;
 | 
						|
        }
 | 
						|
        if (!filtered)
 | 
						|
        {
 | 
						|
            filtered = new float[size];
 | 
						|
            for (int i = 0; i < size; ++i)
 | 
						|
                filtered[i] = amp2[i];
 | 
						|
        }
 | 
						|
        float bwcomp = 1 - bw;
 | 
						|
        g.setfg(0, 255, 0);
 | 
						|
        for (int i = 0; i < size; ++i)
 | 
						|
        {
 | 
						|
            filtered[i] = amp2[i] * bw + filtered[i] * bwcomp;
 | 
						|
            float db = filtered[i] ? 10 * logf(filtered[i]) / logf(10) : db0;
 | 
						|
            int is = (i < size / 2) ? i : i - size;
 | 
						|
            int x = g.w / 2 + is * hzoom * g.w / size;
 | 
						|
            int y = g.h - 1 - (db - db0) * g.h / dbrange;
 | 
						|
            g.line(x, g.h - 1, x, y);
 | 
						|
        }
 | 
						|
        if (g.buttons)
 | 
						|
        {
 | 
						|
            char s[256];
 | 
						|
            float freq = Fc + Fs * (g.mx - g.w / 2) / g.w / hzoom;
 | 
						|
            float val = db0 + (float)((g.h - 1) - g.my) * dbrange / g.h;
 | 
						|
            snprintf(s, sizeof(s), "%f.3 Hz   %f.2 dB", freq, val);
 | 
						|
            g.setfg(255, 255, 255);
 | 
						|
            g.text(16, 16, s);
 | 
						|
        }
 | 
						|
        // Draw cursors
 | 
						|
        g.setfg(255, 255, 0);
 | 
						|
        for (int i = 0; i < ncursors; ++i)
 | 
						|
        {
 | 
						|
            int x = g.w / 2 + (cursors[i] - Fc) * hzoom * g.w / Fs;
 | 
						|
            g.line(x, 0, x, g.h - 1);
 | 
						|
        }
 | 
						|
        g.show();
 | 
						|
        g.sync();
 | 
						|
    }
 | 
						|
    void draw_begin()
 | 
						|
    {
 | 
						|
        g.clear();
 | 
						|
        // dB scale
 | 
						|
        g.setfg(64, 64, 64);
 | 
						|
        for (float db = floorf(db0); db < db0 + dbrange; ++db)
 | 
						|
        {
 | 
						|
            int y = g.h - 1 - (db - db0) * g.h / dbrange;
 | 
						|
            g.line(0, y, g.w - 1, y);
 | 
						|
        }
 | 
						|
        // DC line
 | 
						|
        g.setfg(255, 255, 255);
 | 
						|
        g.line(g.w / 2, 0, g.w / 2, g.h);
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
template <typename T>
 | 
						|
struct genscope : runnable
 | 
						|
{
 | 
						|
    struct render
 | 
						|
    {
 | 
						|
        int x, y;
 | 
						|
        char dir; // 'h'orizontal or 'v'ertical
 | 
						|
    };
 | 
						|
    struct chanspec
 | 
						|
    {
 | 
						|
        pipebuf<T> *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 < nchans; ++i)
 | 
						|
        {
 | 
						|
            if (!specs[i].in)
 | 
						|
            {
 | 
						|
                chans[i].in = NULL;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                chans[i].spec = specs[i];
 | 
						|
                chans[i].in = new pipereader<T>(*specs[i].in);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        g.clear();
 | 
						|
        gettimeofday(&tv, NULL);
 | 
						|
    }
 | 
						|
    struct channel
 | 
						|
    {
 | 
						|
        chanspec spec;
 | 
						|
        pipereader<T> *in;
 | 
						|
    } * chans;
 | 
						|
    int nchans;
 | 
						|
    struct timeval tv;
 | 
						|
    void run()
 | 
						|
    {
 | 
						|
        g.setfg(0, 255, 0);
 | 
						|
        for (channel *pc = chans; pc < chans + nchans; ++pc)
 | 
						|
        {
 | 
						|
            if (!pc->in)
 | 
						|
                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];
 | 
						|
            snprintf(txt, sizeof(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 leansdr
 | 
						|
 | 
						|
#endif // LEANSDR_GUI_H
 |