2
0
forked from Ivasoft/DSView
Files
DSView/DSView/pv/view/spectrumtrace.cpp
2023-01-09 17:28:50 +08:00

500 lines
15 KiB
C++

/*
* This file is part of the PulseView project.
* DSView is based on PulseView.
*
* Copyright (C) 2016 DreamSourceLab <support@dreamsourcelab.com>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <algorithm>
#include <math.h>
#include <QTextStream>
#include <boost/functional/hash.hpp>
#include <stdlib.h>
#include "spectrumtrace.h"
#include "../sigsession.h"
#include "../data/dsosnapshot.h"
#include "../view/dsosignal.h"
#include "../view/viewport.h"
#include "../data/spectrumstack.h"
#include "../dsvdef.h"
using namespace boost;
using namespace std;
namespace pv {
namespace view {
const int SpectrumTrace::UpMargin = 0;
const int SpectrumTrace::DownMargin = 0;
const int SpectrumTrace::RightMargin = 30;
const QString SpectrumTrace::FFT_ViewMode[2] = {
"Linear RMS",
"DBV RMS"
};
const QString SpectrumTrace::FreqPrefixes[9] =
{"", "", "", "", "K", "M", "G", "T", "P"};
const int SpectrumTrace::FirstSIPrefixPower = -9;
const int SpectrumTrace::LastSIPrefixPower = 15;
const int SpectrumTrace::Pricision = 2;
const int SpectrumTrace::FreqMinorDivNum = 10;
const int SpectrumTrace::TickHeight = 15;
const int SpectrumTrace::VolDivNum = 5;
const int SpectrumTrace::DbvRanges[4] = {
100,
120,
150,
200,
};
const int SpectrumTrace::HoverPointSize = 3;
const double SpectrumTrace::VerticalRate = 1.0 / 2000.0;
SpectrumTrace::SpectrumTrace(pv::SigSession *session,
pv::data::SpectrumStack *spectrum_stack, int index) :
Trace("FFT("+QString::number(index)+")", index, SR_CHANNEL_FFT),
_session(session),
_spectrum_stack(spectrum_stack),
_enable(false),
_view_mode(0),
_hover_en(false),
_scale(1),
_offset(0)
{
_typeWidth = 0;
for(auto s : _session->get_signals()) {
if (s->signal_type() == DSO_SIGNAL && index == s->get_index()){
_colour = s->get_colour();
}
}
}
SpectrumTrace::~SpectrumTrace()
{
DESTROY_OBJECT(_spectrum_stack);
}
bool SpectrumTrace::enabled()
{
return _enable;
}
void SpectrumTrace::set_enable(bool enable)
{
_enable = enable;
}
int SpectrumTrace::view_mode()
{
return _view_mode;
}
void SpectrumTrace::set_view_mode(unsigned int mode)
{
assert(mode < sizeof(FFT_ViewMode)/sizeof(FFT_ViewMode[0]));
_view_mode = mode;
}
std::vector<QString> SpectrumTrace::get_view_modes_support()
{
std::vector<QString> modes;
for (unsigned int i = 0; i < sizeof(FFT_ViewMode)/sizeof(FFT_ViewMode[0]); i++) {
modes.push_back(FFT_ViewMode[i]);
}
return modes;
}
pv::data::SpectrumStack* SpectrumTrace::get_spectrum_stack()
{
return _spectrum_stack;
}
void SpectrumTrace::init_zoom()
{
_scale = 1;
_offset = 0;
}
void SpectrumTrace::zoom(double steps, int offset)
{
if (!_view)
return;
const int width = get_view_rect().width();
double pre_offset = _offset + _scale*offset/width;
_scale *= std::pow(3.0/2.0, -steps);
_scale = max(min(_scale, 1.0), 100.0/_spectrum_stack->get_sample_num());
_offset = pre_offset - _scale*offset/width;
_offset = max(min(_offset, 1-_scale), 0.0);
_view->set_update(_viewport, true);
_view->update();
}
void SpectrumTrace::set_offset(double delta)
{
int width = get_view_rect().width();
_offset = _offset + (delta*_scale / width);
_offset = max(min(_offset, 1-_scale), 0.0);
_view->set_update(_viewport, true);
_view->update();
}
double SpectrumTrace::get_offset()
{
return _offset;
}
void SpectrumTrace::set_scale(double scale)
{
_scale = max(min(scale, 1.0), 100.0/_spectrum_stack->get_sample_num());
_view->set_update(_viewport, true);
_view->update();
}
double SpectrumTrace::get_scale()
{
return _scale;
}
void SpectrumTrace::set_dbv_range(int range)
{
_dbv_range = range;
}
int SpectrumTrace::dbv_range()
{
return _dbv_range;
}
std::vector<int> SpectrumTrace::get_dbv_ranges()
{
std::vector<int> range;
for (unsigned int i = 0; i < sizeof(DbvRanges)/sizeof(DbvRanges[0]); i++) {
range.push_back(DbvRanges[i]);
}
return range;
}
QString SpectrumTrace::format_freq(double freq, unsigned precision)
{
if (freq <= 0) {
return "0Hz";
} else {
const int order = floor(log10f(freq));
assert(order >= FirstSIPrefixPower);
assert(order <= LastSIPrefixPower);
const int prefix = floor((order - FirstSIPrefixPower)/ 3.0f);
const double divider = pow(10.0, max(prefix * 3.0 + FirstSIPrefixPower, 0.0));
/*
QString s;
QTextStream ts(&s);
ts.setRealNumberPrecision(precision);
ts << fixed << freq / divider << FreqPrefixes[prefix] << "Hz";
return s;
*/
char buffer[20] = {0};
char format[10] = {0};
QString units = FreqPrefixes[prefix] + "Hz";
sprintf(format, "%%.%df", (int)precision);
sprintf(buffer, format, freq / divider);
strcat(buffer, units.toUtf8().data());
return QString(buffer);
}
}
bool SpectrumTrace::measure(const QPoint &p)
{
_hover_en = false;
if(!_view || !enabled())
return false;
const QRect window = get_view_rect();
if (!window.contains(p))
return false;
const std::vector<double> samples(_spectrum_stack->get_fft_spectrum());
if(samples.empty())
return false;
const unsigned int full_size = (_spectrum_stack->get_sample_num()/2);
const double view_off = full_size * _offset;
const double view_size = full_size*_scale;
const double sample_per_pixels = view_size/window.width();
_hover_index = std::round(p.x() * sample_per_pixels + view_off);
if (_hover_index < full_size)
_hover_en = true;
//_view->set_update(_viewport, true);
_view->update();
return true;
}
void SpectrumTrace::paint_back(QPainter &p, int left, int right, QColor fore, QColor back)
{
if(!_view)
return;
const int height = get_view_rect().height();
const int width = right - left;
fore.setAlpha(View::BackAlpha);
QPen solidPen(fore);
solidPen.setStyle(Qt::SolidLine);
p.setPen(solidPen);
p.setBrush(back.black() > 0x80 ? back.darker() : back.lighter());
p.drawRect(left, UpMargin, width, height);
}
void SpectrumTrace::paint_mid(QPainter &p, int left, int right, QColor fore, QColor back)
{
(void)fore;
(void)back;
if(!_view)
return;
assert(right >= left);
if (enabled()) {
const std::vector<double> samples(_spectrum_stack->get_fft_spectrum());
if(samples.empty())
return;
QColor trace_colour = _colour;
trace_colour.setAlpha(View::ForeAlpha);
p.setPen(trace_colour);
const int full_size = (_spectrum_stack->get_sample_num()/2);
const double view_off = full_size * _offset;
const int view_start = floor(view_off);
const int view_size = full_size*_scale;
QPointF *points = new QPointF[samples.size()];
QPointF *point = points;
const bool dc_ignored = _spectrum_stack->dc_ignored();
const double height = get_view_rect().height();
const double width = right - left;
const double pixels_per_sample = width/view_size;
double vdiv = 0;
double vfactor = 0;
for(auto s : _session->get_signals()) {
if (s->signal_type() == DSO_SIGNAL) {
view::DsoSignal *dsoSig = (view::DsoSignal*)s;
if(dsoSig->get_index() == _spectrum_stack->get_index()) {
vdiv = dsoSig->get_vDialValue();
vfactor = dsoSig->get_factor();
break;
}
}
}
if (_view_mode == 0) {
_vmin = 0;
_vmax = (vdiv*DS_CONF_DSO_HDIVS*vfactor)*VerticalRate;
} else {
_vmax = 20*log10((vdiv*DS_CONF_DSO_HDIVS*vfactor)*VerticalRate);
_vmin = _vmax - _dbv_range;
}
const double scale = height / (_vmax - _vmin);
double x = (view_start-view_off)*pixels_per_sample;
uint64_t sample = view_start;
if (dc_ignored && sample == 0) {
sample++;
x += pixels_per_sample;
}
double min_mag = pow(10.0, _vmin/20);
do{
double mag = samples[sample];
if (_view_mode != 0) {
if (mag < min_mag)
mag = _vmin;
else
mag = 20*log10(mag);
}
const double y = height - (scale * (mag - _vmin));
*point++ = QPointF(x, y);
x += pixels_per_sample;
sample++;
}while(x<right && sample < samples.size());
p.drawPolyline(points, point - points);
delete[] points;
}
}
void SpectrumTrace::paint_fore(QPainter &p, int left, int right, QColor fore, QColor back)
{
using namespace Qt;
(void)back;
if(!_view)
return;
assert(right >= left);
(void)left;
(void)right;
const int text_height = p.boundingRect(0, 0, INT_MAX, INT_MAX,
AlignLeft | AlignTop, "8").height();
const double width = get_view_rect().width();
const double height = get_view_rect().height();
double blank_top = 0;
double blank_right = width;
// horizontal ruler
const double NyFreq = _session->cur_snap_samplerate() / (2.0 * _spectrum_stack->get_sample_interval());
const double deltaFreq = _session->cur_snap_samplerate() * 1.0 /
(_spectrum_stack->get_sample_num() * _spectrum_stack->get_sample_interval());
const double FreqRange = NyFreq * _scale;
const double FreqOffset = NyFreq * _offset;
const int order = (int)floor(log10(FreqRange));
const double multiplier = (pow(10.0, order) == FreqRange) ? FreqRange/10 : pow(10.0, order);
const double freq_per_pixel = FreqRange / width;
p.setPen(fore);
p.setBrush(Qt::NoBrush);
double tick_freq = multiplier * (int)floor(FreqOffset / multiplier);
int division = (int)round(tick_freq * FreqMinorDivNum / multiplier);
double x = (tick_freq - FreqOffset) / freq_per_pixel;
do{
if (division%FreqMinorDivNum == 0) {
QString freq_str = format_freq(tick_freq);
double typical_width = p.boundingRect(0, 0, INT_MAX, INT_MAX,
AlignLeft | AlignTop, freq_str).width() + 10;
p.drawLine(x, 1, x, TickHeight);
if (x > typical_width/2 && (width-x) > typical_width/2)
p.drawText(x-typical_width/2, TickHeight, typical_width, text_height,
AlignCenter | AlignTop | TextDontClip, freq_str);
} else {
p.drawLine(x, 1, x, TickHeight/2);
}
tick_freq += multiplier/FreqMinorDivNum;
division++;
x = (tick_freq - FreqOffset) / freq_per_pixel;
} while(x < width);
blank_top = max(blank_top, (double)TickHeight + text_height);
// delta Frequency
QString freq_str = QString::fromWCharArray(L" \u0394") + "Freq: " + format_freq(deltaFreq,4);
p.drawText(0, 0, width, get_view_rect().height(),
AlignRight | AlignBottom | TextDontClip, freq_str);
double delta_left = width-p.boundingRect(0, 0, INT_MAX, INT_MAX,
AlignLeft | AlignTop, freq_str).width();
blank_right = min(delta_left, blank_right);
// Vertical ruler
const double vRange = _vmax - _vmin;
const double vOffset = _vmin;
const double vol_per_tick = vRange / VolDivNum;
p.setPen(fore);
p.setBrush(Qt::NoBrush);
double tick_vol = vol_per_tick + vOffset;
double y = height - height / VolDivNum;
const QString unit = (_view_mode == 0) ? "" : "dbv";
do{
if (y > text_height && y < (height - text_height)) {
QString vol_str = QString::number(tick_vol, 'f', Pricision) + unit;
double vol_width = p.boundingRect(0, 0, INT_MAX, INT_MAX,
AlignLeft | AlignTop, vol_str).width();
p.drawLine(width, y, width-TickHeight/2, y);
p.drawText(width-TickHeight-vol_width, y-text_height/2, vol_width, text_height,
AlignCenter | AlignTop | TextDontClip, vol_str);
blank_right = min(width-TickHeight-vol_width, blank_right);
}
tick_vol += vol_per_tick;
y -= height / VolDivNum;
} while(y > 0);
// Hover measure
if (_hover_en) {
const std::vector<double> samples(_spectrum_stack->get_fft_spectrum());
if(samples.empty())
return;
const int full_size = (_spectrum_stack->get_sample_num()/2);
const double view_off = full_size * _offset;
const int view_size = full_size*_scale;
const double scale = height / (_vmax - _vmin);
const double pixels_per_sample = width/view_size;
double x = (_hover_index-view_off)*pixels_per_sample;
double min_mag = pow(10.0, _vmin/20);
_hover_value = samples[_hover_index];
if (_view_mode != 0) {
if (_hover_value < min_mag)
_hover_value = _vmin;
else
_hover_value = 20*log10(_hover_value);
}
const double y = height - (scale * (_hover_value - _vmin));
_hover_point = QPointF(x, y);
p.setPen(QPen(fore, 1, Qt::DashLine));
p.setBrush(Qt::NoBrush);
p.drawLine(_hover_point.x(), 0, _hover_point.x(), height);
QString hover_str = QString::number(_hover_value, 'f', 4) + unit + "@" + format_freq(deltaFreq * _hover_index, 4);
const int hover_width = p.boundingRect(0, 0, INT_MAX, INT_MAX,
AlignLeft | AlignTop, hover_str).width();
const int hover_height = p.boundingRect(0, 0, INT_MAX, INT_MAX,
AlignLeft | AlignTop, hover_str).height();
QRectF hover_rect(_hover_point.x(), _hover_point.y()-hover_height, hover_width, hover_height);
if (hover_rect.right() > blank_right)
hover_rect.moveRight(min(_hover_point.x(), blank_right));
if (hover_rect.top() < blank_top)
hover_rect.moveTop(max(_hover_point.y(), blank_top));
if (hover_rect.top() > 0)
p.drawText(hover_rect, AlignCenter | AlignTop | TextDontClip, hover_str);
p.setPen(Qt::NoPen);
p.setBrush(fore);
p.drawEllipse(_hover_point, HoverPointSize, HoverPointSize);
}
}
void SpectrumTrace::paint_type_options(QPainter &p, int right, const QPoint pt, QColor fore)
{
(void)p;
(void)pt;
(void)right;
(void)fore;
}
QRect SpectrumTrace::get_view_rect()
{
assert(_viewport);
return QRect(0, UpMargin,
_viewport->width() - RightMargin,
_viewport->height() - UpMargin - DownMargin);
}
} // namespace view
} // namespace pv