mirror of https://github.com/hmatuschek/libsdr
Cleanup.
parent
cbaa2e0ec6
commit
f1d9b03254
@ -1,7 +0,0 @@
|
||||
#ifndef __SDR_GUI_GUI_HH__
|
||||
#define __SDR_GUI_GUI_HH__
|
||||
|
||||
#include "spectrumview.hh"
|
||||
#include "waterfallview.hh"
|
||||
|
||||
#endif
|
||||
@ -1,271 +0,0 @@
|
||||
#include "spectrum.hh"
|
||||
#include "config.hh"
|
||||
|
||||
using namespace sdr;
|
||||
using namespace sdr::gui;
|
||||
|
||||
|
||||
/* ********************************************************************************************* *
|
||||
* SpectrumProvider
|
||||
* ********************************************************************************************* */
|
||||
SpectrumProvider::SpectrumProvider(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
SpectrumProvider::~SpectrumProvider() {
|
||||
// pass...
|
||||
}
|
||||
|
||||
|
||||
/* ********************************************************************************************* *
|
||||
* Spectrum
|
||||
* ********************************************************************************************* */
|
||||
Spectrum::Spectrum(double rrate, size_t fftsize, size_t max_avg, QObject *parent) :
|
||||
SpectrumProvider(parent), _rrate(rrate), _fft_size(fftsize), _fft_buffer(fftsize),
|
||||
_compute(fftsize), _spectrum(fftsize), _sample_count(0), _N_samples(0),
|
||||
_trafo_count(0), _Ntrafo(max_avg),
|
||||
_samples_left(0), _input_type(Config::Type_UNDEFINED), _sample_rate(0)
|
||||
{
|
||||
// Construct FFT plan
|
||||
_plan = fftw_plan_dft_1d(
|
||||
fftsize, (fftw_complex *)_fft_buffer.ptr(), (fftw_complex *)_fft_buffer.ptr(),
|
||||
FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
|
||||
// Set spectrum to 0:
|
||||
for (size_t i=0; i<_fft_size; i++) { _spectrum[i] = 0; _compute[i] = 0; }
|
||||
}
|
||||
|
||||
Spectrum::~Spectrum() {
|
||||
fftw_destroy_plan(_plan);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Spectrum::config(const Config &src_cfg) {
|
||||
// Requires at least sample rate and type
|
||||
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
|
||||
|
||||
// Store sample rate
|
||||
_sample_rate = src_cfg.sampleRate();
|
||||
_samples_left = 0;
|
||||
_trafo_count = 0; _sample_count=0;
|
||||
_input_type = src_cfg.type();
|
||||
// Compute number of averages to compute to meet rrate approximately
|
||||
_N_samples = _sample_rate/(_Ntrafo*_rrate);
|
||||
|
||||
LogMessage msg(LOG_DEBUG);
|
||||
msg << "Configured SpectrumView: " << std::endl
|
||||
<< " Data type: " << _input_type << std::endl
|
||||
<< " sample-rate: " << _sample_rate << std::endl
|
||||
<< " FFT size: " << _fft_size << std::endl
|
||||
<< " # sample drops: " << _N_samples-1 << std::endl
|
||||
<< " # averages: " << _Ntrafo << std::endl
|
||||
<< " refresh rate: " << _sample_rate/(_N_samples*_Ntrafo) << "Hz";
|
||||
Logger::get().log(msg);
|
||||
|
||||
// Signal spectrum reconfiguration
|
||||
emit spectrumConfigured();
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Spectrum::isInputReal() const {
|
||||
switch (_input_type) {
|
||||
case Config::Type_u8:
|
||||
case Config::Type_s8:
|
||||
case Config::Type_u16:
|
||||
case Config::Type_s16:
|
||||
case Config::Type_f32:
|
||||
case Config::Type_f64:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
double
|
||||
Spectrum::sampleRate() const {
|
||||
return _sample_rate;
|
||||
}
|
||||
|
||||
size_t
|
||||
Spectrum::fftSize() const {
|
||||
return _fft_size;
|
||||
}
|
||||
|
||||
const Buffer<double> &
|
||||
Spectrum::spectrum() const {
|
||||
return _spectrum;
|
||||
}
|
||||
|
||||
void
|
||||
Spectrum::handleBuffer(const RawBuffer &buffer, bool allow_overwrite)
|
||||
{
|
||||
double scale=1, offset=0;
|
||||
switch (_input_type) {
|
||||
case Config::Type_u8: scale = 1<<8; offset=-128; break;
|
||||
case Config::Type_cu8: scale = 1<<8; offset=-128; break;
|
||||
case Config::Type_s8: scale = 1<<8; offset=0; break;
|
||||
case Config::Type_cs8: scale = 1<<8; offset=0; break;
|
||||
case Config::Type_u16: scale = 1<<16; offset=-32768; break;
|
||||
case Config::Type_cu16: scale = 1<<16; offset=-32768; break;
|
||||
case Config::Type_s16: scale = 1<<16; offset=0; break;
|
||||
case Config::Type_cs16: scale = 1<<16; offset=0; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Dispatch by input type:
|
||||
if (Config::Type_u8 == _input_type) {
|
||||
Buffer<uint8_t> input(buffer);
|
||||
for (size_t i=0; i<input.size(); i++, _sample_count++) {
|
||||
// Skip until _N_samples is reached
|
||||
if (_sample_count < _N_samples) { continue; }
|
||||
// Copy value into buffer
|
||||
_fft_buffer[_samples_left] = (double(input[i])+offset)/scale; _samples_left++;
|
||||
// Once a FFT buffer is completed -> transform
|
||||
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
|
||||
}
|
||||
} else if (Config::Type_s8 == _input_type) {
|
||||
Buffer<int8_t> input(buffer);
|
||||
for (size_t i=0; i<input.size(); i++, _sample_count++) {
|
||||
// Skip until _N_samples is reached
|
||||
if (_sample_count < _N_samples) { continue; }
|
||||
// Copy value into buffer
|
||||
_fft_buffer[_samples_left] = (double(input[i])+offset)/scale; _samples_left++;
|
||||
// Once a FFT buffer is completed -> transform
|
||||
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
|
||||
}
|
||||
} else if (Config::Type_u16 == _input_type) {
|
||||
Buffer<uint16_t> input(buffer);
|
||||
for (size_t i=0; i<input.size(); i++, _sample_count++) {
|
||||
// Skip until _N_samples is reached
|
||||
if (_sample_count < _N_samples) { continue; }
|
||||
// Copy value into buffer
|
||||
_fft_buffer[_samples_left] = (double(input[i])+offset)/scale; _samples_left++;
|
||||
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
|
||||
}
|
||||
} else if (Config::Type_s16 == _input_type) {
|
||||
Buffer<int16_t> input(buffer);
|
||||
for (size_t i=0; i<input.size(); i++, _sample_count++) {
|
||||
// Skip until _N_samples is reached
|
||||
if (_sample_count < _N_samples) { continue; }
|
||||
// Copy value into buffer
|
||||
_fft_buffer[_samples_left] = (double(input[i])+offset)/scale; _samples_left++;
|
||||
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
|
||||
}
|
||||
} else if (Config::Type_f32 == _input_type) {
|
||||
Buffer<float> input(buffer);
|
||||
for (size_t i=0; i<input.size(); i++, _sample_count++) {
|
||||
// Skip until _N_samples is reached
|
||||
if (_sample_count < _N_samples) { continue; }
|
||||
// Copy value into buffer
|
||||
_fft_buffer[_samples_left] = input[i]; _samples_left++;
|
||||
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
|
||||
}
|
||||
} else if (Config::Type_f64 == _input_type) {
|
||||
Buffer<double> input(buffer);
|
||||
for (size_t i=0; i<input.size(); i++, _sample_count++) {
|
||||
// Skip until _N_samples is reached
|
||||
if (_sample_count < _N_samples) { continue; }
|
||||
// Copy value into buffer
|
||||
_fft_buffer[_samples_left] = input[i]; _samples_left++;
|
||||
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0;}
|
||||
}
|
||||
} else if (Config::Type_cu8 == _input_type) {
|
||||
Buffer< std::complex<uint8_t> > input(buffer);
|
||||
for (size_t i=0; i<input.size(); i++, _sample_count++) {
|
||||
// Skip until _N_samples is reached
|
||||
if (_sample_count < _N_samples) { continue; }
|
||||
// Copy value into buffer
|
||||
_fft_buffer[_samples_left] = std::complex<double>(
|
||||
double(input[i].real())+offset,
|
||||
double(input[i].imag())+offset)/scale;
|
||||
_samples_left++;
|
||||
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
|
||||
}
|
||||
} else if (Config::Type_cs8 == _input_type) {
|
||||
Buffer< std::complex<int8_t> > input(buffer);
|
||||
for (size_t i=0; i<input.size(); i++, _sample_count++) {
|
||||
// Skip until _N_samples is reached
|
||||
if (_sample_count < _N_samples) { continue; }
|
||||
// Copy value into buffer
|
||||
_fft_buffer[_samples_left] = std::complex<double>(
|
||||
double(input[i].real())+offset,
|
||||
double(input[i].imag())+offset)/scale;
|
||||
_samples_left++;
|
||||
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
|
||||
}
|
||||
} else if (Config::Type_cu16 == _input_type) {
|
||||
Buffer< std::complex<uint16_t> > input(buffer);
|
||||
for (size_t i=0; i<input.size(); i++, _sample_count++) {
|
||||
// Skip until _N_samples is reached
|
||||
if (_sample_count < _N_samples) { continue; }
|
||||
// Copy value into buffer
|
||||
_fft_buffer[_samples_left] = std::complex<double>(
|
||||
double(input[i].real())+offset,double(input[i].imag())+offset)/scale;
|
||||
_samples_left++;
|
||||
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
|
||||
}
|
||||
} else if (Config::Type_cs16 == _input_type) {
|
||||
Buffer< std::complex<int16_t> > input(buffer);
|
||||
for (size_t i=0; i<input.size(); i++, _sample_count++) {
|
||||
// Skip until _N_samples is reached
|
||||
if (_sample_count < _N_samples) { continue; }
|
||||
// Copy value into buffer
|
||||
_fft_buffer[_samples_left] = std::complex<double>(
|
||||
double(input[i].real())+offset,double(input[i].imag())+offset)/scale;
|
||||
_samples_left++;
|
||||
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
|
||||
}
|
||||
} else if (Config::Type_cf32 == _input_type) {
|
||||
Buffer< std::complex<float> > input(buffer);
|
||||
for (size_t i=0; i<input.size(); i++, _sample_count++) {
|
||||
// Skip until _N_samples is reached
|
||||
if (_sample_count < _N_samples) { continue; }
|
||||
// Copy value into buffer
|
||||
_fft_buffer[_samples_left] = input[i]; _samples_left++;
|
||||
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
|
||||
}
|
||||
} else if (Config::Type_cf64 == _input_type) {
|
||||
Buffer< std::complex<double> > input(buffer);
|
||||
for (size_t i=0; i<input.size(); i++, _sample_count++) {
|
||||
// Skip until _N_samples is reached
|
||||
if (_sample_count < _N_samples) { continue; }
|
||||
// Copy value into buffer
|
||||
_fft_buffer[_samples_left] = input[i]; _samples_left++;
|
||||
if (_samples_left == _fft_size) { _updateFFT(); _samples_left=0; _sample_count=0; }
|
||||
}
|
||||
} else {
|
||||
RuntimeError err;
|
||||
err << "SpectrumView: Can not process buffer of type " << _input_type
|
||||
<< ", unsupported type.";
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Spectrum::_updateFFT() {
|
||||
// calc fft
|
||||
fftw_execute(_plan);
|
||||
// Update _compute with PSD
|
||||
for(size_t i=0; i<_fft_size; i++) {
|
||||
_compute[i] += std::real(std::conj(_fft_buffer[i])*_fft_buffer[i])/_Ntrafo;
|
||||
}
|
||||
_trafo_count++;
|
||||
// If number of averages reached:
|
||||
if (_trafo_count == _Ntrafo) {
|
||||
// copy _compute to _spectrum and reset _compute to 0
|
||||
for (size_t i=0; i<_fft_size; i++) {
|
||||
_spectrum[i] = _compute[i];
|
||||
_compute[i] = 0;
|
||||
}
|
||||
// done...
|
||||
_trafo_count=0;
|
||||
// signal spectrum update
|
||||
emit spectrumUpdated();
|
||||
}
|
||||
}
|
||||
@ -1,114 +0,0 @@
|
||||
#ifndef __SDR_GUI_SPECTRUM_HH__
|
||||
#define __SDR_GUI_SPECTRUM_HH__
|
||||
|
||||
#include <QObject>
|
||||
#include <fftw3.h>
|
||||
#include "sdr.hh"
|
||||
|
||||
|
||||
namespace sdr {
|
||||
namespace gui {
|
||||
|
||||
|
||||
class SpectrumProvider: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpectrumProvider(QObject *parent=0);
|
||||
|
||||
virtual ~SpectrumProvider();
|
||||
|
||||
/** Returns true if the input is real. */
|
||||
virtual bool isInputReal() const = 0;
|
||||
|
||||
/** Retunrs the sample rate. */
|
||||
virtual double sampleRate() const = 0;
|
||||
|
||||
/** Returns the FFT size. */
|
||||
virtual size_t fftSize() const = 0;
|
||||
|
||||
/** Returns the current spectrum. */
|
||||
virtual const Buffer<double> &spectrum() const = 0;
|
||||
|
||||
signals:
|
||||
/** Gets emitted once the spectrum was updated. */
|
||||
void spectrumUpdated();
|
||||
/** Gets emitted once the spectrum was reconfigured. */
|
||||
void spectrumConfigured();
|
||||
};
|
||||
|
||||
|
||||
/** Calculates the power spectrum of a singnal. The spectrum gets updated with a specified
|
||||
* rate (if possible). */
|
||||
class Spectrum : public SpectrumProvider, public SinkBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/** Constructs a new spectrum recorder (sink).
|
||||
* @param rrate Specifies the desired refresh-rate of the spectrum.
|
||||
* @param fftsize Specifies the size (bins) of the spectrum.
|
||||
* @param max_avg Specifies the max. number of averages to take.
|
||||
* @param parent Specifies the parent of the @c Spectrum instance. */
|
||||
explicit Spectrum(double rrate=2, size_t fftsize=1024, size_t max_avg=1, QObject *parent = 0);
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~Spectrum();
|
||||
|
||||
/** Configures the Sink. */
|
||||
virtual void config(const Config &src_cfg);
|
||||
|
||||
/** Receives the data stream and updates the _fft_buffer. */
|
||||
virtual void handleBuffer(const RawBuffer &buffer, bool allow_overwrite);
|
||||
|
||||
/** Returns true if the input is real. */
|
||||
bool isInputReal() const;
|
||||
|
||||
/** Retunrs the sample rate. */
|
||||
double sampleRate() const;
|
||||
|
||||
/** Returns the FFT size. */
|
||||
size_t fftSize() const;
|
||||
|
||||
/** Returns the current spectrum. */
|
||||
const Buffer<double> &spectrum() const;
|
||||
|
||||
protected:
|
||||
/** Updates the FFT in the _compute buffer. */
|
||||
void _updateFFT();
|
||||
|
||||
protected:
|
||||
/** The desired refresh rate for the spectrum plot. */
|
||||
double _rrate;
|
||||
/** Size of the FFT (resolution). */
|
||||
size_t _fft_size;
|
||||
/** Input and compute buffer for the FFT. */
|
||||
Buffer< std::complex<double> > _fft_buffer;
|
||||
/** Summation buffer of the spectrum. */
|
||||
Buffer< double > _compute;
|
||||
/** The current spectrum. */
|
||||
Buffer< double > _spectrum;
|
||||
/** Specifies the number of received samples since last FFT. */
|
||||
size_t _sample_count;
|
||||
/** Specifies the number of samples to drop before performing a FFT. */
|
||||
size_t _N_samples;
|
||||
/** Number of transforms in the _compute buffer. */
|
||||
size_t _trafo_count;
|
||||
/** Max. number of transforms. */
|
||||
size_t _Ntrafo;
|
||||
/** Number of samples, left in the buffer by the previous input. */
|
||||
size_t _samples_left;
|
||||
/** The input type. */
|
||||
Config::Type _input_type;
|
||||
/** The sample-rate of the input. */
|
||||
double _sample_rate;
|
||||
/** The FFTW plan. */
|
||||
fftw_plan _plan;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SPECTRUM_HH
|
||||
@ -1,192 +0,0 @@
|
||||
#include "spectrumview.hh"
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QPaintEngine>
|
||||
#include <QResizeEvent>
|
||||
#include <QFontMetrics>
|
||||
|
||||
using namespace sdr;
|
||||
using namespace sdr::gui;
|
||||
|
||||
SpectrumView::SpectrumView(SpectrumProvider *spectrum, QWidget *parent)
|
||||
: QWidget(parent), _spectrum(spectrum), _numXTicks(11), _numYTicks(6),
|
||||
_maxF(std::numeric_limits<double>::infinity()), _mindB(-60)
|
||||
{
|
||||
// Assemble pens
|
||||
_axisPen = QPen(Qt::black);
|
||||
_axisPen.setWidth(3);
|
||||
_axisPen.setStyle(Qt::SolidLine);
|
||||
_graphPen = QPen(Qt::blue);
|
||||
_axisPen.setWidth(2);
|
||||
_axisPen.setStyle(Qt::SolidLine);
|
||||
|
||||
// Connect to update signal
|
||||
QObject::connect(_spectrum, SIGNAL(spectrumUpdated()), this, SLOT(update()));
|
||||
QObject::connect(_spectrum, SIGNAL(spectrumConfigured()), this, SLOT(update()));
|
||||
}
|
||||
|
||||
SpectrumView::~SpectrumView() {
|
||||
// pass...
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpectrumView::mouseReleaseEvent(QMouseEvent *evt) {
|
||||
// If event is accepted -> determine frequency
|
||||
if ((evt->pos().x() < _plotArea.left()) || (evt->pos().x() > _plotArea.right())) { return; }
|
||||
double f=0;
|
||||
|
||||
if (_spectrum->isInputReal()) {
|
||||
double dfdx = _spectrum->sampleRate()/(2*_plotArea.width());
|
||||
f = dfdx * (evt->pos().x()-_plotArea.left());
|
||||
} else {
|
||||
double dfdx = _spectrum->sampleRate()/_plotArea.width();
|
||||
f = -_spectrum->sampleRate()/2 + dfdx * (evt->pos().x()-_plotArea.left());
|
||||
}
|
||||
|
||||
emit click(f);
|
||||
|
||||
// Forward to default impl:
|
||||
QWidget::mouseReleaseEvent(evt);
|
||||
// redraw
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpectrumView::resizeEvent(QResizeEvent *evt) {
|
||||
// First, forward to default impl.
|
||||
QWidget::resizeEvent(evt);
|
||||
|
||||
// If resize event was accepted, recalc plot area
|
||||
if (evt->isAccepted()) {
|
||||
QSize ws = evt->size();
|
||||
QFontMetrics fm(_axisFont);
|
||||
int leftMargin = 15 + 6*fm.width("x");
|
||||
int topMargin = 10;
|
||||
int rightMargin = 3*fm.width("x");
|
||||
int bottomMargin = 15 + 2*fm.xHeight();
|
||||
_plotArea = QRect(
|
||||
leftMargin, topMargin,
|
||||
ws.width()-leftMargin-rightMargin,
|
||||
ws.height()-bottomMargin-topMargin);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SpectrumView::paintEvent(QPaintEvent *evt) {
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.save();
|
||||
|
||||
// Clip region to update
|
||||
painter.setClipRect(evt->rect());
|
||||
|
||||
// Draw background
|
||||
painter.fillRect(QRect(0,0, this->width(), this->height()), QColor(Qt::white));
|
||||
|
||||
_drawAxis(painter);
|
||||
|
||||
_drawGraph(painter);
|
||||
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpectrumView::_drawGraph(QPainter &painter) {
|
||||
// Draw spectrum
|
||||
painter.save();
|
||||
painter.setClipRect(_plotArea);
|
||||
painter.setPen(_graphPen);
|
||||
double height = _plotArea.height();
|
||||
double width = _plotArea.width();
|
||||
|
||||
if (_spectrum->isInputReal()) {
|
||||
double df = double(2*width)/_spectrum->fftSize();
|
||||
double dh = double(height)/_mindB;
|
||||
int xoff = _plotArea.x();
|
||||
int yoff = _plotArea.y();
|
||||
for (size_t i=1; i<_spectrum->fftSize()/2; i++) {
|
||||
int x1 = xoff+df*(i-1), x2 = xoff+df*(i);
|
||||
int y1 = yoff+dh*(10*log10(_spectrum->spectrum()[i-1])-10*log10(_spectrum->fftSize()));
|
||||
int y2 = yoff+dh*(10*log10(_spectrum->spectrum()[i])-10*log10(_spectrum->fftSize()));
|
||||
painter.drawLine(x1, y1, x2, y2);
|
||||
}
|
||||
} else {
|
||||
double df = double(width)/_spectrum->fftSize();
|
||||
double dh = double(height)/_mindB;
|
||||
int xoff = _plotArea.x();
|
||||
int yoff = _plotArea.y();
|
||||
for (size_t i=1; i<_spectrum->fftSize(); i++) {
|
||||
int idx1 = (_spectrum->fftSize()/2+i-1) % _spectrum->fftSize();
|
||||
int idx2 = (_spectrum->fftSize()/2+i) % _spectrum->fftSize();
|
||||
int x1 = xoff+df*(i-1), x2 = xoff+df*(i);
|
||||
int y1 = yoff+dh*(10*log10(_spectrum->spectrum()[idx1])-10*log10(_spectrum->fftSize()));
|
||||
int y2 = yoff+dh*(10*log10(_spectrum->spectrum()[idx2])-10*log10(_spectrum->fftSize()));
|
||||
painter.drawLine(x1,y1, x2,y2);
|
||||
}
|
||||
}
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SpectrumView::_drawAxis(QPainter &painter) {
|
||||
painter.save();
|
||||
// Get some sizes
|
||||
double height = _plotArea.height();
|
||||
double width = _plotArea.width();
|
||||
|
||||
painter.setPen(_axisPen);
|
||||
|
||||
// Axes
|
||||
painter.drawLine(_plotArea.topLeft(), _plotArea.bottomLeft());
|
||||
painter.drawLine(_plotArea.bottomLeft(), _plotArea.bottomRight());
|
||||
|
||||
// draw y-ticks & labels
|
||||
double dh = double(height)/_mindB;
|
||||
double x = _plotArea.topLeft().x();
|
||||
double y = _plotArea.topLeft().y();
|
||||
double ddB = _mindB/(_numYTicks-1);
|
||||
QFontMetrics fm(_axisFont); QRect bb; double db=0;
|
||||
for (size_t i=0; i<_numYTicks; i++, db+=ddB) {
|
||||
QString label = QString("%1").arg(db);
|
||||
bb = fm.boundingRect(label);
|
||||
y = _plotArea.topLeft().y() + db*dh;
|
||||
bb.translate(x-8-bb.width(),y+fm.strikeOutPos());
|
||||
painter.drawLine(x-5,y, x,y);
|
||||
painter.drawText(bb, label);
|
||||
}
|
||||
|
||||
// Draw x ticks & labels (real spectrum)
|
||||
if (_spectrum->isInputReal()) {
|
||||
double dx = double(width)/(_numXTicks-1);
|
||||
double maxF = std::min(_maxF, _spectrum->sampleRate()/2);
|
||||
double df = maxF/(_numXTicks-1);
|
||||
double x = _plotArea.bottomLeft().x();
|
||||
double y = _plotArea.bottomLeft().y();
|
||||
double f = 0;
|
||||
for (size_t i=0; i<11; i++, f +=df, x += dx) {
|
||||
QString label = QString("%1").arg(f);
|
||||
bb = fm.boundingRect(label);
|
||||
bb.translate(x-bb.width()/2,y+8+fm.overlinePos());
|
||||
painter.drawLine(x,y, x,y+5);
|
||||
painter.drawText(bb, label);
|
||||
}
|
||||
} else {
|
||||
double dx = double(width)/(_numXTicks-1);
|
||||
double df = _spectrum->sampleRate()/(_numXTicks-1);
|
||||
double x = _plotArea.bottomLeft().x();
|
||||
double y = _plotArea.bottomLeft().y();
|
||||
double f = -_spectrum->sampleRate()/2;
|
||||
for (size_t i=0; i<_numXTicks; i++, f +=df, x += dx) {
|
||||
QString label = QString("%1").arg(f);
|
||||
bb = fm.boundingRect(label);
|
||||
bb.translate(x-bb.width()/2,y+8+fm.overlinePos());
|
||||
painter.drawLine(x,y, x,y+5);
|
||||
painter.drawText(bb, label);
|
||||
}
|
||||
}
|
||||
painter.restore();
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
#ifndef __SDR_GUI_SPECTRUMVIEW_HH__
|
||||
#define __SDR_GUI_SPECTRUMVIEW_HH__
|
||||
|
||||
#include <QWidget>
|
||||
#include <QPen>
|
||||
|
||||
#include "sdr.hh"
|
||||
#include "spectrum.hh"
|
||||
|
||||
|
||||
namespace sdr {
|
||||
namespace gui {
|
||||
|
||||
/** A simple widget to display a PSD with live update. */
|
||||
class SpectrumView: public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/** @param rrate Specifies the (approx) refreshrate of the FFT plot. */
|
||||
explicit SpectrumView(SpectrumProvider *spectrum, QWidget *parent=0);
|
||||
|
||||
inline size_t numXTicks() const { return _numXTicks; }
|
||||
inline void setNumXTicks(size_t N) { _numXTicks=N; update(); }
|
||||
|
||||
inline size_t numYTicks() const { return _numYTicks; }
|
||||
inline void setNumYTicks(size_t N) { _numYTicks = N; }
|
||||
|
||||
inline double mindB() const { return _mindB; }
|
||||
inline void setMindB(double mindB) { _mindB = mindB; }
|
||||
|
||||
/// Destructor.
|
||||
virtual ~SpectrumView();
|
||||
|
||||
signals:
|
||||
void click(double f);
|
||||
|
||||
protected:
|
||||
/** Handles mouse clicks. */
|
||||
virtual void mouseReleaseEvent(QMouseEvent *evt);
|
||||
|
||||
/** Updates _plotArea on resize events. */
|
||||
virtual void resizeEvent(QResizeEvent *evt);
|
||||
/** Draws the spectrum. */
|
||||
virtual void paintEvent(QPaintEvent *evt);
|
||||
/** Draws the spectrum graph */
|
||||
void _drawGraph(QPainter &painter);
|
||||
/** Draw the axes, ticks and labels. */
|
||||
void _drawAxis(QPainter &painter);
|
||||
|
||||
|
||||
protected:
|
||||
/** Holds a weak reference to the spectrum recorder object. */
|
||||
SpectrumProvider *_spectrum;
|
||||
/// The font being used for axis labels
|
||||
QFont _axisFont;
|
||||
/// The plot area
|
||||
QRect _plotArea;
|
||||
/// Axis pen.
|
||||
QPen _axisPen;
|
||||
/// The pen used to draw the graph.
|
||||
QPen _graphPen;
|
||||
|
||||
size_t _numXTicks;
|
||||
size_t _numYTicks;
|
||||
double _maxF;
|
||||
/** Lower bound of the PSD plot. */
|
||||
double _mindB;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __SDR_GUI_SPECTRUMVIEW_HH__
|
||||
@ -1,212 +0,0 @@
|
||||
#include "waterfallview.hh"
|
||||
#include <QPainter>
|
||||
#include <QPaintEvent>
|
||||
#include <QDebug>
|
||||
|
||||
using namespace sdr;
|
||||
using namespace sdr::gui;
|
||||
|
||||
|
||||
|
||||
/* ****************************************************************************************** *
|
||||
* Implementation of ColorMap
|
||||
* ****************************************************************************************** */
|
||||
ColorMap::ColorMap(double min, double max)
|
||||
: _min(min), _max(max)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
ColorMap::~ColorMap() {
|
||||
// pass...
|
||||
}
|
||||
|
||||
/* ****************************************************************************************** *
|
||||
* Implementation of GrayScaleColorMap
|
||||
* ****************************************************************************************** */
|
||||
GrayScaleColorMap::GrayScaleColorMap(double min, double max)
|
||||
: ColorMap(min, max)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
GrayScaleColorMap::~GrayScaleColorMap() {
|
||||
// pass...
|
||||
}
|
||||
|
||||
QColor
|
||||
GrayScaleColorMap::map(const double &value) {
|
||||
int h = 255*value;
|
||||
return QColor(h,h,h);
|
||||
}
|
||||
|
||||
|
||||
/* ****************************************************************************************** *
|
||||
* Implementation of LinearColorMap
|
||||
* ****************************************************************************************** */
|
||||
LinearColorMap::LinearColorMap(const QVector<QColor> &colors, double min, double max)
|
||||
: ColorMap(min, max), _colors(colors)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
LinearColorMap::~LinearColorMap() {
|
||||
// pass...
|
||||
}
|
||||
|
||||
QColor
|
||||
LinearColorMap::map(const double &value) {
|
||||
// Calc indices
|
||||
double upper = ceil(value*(_colors.size()-1));
|
||||
double lower = floor(value*(_colors.size()-1));
|
||||
int idx = int(lower);
|
||||
if (lower == upper) { return _colors[idx]; }
|
||||
double dv = upper-(value*(_colors.size()-1));
|
||||
double dr = dv * (_colors[idx].red() - _colors[idx+1].red());
|
||||
double dg = dv * (_colors[idx].green() - _colors[idx+1].green());
|
||||
double db = dv * (_colors[idx].blue() - _colors[idx+1].blue());
|
||||
return QColor(int(_colors[idx+1].red()+dr),
|
||||
int(_colors[idx+1].green()+dg),
|
||||
int(_colors[idx+1].blue()+db));
|
||||
}
|
||||
|
||||
|
||||
/* ****************************************************************************************** *
|
||||
* Implementation of WaterFallView
|
||||
* ****************************************************************************************** */
|
||||
WaterFallView::WaterFallView(SpectrumProvider *spectrum, size_t hist, Direction dir, QWidget *parent)
|
||||
: QWidget(parent), _spectrum(spectrum), _N(_spectrum->fftSize()), _M(hist), _dir(dir),
|
||||
_waterfall(_N,_M)
|
||||
{
|
||||
switch (dir) {
|
||||
case BOTTOM_UP:
|
||||
case TOP_DOWN:
|
||||
setMinimumHeight(hist);
|
||||
break;
|
||||
case LEFT_RIGHT:
|
||||
case RIGHT_LEFT:
|
||||
setMinimumWidth(hist);
|
||||
break;
|
||||
}
|
||||
|
||||
// Fill waterfall pixmap
|
||||
_waterfall.fill(Qt::black);
|
||||
// Create color map
|
||||
//_colorMap = new GrayScaleColorMap(-120, 0);
|
||||
QVector<QColor> colors; colors.reserve(4);
|
||||
colors << Qt::black << Qt::red << Qt::yellow << Qt::white;
|
||||
_colorMap = new LinearColorMap(colors, -60, 0);
|
||||
|
||||
// Get notified once a new spectrum is available:
|
||||
QObject::connect(_spectrum, SIGNAL(spectrumUpdated()), this, SLOT(_onSpectrumUpdated()));
|
||||
QObject::connect(_spectrum, SIGNAL(spectrumConfigured()), this, SLOT(_onSpectrumConfigure()));
|
||||
}
|
||||
|
||||
void
|
||||
WaterFallView::_onSpectrumUpdated() {
|
||||
if (_waterfall.isNull() || (_M==0) || (_N==0)) { return; }
|
||||
|
||||
QPainter painter(&_waterfall);
|
||||
// scroll pixmap one pixel up
|
||||
QRect target(0,0, _N, _M-1), source(0,1,_N,_M-1);
|
||||
painter.drawPixmap(target, _waterfall, source);
|
||||
|
||||
// Draw new spectrum
|
||||
for (size_t i=0; i<_N; i++) {
|
||||
int idx = (_spectrum->fftSize()/2+i) % _spectrum->fftSize();
|
||||
double value;
|
||||
if ((TOP_DOWN == _dir) || (LEFT_RIGHT == _dir)) {
|
||||
value = 10*log10(_spectrum->spectrum()[_spectrum->fftSize()-1-idx])-10*log10(_N);
|
||||
} else {
|
||||
value = 10*log10(_spectrum->spectrum()[idx])-10*log10(_N);
|
||||
}
|
||||
// Reset NaNs
|
||||
if (value != value) { value = std::numeric_limits<double>::min(); }
|
||||
painter.setPen((*_colorMap)(value));
|
||||
painter.drawPoint(i, _M-1);
|
||||
}
|
||||
|
||||
// Trigger update
|
||||
this->update();
|
||||
}
|
||||
|
||||
void
|
||||
WaterFallView::_onSpectrumConfigure() {
|
||||
// Update spectrum width
|
||||
_N = _spectrum->fftSize();
|
||||
// Replace WaterFall pixmap
|
||||
_waterfall = QPixmap(_N, _M);
|
||||
// Fill waterfall pixmap
|
||||
_waterfall.fill(Qt::black);
|
||||
|
||||
// Trigger update
|
||||
this->update();
|
||||
}
|
||||
|
||||
void
|
||||
WaterFallView::mouseReleaseEvent(QMouseEvent *evt) {
|
||||
// If event is accepted -> determine frequency
|
||||
if ((evt->pos().x() < 0) || (evt->pos().x() > this->width())) { return; }
|
||||
double f=0;
|
||||
|
||||
if ((BOTTOM_UP == _dir) || (TOP_DOWN == _dir)) {
|
||||
double dfdx = _spectrum->sampleRate()/this->width();
|
||||
f = -_spectrum->sampleRate()/2 + dfdx*evt->pos().x();
|
||||
emit click(f);
|
||||
} else {
|
||||
double dfdx = _spectrum->sampleRate()/this->height();
|
||||
f = -_spectrum->sampleRate()/2 + dfdx*evt->pos().y();
|
||||
emit click(f);
|
||||
}
|
||||
|
||||
// Forward to default impl:
|
||||
QWidget::mouseReleaseEvent(evt);
|
||||
|
||||
// redraw
|
||||
this->update();
|
||||
}
|
||||
|
||||
void
|
||||
WaterFallView::paintEvent(QPaintEvent *evt)
|
||||
{
|
||||
QWidget::paintEvent(evt);
|
||||
|
||||
QPainter painter(this);
|
||||
|
||||
painter.save();
|
||||
painter.setRenderHints(QPainter::SmoothPixmapTransform|QPainter::Antialiasing);
|
||||
// Assemble trafo
|
||||
QTransform trafo;
|
||||
switch (_dir) {
|
||||
case BOTTOM_UP:
|
||||
trafo.scale(this->width()/qreal(_N), this->height()/qreal(_M));
|
||||
break;
|
||||
case LEFT_RIGHT:
|
||||
trafo.scale(this->width()/qreal(_M), this->height()/qreal(_N));
|
||||
trafo.translate(_M,0);
|
||||
trafo.rotate(90);
|
||||
break;
|
||||
case TOP_DOWN:
|
||||
trafo.scale(this->width()/qreal(_N), this->height()/qreal(_M));
|
||||
trafo.translate(_N,_M);
|
||||
trafo.rotate(180);
|
||||
break;
|
||||
case RIGHT_LEFT:
|
||||
trafo.scale(this->width()/qreal(_M), this->height()/qreal(_N));
|
||||
trafo.translate(0,_N);
|
||||
trafo.rotate(270);
|
||||
break;
|
||||
}
|
||||
painter.setTransform(trafo);
|
||||
QRect exposedRect = painter.matrix().inverted()
|
||||
.mapRect(evt->rect())
|
||||
.adjusted(-1, -1, 1, 1);
|
||||
// the adjust is to account for half pixels along edges
|
||||
painter.drawPixmap(exposedRect, _waterfall, exposedRect);
|
||||
//painter.drawPixmap(0,0, _waterfall);
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,119 +0,0 @@
|
||||
#ifndef __SDR_GUI_WATERFALLVIEW_HH__
|
||||
#define __SDR_GUI_WATERFALLVIEW_HH__
|
||||
|
||||
#include <QWidget>
|
||||
#include <QImage>
|
||||
#include "spectrum.hh"
|
||||
|
||||
|
||||
namespace sdr {
|
||||
namespace gui {
|
||||
|
||||
/** Interface for all color maps. */
|
||||
class ColorMap
|
||||
{
|
||||
protected:
|
||||
/** Hidden constructor. */
|
||||
ColorMap(double min, double max);
|
||||
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~ColorMap();
|
||||
|
||||
/** Maps a value to a color. */
|
||||
inline QColor operator()(const double &value) {
|
||||
if (value > _max) { return this->map(1); }
|
||||
if (value < _min) { return this->map(0); }
|
||||
return this->map((value-_min)/(_max-_min));
|
||||
}
|
||||
|
||||
/** Maps a value on the interval [0,1] to a color.
|
||||
* Needs to be implemented by all sub-classes. */
|
||||
virtual QColor map(const double &value) = 0;
|
||||
|
||||
protected:
|
||||
/** Minimum value. */
|
||||
double _min;
|
||||
/** Maximum value. */
|
||||
double _max;
|
||||
};
|
||||
|
||||
/** A simple gray-scale color map. */
|
||||
class GrayScaleColorMap: public ColorMap
|
||||
{
|
||||
public:
|
||||
/** Constructor.
|
||||
* @param mindB Specifices the minimum value in dB of the color-scale. Mapping values [min, max]
|
||||
* to a gray-scale. */
|
||||
GrayScaleColorMap(double min, double max);
|
||||
/** Destructor. */
|
||||
virtual ~GrayScaleColorMap();
|
||||
/** Implements the color mapping. */
|
||||
virtual QColor map(const double &value);
|
||||
};
|
||||
|
||||
/** A linear interpolating color map. */
|
||||
class LinearColorMap: public ColorMap {
|
||||
public:
|
||||
LinearColorMap(const QVector<QColor> &colors, double min, double max);
|
||||
/** Destructor. */
|
||||
virtual ~LinearColorMap();
|
||||
virtual QColor map(const double &value);
|
||||
|
||||
protected:
|
||||
QVector<QColor> _colors;
|
||||
};
|
||||
|
||||
|
||||
/** A simple waterfall display of the spectrogram. */
|
||||
class WaterFallView : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
typedef enum {
|
||||
BOTTOM_UP, TOP_DOWN, LEFT_RIGHT, RIGHT_LEFT
|
||||
} Direction;
|
||||
|
||||
public:
|
||||
/** Constructor.
|
||||
* @param spectrum Specifies the spectrum sink.
|
||||
* @param hist Specifies the number of PSDs to display.
|
||||
* @param parent The parent widget. */
|
||||
explicit WaterFallView(SpectrumProvider *spectrum, size_t hist=100, Direction dir=BOTTOM_UP, QWidget *parent = 0);
|
||||
|
||||
signals:
|
||||
void click(double f);
|
||||
|
||||
protected:
|
||||
/** Handles mouse clicks. */
|
||||
virtual void mouseReleaseEvent(QMouseEvent *evt);
|
||||
|
||||
/** Draws the scaled waterfall spectrogram. */
|
||||
virtual void paintEvent(QPaintEvent *evt);
|
||||
|
||||
protected slots:
|
||||
/** Gets called once a new PSD is available. */
|
||||
void _onSpectrumUpdated();
|
||||
/** Gets called once the spectrum provider gets reconfigured. */
|
||||
void _onSpectrumConfigure();
|
||||
|
||||
protected:
|
||||
/** The spectrum sink. */
|
||||
SpectrumProvider *_spectrum;
|
||||
/** The size of the spectrum. */
|
||||
size_t _N;
|
||||
/** "Height of the spectrum. */
|
||||
size_t _M;
|
||||
/** Specifies the direction of the waterfall. */
|
||||
Direction _dir;
|
||||
/** The waterfall spectrogram. */
|
||||
QPixmap _waterfall;
|
||||
/** The color map to be used. */
|
||||
ColorMap *_colorMap;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // WATERFALLVIEW_HH
|
||||
Loading…
Reference in New Issue