1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-03-31 12:15:36 -04:00
sdrangel/sdrgui/gui/glspectrumview.cpp
2026-03-25 19:06:11 +00:00

5900 lines
201 KiB
C++

///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2014 John Greb <hexameron@spam.no> //
// Copyright (C) 2015-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2015 Hoernchen <la@tfc-server.de> //
// Copyright (C) 2018 beta-tester <alpha-beta-release@gmx.net> //
// Copyright (C) 2022-2023 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// Copyright (C) 2023 Arne Jünemann <das-iro@das-iro.de> //
// //
// 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 as 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 V3 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <algorithm>
#include <QtAlgorithms>
#include <QMouseEvent>
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions>
#include <QPainter>
#include <QFontDatabase>
#include <QWindow>
#include <QGestureEvent>
#include <QPanGesture>
#include <QPinchGesture>
#include "maincore.h"
#include "dsp/spectrumvis.h"
#include "gui/glspectrumview.h"
#include "gui/spectrummeasurements.h"
#include "settings/mainsettings.h"
#include "util/messagequeue.h"
#include "util/db.h"
#include "util/profiler.h"
#include "util/csv.h"
#include <QDebug>
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportSampleRate, Message)
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportWaterfallShare, Message)
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportFFTOverlap, Message)
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportPowerScale, Message)
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportCalibrationShift, Message)
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportHistogramMarkersChange, Message)
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgReportWaterfallMarkersChange, Message)
MESSAGE_CLASS_DEFINITION(GLSpectrumView::MsgFrequencyZooming, Message)
const float GLSpectrumView::m_maxFrequencyZoom = 50.0f;
const float GLSpectrumView::m_annotationMarkerHeight = 20.0f;
GLSpectrumView::GLSpectrumView(QWidget* parent) :
QOpenGLWidget(parent),
m_markersDisplay(SpectrumSettings::MarkersDisplaySpectrum),
m_histogramFindPeaks(false),
m_cursorState(CSNormal),
m_cursorChannel(0),
m_spectrumVis(nullptr),
m_fpsPeriodMs(50),
m_mouseInside(false),
m_changesPending(true),
m_redrawAll(false),
m_centerFrequency(100000000),
m_referenceLevel(0.0f),
m_minReferenceLevel(-120.0f),
m_maxReferenceLevel(0.0f),
m_powerRange(100.0),
m_minPowerRange(1.0f),
m_maxPowerRange(120.0f),
m_linear(false),
m_decay(1),
m_sampleRate(500000),
m_timingRate(1),
m_fftOverlap(0),
m_fftSize(512),
m_nbBins(512),
m_fftMin(0),
m_fftMax(512),
m_displayGrid(true),
m_displayGridIntensity(5),
m_displayTraceIntensity(50),
m_invertedWaterfall(true),
m_displayMaxHold(false),
m_currentSpectrum(nullptr),
m_displayCurrent(false),
m_leftMargin(0),
m_rightMargin(0),
m_topMargin(0),
m_frequencyScaleHeight(0),
m_histogramHeight(80),
m_waterfallHeight(0),
m_bottomMargin(0),
m_waterfallBuffer(nullptr),
m_waterfallBufferPos(0),
m_waterfallTextureHeight(-1),
m_waterfallTexturePos(0),
m_displayWaterfall(true),
m_ssbSpectrum(false),
m_lsbDisplay(false),
m_3DSpectrogramBuffer(nullptr),
m_3DSpectrogramBufferPos(0),
m_3DSpectrogramTextureHeight(-1),
m_3DSpectrogramTexturePos(0),
m_display3DSpectrogram(false),
m_rotate3DSpectrogram(false),
m_pan3DSpectrogram(false),
m_scaleZ3DSpectrogram(false),
m_3DSpectrogramStyle(SpectrumSettings::Outline),
m_spectrumColor(255, 255, 63),
m_colorMapName("Angel"),
m_scrollFrequency(false),
m_scrollStartCenterFreq(0),
m_pinching(false),
m_pinching3D(false),
m_frequencyRequested(false),
m_nextFrequencyValid(false),
m_histogramBuffer(nullptr),
m_histogram(nullptr),
m_displayHistogram(true),
m_displayChanged(false),
m_displaySourceOrSink(true),
m_displayStreamIndex(0),
m_matrixLoc(0),
m_colorLoc(0),
m_useCalibration(false),
m_calibrationGain(1.0),
m_calibrationShiftdB(0.0),
m_calibrationInterpMode(SpectrumSettings::CalibInterpLinear),
m_messageQueueToGUI(nullptr),
m_openGLLogger(nullptr),
m_isDeviceSpectrum(false),
m_measurements(nullptr),
m_measurement(SpectrumSettings::MeasurementNone),
m_measurementCenterFrequencyOffset(0),
m_measurementBandwidth(10000),
m_measurementChSpacing(10000),
m_measurementAdjChBandwidth(10000),
m_measurementHarmonics(5),
m_measurementPeaks(5),
m_measurementHighlight(true),
m_measurementPrecision(1),
m_measurementMemMasks(0),
m_maskTestCount(SpectrumSettings::m_maxSpectrumMemories),
m_maskFailCount(SpectrumSettings::m_maxSpectrumMemories),
m_maskFails(SpectrumSettings::m_maxSpectrumMemories),
m_displayRBW(false),
m_displayCursorStats(false),
m_displayPeakStats(false),
m_cursorOverSpectrum(false),
m_cursorFrequency(0.0f),
m_spectrumBuffer(0),
m_spectrumBufferFFTSize(0),
m_spectrumBufferMaxSize(100000),
m_scrollBar(nullptr),
m_scrollBarEnabled(false),
m_scrollBarValue(0),
m_waterfallTimeUnits(SpectrumSettings::TimeOffset),
m_waterfallTimeFormat("hh:mm:ss"),
m_spectrumMemory(SpectrumSettings::m_maxSpectrumMemories)
{
// Enable multisampling anti-aliasing (MSAA)
int multisamples = MainCore::instance()->getSettings().getMultisampling();
if (multisamples > 0)
{
QSurfaceFormat format;
format.setSamples(multisamples);
setFormat(format);
}
setObjectName("GLSpectrum");
setAutoFillBackground(false);
setAttribute(Qt::WA_OpaquePaintEvent, true);
setAttribute(Qt::WA_NoSystemBackground, true);
setMouseTracking(true);
setMinimumSize(360, 200);
m_waterfallShare = 0.5;
for (int i = 0; i <= 239; i++)
{
QColor c;
c.setHsv(239 - i, 255, 15 + i);
((quint8*)&m_waterfallPalette[i])[0] = c.red();
((quint8*)&m_waterfallPalette[i])[1] = c.green();
((quint8*)&m_waterfallPalette[i])[2] = c.blue();
((quint8*)&m_waterfallPalette[i])[3] = c.alpha();
}
m_waterfallPalette[239] = 0xffffffff;
m_histogramPalette[0] = 0;
for (int i = 1; i < 240; i++)
{
QColor c;
int light = i < 60 ? 128 + (60-i) : 128;
int sat = i < 60 ? 140 + i : i < 180 ? 200 : 200 - (i-180);
c.setHsl(239 - i, sat, light);
((quint8*)&m_histogramPalette[i])[0] = c.red();
((quint8*)&m_histogramPalette[i])[1] = c.green();
((quint8*)&m_histogramPalette[i])[2] = c.blue();
((quint8*)&m_histogramPalette[i])[3] = c.alpha();
}
// 4.2.3 palette
// for (int i = 1; i < 240; i++)
// {
// QColor c;
// int val = i < 60 ? 255 : 200;
// int sat = i < 60 ? 128 : i < 180 ? 255 : 180;
// c.setHsv(239 - i, sat, val);
// ((quint8*)&m_histogramPalette[i])[0] = c.red();
// ((quint8*)&m_histogramPalette[i])[1] = c.green();
// ((quint8*)&m_histogramPalette[i])[2] = c.blue();
// ((quint8*)&m_histogramPalette[i])[3] = c.alpha();
// }
// Original palette:
// for(int i = 16; i < 240; i++) {
// QColor c;
// c.setHsv(239 - i, 255 - ((i < 200) ? 0 : (i - 200) * 3), 150 + ((i < 100) ? i : 100));
// ((quint8*)&m_histogramPalette[i])[0] = c.red();
// ((quint8*)&m_histogramPalette[i])[1] = c.green();
// ((quint8*)&m_histogramPalette[i])[2] = c.blue();
// ((quint8*)&m_histogramPalette[i])[3] = c.alpha();
// }
// for(int i = 1; i < 16; i++) {
// QColor c;
// c.setHsv(255, 128, 48 + i * 4);
// ((quint8*)&m_histogramPalette[i])[0] = c.red();
// ((quint8*)&m_histogramPalette[i])[1] = c.green();
// ((quint8*)&m_histogramPalette[i])[2] = c.blue();
// ((quint8*)&m_histogramPalette[i])[3] = c.alpha();
// }
m_decayDivisor = 1;
m_decayDivisorCount = m_decayDivisor;
m_histogramStroke = 30;
m_timeScale.setFont(font());
m_timeScale.setOrientation(Qt::Vertical);
m_timeScale.setRange(Unit::Time, 0, 1);
m_powerScale.setFont(font());
m_powerScale.setOrientation(Qt::Vertical);
m_frequencyScale.setFont(font());
m_frequencyScale.setOrientation(Qt::Horizontal);
m_textOverlayFont = font(); // QFontDatabase::systemFont(QFontDatabase::FixedFont);
m_textOverlayFont.setBold(true);
// m_textOverlayFont.setPointSize(font().pointSize() - 1);
resetFrequencyZoom();
m_timer.setTimerType(Qt::PreciseTimer);
connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
m_timer.start(m_fpsPeriodMs);
// Handle KeyEvents
setFocusPolicy(Qt::StrongFocus);
installEventFilter(this);
grabGesture(Qt::PinchGesture);
}
GLSpectrumView::~GLSpectrumView()
{
QMutexLocker mutexLocker(&m_mutex);
if (m_waterfallBuffer)
{
delete m_waterfallBuffer;
m_waterfallBuffer = nullptr;
}
if (m_3DSpectrogramBuffer)
{
delete m_3DSpectrogramBuffer;
m_3DSpectrogramBuffer = nullptr;
}
if (m_histogramBuffer)
{
delete m_histogramBuffer;
m_histogramBuffer = nullptr;
}
if (m_histogram)
{
delete[] m_histogram;
m_histogram = nullptr;
}
if (m_openGLLogger)
{
delete m_openGLLogger;
m_openGLLogger = nullptr;
}
clearSpectrumBuffer();
}
void GLSpectrumView::queueRequestCenterFrequency(qint64 frequency)
{
if (!m_frequencyRequested)
{
m_frequencyRequested = true;
m_requestedFrequency = frequency;
emit requestCenterFrequency(frequency);
}
else
{
m_nextFrequencyValid = true;
m_nextFrequency = frequency;
}
}
void GLSpectrumView::setCenterFrequency(qint64 frequency)
{
m_mutex.lock();
m_centerFrequency = frequency;
// Handle queued frequency requests
if (m_frequencyRequested && (frequency == m_requestedFrequency))
{
m_frequencyRequested = false;
if (m_nextFrequencyValid)
{
m_nextFrequencyValid = false;
queueRequestCenterFrequency(m_nextFrequency);
}
}
if (m_useCalibration) {
updateCalibrationPoints();
}
m_changesPending = true;
m_mutex.unlock();
update();
}
void GLSpectrumView::setReferenceLevel(Real referenceLevel)
{
m_mutex.lock();
m_referenceLevel = referenceLevel;
m_changesPending = true;
redrawWaterfallAnd3DSpectrogram();
m_mutex.unlock();
update();
}
void GLSpectrumView::setPowerRange(Real powerRange)
{
m_mutex.lock();
m_powerRange = powerRange;
m_changesPending = true;
redrawWaterfallAnd3DSpectrogram();
m_mutex.unlock();
update();
}
void GLSpectrumView::setDecay(int decay)
{
m_decay = decay < 0 ? 0 : decay > 20 ? 20 : decay;
}
void GLSpectrumView::setDecayDivisor(int decayDivisor)
{
m_decayDivisor = decayDivisor < 1 ? 1 : decayDivisor > 20 ? 20 : decayDivisor;
}
void GLSpectrumView::setHistoStroke(int stroke)
{
m_histogramStroke = stroke < 1 ? 1 : stroke > 60 ? 60 : stroke;
}
void GLSpectrumView::setSampleRate(qint32 sampleRate)
{
m_mutex.lock();
m_sampleRate = sampleRate;
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportSampleRate(m_sampleRate));
}
m_changesPending = true;
m_mutex.unlock();
update();
}
void GLSpectrumView::setTimingRate(qint32 timingRate)
{
m_mutex.lock();
m_timingRate = timingRate;
m_changesPending = true;
m_mutex.unlock();
update();
}
void GLSpectrumView::setFFTOverlap(int overlap)
{
m_mutex.lock();
m_fftOverlap = overlap;
m_changesPending = true;
m_mutex.unlock();
update();
}
void GLSpectrumView::setDisplayWaterfall(bool display)
{
m_mutex.lock();
m_displayWaterfall = display;
if (!display)
{
m_waterfallMarkers.clear();
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportWaterfallMarkersChange());
}
}
m_changesPending = true;
stopDrag();
m_mutex.unlock();
update();
}
void GLSpectrumView::setDisplay3DSpectrogram(bool display)
{
m_mutex.lock();
m_display3DSpectrogram = display;
m_changesPending = true;
stopDrag();
m_mutex.unlock();
update();
}
void GLSpectrumView::setSpectrumStyle(SpectrumSettings::SpectrumStyle style)
{
m_spectrumStyle = style;
update();
}
void GLSpectrumView::set3DSpectrogramStyle(SpectrumSettings::SpectrogramStyle style)
{
m_3DSpectrogramStyle = style;
update();
}
void GLSpectrumView::setSpectrumColor(QRgb color)
{
m_mutex.lock();
m_spectrumColor = QColor(color);
m_changesPending = true;
m_redrawAll = true;
m_mutex.unlock();
update();
}
void GLSpectrumView::setColorMapName(const QString &colorMapName)
{
m_mutex.lock();
m_colorMapName = colorMapName;
m_changesPending = true;
m_redrawAll = true;
m_mutex.unlock();
update();
}
void GLSpectrumView::setSsbSpectrum(bool ssbSpectrum)
{
m_ssbSpectrum = ssbSpectrum;
update();
}
void GLSpectrumView::setLsbDisplay(bool lsbDisplay)
{
m_lsbDisplay = lsbDisplay;
update();
}
void GLSpectrumView::setInvertedWaterfall(bool inv)
{
m_mutex.lock();
m_invertedWaterfall = inv;
m_changesPending = true;
stopDrag();
if (m_scrollBar) {
m_scrollBar->setInvertedAppearance(!inv);
}
m_mutex.unlock();
update();
}
void GLSpectrumView::setDisplayMaxHold(bool display)
{
m_mutex.lock();
m_displayMaxHold = display;
if (!m_displayMaxHold && !m_displayCurrent && !m_displayHistogram)
{
m_histogramMarkers.clear();
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportHistogramMarkersChange());
}
}
m_changesPending = true;
stopDrag();
m_mutex.unlock();
update();
}
void GLSpectrumView::setDisplayCurrent(bool display)
{
m_mutex.lock();
m_displayCurrent = display;
if (!m_displayMaxHold && !m_displayCurrent && !m_displayHistogram)
{
m_histogramMarkers.clear();
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportHistogramMarkersChange());
}
}
m_changesPending = true;
stopDrag();
m_mutex.unlock();
update();
}
void GLSpectrumView::setDisplayHistogram(bool display)
{
m_mutex.lock();
m_displayHistogram = display;
if (!m_displayMaxHold && !m_displayCurrent && !m_displayHistogram)
{
m_histogramMarkers.clear();
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportHistogramMarkersChange());
}
}
m_changesPending = true;
stopDrag();
m_mutex.unlock();
update();
}
void GLSpectrumView::setDisplayGrid(bool display)
{
m_displayGrid = display;
update();
}
void GLSpectrumView::setDisplayGridIntensity(int intensity)
{
m_displayGridIntensity = intensity;
if (m_displayGridIntensity > 100) {
m_displayGridIntensity = 100;
} else if (m_displayGridIntensity < 0) {
m_displayGridIntensity = 0;
}
update();
}
void GLSpectrumView::setDisplayTraceIntensity(int intensity)
{
m_displayTraceIntensity = intensity;
if (m_displayTraceIntensity > 100) {
m_displayTraceIntensity = 100;
} else if (m_displayTraceIntensity < 0) {
m_displayTraceIntensity = 0;
}
update();
}
void GLSpectrumView::setFreqScaleTruncationMode(bool mode)
{
m_frequencyScale.setTruncateMode(mode);
update();
}
void GLSpectrumView::setLinear(bool linear)
{
m_mutex.lock();
m_linear = linear;
m_changesPending = true;
m_mutex.unlock();
update();
}
void GLSpectrumView::setUseCalibration(bool useCalibration)
{
m_mutex.lock();
m_useCalibration = useCalibration;
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportCalibrationShift(m_useCalibration ? m_calibrationShiftdB : 0.0));
}
m_changesPending = true;
m_mutex.unlock();
update();
}
void GLSpectrumView::setMeasurementParams(
SpectrumSettings::Measurement measurement,
int centerFrequencyOffset,
int bandwidth,
int chSpacing,
int adjChBandwidth,
int harmonics,
int peaks,
bool highlight,
int precision,
unsigned memoryMask
)
{
m_mutex.lock();
m_measurement = measurement;
m_measurementCenterFrequencyOffset = centerFrequencyOffset;
m_measurementBandwidth = bandwidth;
m_measurementChSpacing = chSpacing;
m_measurementAdjChBandwidth = adjChBandwidth;
m_measurementHarmonics = harmonics;
m_measurementPeaks = peaks;
m_measurementHighlight = highlight;
m_measurementPrecision = precision;
m_measurementMemMasks = memoryMask;
m_changesPending = true;
if (m_measurements) {
m_measurements->setMeasurementParams(measurement, peaks, precision, memoryMask);
}
m_mutex.unlock();
update();
}
void GLSpectrumView::resetMeasurements()
{
m_mutex.lock();
for (std::size_t i = 0; i < m_maskFails.size(); ++i)
{
m_maskTestCount[i] = 0;
m_maskFailCount[i] = 0;
int s = std::min((int) m_maskFails[i].size(), (int) m_spectrumMemory[i].m_spectrum.size());
for (int j = 0; j < s; j++) {
m_maskFails[i][j] = m_spectrumMemory[i].m_spectrum[j];
}
}
m_mutex.unlock();
}
void GLSpectrumView::addChannelMarker(ChannelMarker* channelMarker)
{
m_mutex.lock();
connect(channelMarker, SIGNAL(changedByAPI()), this, SLOT(channelMarkerChanged()));
connect(channelMarker, SIGNAL(destroyed(QObject*)), this, SLOT(channelMarkerDestroyed(QObject*)));
m_channelMarkerStates.append(new ChannelMarkerState(channelMarker));
m_changesPending = true;
stopDrag();
m_mutex.unlock();
update();
}
void GLSpectrumView::removeChannelMarker(ChannelMarker* channelMarker)
{
m_mutex.lock();
for (int i = 0; i < m_channelMarkerStates.size(); ++i)
{
if (m_channelMarkerStates[i]->m_channelMarker == channelMarker)
{
channelMarker->disconnect(this);
delete m_channelMarkerStates.takeAt(i);
m_changesPending = true;
stopDrag();
m_mutex.unlock();
update();
return;
}
}
m_mutex.unlock();
}
void GLSpectrumView::setHistogramMarkers(const QList<SpectrumHistogramMarker>& histogramMarkers)
{
m_mutex.lock();
m_histogramMarkers = histogramMarkers;
updateHistogramMarkers();
m_changesPending = true;
m_mutex.unlock();
update();
}
void GLSpectrumView::setWaterfallMarkers(const QList<SpectrumWaterfallMarker>& waterfallMarkers)
{
m_mutex.lock();
m_waterfallMarkers = waterfallMarkers;
updateWaterfallMarkers();
m_changesPending = true;
m_mutex.unlock();
update();
}
void GLSpectrumView::setAnnotationMarkers(const QList<SpectrumAnnotationMarker>& annotationMarkers)
{
m_mutex.lock();
m_annotationMarkers = annotationMarkers;
updateAnnotationMarkers();
m_changesPending = true;
m_mutex.unlock();
update();
}
void GLSpectrumView::setMarkersDisplay(SpectrumSettings::MarkersDisplay markersDisplay)
{
m_mutex.lock();
m_markersDisplay = markersDisplay;
updateMarkersDisplay();
m_changesPending = true;
m_mutex.unlock();
update();
}
void GLSpectrumView::setCalibrationPoints(const QList<SpectrumCalibrationPoint>& calibrationPoints)
{
m_mutex.lock();
m_calibrationPoints = calibrationPoints;
updateCalibrationPoints();
m_changesPending = true;
m_mutex.unlock();
update();
}
void GLSpectrumView::setCalibrationInterpMode(SpectrumSettings::CalibrationInterpolationMode mode)
{
m_mutex.lock();
m_calibrationInterpMode = mode;
updateCalibrationPoints();
m_changesPending = true;
m_mutex.unlock();
update();
}
float GLSpectrumView::getPowerMax() const
{
return m_linear ? m_powerScale.getRangeMax() : CalcDb::powerFromdB(m_powerScale.getRangeMax());
}
float GLSpectrumView::getTimeMax() const
{
return m_timeScale.getRangeMax();
}
void GLSpectrumView::setWaterfallTimeFormat(SpectrumSettings::WaterfallTimeUnits waterfallTimeUnits, const QString& format)
{
QMutexLocker mutexLocker(&m_mutex);
m_waterfallTimeUnits = waterfallTimeUnits;
m_waterfallTimeFormat = format;
if (m_waterfallTimeUnits == SpectrumSettings::TimeOffset) {
m_timeScale.setTickFormatter(nullptr);
} else {
m_timeScale.setTickFormatter(this);
}
m_changesPending = true;
}
void GLSpectrumView::setStatusLine(bool displayRBW, bool displayCursorStats, bool displayPeakStats)
{
m_displayRBW = displayRBW;
m_displayCursorStats = displayCursorStats;
m_displayPeakStats = displayPeakStats;
m_displayChanged = true;
}
void GLSpectrumView::setScrolling(bool enabled, int length)
{
m_scrollBar->setVisible(enabled); // Must call before we lock mutex, otherwise we can deadlock
QMutexLocker mutexLocker(&m_mutex);
if (!enabled)
{
if (!m_spectrumBuffer.isEmpty()) {
clearSpectrumBuffer();
}
}
else
{
while (m_spectrumBuffer.size() > (std::size_t) length)
{
if (m_currentSpectrum == m_spectrumBuffer.takeFirst().m_spectrum) {
m_currentSpectrum = nullptr;
}
delete[] m_spectrumBuffer.takeFirst().m_spectrum;
}
}
m_spectrumBuffer.resize(length);
m_spectrumBufferMaxSize = length;
m_scrollBarEnabled = enabled;
}
void GLSpectrumView::setScrollBar(QScrollBar* scrollBar)
{
m_scrollBar = scrollBar;
connect(m_scrollBar, &QScrollBar::valueChanged, this, &GLSpectrumView::scrollBarValueChanged);
}
void GLSpectrumView::updateScrollBar()
{
QMutexLocker mutexLocker(&m_mutex);
if (m_scrollBar && m_waterfallBuffer)
{
qint64 max = std::max<qint64>(0, (m_spectrumBuffer.size() - 1 - m_waterfallBuffer->height()));
m_scrollBar->setMaximum(max);
m_scrollBar->setPageStep(m_waterfallBuffer->height());
}
}
void GLSpectrumView::scrollBarValueChanged(int value)
{
m_scrollBarValue = value;
m_redrawAll = true;
m_changesPending = true; // Update waterfall time scale
m_displayChanged = true;
}
int GLSpectrumView::scrollBarValue() const
{
// We keep a local copy of scroll bar value, so it can be accessed by any thread
return m_scrollBarValue;
}
// Read spectrum from .csv file
void GLSpectrumView::readCSV(QTextStream &in, bool append, QString &error)
{
QMutexLocker mutexLocker(&m_mutex);
if (!append)
{
clearSpectrumBuffer();
m_redrawAll = true;
m_changesPending = true;
}
QHash<QString, int> colIndexes = CSV::readHeader(in, {}, error);
if (error.isEmpty())
{
if (colIndexes.contains("Frequency") && colIndexes.contains("Power"))
{
int frequencyCol = colIndexes.value("Frequency");
int powerCol = colIndexes.value("Power");
int maxCol = std::max({frequencyCol, powerCol});
QStringList cols;
QVector<qint64> frequencies;
QVector<float> power;
while (CSV::readRow(in, &cols))
{
if (cols.size() > maxCol)
{
frequencies.append(cols[frequencyCol].toLongLong());
power.append(cols[powerCol].toFloat());
}
}
if (power.size() != m_fftSize) {
error = QString("CSV data does not contain expected number of points for current FFT size. (Points: %1 - FFT size: %2").arg(power.size()).arg(m_fftSize);
} else {
newSpectrum(power.data(), m_fftSize, m_sampleRate, frequencies[m_fftSize/2], QDateTime::currentDateTime());
}
}
else if (colIndexes.contains("Date and Time") && colIndexes.contains("Center Frequency (Hz)") && colIndexes.contains("Sample Rate (Hz)") && colIndexes.contains("Power"))
{
int dateTimeCol = colIndexes.value("Date and Time");
int frequencyCol = colIndexes.value("Center Frequency (Hz)");
int sampleRateCol = colIndexes.value("Sample Rate (Hz)");
int powerCol = colIndexes.value("Power");
int maxCol = std::max({dateTimeCol, frequencyCol, sampleRateCol, powerCol});
QStringList cols;
QVector<float> spectrum;
while (CSV::readRow(in, &cols))
{
if (cols.size() > maxCol)
{
QDateTime dateTime = QDateTime::fromString(cols[dateTimeCol], Qt::ISODateWithMs);
qint64 centerFrequency = cols[frequencyCol].toLongLong();
quint32 sampleRate = cols[sampleRateCol].toUInt();
spectrum.clear();
for (int i = 0; i < m_fftSize; i++)
{
if (powerCol + i < cols.size()) {
spectrum.append(cols[powerCol + i].toFloat());
}
}
if (spectrum.size() != m_fftSize)
{
error = QString("CSV data does not contain expected number of points for current FFT size. (Points: %1 - FFT size: %2").arg(spectrum.size()).arg(m_fftSize);
break;
}
else
{
newSpectrum(spectrum.data(), m_fftSize, sampleRate, centerFrequency, dateTime);
}
}
}
}
else
{
error = "CSV header does not contain required columns";
}
}
update();
}
// Write spectrum to .csv file
void GLSpectrumView::writeCSV(QTextStream &out)
{
QMutexLocker mutexLocker(&m_mutex);
if (m_spectrumBuffer.isEmpty())
{
float frequency = getCenterFrequency() - (getSampleRate() / 2.0f);
float rbw = getSampleRate() / (float) m_fftSize;
out << "\"Frequency\",\"Power\"\n";
for (int i = 0; i < m_fftSize; i++)
{
out << frequency << "," << m_currentSpectrum[i] << "\n";
frequency += rbw;
}
}
else
{
out << "\"Date and Time\",\"Center Frequency (Hz)\",\"Sample Rate (Hz)\",\"Power\"\n";
for (std::size_t j = 0; j < m_spectrumBuffer.size(); j++)
{
out << m_spectrumBuffer[j].m_dateTime.toString(Qt::ISODateWithMs) << "," << m_spectrumBuffer[j].m_centerFrequency << "," << m_spectrumBuffer[j].m_sampleRate << ",";
for (int i = 0; i < m_spectrumBufferFFTSize; i++) {
out << m_spectrumBuffer[j].m_spectrum[i] << ",";
}
out << "\n";
}
}
}
// Write spectrum/waterfall image to file
bool GLSpectrumView::writeImage(const QString& filename)
{
QImage image = grabFramebuffer();
return image.save(filename);
}
// Get center frequency for currently displayed spectrum (which is selected via the scroll bar)
qint64 GLSpectrumView::getDisplayedCenterFrequency() const
{
int idx = m_spectrumBuffer.size() - 1 - scrollBarValue();
if ((idx >= 0) && (idx < (int) m_spectrumBuffer.size())) {
return m_spectrumBuffer[idx].m_centerFrequency;
} else {
return m_centerFrequency;
}
}
// Get sample rate for currently displayed spectrum (which is selected via the scroll bar)
quint32 GLSpectrumView::getDisplayedSampleRate() const
{
int idx = m_spectrumBuffer.size() - 1 - scrollBarValue();
if ((idx >= 0) && (idx < (int) m_spectrumBuffer.size())) {
return m_spectrumBuffer[idx].m_sampleRate;
} else {
return m_sampleRate;
}
}
void GLSpectrumView::redrawSpectrum()
{
if (m_spectrumBuffer.size() > 0)
{
int idx = m_spectrumBuffer.size() - 1 - scrollBarValue();
if (idx >= 0 && idx < (int) m_spectrumBuffer.size())
{
updateHistogram(m_spectrumBuffer[idx].m_spectrum, m_fftMin, m_nbBins);
m_currentSpectrum = m_spectrumBuffer[idx].m_spectrum;
}
}
}
void GLSpectrumView::redrawWaterfallAnd3DSpectrogram()
{
if (m_waterfallBuffer && m_spectrumBuffer.size() > 0)
{
int idx = m_spectrumBuffer.size() - 1 - m_waterfallBuffer->height() - scrollBarValue();
m_waterfallBufferPos = 0;
m_waterfallTexturePos = 0;
m_3DSpectrogramBufferPos = 0;
m_3DSpectrogramTexturePos = 0;
for (int i = 0; i < m_waterfallBuffer->height(); i++)
{
if (idx >= 0 && idx < m_spectrumBuffer.size())
{
updateWaterfall(m_spectrumBuffer[idx].m_spectrum, m_fftSize, m_fftMin, m_nbBins);
update3DSpectrogram(m_spectrumBuffer[idx].m_spectrum, m_fftSize, m_fftMin, m_nbBins);
}
else
{
clearWaterfallRow(m_nbBins);
clear3DSpectrogramRow(m_nbBins);
}
idx++;
}
}
}
void GLSpectrumView::measure(const Real *spectrum, bool updateGUI)
{
switch (m_measurement)
{
case SpectrumSettings::MeasurementPeaks:
if (updateGUI) {
measurePeaks(spectrum);
}
break;
case SpectrumSettings::MeasurementChannelPower:
measureChannelPower(spectrum, updateGUI);
break;
case SpectrumSettings::MeasurementAdjacentChannelPower:
measureAdjacentChannelPower(spectrum, updateGUI);
break;
case SpectrumSettings::MeasurementOccupiedBandwidth:
measureOccupiedBandwidth(spectrum, updateGUI);
break;
case SpectrumSettings::Measurement3dBBandwidth:
measure3dBBandwidth(spectrum, updateGUI);
break;
case SpectrumSettings::MeasurementSNR:
measureSNR(spectrum, updateGUI);
measureSFDR(spectrum, updateGUI);
break;
case SpectrumSettings::MeasurementMask:
measureMask(spectrum, m_fftSize, updateGUI);
break;
default:
break;
}
}
// newSpectrum can be called at a much faster rate than paintGL for high sample rates
// Will typically be called from device source engine thread, so shouldn't touch UI
void GLSpectrumView::newSpectrum(const Real *spectrum, int fftSize)
{
PROFILER_START();
QMutexLocker mutexLocker(&m_mutex);
newSpectrum(spectrum, fftSize, m_sampleRate, m_centerFrequency, QDateTime::currentDateTime());
PROFILER_STOP("newSpectrum");
}
void GLSpectrumView::newSpectrum(const Real *spectrum, int fftSize, quint32 sampleRate, qint64 centerFrequency, const QDateTime &dateTime)
{
int offset = 0;
int idx;
m_displayChanged = true;
if (fftSize != m_fftSize)
{
m_fftSize = fftSize;
updateFFTLimits(true);
m_changesPending = true;
}
if (m_scrollBarEnabled)
{
updateSpectrumBuffer(&spectrum[0], m_fftSize, sampleRate, centerFrequency, dateTime);
offset = scrollBarValue();
idx = m_spectrumBuffer.size() - 1 - offset;
m_currentSpectrum = m_spectrumBuffer[idx].m_spectrum;
}
else
{
updateSpectrumNoBuffer(&spectrum[0], m_fftSize);
m_currentSpectrum = m_spectrumNoBuffer.data();
}
measure(spectrum, false);
if (m_changesPending) {
return;
}
if (offset == 0)
{
updateWaterfall(spectrum, m_fftSize, m_fftMin, m_nbBins);
update3DSpectrogram(spectrum, m_fftSize, m_fftMin, m_nbBins);
updateHistogram(spectrum, m_fftMin, m_nbBins);
}
else
{
updateWaterfall(m_spectrumBuffer[idx].m_spectrum, m_fftSize, m_fftMin, m_nbBins);
update3DSpectrogram(m_spectrumBuffer[idx].m_spectrum, m_fftSize, m_fftMin, m_nbBins);
updateHistogram(m_spectrumBuffer[idx].m_spectrum, m_fftMin, m_nbBins);
}
}
void GLSpectrumView::updateSpectrumNoBuffer(const Real *spectrum, int fftSize)
{
if (m_spectrumNoBuffer.size() != (std::size_t) fftSize) {
m_spectrumNoBuffer.resize(fftSize);
}
std::copy(spectrum, spectrum + fftSize, m_spectrumNoBuffer.begin());
}
void GLSpectrumView::clearSpectrumBuffer()
{
m_currentSpectrum = nullptr; // Make sure we aren't pointing in to a buffer we're about to delete
for (const auto& spectrumData : m_spectrumBuffer) {
delete[] spectrumData.m_spectrum;
}
m_spectrumBuffer.clear();
}
void GLSpectrumView::updateSpectrumBuffer(const Real *spectrum, int fftSize, quint32 sampleRate, qint64 centerFrequency, const QDateTime &dateTime)
{
// Clear buffer when FFT size changes
if (fftSize != m_spectrumBufferFFTSize)
{
clearSpectrumBuffer();
m_spectrumBufferFFTSize = fftSize;
}
// Reuse old buffer if possible, otherwise allocate new buffer
Real *buffer = nullptr;
if (m_spectrumBuffer.size() >= (std::size_t) m_spectrumBufferMaxSize) {
buffer = m_spectrumBuffer.takeFirst().m_spectrum;
}
if (!buffer) {
buffer = new Real[fftSize];
}
// Store copy of spectrum and current parameters in spectrum buffer
std::copy(spectrum, spectrum + fftSize, buffer);
Spectrum spectrumData = {buffer, sampleRate, centerFrequency, dateTime};
m_spectrumBuffer.append(spectrumData);
}
void GLSpectrumView::clearWaterfallRow(int nbBins)
{
if (m_waterfallBufferPos < m_waterfallBuffer->height())
{
quint32* pix = (quint32*)m_waterfallBuffer->scanLine(m_waterfallBufferPos);
for (int i = 0; i <= nbBins; i++) {
*pix++ = m_waterfallPalette[0];
}
m_waterfallBufferPos++;
}
}
void GLSpectrumView::updateWaterfall(const Real *spectrum, int fftSize, int fftMin, int nbBins)
{
if (m_waterfallBufferPos < m_waterfallBuffer->height())
{
quint32* pix = (quint32*)m_waterfallBuffer->scanLine(m_waterfallBufferPos);
for (int i = 0; i < nbBins; i++)
{
int v = (int)((spectrum[fftMin + i] - m_referenceLevel) * 2.4 * 100.0 / m_powerRange + 240.0);
v = clampWaterfall(v);
*pix++ = m_waterfallPalette[(int)v];
}
int lastIdx = (fftMin + nbBins) % fftSize;
int v = (int)((spectrum[lastIdx] - m_referenceLevel) * 2.4 * 100.0 / m_powerRange + 240.0);
v = clampWaterfall(v);
*pix++ = m_waterfallPalette[(int)v];
m_waterfallBufferPos++;
}
}
void GLSpectrumView::clear3DSpectrogramRow(int nbBins)
{
if (m_3DSpectrogramBufferPos < m_3DSpectrogramBuffer->height())
{
quint8* pix = (quint8*)m_3DSpectrogramBuffer->scanLine(m_3DSpectrogramBufferPos);
for (int i = 0; i <= nbBins; i++) {
*pix++ = 0;
}
m_3DSpectrogramBufferPos++;
}
}
void GLSpectrumView::update3DSpectrogram(const Real *spectrum, int fftSize, int fftMin, int nbBins)
{
if (m_3DSpectrogramBufferPos < m_3DSpectrogramBuffer->height())
{
quint8* pix = (quint8*)m_3DSpectrogramBuffer->scanLine(m_3DSpectrogramBufferPos);
for (int i = 0; i < nbBins; i++)
{
int v = (int)((spectrum[fftMin + i] - m_referenceLevel) * 2.4 * 100.0 / m_powerRange + 240.0);
v = clampPixel(v);
*pix++ = v;
}
int lastIdx = (fftMin + nbBins) % fftSize;
int v = (int)((spectrum[lastIdx] - m_referenceLevel) * 2.4 * 100.0 / m_powerRange + 240.0);
v = clampPixel(v);
*pix++ = v;
m_3DSpectrogramBufferPos++;
}
}
void GLSpectrumView::updateHistogram(const Real *spectrum, int fftMin, int nbBins)
{
quint8* b = m_histogram;
int fftMulSize = 100 * nbBins;
if ((m_displayHistogram || m_displayMaxHold) && (m_decay != 0))
{
m_decayDivisorCount--;
if ((m_decay > 1) || (m_decayDivisorCount <= 0))
{
for (int i = 0; i < fftMulSize; i++)
{
if (*b > m_decay) {
*b = *b - m_decay;
} else {
*b = 0;
}
b++;
}
m_decayDivisorCount = m_decayDivisor;
}
}
#if 0 //def USE_SSE2
if(m_decay >= 0) { // normal
const __m128 refl = {m_referenceLevel, m_referenceLevel, m_referenceLevel, m_referenceLevel};
const __m128 power = {m_powerRange, m_powerRange, m_powerRange, m_powerRange};
const __m128 mul = {100.0f, 100.0f, 100.0f, 100.0f};
for(int i = 0; i < m_fftSize; i += 4) {
__m128 abc = _mm_loadu_ps (&spectrum[i]);
abc = _mm_sub_ps(abc, refl);
abc = _mm_mul_ps(abc, mul);
abc = _mm_div_ps(abc, power);
abc = _mm_add_ps(abc, mul);
__m128i result = _mm_cvtps_epi32(abc);
for(int j = 0; j < 4; j++) {
int v = ((int*)&result)[j];
if((v >= 0) && (v <= 99)) {
b = m_histogram + (i + j) * 100 + v;
if(*b < 220)
*b += m_histogramStroke; // was 4
else if(*b < 239)
*b += 1;
}
}
}
} else { // draw double pixels
int add = -m_decay * 4;
const __m128 refl = {m_referenceLevel, m_referenceLevel, m_referenceLevel, m_referenceLevel};
const __m128 power = {m_powerRange, m_powerRange, m_powerRange, m_powerRange};
const __m128 mul = {100.0f, 100.0f, 100.0f, 100.0f};
for(int i = 0; i < m_fftSize; i += 4) {
__m128 abc = _mm_loadu_ps (&spectrum[i]);
abc = _mm_sub_ps(abc, refl);
abc = _mm_mul_ps(abc, mul);
abc = _mm_div_ps(abc, power);
abc = _mm_add_ps(abc, mul);
__m128i result = _mm_cvtps_epi32(abc);
for(int j = 0; j < 4; j++) {
int v = ((int*)&result)[j];
if((v >= 1) && (v <= 98)) {
b = m_histogram + (i + j) * 100 + v;
if(b[-1] < 220)
b[-1] += add;
else if(b[-1] < 239)
b[-1] += 1;
if(b[0] < 220)
b[0] += add;
else if(b[0] < 239)
b[0] += 1;
if(b[1] < 220)
b[1] += add;
else if(b[1] < 239)
b[1] += 1;
} else if((v >= 0) && (v <= 99)) {
b = m_histogram + (i + j) * 100 + v;
if(*b < 220)
*b += add;
else if(*b < 239)
*b += 1;
}
}
}
}
#else
for (int i = 0; i < nbBins; i++)
{
int v = (int)((spectrum[fftMin + i] - m_referenceLevel) * 100.0 / m_powerRange + 100.0);
if ((v >= 0) && (v <= 99))
{
b = m_histogram + i * 100 + v;
// capping to 239 as palette values are [0..239]
if (*b + m_histogramStroke <= 239) {
*b += m_histogramStroke; // was 4
} else {
*b = 239;
}
}
}
#endif
}
void GLSpectrumView::initializeGL()
{
QOpenGLContext *glCurrentContext = QOpenGLContext::currentContext();
int majorVersion = 0;
int minorVersion = 0;
if (glCurrentContext)
{
if (QOpenGLContext::currentContext()->isValid())
{
qDebug() << "GLSpectrumView::initializeGL: context:"
<< " major: " << (QOpenGLContext::currentContext()->format()).majorVersion()
<< " minor: " << (QOpenGLContext::currentContext()->format()).minorVersion()
<< " ES: " << (QOpenGLContext::currentContext()->isOpenGLES() ? "yes" : "no");
majorVersion = (QOpenGLContext::currentContext()->format()).majorVersion();
minorVersion = (QOpenGLContext::currentContext()->format()).minorVersion();
}
else {
qDebug() << "GLSpectrumView::initializeGL: current context is invalid";
}
// Enable OpenGL debugging
// Disable for release, as some OpenGL drivers are quite verbose and output
// info on every frame
if (false)
{
QSurfaceFormat format = glCurrentContext->format();
format.setOption(QSurfaceFormat::DebugContext);
glCurrentContext->setFormat(format);
if (glCurrentContext->hasExtension(QByteArrayLiteral("GL_KHR_debug")))
{
m_openGLLogger = new QOpenGLDebugLogger(this);
m_openGLLogger->initialize();
connect(m_openGLLogger, &QOpenGLDebugLogger::messageLogged, this, &GLSpectrumView::openGLDebug);
m_openGLLogger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
}
else
{
qDebug() << "GLSpectrumView::initializeGL: GL_KHR_debug not available";
}
}
}
else
{
qCritical() << "GLSpectrumView::initializeGL: no current context";
return;
}
QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions();
glFunctions->initializeOpenGLFunctions();
//glDisable(GL_DEPTH_TEST);
m_glShaderSimple.initializeGL(majorVersion, minorVersion);
m_glShaderLeftScale.initializeGL(majorVersion, minorVersion);
m_glShaderFrequencyScale.initializeGL(majorVersion, minorVersion);
m_glShaderWaterfall.initializeGL(majorVersion, minorVersion);
m_glShaderHistogram.initializeGL(majorVersion, minorVersion);
m_glShaderColorMap.initializeGL(majorVersion, minorVersion);
m_glShaderTextOverlay.initializeGL(majorVersion, minorVersion);
m_glShaderInfo.initializeGL(majorVersion, minorVersion);
m_glShaderSpectrogram.initializeGL(majorVersion, minorVersion);
m_glShaderSpectrogramTimeScale.initializeGL(majorVersion, minorVersion);
m_glShaderSpectrogramPowerScale.initializeGL(majorVersion, minorVersion);
}
void GLSpectrumView::openGLDebug(const QOpenGLDebugMessage &debugMessage)
{
qDebug() << "GLSpectrumView::openGLDebug: " << debugMessage;
}
void GLSpectrumView::resizeGL(int width, int height)
{
QMutexLocker mutexLocker(&m_mutex);
QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions();
glFunctions->glViewport(0, 0, width, height);
m_changesPending = true;
}
void GLSpectrumView::clearSpectrumHistogram()
{
if (!m_mutex.tryLock(2)) {
return;
}
memset(m_histogram, 0x00, 100 * m_nbBins);
m_mutex.unlock();
update();
}
void GLSpectrumView::paintGL()
{
PROFILER_START()
if (!m_mutex.tryLock(3)) { // Give time for newSpectrum to complete
return;
}
if (!m_changesPending && (m_waterfallTimeUnits != SpectrumSettings::TimeOffset))
{
// Waterfall timescale can change on every repaint when displaying real time
m_timeScale.requestReCalc();
paintLeftScales();
if (!m_changesPending) {
m_glShaderLeftScale.initTexture(m_leftMarginPixmap.toImage());
}
}
if (m_changesPending)
{
applyChanges();
m_changesPending = false;
}
if (m_nbBins <= 0)
{
m_mutex.unlock();
return;
}
QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions();
glFunctions->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glFunctions->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
QMatrix4x4 spectrogramGridMatrix;
float devicePixelRatio;
if (m_display3DSpectrogram)
{
m_glShaderSpectrogram.applyTransform(spectrogramGridMatrix);
// paint 3D spectrogram
if (m_3DSpectrogramTexturePos + m_3DSpectrogramBufferPos < m_3DSpectrogramTextureHeight)
{
m_glShaderSpectrogram.subTexture(0, m_3DSpectrogramTexturePos, m_nbBins + 1, m_3DSpectrogramBufferPos, m_3DSpectrogramBuffer->scanLine(0));
m_3DSpectrogramTexturePos += m_3DSpectrogramBufferPos;
}
else
{
int breakLine = m_3DSpectrogramTextureHeight - m_3DSpectrogramTexturePos;
int linesLeft = m_3DSpectrogramTexturePos + m_3DSpectrogramBufferPos - m_3DSpectrogramTextureHeight;
m_glShaderSpectrogram.subTexture(0, m_3DSpectrogramTexturePos, m_nbBins + 1, breakLine, m_3DSpectrogramBuffer->scanLine(0));
m_glShaderSpectrogram.subTexture(0, 0, m_nbBins + 1, linesLeft, m_3DSpectrogramBuffer->scanLine(breakLine));
m_3DSpectrogramTexturePos = linesLeft;
}
m_3DSpectrogramBufferPos = 0;
float prop_y = m_3DSpectrogramTexturePos / (m_3DSpectrogramTextureHeight - 1.0);
// Temporarily reduce viewport to waterfall area so anything outside is clipped
if (window()->windowHandle()) {
devicePixelRatio = window()->windowHandle()->devicePixelRatio();
} else {
devicePixelRatio = 1.0f;
}
glFunctions->glViewport(0, m_3DSpectrogramBottom*devicePixelRatio, width()*devicePixelRatio, m_waterfallHeight*devicePixelRatio);
m_glShaderSpectrogram.drawSurface(m_3DSpectrogramStyle, spectrogramGridMatrix, prop_y, m_invertedWaterfall);
glFunctions->glViewport(0, 0, width()*devicePixelRatio, height()*devicePixelRatio);
}
else if (m_displayWaterfall)
{
// paint 2D waterfall
{
GLfloat vtx1[] = {
0, m_invertedWaterfall ? 0.0f : 1.0f,
1, m_invertedWaterfall ? 0.0f : 1.0f,
1, m_invertedWaterfall ? 1.0f : 0.0f,
0, m_invertedWaterfall ? 1.0f : 0.0f
};
if (m_waterfallTexturePos + m_waterfallBufferPos < m_waterfallTextureHeight)
{
m_glShaderWaterfall.subTexture(0, m_waterfallTexturePos, m_nbBins + 1, m_waterfallBufferPos, m_waterfallBuffer->scanLine(0));
m_waterfallTexturePos += m_waterfallBufferPos;
}
else
{
int breakLine = m_waterfallTextureHeight - m_waterfallTexturePos;
int linesLeft = m_waterfallTexturePos + m_waterfallBufferPos - m_waterfallTextureHeight;
m_glShaderWaterfall.subTexture(0, m_waterfallTexturePos, m_nbBins + 1, breakLine, m_waterfallBuffer->scanLine(0));
m_glShaderWaterfall.subTexture(0, 0, m_nbBins + 1, linesLeft, m_waterfallBuffer->scanLine(breakLine));
m_waterfallTexturePos = linesLeft;
}
m_waterfallBufferPos = 0;
float prop_y = m_waterfallTexturePos / (m_waterfallTextureHeight - 1.0);
float off = 1.0 / (m_waterfallTextureHeight - 1.0);
GLfloat tex1[] = {
0, prop_y + 1 - off,
1, prop_y + 1 - off,
1, prop_y,
0, prop_y
};
m_glShaderWaterfall.drawSurface(m_glWaterfallBoxMatrix, tex1, vtx1, 4);
}
// paint channels
if (m_mouseInside)
{
for (int i = 0; i < m_channelMarkerStates.size(); ++i)
{
ChannelMarkerState* dv = m_channelMarkerStates[i];
if (dv->m_channelMarker->getVisible()
&& (dv->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink)
&& dv->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
{
{
GLfloat q3[] {
0, 0,
1, 0,
1, 1,
0, 1,
0.5, 0,
0.5, 1,
};
QVector4D color(dv->m_channelMarker->getColor().redF(), dv->m_channelMarker->getColor().greenF(), dv->m_channelMarker->getColor().blueF(), 0.3f);
m_glShaderSimple.drawSurface(dv->m_glMatrixWaterfall, color, q3, 4);
QVector4D colorLine(0.8f, 0.8f, 0.6f, 1.0f);
m_glShaderSimple.drawSegments(dv->m_glMatrixDsbWaterfall, colorLine, &q3[8], 2);
}
}
}
}
// draw rect around
{
GLfloat q3[] {
1, 1,
0, 1,
0, 0,
1, 0
};
QVector4D color(1.0f, 1.0f, 1.0f, 0.5f);
m_glShaderSimple.drawContour(m_glWaterfallBoxMatrix, color, q3, 4);
}
}
// When zoomed, use the next sample off screen. When not, replicate the Nyquist bin
const int lastSampleIdx = (m_fftMin + m_nbBins) % m_fftSize;
// paint histogram
if (m_displayHistogram || m_displayMaxHold || m_displayCurrent)
{
if (m_displayHistogram)
{
{
// import new lines into the texture
quint32* pix;
quint8* bs = m_histogram;
for (int y = 0; y < 100; y++)
{
quint8* b = bs;
pix = (quint32*)m_histogramBuffer->scanLine(99 - y);
for (int x = 0; x < m_nbBins; x++)
{
*pix = m_histogramPalette[*b];
pix++;
b += 100;
}
bs++;
}
GLfloat vtx1[] = {
0, 0,
1, 0,
1, 1,
0, 1
};
GLfloat tex1[] = {
0, 0,
1, 0,
1, 1,
0, 1
};
m_glShaderHistogram.subTexture(0, 0, m_nbBins, 100, m_histogramBuffer->scanLine(0));
m_glShaderHistogram.drawSurface(m_glHistogramBoxMatrix, tex1, vtx1, 4);
}
}
// paint channels
if (m_mouseInside)
{
// Effective BW overlays
for (int i = 0; i < m_channelMarkerStates.size(); ++i)
{
ChannelMarkerState* dv = m_channelMarkerStates[i];
if (dv->m_channelMarker->getVisible()
&& (dv->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink)
&& dv->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
{
{
GLfloat q3[] {
0, 0,
1, 0,
1, 1,
0, 1,
0.5, 0,
0.5, 1
};
QVector4D color(dv->m_channelMarker->getColor().redF(), dv->m_channelMarker->getColor().greenF(), dv->m_channelMarker->getColor().blueF(), 0.3f);
m_glShaderSimple.drawSurface(dv->m_glMatrixHistogram, color, q3, 4);
QVector4D colorLine(0.8f, 0.8f, 0.6f, 1.0f);
if (dv->m_channelMarker->getSidebands() != ChannelMarker::dsb) {
q3[6] = 0.5;
}
m_glShaderSimple.drawSegments(dv->m_glMatrixDsbHistogram, colorLine, &q3[8], 2);
m_glShaderSimple.drawSegments(dv->m_glMatrixFreqScale, colorLine, q3, 2);
}
}
}
}
}
// paint left scales (time and power)
if (m_displayWaterfall || m_displayMaxHold || m_displayCurrent || m_displayHistogram )
{
{
GLfloat vtx1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
m_glShaderLeftScale.drawSurface(m_glLeftScaleBoxMatrix, tex1, vtx1, 4);
}
}
// paint frequency scale
if (m_displayWaterfall || m_displayMaxHold || m_displayCurrent || m_displayHistogram)
{
{
GLfloat vtx1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
m_glShaderFrequencyScale.drawSurface(m_glFrequencyScaleBoxMatrix, tex1, vtx1, 4);
}
// paint channels
// Effective bandwidth overlays
for (int i = 0; i < m_channelMarkerStates.size(); ++i)
{
ChannelMarkerState* dv = m_channelMarkerStates[i];
// frequency scale channel overlay
if (dv->m_channelMarker->getVisible()
&& (dv->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink)
&& dv->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
{
{
GLfloat q3[] {
1, 0.2,
0, 0.2,
0, 0,
1, 0,
0.5, 0,
0.5, 1
};
QVector4D color(dv->m_channelMarker->getColor().redF(), dv->m_channelMarker->getColor().greenF(), dv->m_channelMarker->getColor().blueF(), 0.5f);
m_glShaderSimple.drawSurface(dv->m_glMatrixFreqScale, color, q3, 4);
if (dv->m_channelMarker->getHighlighted())
{
QVector4D colorLine(0.8f, 0.8f, 0.6f, 1.0f);
m_glShaderSimple.drawSegments(dv->m_glMatrixDsbFreqScale, colorLine, &q3[8], 2);
m_glShaderSimple.drawSegments(dv->m_glMatrixFreqScale, colorLine, &q3[4], 2);
}
}
}
}
}
// paint 3D spectrogram scales
if (m_display3DSpectrogram && m_displayGrid)
{
glFunctions->glViewport(0, m_3DSpectrogramBottom*devicePixelRatio, width()*devicePixelRatio, m_waterfallHeight*devicePixelRatio);
{
GLfloat l = m_spectrogramTimePixmap.width() / (GLfloat) width();
GLfloat r = m_rightMargin / (GLfloat) width();
GLfloat h = m_frequencyPixmap.height() / (GLfloat) m_waterfallHeight;
GLfloat vtx1[] = {
-l, - h,
1.0f+r, -h,
1.0f+r, 0.0f,
-l, 0.0f
};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
m_glShaderFrequencyScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4);
}
{
GLfloat w = m_spectrogramTimePixmap.width() / (GLfloat) width();
GLfloat h = (m_bottomMargin/2) / (GLfloat) m_waterfallHeight; // m_bottomMargin is fm.ascent
GLfloat vtx1[] = {
-w, 0.0f-h,
0.0f, 0.0f-h,
0.0f, 1.0f+h,
-w, 1.0f+h
};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
m_glShaderSpectrogramTimeScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4);
}
{
GLfloat w = m_spectrogramPowerPixmap.width() / (GLfloat) width();
GLfloat h = m_topMargin / (GLfloat) m_spectrogramPowerPixmap.height();
GLfloat vtx1[] = {
-w, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 1.0f+h,
-w, 1.0f, 1.0f+h,
};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
m_glShaderSpectrogramPowerScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4, 3);
}
glFunctions->glViewport(0, 0, width()*devicePixelRatio, height()*devicePixelRatio);
}
// paint max hold lines on top of histogram
if (m_displayMaxHold)
{
if (m_maxHold.size() < (uint) m_nbBins) {
m_maxHold.resize(m_nbBins);
}
for (int i = 0; i < m_nbBins; i++)
{
int j;
quint8* bs = m_histogram + i * 100;
for (j = 99; j >= 0; j--)
{
if (bs[j] > 0) {
break;
}
}
// m_referenceLevel : top
// m_referenceLevel - m_powerRange : bottom
m_maxHold[i] = ((j - 99) * m_powerRange) / 99.0 + m_referenceLevel;
}
// Fill under max hold line
if (m_spectrumStyle != SpectrumSettings::Line)
{
GLfloat *q3 = m_q3ColorMap.m_array;
for (int i = 0; i < m_nbBins; i++)
{
Real v = clampPower(m_maxHold[i] - m_referenceLevel);
q3[4*i] = (GLfloat)i;
q3[4*i+1] = -m_powerRange;
q3[4*i+2] = (GLfloat)i;
q3[4*i+3] = v;
}
q3[4*m_nbBins] = (GLfloat) m_nbBins;
q3[4*m_nbBins+1] = -m_powerRange;
q3[4*m_nbBins+2] = (GLfloat) m_nbBins;
q3[4*m_nbBins+3] = clampPower(m_maxHold[lastSampleIdx] - m_referenceLevel);
QVector4D color(0.5f, 0.0f, 0.0f, (float) m_displayTraceIntensity / 100.0f);
m_glShaderSimple.drawSurfaceStrip(m_glHistogramSpectrumMatrix, color, q3, 2*(m_nbBins+1));
}
// Max hold line
{
GLfloat *q3 = m_q3FFT.m_array;
for (int i = 0; i < m_nbBins; i++)
{
Real v = clampPower(m_maxHold[i] - m_referenceLevel);
q3[2*i] = (Real) i;
q3[2*i+1] = v;
}
q3[2*m_nbBins] = (GLfloat) m_nbBins;
q3[2*m_nbBins+1] = clampPower(m_maxHold[lastSampleIdx] - m_referenceLevel);
QVector4D color(1.0f, 0.0f, 0.0f, (float) m_displayTraceIntensity / 100.0f);
m_glShaderSimple.drawPolyline(m_glHistogramSpectrumMatrix, color, q3, m_nbBins+1);
}
}
// paint mask violations
if ((m_measurement == SpectrumSettings::MeasurementMask) && m_measurementHighlight)
{
for (int m = 0; m < m_spectrumMemory.size(); m++)
{
if ( (m_spectrumMemory[m].m_spectrum.size() == m_fftSize)
&& (m_maskFails[m].size() == (std::size_t) m_fftSize)
&& ((m_measurementMemMasks & (1 << m)) != 0)
)
{
GLfloat *q3 = m_q3ColorMap.m_array;
for (int i = 0; i < m_nbBins; i++)
{
Real v1 = clampPower(m_maskFails[m][m_fftMin+i] - m_referenceLevel);
Real v2 = clampPower(m_spectrumMemory[m].m_spectrum[m_fftMin+i] - m_referenceLevel);
q3[4*i] = (GLfloat)i;
q3[4*i+1] = v2;
q3[4*i+2] = (GLfloat)i;
q3[4*i+3] = v1;
}
q3[4*m_nbBins] = (GLfloat) m_nbBins;
q3[4*m_nbBins+1] = clampPower(m_maskFails[m][lastSampleIdx] - m_referenceLevel);
q3[4*m_nbBins+2] = (GLfloat) m_nbBins;
q3[4*m_nbBins+3] = q3[3];
QVector4D color(0.5f, 0.0f, 0.0f, (float) m_displayTraceIntensity / 100.0f);
m_glShaderSimple.drawSurfaceStrip(m_glHistogramSpectrumMatrix, color, q3, 2*(m_nbBins+1));
}
}
}
// paint memory spectrum as lines
if (m_displayCurrent)
{
for (const auto& memory : m_spectrumMemory)
{
if (memory.m_display && (memory.m_spectrum.size() == m_fftSize))
{
QColor colorF = QColor::fromRgba(memory.m_color);
// Draw label
if (!memory.m_label.isEmpty())
{
float y = (m_powerScale.getRangeMax() - memory.m_spectrum[m_fftMin]) / m_powerScale.getRange() * m_histogramRect.height();
float h = m_topMargin / (float) height();
if ((y >= m_histogramRect.top()) && (y + h < m_histogramRect.bottom()))
{
drawTextOverlay(
memory.m_label,
colorF,
m_textOverlayFont,
0.0f,
y,
true,
false, // text above the line
m_histogramRect);
}
}
GLfloat *q3;
// Draw line
q3 = m_q3FFT.m_array;
for (int i = 0; i < m_nbBins; i++)
{
Real v = clampPower(memory.m_spectrum[m_fftMin+i] - m_referenceLevel);
q3[2*i] = (Real) i;
q3[2*i+1] = v;
}
q3[2*m_nbBins] = (GLfloat) m_nbBins;
q3[2*m_nbBins+1] = clampPower(memory.m_spectrum[lastSampleIdx] - m_referenceLevel);
QVector4D color;
color = QVector4D(colorF.redF(), colorF.greenF(), colorF.blueF(), colorF.alphaF());
m_glShaderSimple.drawPolyline(m_glHistogramSpectrumMatrix, color, q3, m_nbBins+1);
}
}
}
// paint current spectrum line on top of histogram
if (m_displayCurrent && m_currentSpectrum)
{
Real bottom = -m_powerRange;
GLfloat *q3;
if (m_spectrumStyle != SpectrumSettings::Line)
{
q3 = m_q3ColorMap.m_array;
// Fill under line
for (int i = 0; i < m_nbBins; i++)
{
Real v = clampPower(m_currentSpectrum[m_fftMin + i] - m_referenceLevel);
q3[4*i] = (GLfloat)i;
q3[4*i+1] = bottom;
q3[4*i+2] = (GLfloat)i;
q3[4*i+3] = v;
}
q3[4*m_nbBins] = (GLfloat) m_nbBins;
q3[4*m_nbBins+1] = bottom;
q3[4*m_nbBins+2] = (GLfloat) m_nbBins;
q3[4*m_nbBins+3] = clampPower(m_currentSpectrum[lastSampleIdx] - m_referenceLevel);
QVector4D color(m_spectrumColor.redF(), m_spectrumColor.greenF(), m_spectrumColor.blueF(), (float) m_displayTraceIntensity / 100.0f);
if (m_spectrumStyle == SpectrumSettings::Gradient) {
m_glShaderColorMap.drawSurfaceStrip(m_glHistogramSpectrumMatrix, q3, 2*(m_nbBins+1), bottom, 0.75f);
} else {
m_glShaderSimple.drawSurfaceStrip(m_glHistogramSpectrumMatrix, color, q3, 2*(m_nbBins+1));
}
}
{
if (m_histogramFindPeaks) {
m_peakFinder.init(m_currentSpectrum[m_fftMin]);
}
// Draw line
q3 = m_q3FFT.m_array;
for (int i = 0; i < m_nbBins; i++)
{
Real v = clampPower(m_currentSpectrum[m_fftMin + i] - m_referenceLevel);
q3[2*i] = (Real) i;
q3[2*i+1] = v;
if (m_histogramFindPeaks && (i > 0)) {
m_peakFinder.push(m_currentSpectrum[m_fftMin + i], i == m_nbBins - 1);
}
}
q3[2*m_nbBins] = (GLfloat) m_nbBins;
q3[2*m_nbBins+1] = clampPower(m_currentSpectrum[lastSampleIdx] - m_referenceLevel);
QVector4D color;
if (m_spectrumStyle == SpectrumSettings::Gradient) {
color = QVector4D(m_colorMap[255*3], m_colorMap[255*3+1], m_colorMap[255*3+2], (float) m_displayTraceIntensity / 100.0f);
} else {
color = QVector4D(m_spectrumColor.redF(), m_spectrumColor.greenF(), m_spectrumColor.blueF(), (float) m_displayTraceIntensity / 100.0f);
}
m_glShaderSimple.drawPolyline(m_glHistogramSpectrumMatrix, color, q3, m_nbBins+1);
if (m_histogramFindPeaks) {
m_peakFinder.sortPeaks();
}
}
}
if (m_displayCurrent && m_currentSpectrum && (m_markersDisplay & SpectrumSettings::MarkersDisplaySpectrum))
{
if (m_histogramFindPeaks) {
updateHistogramPeaks();
}
drawSpectrumMarkers();
}
if (m_markersDisplay & SpectrumSettings::MarkersDisplayAnnotations) {
drawAnnotationMarkers();
}
// paint waterfall grid
if (m_displayWaterfall && m_displayGrid)
{
const ScaleEngine::TickList* tickList;
const ScaleEngine::Tick* tick;
tickList = &m_timeScale.getTickList();
{
GLfloat *q3 = m_q3TickTime.m_array;
int effectiveTicks = 0;
for (int i= 0; i < tickList->count(); i++)
{
tick = &(*tickList)[i];
if (tick->major)
{
if (tick->textSize > 0)
{
float y = tick->pos / m_timeScale.getSize();
q3[4*effectiveTicks] = 0;
q3[4*effectiveTicks+1] = y;
q3[4*effectiveTicks+2] = 1;
q3[4*effectiveTicks+3] = y;
effectiveTicks++;
}
}
}
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
m_glShaderSimple.drawSegments(m_glWaterfallBoxMatrix, color, q3, 2*effectiveTicks);
}
tickList = &m_frequencyScale.getTickList();
{
GLfloat *q3 = m_q3TickFrequency.m_array;
int effectiveTicks = 0;
for (int i= 0; i < tickList->count(); i++)
{
tick = &(*tickList)[i];
if (tick->major)
{
if (tick->textSize > 0)
{
float x = tick->pos / m_frequencyScale.getSize();
q3[4*effectiveTicks] = x;
q3[4*effectiveTicks+1] = 0;
q3[4*effectiveTicks+2] = x;
q3[4*effectiveTicks+3] = 1;
effectiveTicks++;
}
}
}
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
m_glShaderSimple.drawSegments(m_glWaterfallBoxMatrix, color, q3, 2*effectiveTicks);
}
}
// paint 3D spectrogram grid - this is drawn on top of signal, so that appears slightly transparent
// x-axis is freq, y time and z power
if (m_displayGrid && m_display3DSpectrogram)
{
const ScaleEngine::TickList* tickList;
const ScaleEngine::Tick* tick;
glFunctions->glViewport(0, m_3DSpectrogramBottom*devicePixelRatio, width()*devicePixelRatio, m_waterfallHeight*devicePixelRatio);
tickList = &m_powerScale.getTickList();
{
GLfloat *q3 = m_q3TickPower.m_array;
int effectiveTicks = 0;
for (int i= 0; i < tickList->count(); i++)
{
tick = &(*tickList)[i];
if (tick->major)
{
if (tick->textSize > 0)
{
float y = tick->pos / m_powerScale.getSize();
q3[6*effectiveTicks] = 0.0;
q3[6*effectiveTicks+1] = 1.0;
q3[6*effectiveTicks+2] = y;
q3[6*effectiveTicks+3] = 1.0;
q3[6*effectiveTicks+4] = 1.0;
q3[6*effectiveTicks+5] = y;
effectiveTicks++;
}
}
}
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks, 3);
}
tickList = &m_timeScale.getTickList();
{
GLfloat *q3 = m_q3TickTime.m_array;
int effectiveTicks = 0;
for (int i= 0; i < tickList->count(); i++)
{
tick = &(*tickList)[i];
if (tick->major)
{
if (tick->textSize > 0)
{
float y = tick->pos / m_timeScale.getSize();
q3[4*effectiveTicks] = 0.0;
q3[4*effectiveTicks+1] = 1.0 - y;
q3[4*effectiveTicks+2] = 1.0;
q3[4*effectiveTicks+3] = 1.0 - y;
effectiveTicks++;
}
}
}
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks);
}
tickList = &m_frequencyScale.getTickList();
{
GLfloat *q3 = m_q3TickFrequency.m_array;
int effectiveTicks = 0;
for (int i= 0; i < tickList->count(); i++)
{
tick = &(*tickList)[i];
if (tick->major)
{
if (tick->textSize > 0)
{
float x = tick->pos / m_frequencyScale.getSize();
q3[4*effectiveTicks] = x;
q3[4*effectiveTicks+1] = -0.0;
q3[4*effectiveTicks+2] = x;
q3[4*effectiveTicks+3] = 1.0;
effectiveTicks++;
}
}
}
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks);
}
{
GLfloat *q3 = m_q3TickFrequency.m_array;
int effectiveTicks = 0;
for (int i= 0; i < tickList->count(); i++)
{
tick = &(*tickList)[i];
if (tick->major)
{
if (tick->textSize > 0)
{
float x = tick->pos / m_frequencyScale.getSize();
q3[6*effectiveTicks] = x;
q3[6*effectiveTicks+1] = 1.0;
q3[6*effectiveTicks+2] = 0.0;
q3[6*effectiveTicks+3] = x;
q3[6*effectiveTicks+4] = 1.0;
q3[6*effectiveTicks+5] = 1.0;
effectiveTicks++;
}
}
}
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks, 3);
}
glFunctions->glViewport(0, 0, width()*devicePixelRatio, height()*devicePixelRatio);
}
// paint histogram grid
if ((m_displayHistogram || m_displayMaxHold || m_displayCurrent) && (m_displayGrid))
{
const ScaleEngine::TickList* tickList;
const ScaleEngine::Tick* tick;
tickList = &m_powerScale.getTickList();
{
GLfloat *q3 = m_q3TickPower.m_array;
int effectiveTicks = 0;
for (int i= 0; i < tickList->count(); i++)
{
tick = &(*tickList)[i];
if (tick->major)
{
if (tick->textSize > 0)
{
float y = tick->pos / m_powerScale.getSize();
q3[4*effectiveTicks] = 0;
q3[4*effectiveTicks+1] = 1-y;
q3[4*effectiveTicks+2] = 1;
q3[4*effectiveTicks+3] = 1-y;
effectiveTicks++;
}
}
}
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, color, q3, 2*effectiveTicks);
}
tickList = &m_frequencyScale.getTickList();
{
GLfloat *q3 = m_q3TickFrequency.m_array;
int effectiveTicks = 0;
for (int i= 0; i < tickList->count(); i++)
{
tick = &(*tickList)[i];
if (tick->major)
{
if (tick->textSize > 0)
{
float x = tick->pos / m_frequencyScale.getSize();
q3[4*effectiveTicks] = x;
q3[4*effectiveTicks+1] = 0;
q3[4*effectiveTicks+2] = x;
q3[4*effectiveTicks+3] = 1;
effectiveTicks++;
}
}
}
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, color, q3, 2*effectiveTicks);
}
}
// paint rect around histogram (do last, so on top of filled spectrum)
if (m_displayHistogram || m_displayMaxHold || m_displayCurrent)
{
{
GLfloat q3[] {
1, 1,
0, 1,
0, 0,
1, 0
};
QVector4D color(1.0f, 1.0f, 1.0f, 0.5f);
m_glShaderSimple.drawContour(m_glHistogramBoxMatrix, color, q3, 4);
}
}
// Paint info line
{
GLfloat vtx1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
m_glShaderInfo.drawSurface(m_glInfoBoxMatrix, tex1, vtx1, 4);
}
if (m_displayCursorStats || m_displayPeakStats) {
paintStatusLineRight();
}
if (m_currentSpectrum) {
measure(&m_currentSpectrum[m_fftMin], true);
}
m_mutex.unlock();
#ifdef ENABLE_PROFILER
if (m_profileName.isEmpty())
{
// Try to use the window name for the profile name
QString windowTitle;
for (QWidget *widget = parentWidget(); widget != nullptr; widget = widget->parentWidget())
{
windowTitle = widget->windowTitle();
if (!windowTitle.isEmpty()) {
break;
}
}
// Add this address so we get per-spectrum profile data
if (windowTitle.isEmpty()) {
m_profileName = QString("Spectrum @%1").arg((quint64)this, 0, 16);
} else {
m_profileName = QString("%1 @%2").arg(windowTitle).arg((quint64)this, 0, 16);
}
}
#endif
PROFILER_STOP(m_profileName)
} // paintGL
// Highlight power band for SFDR
void GLSpectrumView::drawPowerBandMarkers(float max, float min, const QVector4D &color)
{
float p1 = (m_powerScale.getRangeMax() - min) / m_powerScale.getRange();
float p2 = (m_powerScale.getRangeMax() - max) / m_powerScale.getRange();
GLfloat q3[] {
1, p2,
0, p2,
0, p1,
1, p1,
0, p1,
0, p2
};
m_glShaderSimple.drawSurface(m_glHistogramBoxMatrix, color, q3, 4);
}
// Highlight bandwidth being measured
void GLSpectrumView::drawBandwidthMarkers(int64_t centerFrequency, int bandwidth, const QVector4D &color)
{
float f1 = (centerFrequency - bandwidth / 2);
float f2 = (centerFrequency + bandwidth / 2);
float x1 = (f1 - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
float x2 = (f2 - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
GLfloat q3[] {
x2, 1,
x1, 1,
x1, 0,
x2, 0,
x1, 0,
x1, 1
};
m_glShaderSimple.drawSurface(m_glHistogramBoxMatrix, color, q3, 4);
}
// Highlight peak being measured. Note that the peak isn't always at the center
void GLSpectrumView::drawPeakMarkers(int64_t startFrequency, int64_t endFrequency, const QVector4D &color)
{
float x1 = (startFrequency - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
float x2 = (endFrequency - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
GLfloat q3[] {
x2, 1,
x1, 1,
x1, 0,
x2, 0,
x1, 0,
x1, 1
};
m_glShaderSimple.drawSurface(m_glHistogramBoxMatrix, color, q3, 4);
}
void GLSpectrumView::drawSpectrumMarkers()
{
if (!m_currentSpectrum) {
return;
}
QVector4D lineColor(1.0f, 1.0f, 1.0f, 0.3f);
// paint histogram markers
if (m_histogramMarkers.size() > 0)
{
for (int i = 0; i < m_histogramMarkers.size(); i++)
{
if (!m_histogramMarkers.at(i).m_show) {
continue;
}
QPointF ypoint = m_histogramMarkers.at(i).m_point;
QString powerStr = m_histogramMarkers.at(i).m_powerStr;
if (m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePower)
{
float power = m_linear ?
m_currentSpectrum[m_fftMin + m_histogramMarkers.at(i).m_fftBin] * (m_useCalibration ? m_calibrationGain : 1.0f):
m_currentSpectrum[m_fftMin + m_histogramMarkers.at(i).m_fftBin] + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
ypoint.ry() =
(m_powerScale.getRangeMax() - power) / m_powerScale.getRange();
ypoint.ry() = ypoint.ry() < 0 ?
0 :
ypoint.ry() > 1 ? 1 : ypoint.ry();
powerStr = displayPower(
power,
m_linear ? 'e' : 'f',
m_linear ? 3 : 1
);
}
else if (m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePowerMax)
{
float power = m_currentSpectrum[m_fftMin + m_histogramMarkers.at(i).m_fftBin];
if ((m_histogramMarkers.at(i).m_holdReset) || (power > m_histogramMarkers[i].m_powerMax))
{
m_histogramMarkers[i].m_powerMax = power;
m_histogramMarkers[i].m_holdReset = false;
}
float powerMax = m_linear ?
m_histogramMarkers[i].m_powerMax * (m_useCalibration ? m_calibrationGain : 1.0f) :
m_histogramMarkers[i].m_powerMax + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
ypoint.ry() =
(m_powerScale.getRangeMax() - powerMax) / m_powerScale.getRange();
ypoint.ry() = ypoint.ry() < 0 ?
0 : ypoint.ry() > 1 ?
1 : ypoint.ry();
powerStr = displayPower(
powerMax,
m_linear ? 'e' : 'f',
m_linear ? 3 : 1
);
}
// crosshairs
GLfloat h[] {
(float) m_histogramMarkers.at(i).m_point.x(), 0,
(float) m_histogramMarkers.at(i).m_point.x(), 1
};
m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, lineColor, h, 2);
GLfloat v[] {
0, (float) ypoint.y(),
1, (float) ypoint.y()
};
m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, lineColor, v, 2);
QColor textColor = m_histogramMarkers.at(i).m_markerColor;
// text
if (i == 0)
{
drawTextOverlay(
m_histogramMarkers.at(i).m_frequencyStr,
textColor,
m_textOverlayFont,
m_histogramMarkers.at(i).m_point.x() * m_histogramRect.width(),
(m_invertedWaterfall || (m_waterfallHeight == 0)) ? m_histogramRect.height() : 0,
m_histogramMarkers.at(i).m_point.x() < 0.5f,
!m_invertedWaterfall && (m_waterfallHeight != 0),
m_histogramRect);
drawTextOverlay(
powerStr,
textColor,
m_textOverlayFont,
0,
ypoint.y() * m_histogramRect.height(),
true,
ypoint.y() < 0.5f,
m_histogramRect);
}
else
{
textColor.setAlpha(192);
float power0, poweri;
if (m_histogramMarkers.at(0).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePower) {
power0 = m_currentSpectrum[m_fftMin + m_histogramMarkers.at(0).m_fftBin];
} else if (m_histogramMarkers.at(0).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePowerMax) {
power0 = m_histogramMarkers.at(0).m_powerMax;
} else {
power0 = m_linear ? m_histogramMarkers.at(0).m_power : CalcDb::dbPower(m_histogramMarkers.at(0).m_power);
}
if (m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePower) {
poweri = m_currentSpectrum[m_fftMin + m_histogramMarkers.at(i).m_fftBin];
} else if (m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePowerMax) {
poweri = m_histogramMarkers.at(i).m_powerMax;
} else {
poweri = m_linear ? m_histogramMarkers.at(i).m_power : CalcDb::dbPower(m_histogramMarkers.at(i).m_power);
}
QString deltaPowerStr;
if (m_linear) {
deltaPowerStr = QString::number(poweri - power0, 'e', 3);
} else {
deltaPowerStr = QString::number(poweri - power0, 'f', 1);
}
drawTextOverlay(
m_histogramMarkers.at(i).m_deltaFrequencyStr,
textColor,
m_textOverlayFont,
m_histogramMarkers.at(i).m_point.x() * m_histogramRect.width(),
(m_invertedWaterfall || (m_waterfallHeight == 0)) ? 0 : m_histogramRect.height(),
m_histogramMarkers.at(i).m_point.x() < 0.5f,
(m_invertedWaterfall || (m_waterfallHeight == 0)),
m_histogramRect);
drawTextOverlay(
deltaPowerStr,
textColor,
m_textOverlayFont,
m_histogramRect.width(),
ypoint.y() * m_histogramRect.height(),
false,
ypoint.y() < 0.5f,
m_histogramRect);
}
}
}
// paint waterfall markers
if (m_waterfallMarkers.size() > 0)
{
// crosshairs
for (int i = 0; i < m_waterfallMarkers.size(); i++)
{
if (!m_waterfallMarkers.at(i).m_show) {
continue;
}
GLfloat h[] {
(float) m_waterfallMarkers.at(i).m_point.x(), 0,
(float) m_waterfallMarkers.at(i).m_point.x(), 1
};
m_glShaderSimple.drawSegments(m_glWaterfallBoxMatrix, lineColor, h, 2);
GLfloat v[] {
0, (float) m_waterfallMarkers.at(i).m_point.y(),
1, (float) m_waterfallMarkers.at(i).m_point.y()
};
m_glShaderSimple.drawSegments(m_glWaterfallBoxMatrix, lineColor, v, 2);
// }
// text
// for (int i = 0; i < m_waterfallMarkers.size(); i++)
// {
QColor textColor = m_waterfallMarkers.at(i).m_markerColor;
textColor.setAlpha(192);
if (i == 0)
{
drawTextOverlay(
m_waterfallMarkers.at(i).m_frequencyStr,
textColor,
m_textOverlayFont,
m_waterfallMarkers.at(i).m_point.x() * m_waterfallRect.width(),
(!m_invertedWaterfall || (m_histogramHeight == 0)) ? m_waterfallRect.height() : 0,
m_waterfallMarkers.at(i).m_point.x() < 0.5f,
m_invertedWaterfall && (m_histogramHeight != 0),
m_waterfallRect);
drawTextOverlay(
m_waterfallMarkers.at(i).m_timeStr,
textColor,
m_textOverlayFont,
0,
m_waterfallMarkers.at(i).m_point.y() * m_waterfallRect.height(),
true,
m_waterfallMarkers.at(i).m_point.y() < 0.5f,
m_waterfallRect);
}
else
{
drawTextOverlay(
m_waterfallMarkers.at(i).m_deltaFrequencyStr,
textColor,
m_textOverlayFont,
m_waterfallMarkers.at(i).m_point.x() * m_waterfallRect.width(),
(!m_invertedWaterfall || (m_histogramHeight == 0)) ? 0 : m_waterfallRect.height(),
m_waterfallMarkers.at(i).m_point.x() < 0.5f,
!m_invertedWaterfall || (m_histogramHeight == 0),
m_waterfallRect);
drawTextOverlay(
m_waterfallMarkers.at(i).m_deltaTimeStr,
textColor,
m_textOverlayFont,
m_waterfallRect.width(),
m_waterfallMarkers.at(i).m_point.y() * m_waterfallRect.height(),
false,
m_waterfallMarkers.at(i).m_point.y() < 0.5f,
m_waterfallRect);
}
}
}
}
void GLSpectrumView::drawAnnotationMarkers()
{
if ((!m_currentSpectrum) || (m_visibleAnnotationMarkers.size() == 0)) {
return;
}
float h = m_annotationMarkerHeight / (float) m_histogramHeight;
float htop = 1.0f / (float) m_histogramHeight;
for (const auto &marker : m_visibleAnnotationMarkers)
{
if (marker->m_show == SpectrumAnnotationMarker::Hidden) {
continue;
}
QVector4D color(marker->m_markerColor.redF(), marker->m_markerColor.greenF(), marker->m_markerColor.blueF(), 0.5f);
if (marker->m_bandwidth == 0)
{
GLfloat d[] {
marker->m_startPos, htop,
marker->m_startPos, h
};
m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, color, d, 2);
}
else
{
GLfloat q3[] {
marker->m_stopPos, h,
marker->m_startPos, h,
marker->m_startPos, htop,
marker->m_stopPos, htop
};
m_glShaderSimple.drawSurface(m_glHistogramBoxMatrix, color, q3, 4);
}
// Always draw a line in the top area, so we can see where bands start/stop when contiguous
// When show is ShowFull, we draw at full height of spectrum
bool full = marker->m_show == SpectrumAnnotationMarker::ShowFull;
GLfloat d1[] {
marker->m_startPos, full ? 0 : htop,
marker->m_startPos, full ? 1 : h,
};
m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, color, d1, 2);
if (marker->m_bandwidth != 0)
{
GLfloat d2[] {
marker->m_stopPos, full ? 0 : htop,
marker->m_stopPos, full ? 1 : h,
};
m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, color, d2, 2);
}
if ((marker->m_show == SpectrumAnnotationMarker::ShowFull) || (marker->m_show == SpectrumAnnotationMarker::ShowText))
{
float txtpos = marker->m_startPos < 0.5f ?
marker->m_startPos :
marker->m_stopPos;
drawTextOverlay(
marker->m_text,
QColor(255, 255, 255, 192),
m_textOverlayFont,
txtpos * m_histogramRect.width(),
0,
marker->m_startPos < 0.5f,
true,
m_histogramRect);
}
}
}
// Find and display peaks
void GLSpectrumView::measurePeaks(const Real *spectrum)
{
// Copy current spectrum so we can modify it
Real *spectrumCopy = new Real[m_nbBins];
std::copy(spectrum, spectrum + m_nbBins, spectrumCopy);
for (int i = 0; i < m_measurementPeaks; i++)
{
// Find peak
int peakBin = findPeakBin(spectrumCopy);
int left, right;
peakWidth(spectrumCopy, peakBin, left, right, 0, m_nbBins);
left++;
right--;
float power = m_linear ?
spectrumCopy[peakBin] * (m_useCalibration ? m_calibrationGain : 1.0f) :
spectrumCopy[peakBin] + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
int64_t frequency = binToFrequency(peakBin);
// Add to table
if (m_measurements) {
m_measurements->setPeak(i, frequency, power);
}
if (m_measurementHighlight)
{
float x = peakBin / (float)m_nbBins;
float y = (m_powerScale.getRangeMax() - power) / m_powerScale.getRange();
QString text = QString::number(i + 1);
drawTextOverlayCentered(
text,
QColor(255, 255, 255),
m_textOverlayFont,
x * m_histogramRect.width(),
y * m_histogramRect.height(),
m_histogramRect);
}
// Remove peak from spectrum so not found on next pass
for (int j = left; j <= right; j++) {
spectrumCopy[j] = -std::numeric_limits<float>::max();
}
}
delete[] spectrumCopy;
}
// Calculate and display channel power
void GLSpectrumView::measureChannelPower(const Real *spectrum, bool updateGUI)
{
float power;
qint64 centerFrequency = getDisplayedCenterFrequency();
power = calcChannelPower(spectrum, centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth);
if (m_measurements) {
m_measurements->setChannelPower(power, updateGUI);
}
if (m_measurementHighlight && updateGUI) {
drawBandwidthMarkers(centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth, m_measurementLightMarkerColor);
}
}
// Calculate and display channel power and adjacent channel power
void GLSpectrumView::measureAdjacentChannelPower(const Real *spectrum, bool updateGUI)
{
float power, powerLeft, powerRight;
qint64 centerFrequency = getDisplayedCenterFrequency();
power = calcChannelPower(spectrum, centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth);
powerLeft = calcChannelPower(spectrum, centerFrequency + m_measurementCenterFrequencyOffset - m_measurementChSpacing, m_measurementAdjChBandwidth);
powerRight = calcChannelPower(spectrum, centerFrequency + m_measurementCenterFrequencyOffset + m_measurementChSpacing, m_measurementAdjChBandwidth);
float leftDiff = powerLeft - power;
float rightDiff = powerRight - power;
if (m_measurements) {
m_measurements->setAdjacentChannelPower(powerLeft, leftDiff, power, powerRight, rightDiff, updateGUI);
}
if (m_measurementHighlight && updateGUI)
{
drawBandwidthMarkers(centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth, m_measurementLightMarkerColor);
drawBandwidthMarkers(centerFrequency + m_measurementCenterFrequencyOffset - m_measurementChSpacing, m_measurementAdjChBandwidth, m_measurementDarkMarkerColor);
drawBandwidthMarkers(centerFrequency + m_measurementCenterFrequencyOffset + m_measurementChSpacing, m_measurementAdjChBandwidth, m_measurementDarkMarkerColor);
}
}
// Measure bandwidth that has 99% of power
void GLSpectrumView::measureOccupiedBandwidth(const Real *spectrum, bool updateGUI)
{
float hzPerBin = getDisplayedSampleRate() / (float) m_fftSize;
qint64 centerFrequency = getDisplayedCenterFrequency();
int start = frequencyToBin(centerFrequency + m_measurementCenterFrequencyOffset);
float totalPower, power = 0.0f;
int step = 0;
int width = 0;
int idx = start;
float gain = m_useCalibration ? m_calibrationGain : 1.0f;
float shift = m_useCalibration ? m_calibrationShiftdB : 0.0f;
totalPower = CalcDb::powerFromdB(calcChannelPower(spectrum, centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth));
do
{
if ((idx >= 0) && (idx < m_nbBins))
{
if (m_linear) {
power += spectrum[idx] * gain;
} else {
power += CalcDb::powerFromdB(spectrum[idx]) + shift;
}
width++;
}
step++;
if ((step & 1) == 1) {
idx -= step;
} else {
idx += step;
}
}
while (((power / totalPower) < 0.99f) && (step < m_nbBins));
float occupiedBandwidth = width * hzPerBin;
if (m_measurements) {
m_measurements->setOccupiedBandwidth(occupiedBandwidth, updateGUI);
}
if (m_measurementHighlight && updateGUI)
{
qint64 centerFrequency = getDisplayedCenterFrequency();
drawBandwidthMarkers(centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth, m_measurementDarkMarkerColor);
drawBandwidthMarkers(centerFrequency + m_measurementCenterFrequencyOffset, occupiedBandwidth, m_measurementLightMarkerColor);
}
}
// Measure bandwidth -3dB from peak
void GLSpectrumView::measure3dBBandwidth(const Real *spectrum, bool updateGUI)
{
// Find max peak and it's power in dB
int peakBin = findPeakBin(spectrum);
float peakPower = m_linear ? CalcDb::dbPower(spectrum[peakBin]) : spectrum[peakBin];
// Search right until 3dB from peak
int rightBin = peakBin;
for (int i = peakBin + 1; i < m_nbBins; i++)
{
float power = m_linear ? CalcDb::dbPower(spectrum[i]) : spectrum[i];
if (peakPower - power > 3.0f)
{
rightBin = i - 1;
break;
}
}
// Search left until 3dB from peak
int leftBin = peakBin;
for (int i = peakBin - 1; i >= 0; i--)
{
float power = m_linear ? CalcDb::dbPower(spectrum[i]) : spectrum[i];
if (peakPower - power > 3.0f)
{
leftBin = i + 1;
break;
}
}
// Calculate bandwidth
int bins = rightBin - leftBin - 1;
bins = std::max(1, bins);
float hzPerBin = getDisplayedSampleRate() / (float) m_fftSize;
float bandwidth = bins * hzPerBin;
int centerBin = leftBin + (rightBin - leftBin) / 2;
float centerFrequency = binToFrequency(centerBin);
if (m_measurements) {
m_measurements->set3dBBandwidth(bandwidth, updateGUI);
}
if (m_measurementHighlight && updateGUI) {
drawBandwidthMarkers(centerFrequency, bandwidth, m_measurementLightMarkerColor);
}
}
const QVector4D GLSpectrumView::m_measurementLightMarkerColor = QVector4D(0.6f, 0.6f, 0.6f, 0.2f);
const QVector4D GLSpectrumView::m_measurementDarkMarkerColor = QVector4D(0.6f, 0.6f, 0.6f, 0.15f);
// Find the width of a peak, by seaching in either direction until
// power is no longer falling
void GLSpectrumView::peakWidth(const Real *spectrum, int center, int &left, int &right, int maxLeft, int maxRight) const
{
float prevLeft = spectrum[center];
float prevRight = spectrum[center];
left = center - 1;
right = center + 1;
// Use <= as SSB spectrums have duplicated values
while ((left > maxLeft) && (spectrum[left] <= prevLeft) && (right < maxRight) && (spectrum[right] <= prevRight))
{
prevLeft = spectrum[left];
left--;
prevRight = spectrum[right];
right++;
}
}
int GLSpectrumView::findPeakBin(const Real *spectrum) const
{
int bin;
float power;
bin = 0;
power = spectrum[0];
for (int i = 1; i < m_nbBins; i++)
{
if (spectrum[i] > power)
{
power = spectrum[i];
bin = i;
}
}
return bin;
}
float GLSpectrumView::calPower(float power) const
{
if (m_linear) {
return power * (m_useCalibration ? m_calibrationGain : 1.0f);
} else {
return CalcDb::powerFromdB(power) + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
}
}
int GLSpectrumView::frequencyToBin(int64_t frequency) const
{
float rbw = (m_ssbSpectrum ? (getDisplayedSampleRate()/2) : getDisplayedSampleRate()) / (float)m_fftSize;
return (frequency - m_frequencyScale.getRangeMin()) / rbw;
}
int64_t GLSpectrumView::binToFrequency(int bin) const
{
float rbw = (m_ssbSpectrum ? (getDisplayedSampleRate()/2) : getDisplayedSampleRate()) / (float)m_fftSize;
return m_frequencyScale.getRangeMin() + bin * rbw;
}
// Find a peak and measure SNR / THD / SINAD
void GLSpectrumView::measureSNR(const Real *spectrum, bool updateGUI)
{
// Find bin with max peak - that will be our signal
int sig = findPeakBin(spectrum);
int sigLeft, sigRight;
peakWidth(spectrum, sig, sigLeft, sigRight, 0, m_nbBins);
int sigBins = sigRight - sigLeft - 1;
int binsLeft = sig - sigLeft;
int binsRight = sigRight - sig;
// Highlight the signal
float sigFreq = binToFrequency(sig);
if (m_measurementHighlight && updateGUI) {
drawPeakMarkers(binToFrequency(sigLeft+1), binToFrequency(sigRight-1), m_measurementLightMarkerColor);
}
// Find the harmonics and highlight them
QList<int> hBinsLeft;
QList<int> hBinsRight;
QList<int> hBinsBins;
for (int h = 2; h < m_measurementHarmonics + 2; h++)
{
float hFreq = sigFreq * h;
if (hFreq < m_frequencyScale.getRangeMax())
{
int hBin = frequencyToBin(hFreq);
// Check if peak is an adjacent bin
if (spectrum[hBin-1] > spectrum[hBin]) {
hBin--;
} else if (spectrum[hBin+1] > spectrum[hBin]) {
hBin++;
}
hFreq = binToFrequency(hBin);
int hLeft, hRight;
peakWidth(spectrum, hBin, hLeft, hRight, hBin - binsLeft, hBin + binsRight);
int hBins = hRight - hLeft - 1;
if (m_measurementHighlight && updateGUI) {
drawPeakMarkers(binToFrequency(hLeft+1), binToFrequency(hRight-1), m_measurementDarkMarkerColor);
}
hBinsLeft.append(hLeft);
hBinsRight.append(hRight);
hBinsBins.append(hBins);
}
}
// Integrate signal, harmonic and noise power
float sigPower = 0.0f;
float noisePower = 0.0f;
float harmonicPower = 0.0f;
QList<float> noise;
float gain = m_useCalibration ? m_calibrationGain : 1.0f;
float shift = m_useCalibration ? m_calibrationShiftdB : 0.0f;
for (int i = 0; i < m_nbBins; i++)
{
float power;
if (m_linear) {
power = spectrum[i] * gain;
} else {
power = CalcDb::powerFromdB(spectrum[i]) + shift;
}
// Signal power
if ((i > sigLeft) && (i < sigRight))
{
sigPower += power;
continue;
}
// Harmonics
for (int h = 0; h < hBinsLeft.size(); h++)
{
if ((i > hBinsLeft[h]) && (i < hBinsRight[h]))
{
harmonicPower += power;
continue;
}
}
// Noise
noisePower += power;
noise.append(power);
}
// Calculate median of noise
float noiseMedian = 0.0;
if (noise.size() > 0)
{
auto m = noise.begin() + noise.size()/2;
std::nth_element(noise.begin(), m, noise.end());
noiseMedian = noise[noise.size()/2];
}
// Assume we have similar noise where the signal and harmonics are
float inBandNoise = noiseMedian * sigBins;
noisePower += inBandNoise;
sigPower -= inBandNoise;
for (auto hBins : hBinsBins)
{
float hNoise = noiseMedian * hBins;
noisePower += hNoise;
harmonicPower -= hNoise;
}
if (m_measurements)
{
// Calculate SNR in dB over full bandwidth
float snr = CalcDb::dbPower(sigPower / noisePower);
// Calculate SNR, where noise is median of noise summed over signal b/w
float snfr = CalcDb::dbPower(sigPower / inBandNoise);
// Calculate THD - Total harmonic distortion
float thd = harmonicPower / sigPower;
float thdDB = CalcDb::dbPower(thd);
// Calculate THD+N - Total harmonic distortion plus noise
float thdpn = CalcDb::dbPower((harmonicPower + noisePower) / sigPower);
// Calculate SINAD - Signal to noise and distotion ratio (Should be -THD+N)
float sinad = CalcDb::dbPower((sigPower + harmonicPower + noisePower) / (harmonicPower + noisePower));
m_measurements->setSNR(snr, snfr, thdDB, thdpn, sinad, updateGUI);
}
}
void GLSpectrumView::measureSFDR(const Real *spectrum, bool updateGUI)
{
// Find first peak which is our signal
int peakBin = findPeakBin(spectrum);
int peakLeft, peakRight;
peakWidth(spectrum, peakBin, peakLeft, peakRight, 0, m_nbBins);
// Find next largest peak, which is the spur
int nextPeakBin = -1;
float nextPeakPower = -std::numeric_limits<float>::max();
for (int i = 0; i < m_nbBins; i++)
{
if ((i < peakLeft) || (i > peakRight))
{
if (spectrum[i] > nextPeakPower)
{
nextPeakBin = i;
nextPeakPower = spectrum[i];
}
}
}
if (nextPeakBin != -1)
{
// Calculate SFDR in dB from difference between two peaks
float peakPower = calPower(spectrum[peakBin]);
float nextPeakPower = calPower(spectrum[nextPeakBin]);
float peakPowerDB = CalcDb::dbPower(peakPower);
float nextPeakPowerDB = CalcDb::dbPower(nextPeakPower);
float sfdr = peakPowerDB - nextPeakPowerDB;
// Display
if (m_measurements) {
m_measurements->setSFDR(sfdr, updateGUI);
}
if (m_measurementHighlight && updateGUI)
{
if (m_linear) {
drawPowerBandMarkers(peakPower, nextPeakPower, m_measurementDarkMarkerColor);
} else {
drawPowerBandMarkers(peakPowerDB, nextPeakPowerDB, m_measurementDarkMarkerColor);
}
}
}
}
// Find power and frequency of max peak in current spectrum
void GLSpectrumView::findPeak(const Real *spectrum, float &power, float &frequency) const
{
int bin;
bin = 0;
power = spectrum[0];
for (int i = 1; i < m_nbBins; i++)
{
if (spectrum[i] > power)
{
power = spectrum[i];
bin = i;
}
}
power = m_linear ?
power * (m_useCalibration ? m_calibrationGain : 1.0f) :
power + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
frequency = binToFrequency(bin);
}
// Calculate channel power in dB
float GLSpectrumView::calcChannelPower(const Real *spectrum, int64_t centerFrequency, int channelBandwidth) const
{
float hzPerBin = getDisplayedSampleRate() / (float) m_fftSize;
int bins = channelBandwidth / hzPerBin;
int start = frequencyToBin(centerFrequency) - (bins / 2);
int end = start + bins;
float power = 0.0;
start = std::max(start, 0);
end = std::min(end, m_nbBins);
if (m_linear)
{
float gain = m_useCalibration ? m_calibrationGain : 1.0f;
for (int i = start; i < end; i++) {
power += spectrum[i] * gain;
}
}
else
{
float shift = m_useCalibration ? m_calibrationShiftdB : 0.0f;
for (int i = start; i < end; i++) {
power += CalcDb::powerFromdB(spectrum[i]) + shift;
}
}
return CalcDb::dbPower(power);
}
// Test if current spectrum exceeds any of the masks held in spectrum memories
void GLSpectrumView::measureMask(const Real *spectrum, int fftSize, bool updateGUI)
{
if (!updateGUI)
{
for (int m = 0; m < m_spectrumMemory.size(); m++)
{
if ((m_measurementMemMasks & (1 << m)) != 0)
{
if (m_maskFails[m].size() < (std::size_t) fftSize) {
m_maskFails[m].resize(fftSize);
}
int s = std::min((int) m_spectrumMemory[m].m_spectrum.size(), fftSize);
bool fail = false;
for (int i = 0; i < s; i++)
{
if (spectrum[i] > m_spectrumMemory[m].m_spectrum[i])
{
m_maskFails[m][i] = std::max(m_maskFails[m][i], spectrum[i]);
fail = true;
}
}
m_maskTestCount[m]++;
if (fail) {
m_maskFailCount[m]++;
}
}
}
}
else
{
for (int m = 0; m < m_spectrumMemory.size(); m++)
{
if ((m_measurementMemMasks & (1 << m)) != 0)
{
if (m_measurements && updateGUI) {
m_measurements->setMaskTestResult(m, m_maskTestCount[m], m_maskFailCount[m]);
}
}
}
}
}
void GLSpectrumView::stopDrag()
{
if (m_cursorState != CSNormal)
{
if ((m_cursorState == CSSplitterMoving) || (m_cursorState == CSChannelMoving)) {
releaseMouse();
}
setCursor(Qt::ArrowCursor);
m_cursorState = CSNormal;
}
}
// Get text to display on waterfall vertical axis when displaying system time
// value is [0,m_waterfallHeight], as set in setTimeScaleRange()
QString GLSpectrumView::formatTick(double value) const
{
int idx = value - scrollBarValue() + m_spectrumBuffer.size() - 1 - m_waterfallHeight;
if ((idx >= 0) && (idx < (int) m_spectrumBuffer.size()))
{
QDateTime dt = m_spectrumBuffer[idx].m_dateTime;
if (m_waterfallTimeUnits == SpectrumSettings::LocalTime) {
dt = dt.toLocalTime();
} else {
dt = dt.toUTC();
}
return dt.toString(m_waterfallTimeFormat);
}
else
{
return "";
}
}
void GLSpectrumView::setTimeScaleRange()
{
if (m_waterfallTimeUnits != SpectrumSettings::TimeOffset)
{
// System clock times from when spectrum was captured - mapped to actual times in formatTick()
if (m_invertedWaterfall) {
m_timeScale.setRange(Unit::None, m_waterfallHeight, 0);
} else {
m_timeScale.setRange(Unit::None, 0, m_waterfallHeight);
}
}
else if (getDisplayedSampleRate() > 0)
{
float timeScaleDiv = ((float)getDisplayedSampleRate() / (float)m_timingRate);
if (m_fftSize > m_fftOverlap) {
timeScaleDiv *= m_fftSize / (float)(m_fftSize - m_fftOverlap);
}
int idx = scrollBarValue();
float timeMin = (idx * m_fftSize) / timeScaleDiv;
float timeMax = ((idx + m_waterfallHeight) * m_fftSize) / timeScaleDiv;
if (!m_invertedWaterfall) {
m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, timeMax / timeScaleDiv, timeMin);
} else {
m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, timeMin, timeMax);
}
}
else
{
if (!m_invertedWaterfall) {
m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 0, 1);
} else {
m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 1, 0);
}
}
}
void GLSpectrumView::paintLeftScales()
{
QFontMetrics fm(font());
int M = fm.horizontalAdvance("-");
float maxSize = 0.0f;
m_leftMarginPixmap = QPixmap(m_leftMargin - 1, height());
m_leftMarginPixmap.fill(Qt::transparent);
{
QPainter painter(&m_leftMarginPixmap);
painter.setPen(QColor(0xf0, 0xf0, 0xff));
painter.setFont(font());
const ScaleEngine::TickList* tickList;
const ScaleEngine::Tick* tick;
if (m_displayWaterfall) {
tickList = &m_timeScale.getTickList();
for (int i = 0; i < tickList->count(); i++) {
tick = &(*tickList)[i];
if (tick->major)
{
if (tick->textSize > 0)
painter.drawText(QPointF(m_leftMargin - M - tick->textSize, m_waterfallTop + fm.ascent() + tick->textPos), tick->text);
maxSize = std::max(maxSize, tick->textSize);
}
}
}
if (m_displayHistogram || m_displayMaxHold || m_displayCurrent) {
tickList = &m_powerScale.getTickList();
for (int i = 0; i < tickList->count(); i++) {
tick = &(*tickList)[i];
if (tick->major) {
if (tick->textSize > 0)
painter.drawText(QPointF(m_leftMargin - M - tick->textSize, m_histogramTop + m_histogramHeight - tick->textPos - 1), tick->text);
maxSize = std::max(maxSize, tick->textSize);
}
}
}
}
if (maxSize >= m_leftMargin) {
m_changesPending = true; // Recalculate margin
}
}
void GLSpectrumView::applyChanges()
{
if (m_nbBins <= 0) {
return;
}
qint64 centerFrequency = getDisplayedCenterFrequency();
QFontMetrics fm(font());
int M = fm.horizontalAdvance("-");
m_topMargin = fm.ascent() * 2;
m_bottomMargin = fm.ascent() * 1;
m_infoHeight = fm.height() * 3;
m_waterfallTop = 0;
m_frequencyScaleHeight = fm.height() * 3; // +1 line for marker frequency scale
int frequencyScaleTop = 0;
m_histogramTop = 0;
//int m_leftMargin;
m_rightMargin = fm.horizontalAdvance("000");
// displays both histogram and waterfall
if ((m_displayWaterfall || m_display3DSpectrogram) && (m_displayHistogram || m_displayMaxHold || m_displayCurrent))
{
m_waterfallHeight = height() * m_waterfallShare - 1;
if (m_waterfallHeight < 0) {
m_waterfallHeight = 0;
}
if (m_invertedWaterfall)
{
m_histogramTop = m_topMargin;
m_histogramHeight = height() - m_topMargin - m_waterfallHeight - m_frequencyScaleHeight - m_bottomMargin;
m_waterfallTop = m_histogramTop + m_histogramHeight + m_frequencyScaleHeight + 1;
frequencyScaleTop = m_histogramTop + m_histogramHeight + 1;
}
else
{
m_waterfallTop = m_topMargin;
frequencyScaleTop = m_waterfallTop + m_waterfallHeight + 1;
m_histogramTop = m_waterfallTop + m_waterfallHeight + m_frequencyScaleHeight + 1;
m_histogramHeight = height() - m_topMargin - m_waterfallHeight - m_frequencyScaleHeight - m_bottomMargin;
}
m_timeScale.setSize(m_waterfallHeight);
setTimeScaleRange();
m_leftMargin = m_timeScale.getScaleWidth();
setPowerScale(m_histogramHeight);
m_leftMargin += 2 * M;
setFrequencyScale();
m_glWaterfallBoxMatrix.setToIdentity();
m_glWaterfallBoxMatrix.translate(
-1.0f + ((float)(2*m_leftMargin) / (float) width()),
1.0f - ((float)(2*m_waterfallTop) / (float) height())
);
m_glWaterfallBoxMatrix.scale(
((float) 2 * (width() - m_leftMargin - m_rightMargin)) / (float) width(),
(float) (-2*m_waterfallHeight) / (float) height()
);
m_glHistogramBoxMatrix.setToIdentity();
m_glHistogramBoxMatrix.translate(
-1.0f + ((float)(2*m_leftMargin) / (float) width()),
1.0f - ((float)(2*m_histogramTop) / (float) height())
);
m_glHistogramBoxMatrix.scale(
((float) 2 * (width() - m_leftMargin - m_rightMargin)) / (float) width(),
(float) (-2*m_histogramHeight) / (float) height()
);
m_glHistogramSpectrumMatrix.setToIdentity();
m_glHistogramSpectrumMatrix.translate(
-1.0f + ((float)(2*m_leftMargin) / (float) width()),
1.0f - ((float)(2*m_histogramTop) / (float) height())
);
m_glHistogramSpectrumMatrix.scale(
((float) 2 * (width() - m_leftMargin - m_rightMargin)) / ((float) width() * (float)(m_nbBins)),
((float) 2*m_histogramHeight / height()) / m_powerRange
);
// m_frequencyScaleRect = QRect(
// 0,
// frequencyScaleTop,
// width(),
// m_frequencyScaleHeight
// );
m_glFrequencyScaleBoxMatrix.setToIdentity();
m_glFrequencyScaleBoxMatrix.translate (
-1.0f,
1.0f - ((float) 2*frequencyScaleTop / (float) height())
);
m_glFrequencyScaleBoxMatrix.scale (
2.0f,
(float) -2*m_frequencyScaleHeight / (float) height()
);
m_glLeftScaleBoxMatrix.setToIdentity();
m_glLeftScaleBoxMatrix.translate(-1.0f, 1.0f);
m_glLeftScaleBoxMatrix.scale(
(float)(2*(m_leftMargin - 1)) / (float) width(),
-2.0f
);
}
// displays waterfall/3D spectrogram only
else if (m_displayWaterfall || m_display3DSpectrogram)
{
m_histogramHeight = 0;
m_histogramTop = 0;
m_bottomMargin = m_frequencyScaleHeight;
m_waterfallHeight = height() - m_topMargin - m_frequencyScaleHeight;
m_waterfallTop = m_topMargin;
frequencyScaleTop = m_topMargin + m_waterfallHeight + 1;
m_timeScale.setSize(m_waterfallHeight);
setTimeScaleRange();
m_leftMargin = m_timeScale.getScaleWidth();
setPowerScale((height() - m_topMargin - m_bottomMargin) / 2.0);
m_leftMargin += 2 * M;
setFrequencyScale();
m_glWaterfallBoxMatrix.setToIdentity();
m_glWaterfallBoxMatrix.translate(
-1.0f + ((float)(2*m_leftMargin) / (float) width()),
1.0f - ((float)(2*m_topMargin) / (float) height())
);
m_glWaterfallBoxMatrix.scale(
((float) 2 * (width() - m_leftMargin - m_rightMargin)) / (float) width(),
(float) (-2*m_waterfallHeight) / (float) height()
);
// m_frequencyScaleRect = QRect(
// 0,
// frequencyScaleTop,
// width(),
// m_frequencyScaleHeight
// );
m_glFrequencyScaleBoxMatrix.setToIdentity();
m_glFrequencyScaleBoxMatrix.translate (
-1.0f,
1.0f - ((float) 2*frequencyScaleTop / (float) height())
);
m_glFrequencyScaleBoxMatrix.scale (
2.0f,
(float) -2*m_frequencyScaleHeight / (float) height()
);
m_glLeftScaleBoxMatrix.setToIdentity();
m_glLeftScaleBoxMatrix.translate(-1.0f, 1.0f);
m_glLeftScaleBoxMatrix.scale(
(float)(2*(m_leftMargin - 1)) / (float) width(),
-2.0f
);
}
// displays histogram only
else if (m_displayHistogram || m_displayMaxHold || m_displayCurrent)
{
m_bottomMargin = m_frequencyScaleHeight;
frequencyScaleTop = height() - m_bottomMargin;
m_histogramTop = m_topMargin - 1;
m_waterfallHeight = 0;
m_histogramHeight = height() - m_topMargin - m_frequencyScaleHeight;
m_leftMargin = 0;
setPowerScale(m_histogramHeight);
m_leftMargin += 2 * M;
setFrequencyScale();
m_glHistogramSpectrumMatrix.setToIdentity();
m_glHistogramSpectrumMatrix.translate(
-1.0f + ((float)(2*m_leftMargin) / (float) width()),
1.0f - ((float)(2*m_histogramTop) / (float) height())
);
m_glHistogramSpectrumMatrix.scale(
((float) 2 * (width() - m_leftMargin - m_rightMargin)) / ((float) width() * (float)(m_nbBins)),
((float) 2*(height() - m_topMargin - m_frequencyScaleHeight)) / (height()*m_powerRange)
);
m_glHistogramBoxMatrix.setToIdentity();
m_glHistogramBoxMatrix.translate(
-1.0f + ((float)(2*m_leftMargin) / (float) width()),
1.0f - ((float)(2*m_histogramTop) / (float) height())
);
m_glHistogramBoxMatrix.scale(
((float) 2 * (width() - m_leftMargin - m_rightMargin)) / (float) width(),
(float) (-2*(height() - m_topMargin - m_frequencyScaleHeight)) / (float) height()
);
// m_frequencyScaleRect = QRect(
// 0,
// frequencyScaleTop,
// width(),
// m_frequencyScaleHeight
// );
m_glFrequencyScaleBoxMatrix.setToIdentity();
m_glFrequencyScaleBoxMatrix.translate (
-1.0f,
1.0f - ((float) 2*frequencyScaleTop / (float) height())
);
m_glFrequencyScaleBoxMatrix.scale (
2.0f,
(float) -2*m_frequencyScaleHeight / (float) height()
);
m_glLeftScaleBoxMatrix.setToIdentity();
m_glLeftScaleBoxMatrix.translate(-1.0f, 1.0f);
m_glLeftScaleBoxMatrix.scale(
(float)(2*(m_leftMargin - 1)) / (float) width(),
-2.0f
);
}
else
{
m_leftMargin = 2;
m_waterfallHeight = 0;
}
m_glShaderSpectrogram.setScaleX(((width() - m_leftMargin - m_rightMargin) / (float)m_waterfallHeight));
m_glShaderSpectrogram.setScaleZ((m_histogramHeight != 0 ? m_histogramHeight : m_waterfallHeight / 4) / (float)(width() - m_leftMargin - m_rightMargin));
// bounding boxes
m_frequencyScaleRect = QRect(
0,
frequencyScaleTop,
width(),
m_frequencyScaleHeight
);
if ((m_invertedWaterfall) || (m_waterfallHeight == 0))
{
m_histogramRect = QRectF(
(float) m_leftMargin / (float) width(),
(float) m_topMargin / (float) height(),
(float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
(float) (m_histogramHeight) / (float) height()
);
}
else
{
m_histogramRect = QRectF(
(float) m_leftMargin / (float) width(),
(float) (m_waterfallTop + m_waterfallHeight + m_frequencyScaleHeight) / (float) height(),
(float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
(float) m_histogramHeight / (float) height()
);
}
if (!m_invertedWaterfall || (m_histogramHeight == 0))
{
m_waterfallRect = QRectF(
(float) m_leftMargin / (float) width(),
(float) m_topMargin / (float) height(),
(float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
(float) m_waterfallHeight / (float) height()
);
}
else
{
m_waterfallRect = QRectF(
(float) m_leftMargin / (float) width(),
(float) (m_topMargin + m_histogramHeight + m_frequencyScaleHeight) / (float) height(),
(float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
(float) (m_waterfallHeight) / (float) height()
);
}
m_glShaderSpectrogram.setAspectRatio((width() - m_leftMargin - m_rightMargin) / (float)m_waterfallHeight);
m_3DSpectrogramBottom = m_bottomMargin;
if (!m_invertedWaterfall) {
m_3DSpectrogramBottom += m_histogramHeight + m_frequencyScaleHeight + 1;
}
// channel overlays
int64_t centerFrequencyUnused;
int frequencySpan;
getFrequencyZoom(centerFrequencyUnused, frequencySpan);
for (int i = 0; i < m_channelMarkerStates.size(); ++i)
{
ChannelMarkerState* dv = m_channelMarkerStates[i];
qreal xc, pw, nw, dsbw;
ChannelMarker::sidebands_t sidebands = dv->m_channelMarker->getSidebands();
xc = centerFrequency + dv->m_channelMarker->getCenterFrequency(); // marker center frequency
dsbw = dv->m_channelMarker->getBandwidth();
if (sidebands == ChannelMarker::usb)
{
nw = dv->m_channelMarker->getLowCutoff() + dv->m_channelMarker->getShift(); // negative bandwidth
int bw = dv->m_channelMarker->getBandwidth() / 2;
pw = (qreal) bw + dv->m_channelMarker->getShift(); // positive bandwidth
}
else if (sidebands == ChannelMarker::lsb)
{
pw = dv->m_channelMarker->getLowCutoff() + dv->m_channelMarker->getShift();
int bw = dv->m_channelMarker->getBandwidth() / 2;
nw = (qreal) bw + dv->m_channelMarker->getShift();
}
else if (sidebands == ChannelMarker::vusb)
{
nw = -dv->m_channelMarker->getOppositeBandwidth() + dv->m_channelMarker->getShift(); // negative bandwidth
pw = dv->m_channelMarker->getBandwidth() + dv->m_channelMarker->getShift(); // positive bandwidth
}
else if (sidebands == ChannelMarker::vlsb) {
pw = dv->m_channelMarker->getOppositeBandwidth() + dv->m_channelMarker->getShift(); // positive bandwidth
nw = -dv->m_channelMarker->getBandwidth() + dv->m_channelMarker->getShift(); // negative bandwidth
}
else
{
pw = (dsbw / 2) + dv->m_channelMarker->getShift();
nw = -(dsbw / 2) + dv->m_channelMarker->getShift();
}
// draw the DSB rectangle
QMatrix4x4 glMatrixDsb;
glMatrixDsb.setToIdentity();
glMatrixDsb.translate(
-1.0f + 2.0f * ((m_leftMargin + m_frequencyScale.getPosFromValue(xc - (dsbw/2))) / (float) width()),
1.0f
);
glMatrixDsb.scale(
2.0f * (dsbw / (float) frequencySpan),
-2.0f
);
dv->m_glMatrixDsbWaterfall = glMatrixDsb;
dv->m_glMatrixDsbWaterfall.translate(
0.0f,
(float) m_waterfallTop / (float) height()
);
dv->m_glMatrixDsbWaterfall.scale(
(float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
(float) m_waterfallHeight / (float) height()
);
dv->m_glMatrixDsbHistogram = glMatrixDsb;
dv->m_glMatrixDsbHistogram.translate(
0.0f,
(float) m_histogramTop / (float) height()
);
dv->m_glMatrixDsbHistogram.scale(
(float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
(float) m_histogramHeight / (float) height()
);
dv->m_glMatrixDsbFreqScale = glMatrixDsb;
dv->m_glMatrixDsbFreqScale.translate(
0.0f,
(float) frequencyScaleTop / (float) height()
);
dv->m_glMatrixDsbFreqScale.scale(
(float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
(float) m_frequencyScaleHeight / (float) height()
);
// draw the effective BW rectangle
QMatrix4x4 glMatrix;
glMatrix.setToIdentity();
glMatrix.translate(
-1.0f + 2.0f * ((m_leftMargin + m_frequencyScale.getPosFromValue(xc + nw)) / (float) width()),
1.0f
);
glMatrix.scale(
2.0f * ((pw-nw) / (float) frequencySpan),
-2.0f
);
dv->m_glMatrixWaterfall = glMatrix;
dv->m_glMatrixWaterfall.translate(
0.0f,
(float) m_waterfallTop / (float) height()
);
dv->m_glMatrixWaterfall.scale(
(float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
(float) m_waterfallHeight / (float) height()
);
dv->m_glMatrixHistogram = glMatrix;
dv->m_glMatrixHistogram.translate(
0.0f,
(float) m_histogramTop / (float) height()
);
dv->m_glMatrixHistogram.scale(
(float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
(float) m_histogramHeight / (float) height()
);
dv->m_glMatrixFreqScale = glMatrix;
dv->m_glMatrixFreqScale.translate(
0.0f,
(float) frequencyScaleTop / (float) height()
);
dv->m_glMatrixFreqScale.scale(
(float) (width() - m_leftMargin - m_rightMargin) / (float) width(),
(float) m_frequencyScaleHeight / (float) height()
);
/*
dv->m_glRect.setRect(
m_frequencyScale.getPosFromValue(centerFrequency + dv->m_channelMarker->getCenterFrequency() - dv->m_channelMarker->getBandwidth() / 2) / (float)(width() - m_leftMargin - m_rightMargin),
0,
(dv->m_channelMarker->getBandwidth() / (float)getDisplayedSampleRate()),
1);
*/
if (m_displayHistogram || m_displayMaxHold || m_displayCurrent || m_displayWaterfall)
{
dv->m_rect.setRect(m_frequencyScale.getPosFromValue(xc) + m_leftMargin - 1,
m_topMargin,
5,
height() - m_topMargin - m_bottomMargin);
}
/*
if(m_displayHistogram || m_displayMaxHold || m_displayWaterfall) {
dv->m_rect.setRect(m_frequencyScale.getPosFromValue(centerFrequency + dv->m_channelMarker->getCenterFrequency()) + m_leftMargin - 1,
m_topMargin,
5,
height() - m_topMargin - m_bottomMargin);
}
*/
}
// prepare left scales (time and power)
paintLeftScales();
m_glShaderLeftScale.initTexture(m_leftMarginPixmap.toImage());
// prepare frequency scale
if (m_displayWaterfall || m_display3DSpectrogram || m_displayHistogram || m_displayMaxHold || m_displayCurrent) {
m_frequencyPixmap = QPixmap(width(), m_frequencyScaleHeight);
m_frequencyPixmap.fill(Qt::transparent);
{
QPainter painter(&m_frequencyPixmap);
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::black);
painter.setBrush(Qt::transparent);
painter.drawRect(m_leftMargin, 0, width() - m_leftMargin, m_frequencyScaleHeight);
painter.setPen(QColor(0xf0, 0xf0, 0xff));
painter.setFont(font());
const ScaleEngine::TickList* tickList = &m_frequencyScale.getTickList();
const ScaleEngine::Tick* tick;
for (int i = 0; i < tickList->count(); i++) {
tick = &(*tickList)[i];
if (tick->major) {
if (tick->textSize > 0)
painter.drawText(QPointF(m_leftMargin + tick->textPos, fm.height() + fm.ascent() / 2 - 1), tick->text);
}
}
// Frequency overlay on highlighted marker
for (int i = 0; i < m_channelMarkerStates.size(); ++i)
{
ChannelMarkerState* dv = m_channelMarkerStates[i];
if (dv->m_channelMarker->getHighlighted()
&& (dv->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink)
&& dv->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
{
qreal xc;
int shift;
//ChannelMarker::sidebands_t sidebands = dv->m_channelMarker->getSidebands();
xc = centerFrequency + dv->m_channelMarker->getCenterFrequency(); // marker center frequency
QString ftext;
switch (dv->m_channelMarker->getFrequencyScaleDisplayType())
{
case ChannelMarker::FScaleDisplay_freq:
ftext = QString::number((centerFrequency + dv->m_channelMarker->getCenterFrequency())/1e6, 'f', 6);
break;
case ChannelMarker::FScaleDisplay_title:
ftext = dv->m_channelMarker->getTitle();
break;
case ChannelMarker::FScaleDisplay_addressSend:
ftext = dv->m_channelMarker->getDisplayAddressSend();
break;
case ChannelMarker::FScaleDisplay_addressReceive:
ftext = dv->m_channelMarker->getDisplayAddressReceive();
break;
default:
ftext = QString::number((centerFrequency + dv->m_channelMarker->getCenterFrequency())/1e6, 'f', 6);
break;
}
if (dv->m_channelMarker->getCenterFrequency() < 0) { // left half of scale
ftext = " " + ftext;
shift = 0;
} else { // right half of scale
ftext = ftext + " ";
shift = - fm.horizontalAdvance(ftext);
}
painter.drawText(QPointF(m_leftMargin + m_frequencyScale.getPosFromValue(xc) + shift, 2*fm.height() + fm.ascent() / 2 - 1), ftext);
}
}
}
m_glShaderFrequencyScale.initTexture(m_frequencyPixmap.toImage());
}
// prepare left scale for spectrogram (time)
{
m_spectrogramTimePixmap = QPixmap(m_leftMargin - 1, fm.ascent() + m_waterfallHeight);
m_spectrogramTimePixmap.fill(Qt::transparent);
{
QPainter painter(&m_spectrogramTimePixmap);
painter.setPen(QColor(0xf0, 0xf0, 0xff));
painter.setFont(font());
const ScaleEngine::TickList* tickList;
const ScaleEngine::Tick* tick;
if (m_display3DSpectrogram) {
tickList = &m_timeScale.getTickList();
for (int i = 0; i < tickList->count(); i++) {
tick = &(*tickList)[i];
if (tick->major) {
if (tick->textSize > 0)
painter.drawText(QPointF(m_leftMargin - M - tick->textSize, fm.height() + tick->textPos), tick->text);
}
}
}
}
m_glShaderSpectrogramTimeScale.initTexture(m_spectrogramTimePixmap.toImage());
}
// prepare vertical scale for spectrogram (power)
{
int h = m_histogramHeight != 0 ? m_histogramHeight : m_waterfallHeight / 4;
m_spectrogramPowerPixmap = QPixmap(m_leftMargin - 1, m_topMargin + h);
m_spectrogramPowerPixmap.fill(Qt::transparent);
{
QPainter painter(&m_spectrogramPowerPixmap);
painter.setPen(QColor(0xf0, 0xf0, 0xff));
painter.setFont(font());
const ScaleEngine::TickList* tickList;
const ScaleEngine::Tick* tick;
if (m_display3DSpectrogram) {
tickList = &m_powerScale.getTickList();
for (int i = 0; i < tickList->count(); i++) {
tick = &(*tickList)[i];
if (tick->major) {
if (tick->textSize > 0)
painter.drawText(QPointF(m_leftMargin - M - tick->textSize, m_topMargin + h - tick->textPos - 1), tick->text);
}
}
}
}
m_glShaderSpectrogramPowerScale.initTexture(m_spectrogramPowerPixmap.toImage());
}
// Top info line
m_glInfoBoxMatrix.setToIdentity();
m_glInfoBoxMatrix.translate (
-1.0f,
1.0f
);
m_glInfoBoxMatrix.scale (
2.0f,
(float) -2*m_infoHeight / (float) height()
);
m_infoRect = QRect(
0,
0,
width(),
m_infoHeight
);
QString infoText;
formatTextInfo(infoText);
m_infoPixmap = QPixmap(width(), m_infoHeight);
m_infoPixmap.fill(Qt::transparent);
{
QPainter painter(&m_infoPixmap);
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::black);
painter.setBrush(Qt::transparent);
painter.drawRect(m_leftMargin, 0, width() - m_leftMargin, m_infoHeight);
painter.setPen(QColor(0xf0, 0xf0, 0xff));
painter.setFont(font());
painter.drawText(QPointF(m_leftMargin, fm.height() + fm.ascent() / 2 - 2), infoText);
}
m_glShaderInfo.initTexture(m_infoPixmap.toImage());
// Peak details in top info line
QString minFrequencyStr = displayFull(centerFrequency - getDisplayedSampleRate()/2); // This can be wider if negative, while max is positive
QString maxFrequencyStr = displayFull(centerFrequency + getDisplayedSampleRate()/2);
m_peakFrequencyMaxStr = minFrequencyStr.size() > maxFrequencyStr.size() ? minFrequencyStr : maxFrequencyStr;
m_peakFrequencyMaxStr = m_peakFrequencyMaxStr.append("Hz");
m_peakPowerMaxStr = m_linear ? "8.000e-10" : "-100.0";
m_peakPowerUnits = m_linear ? "" : "dB";
m_glShaderSpectrogram.initColorMapTexture(m_colorMapName);
m_glShaderColorMap.initColorMapTexture(m_colorMapName);
m_colorMap = ColorMap::getColorMap(m_colorMapName);
// Why only 240 entries in the palette?
for (int i = 0; i <= 239; i++)
{
((quint8*)&m_waterfallPalette[i])[0] = (quint8)(m_colorMap[i*3] * 255.0);
((quint8*)&m_waterfallPalette[i])[1] = (quint8)(m_colorMap[i*3+1] * 255.0);
((quint8*)&m_waterfallPalette[i])[2] = (quint8)(m_colorMap[i*3+2] * 255.0);
((quint8*)&m_waterfallPalette[i])[3] = 255;
}
bool waterfallFFTSizeChanged = true;
if (m_waterfallBuffer) {
waterfallFFTSizeChanged = m_waterfallBuffer->width() != m_nbBins + 1;
}
bool windowSizeChanged = m_waterfallTextureHeight != m_waterfallHeight;
if (waterfallFFTSizeChanged || windowSizeChanged)
{
if (m_waterfallBuffer) {
delete m_waterfallBuffer;
}
m_waterfallBuffer = new QImage(m_nbBins + 1, m_waterfallHeight, QImage::Format_ARGB32);
m_waterfallBuffer->fill(qRgb(0x00, 0x00, 0x00));
if (m_waterfallHeight > 0) {
m_glShaderWaterfall.initTexture(*m_waterfallBuffer);
}
m_waterfallBufferPos = 0;
if (m_3DSpectrogramBuffer) {
delete m_3DSpectrogramBuffer;
}
m_3DSpectrogramBuffer = new QImage(m_nbBins + 1, m_waterfallHeight, QImage::Format_Grayscale8);
m_3DSpectrogramBuffer->fill(qRgb(0x00, 0x00, 0x00));
if (m_waterfallHeight > 0) {
m_glShaderSpectrogram.initTexture(*m_3DSpectrogramBuffer);
}
m_3DSpectrogramBufferPos = 0;
m_waterfallTextureHeight = m_waterfallHeight;
m_waterfallTexturePos = 0;
m_3DSpectrogramTextureHeight = m_waterfallHeight;
m_3DSpectrogramTexturePos = 0;
}
bool histogramFFTSizeChanged = true;
if (m_histogramBuffer) {
histogramFFTSizeChanged = m_histogramBuffer->width() != m_nbBins;
}
if (histogramFFTSizeChanged)
{
if (m_histogramBuffer) {
delete m_histogramBuffer;
}
m_histogramBuffer = new QImage(m_nbBins, 100, QImage::Format_RGB32);
m_histogramBuffer->fill(qRgb(0x00, 0x00, 0x00));
m_glShaderHistogram.initTexture(*m_histogramBuffer, QOpenGLTexture::ClampToEdge);
if (m_histogram) {
delete[] m_histogram;
}
m_histogram = new quint8[100 * m_nbBins];
memset(m_histogram, 0x00, 100 * m_nbBins);
m_q3FFT.allocate(2*(m_nbBins+1));
m_q3ColorMap.allocate(4*(m_nbBins+1));
std::fill(m_q3ColorMap.m_array, m_q3ColorMap.m_array+4*(m_nbBins+1), 0.0f);
}
if (m_redrawAll)
{
redrawSpectrum();
redrawWaterfallAnd3DSpectrogram();
m_redrawAll = false;
}
else if (waterfallFFTSizeChanged || windowSizeChanged)
{
redrawWaterfallAnd3DSpectrogram();
}
else if (histogramFFTSizeChanged)
{
redrawSpectrum();
}
m_q3TickTime.allocate(4*m_timeScale.getTickList().count());
m_q3TickFrequency.allocate(4*m_frequencyScale.getTickList().count());
m_q3TickPower.allocate(6*m_powerScale.getTickList().count()); // 6 as we need 3d points for 3D spectrogram
updateHistogramMarkers();
updateWaterfallMarkers();
updateSortedAnnotationMarkers();
} // applyChanges
void GLSpectrumView::updateHistogramMarkers()
{
if (getDisplayedSampleRate() == 0) {
return;
}
int64_t centerFrequency;
int frequencySpan;
getFrequencyZoom(centerFrequency, frequencySpan);
int effFftSize = m_fftSize * ((float) frequencySpan / (float) getDisplayedSampleRate());
for (int i = 0; i < m_histogramMarkers.size(); i++)
{
float powerI = m_linear ?
m_histogramMarkers.at(i).m_power * (m_useCalibration ? m_calibrationGain : 1.0f) :
CalcDb::dbPower(m_histogramMarkers.at(i).m_power) + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
m_histogramMarkers[i].m_point.rx() =
(m_histogramMarkers[i].m_frequency - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
m_histogramMarkers[i].m_point.ry() =
(m_powerScale.getRangeMax() - powerI) / m_powerScale.getRange();
// m_histogramMarkers[i].m_fftBin =
// (((m_histogramMarkers[i].m_frequency - centerFrequency) / (float) getDisplayedSampleRate()) + 0.5) * m_fftSize;
m_histogramMarkers[i].m_fftBin =
(((m_histogramMarkers[i].m_frequency - centerFrequency) / (float) frequencySpan) + 0.5) * effFftSize;
m_histogramMarkers[i].m_point.rx() = m_histogramMarkers[i].m_point.rx() < 0 ?
0 : m_histogramMarkers[i].m_point.rx() > 1 ?
1 : m_histogramMarkers[i].m_point.rx();
m_histogramMarkers[i].m_point.ry() = m_histogramMarkers[i].m_point.ry() < 0 ?
0 : m_histogramMarkers[i].m_point.ry() > 1 ?
1 : m_histogramMarkers[i].m_point.ry();
m_histogramMarkers[i].m_fftBin = m_histogramMarkers[i].m_fftBin < 0 ?
0 : m_histogramMarkers[i].m_fftBin > m_fftSize - 1 ?
m_fftSize - 1 : m_histogramMarkers[i].m_fftBin;
m_histogramMarkers[i].m_frequencyStr = displayScaled(
m_histogramMarkers[i].m_frequency,
'f',
getPrecision((centerFrequency*1000)/getDisplayedSampleRate()),
false);
m_histogramMarkers[i].m_powerStr = displayPower(
powerI,
m_linear ? 'e' : 'f',
m_linear ? 3 : 1);
if (i > 0)
{
int64_t deltaFrequency = m_histogramMarkers.at(i).m_frequency - m_histogramMarkers.at(0).m_frequency;
m_histogramMarkers[i].m_deltaFrequencyStr = displayScaled(
deltaFrequency,
'f',
getPrecision(deltaFrequency/getDisplayedSampleRate()),
true);
float power0 = m_linear ?
m_histogramMarkers.at(0).m_power * (m_useCalibration ? m_calibrationGain : 1.0f) :
CalcDb::dbPower(m_histogramMarkers.at(0).m_power) + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
m_histogramMarkers[i].m_deltaPowerStr = displayPower(
powerI - power0,
m_linear ? 'e' : 'f',
m_linear ? 3 : 1);
}
}
}
void GLSpectrumView::updateHistogramPeaks()
{
int j = 0;
for (int i = 0; i < m_histogramMarkers.size(); i++)
{
if (j >= (int) m_peakFinder.getPeaks().size()) {
break;
}
int fftBin = m_peakFinder.getPeaks()[j].second;
Real power = m_peakFinder.getPeaks()[j].first;
// qDebug("GLSpectrumView::updateHistogramPeaks: %d %d %f", j, fftBin, power);
if ((m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePower) ||
((m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePowerMax) &&
(m_histogramMarkers.at(i).m_holdReset || (power > m_histogramMarkers.at(i).m_powerMax))))
{
float binSize = m_frequencyScale.getRange() / m_nbBins;
m_histogramMarkers[i].m_fftBin = fftBin;
m_histogramMarkers[i].m_frequency = m_frequencyScale.getRangeMin() + binSize*fftBin;
m_histogramMarkers[i].m_point.rx() = binSize*fftBin / m_frequencyScale.getRange();
if (i == 0)
{
m_histogramMarkers[i].m_frequencyStr = displayScaled(
m_histogramMarkers[i].m_frequency,
'f',
getPrecision((getDisplayedCenterFrequency()*1000)/getDisplayedSampleRate()),
false
);
}
else
{
int64_t deltaFrequency = m_histogramMarkers.at(i).m_frequency - m_histogramMarkers.at(0).m_frequency;
m_histogramMarkers[i].m_deltaFrequencyStr = displayScaled(
deltaFrequency,
'f',
getPrecision(deltaFrequency/getDisplayedSampleRate()),
true
);
}
}
else
{
continue;
}
j++;
}
}
void GLSpectrumView::updateWaterfallMarkers()
{
for (int i = 0; i < m_waterfallMarkers.size(); i++)
{
m_waterfallMarkers[i].m_point.rx() =
(m_waterfallMarkers[i].m_frequency - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
m_waterfallMarkers[i].m_point.ry() =
(m_waterfallMarkers[i].m_time - m_timeScale.getRangeMin()) / m_timeScale.getRange();
m_waterfallMarkers[i].m_point.rx() = m_waterfallMarkers[i].m_point.rx() < 0 ?
0 : m_waterfallMarkers[i].m_point.rx() > 1 ?
1 : m_waterfallMarkers[i].m_point.rx();
m_waterfallMarkers[i].m_point.ry() = m_waterfallMarkers[i].m_point.ry() < 0 ?
0 : m_waterfallMarkers[i].m_point.ry() > 1 ?
1 : m_waterfallMarkers[i].m_point.ry();
m_waterfallMarkers[i].m_frequencyStr = displayScaled(
m_waterfallMarkers[i].m_frequency,
'f',
getPrecision((getDisplayedCenterFrequency()*1000)/getDisplayedSampleRate()),
false);
m_waterfallMarkers[i].m_timeStr = displayScaledF(
m_waterfallMarkers[i].m_time,
'f',
3,
true);
if (i > 0)
{
int64_t deltaFrequency = m_waterfallMarkers.at(i).m_frequency - m_waterfallMarkers.at(0).m_frequency;
m_waterfallMarkers.back().m_deltaFrequencyStr = displayScaled(
deltaFrequency,
'f',
getPrecision(deltaFrequency/getDisplayedSampleRate()),
true);
m_waterfallMarkers.back().m_deltaTimeStr = displayScaledF(
m_waterfallMarkers.at(i).m_time - m_waterfallMarkers.at(0).m_time,
'f',
3,
true);
}
}
}
void GLSpectrumView::updateAnnotationMarkers()
{
emit updateAnnotations(); // Notify other plugins we have updated annotations
if (!(m_markersDisplay & SpectrumSettings::MarkersDisplayAnnotations)) {
return;
}
m_sortedAnnotationMarkers.clear();
for (auto &marker : m_annotationMarkers) {
m_sortedAnnotationMarkers.push_back(&marker);
}
std::sort(m_sortedAnnotationMarkers.begin(), m_sortedAnnotationMarkers.end(), annotationDisplayLessThan);
updateSortedAnnotationMarkers();
}
void GLSpectrumView::updateSortedAnnotationMarkers()
{
if (!(m_markersDisplay & SpectrumSettings::MarkersDisplayAnnotations)) {
return;
}
m_visibleAnnotationMarkers.clear();
for (auto &marker : m_sortedAnnotationMarkers)
{
float startPos = (marker->m_startFrequency - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
float stopPos = ((marker->m_startFrequency + marker->m_bandwidth) - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange();
if ((startPos > 1.0f) || (stopPos < 0.0f)) // out of range
{
continue;
}
m_visibleAnnotationMarkers.push_back(marker);
m_visibleAnnotationMarkers.back()->m_startPos = startPos < 0.0f ? 0.0f : startPos;
m_visibleAnnotationMarkers.back()->m_stopPos = stopPos > 1.0f ? 1.0f : stopPos;
}
}
void GLSpectrumView::updateMarkersDisplay()
{
if (m_markersDisplay & SpectrumSettings::MarkersDisplayAnnotations) {
updateAnnotationMarkers();
}
}
void GLSpectrumView::updateCalibrationPoints()
{
if (m_calibrationPoints.size() == 0)
{
m_calibrationGain = 1.0;
m_calibrationShiftdB = 0.0;
}
else if (m_calibrationPoints.size() == 1)
{
m_calibrationGain = m_calibrationPoints.first().m_powerCalibratedReference /
m_calibrationPoints.first().m_powerRelativeReference;
m_calibrationShiftdB = CalcDb::dbPower(m_calibrationGain);
}
else
{
QList<SpectrumCalibrationPoint> sortedCalibrationPoints = m_calibrationPoints;
std::sort(sortedCalibrationPoints.begin(), sortedCalibrationPoints.end(), calibrationPointsLessThan);
if (getDisplayedCenterFrequency() <= sortedCalibrationPoints.first().m_frequency)
{
m_calibrationGain = m_calibrationPoints.first().m_powerCalibratedReference /
m_calibrationPoints.first().m_powerRelativeReference;
m_calibrationShiftdB = CalcDb::dbPower(m_calibrationGain);
}
else if (getDisplayedCenterFrequency() >= sortedCalibrationPoints.last().m_frequency)
{
m_calibrationGain = m_calibrationPoints.last().m_powerCalibratedReference /
m_calibrationPoints.last().m_powerRelativeReference;
m_calibrationShiftdB = CalcDb::dbPower(m_calibrationGain);
}
else
{
int lowIndex = 0;
int highIndex = sortedCalibrationPoints.size() - 1;
for (int index = 0; index < sortedCalibrationPoints.size(); index++)
{
if (getDisplayedCenterFrequency() < sortedCalibrationPoints[index].m_frequency)
{
highIndex = index;
break;
}
else
{
lowIndex = index;
}
}
// frequency interpolation is always linear
double deltaFrequency = sortedCalibrationPoints[highIndex].m_frequency -
sortedCalibrationPoints[lowIndex].m_frequency;
double shiftFrequency = getDisplayedCenterFrequency() - sortedCalibrationPoints[lowIndex].m_frequency;
double interpolationRatio = shiftFrequency / deltaFrequency;
// calculate low and high gains in linear mode
double gainLow = sortedCalibrationPoints[lowIndex].m_powerCalibratedReference /
sortedCalibrationPoints[lowIndex].m_powerRelativeReference;
double gainHigh = sortedCalibrationPoints[highIndex].m_powerCalibratedReference /
sortedCalibrationPoints[highIndex].m_powerRelativeReference;
// power interpolation depends on interpolation options
if (m_calibrationInterpMode == SpectrumSettings::CalibInterpLinear)
{
m_calibrationGain = gainLow + interpolationRatio*(gainHigh - gainLow); // linear driven
m_calibrationShiftdB = CalcDb::dbPower(m_calibrationGain);
}
else if (m_calibrationInterpMode == SpectrumSettings::CalibInterpLog)
{
m_calibrationShiftdB = CalcDb::dbPower(gainLow)
+ interpolationRatio*(CalcDb::dbPower(gainHigh) - CalcDb::dbPower(gainLow)); // log driven
m_calibrationGain = CalcDb::powerFromdB(m_calibrationShiftdB);
}
}
}
updateHistogramMarkers();
if (m_messageQueueToGUI && m_useCalibration) {
m_messageQueueToGUI->push(new MsgReportCalibrationShift(m_calibrationShiftdB));
}
m_changesPending = true;
}
bool GLSpectrumView::event(QEvent* event)
{
if (event->type() == QEvent::Gesture)
{
QGestureEvent *gestureEvent = static_cast<QGestureEvent *>(event);
if (QPanGesture *pan = static_cast<QPanGesture *>(gestureEvent->gesture(Qt::PanGesture)))
{
if (pan->state() == Qt::GestureStarted)
{
m_scrollStartCenterFreq = getDisplayedCenterFrequency();
}
else if (pan->state() == Qt::GestureUpdated)
{
QPointF offset = pan->offset();
float histogramWidth = width() - m_leftMargin - m_rightMargin;
qint64 frequency = (qint64)(m_scrollStartCenterFreq + -offset.x()/histogramWidth * m_frequencyScale.getRange());
queueRequestCenterFrequency(frequency);
}
return true;
}
else if (QPinchGesture *pinch = static_cast<QPinchGesture *>(gestureEvent->gesture(Qt::PinchGesture)))
{
// Don't get GestureStarted and startCenterPoint is always 0,0
// https://bugreports.qt.io/browse/QTBUG-109205
if (!m_pinching)
{
m_scrollStartCenterFreq = getDisplayedCenterFrequency();
m_pinchStart = pinch->centerPoint();
m_pinching = true;
m_pinching3D = m_display3DSpectrogram && pointInWaterfallOrSpectrogram(mapFromGlobal(m_pinchStart.toPoint()));
}
else
{
if (pinch->changeFlags() & QPinchGesture::CenterPointChanged)
{
if (!m_pinching3D)
{
// Scroll frequency up or down
QPointF offset = pinch->centerPoint() - m_pinchStart;
float histogramWidth = width() - m_leftMargin - m_rightMargin;
qint64 frequency = (qint64)(m_scrollStartCenterFreq + -offset.x()/histogramWidth * m_frequencyScale.getRange());
queueRequestCenterFrequency(frequency);
}
}
if (pinch->changeFlags() & QPinchGesture::ScaleFactorChanged)
{
if (!m_pinching3D)
{
// Zoom in/out of spectrum
QPoint p = mapFromGlobal(pinch->centerPoint().toPoint());
zoomFactor(p, pinch->scaleFactor());
}
else
{
// Scale Z axis of 3D spectragram
m_glShaderSpectrogram.userScaleZ(pinch->scaleFactor());
}
}
if (pinch->state() == Qt::GestureFinished)
{
m_pinching = false;
m_pinching3D = false;
}
}
return true;
}
}
return QOpenGLWidget::event(event);
}
void GLSpectrumView::mouseMoveEvent(QMouseEvent* event)
{
if (m_displayCursorStats)
{
if ((m_displayMaxHold || m_displayCurrent || m_displayHistogram) && pointInHistogram(event->localPos()))
{
m_cursorOverSpectrum = true;
// Calculate frequency under the cursor
const QPointF& ep = event->localPos();
QPointF pHis = ep;
pHis.rx() = (ep.x()/width() - m_histogramRect.left()) / m_histogramRect.width();
pHis.ry() = (ep.y()/height() - m_histogramRect.top()) / m_histogramRect.height();
m_cursorFrequency = m_frequencyScale.getRangeMin() + pHis.x()*m_frequencyScale.getRange();
// Calculate FFT bin under the cursor
float f = (m_cursorFrequency - m_frequencyScale.getRangeMin()) / m_frequencyScale.getRange(); // [0..1] fraction of zoomed frequency range
f += 1.0f / (2.0f * m_nbBins); // Shift by half a bin to get center of bin rather than edge
m_cursorFFTBin = (int) (m_fftMin + f * m_nbBins) % m_fftSize;
m_displayChanged = true;
}
else
{
m_cursorOverSpectrum = false;
}
}
if (m_rotate3DSpectrogram && !m_pinching3D)
{
// Rotate 3D Spectrogram
QPointF delta = m_mousePrevLocalPos - event->localPos();
m_mousePrevLocalPos = event->localPos();
m_glShaderSpectrogram.rotateZ(-delta.x()/2.0f);
m_glShaderSpectrogram.rotateX(-delta.y()/2.0f);
repaint(); // Force repaint in case acquisition is stopped
return;
}
if (m_pan3DSpectrogram)
{
// Pan 3D Spectrogram
QPointF delta = m_mousePrevLocalPos - event->localPos();
m_mousePrevLocalPos = event->localPos();
m_glShaderSpectrogram.translateX(-delta.x()/2.0f/500.0f);
m_glShaderSpectrogram.translateY(delta.y()/2.0f/500.0f);
repaint(); // Force repaint in case acquisition is stopped
return;
}
if (m_scaleZ3DSpectrogram)
{
// Scale 3D Spectrogram in Z dimension
QPointF delta = m_mousePrevLocalPos - event->localPos();
m_mousePrevLocalPos = event->localPos();
m_glShaderSpectrogram.userScaleZ(1.0+(float)delta.y()/20.0);
repaint(); // Force repaint in case acquisition is stopped
return;
}
if (m_scrollFrequency)
{
// Request containing widget to adjust center frequency
// Not all containers will support this - mainly for MainSpectrumGUI
// This can be a little slow on some SDRs, so we use delta from where
// button was originally pressed rather than do it incrementally
QPointF delta = m_mousePrevLocalPos - event->localPos();
float histogramWidth = width() - m_leftMargin - m_rightMargin;
qint64 frequency = (qint64)(m_scrollStartCenterFreq + delta.x()/histogramWidth * m_frequencyScale.getRange());
queueRequestCenterFrequency(frequency);
return;
}
if (m_displayWaterfall || m_displayHistogram || m_displayMaxHold || m_displayCurrent)
{
if (m_frequencyScaleRect.contains(event->pos()))
{
if (m_cursorState == CSNormal)
{
setCursor(Qt::SizeVerCursor);
m_cursorState = CSSplitter;
return;
}
}
else
{
if (m_cursorState == CSSplitter)
{
setCursor(Qt::ArrowCursor);
m_cursorState = CSNormal;
return;
}
}
}
if (m_cursorState == CSSplitterMoving)
{
QMutexLocker mutexLocker(&m_mutex);
float newShare;
if (!m_invertedWaterfall) {
newShare = (float) (event->y() - m_frequencyScaleRect.height()) / (float) height();
} else {
newShare = 1.0 - (float) (event->y() + m_frequencyScaleRect.height()) / (float) height();
}
if (newShare < 0.1) {
newShare = 0.1f;
} else if (newShare > 0.8) {
newShare = 0.8f;
}
m_waterfallShare = newShare;
m_changesPending = true;
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportWaterfallShare(m_waterfallShare));
}
update();
return;
}
else if (m_cursorState == CSChannelMoving)
{
// Determine if user is trying to move the channel outside of the current frequency range
// and if so, request an adjustment to the center frequency
// FIXME: This doesn't take zoom into account, so only works when zoomed out
Real freqAbs = m_frequencyScale.getValueFromPos(event->x() - m_leftMarginPixmap.width() - 1);
Real freqMin = getDisplayedCenterFrequency() - getDisplayedSampleRate() / 2.0f;
Real freqMax = getDisplayedCenterFrequency() + getDisplayedSampleRate() / 2.0f;
if (freqAbs < freqMin) {
queueRequestCenterFrequency(getDisplayedCenterFrequency() - (freqMin - freqAbs));
} else if (freqAbs > freqMax) {
queueRequestCenterFrequency(getDisplayedCenterFrequency() + (freqAbs - freqMax));
}
Real freq = freqAbs - getDisplayedCenterFrequency();
if (m_channelMarkerStates[m_cursorChannel]->m_channelMarker->getMovable()
&& (m_channelMarkerStates[m_cursorChannel]->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink)
&& m_channelMarkerStates[m_cursorChannel]->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
{
m_channelMarkerStates[m_cursorChannel]->m_channelMarker->setCenterFrequencyByCursor(freq);
channelMarkerChanged();
}
}
if (m_displayWaterfall || m_displayHistogram || m_displayMaxHold || m_displayCurrent)
{
for (int i = 0; i < m_channelMarkerStates.size(); ++i)
{
if ((m_channelMarkerStates[i]->m_channelMarker->getSourceOrSinkStream() != m_displaySourceOrSink)
|| !m_channelMarkerStates[i]->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
{
continue;
}
if (m_channelMarkerStates[i]->m_rect.contains(event->pos()))
{
if (m_cursorState == CSNormal)
{
setCursor(Qt::SizeHorCursor);
m_cursorState = CSChannel;
m_cursorChannel = i;
m_channelMarkerStates[i]->m_channelMarker->setHighlightedByCursor(true);
channelMarkerChanged();
return;
}
else if (m_cursorState == CSChannel)
{
return;
}
}
else if (m_channelMarkerStates[i]->m_channelMarker->getHighlighted())
{
// Don't clear highlight while dragging a channel, as we want the
// frequency of the channel to be continuously displayed
if (m_cursorState != CSChannelMoving)
{
m_channelMarkerStates[i]->m_channelMarker->setHighlightedByCursor(false);
channelMarkerChanged();
}
}
}
}
if (m_cursorState == CSChannel)
{
setCursor(Qt::ArrowCursor);
m_cursorState = CSNormal;
return;
}
event->setAccepted(false);
}
void GLSpectrumView::mousePressEvent(QMouseEvent* event)
{
const QPointF& ep = event->localPos();
if ((event->button() == Qt::MiddleButton) && (m_displayMaxHold || m_displayCurrent || m_displayHistogram) && pointInHistogram(ep))
{
m_scrollFrequency = true;
m_scrollStartCenterFreq = getDisplayedCenterFrequency();
m_mousePrevLocalPos = ep;
return;
}
if ((event->button() == Qt::MiddleButton) && m_display3DSpectrogram && pointInWaterfallOrSpectrogram(ep))
{
m_pan3DSpectrogram = true;
m_mousePrevLocalPos = ep;
return;
}
if ((event->button() == Qt::RightButton) && m_display3DSpectrogram && pointInWaterfallOrSpectrogram(ep))
{
m_scaleZ3DSpectrogram = true;
m_mousePrevLocalPos = ep;
return;
}
if (event->button() == Qt::RightButton)
{
QPointF pHis = ep;
bool doUpdate = false;
pHis.rx() = (ep.x()/width() - m_histogramRect.left()) / m_histogramRect.width();
pHis.ry() = (ep.y()/height() - m_histogramRect.top()) / m_histogramRect.height();
if (event->modifiers() & Qt::ShiftModifier)
{
if ((pHis.x() >= 0) && (pHis.x() <= 1) && (pHis.y() >= 0) && (pHis.y() <= 1))
{
m_histogramMarkers.clear();
doUpdate = true;
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportHistogramMarkersChange());
}
}
}
else
{
if ((m_histogramMarkers.size() > 0) && (pHis.x() >= 0) && (pHis.x() <= 1) && (pHis.y() >= 0) && (pHis.y() <= 1))
{
m_histogramMarkers.pop_back();
doUpdate = true;
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportHistogramMarkersChange());
}
}
}
QPointF pWat = ep;
pWat.rx() = (ep.x()/width() - m_waterfallRect.left()) / m_waterfallRect.width();
pWat.ry() = (ep.y()/height() - m_waterfallRect.top()) / m_waterfallRect.height();
if (event->modifiers() & Qt::ShiftModifier)
{
if ((pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1))
{
m_waterfallMarkers.clear();
doUpdate = true;
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportWaterfallMarkersChange());
}
}
}
else
{
if ((m_waterfallMarkers.size() > 0) && (pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1))
{
m_waterfallMarkers.pop_back();
doUpdate = true;
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportWaterfallMarkersChange());
}
}
}
if (doUpdate) {
update();
}
}
else if (event->button() == Qt::LeftButton)
{
if (event->modifiers() & Qt::ShiftModifier)
{
QPointF pHis = ep;
bool doUpdate = false;
pHis.rx() = (ep.x()/width() - m_histogramRect.left()) / m_histogramRect.width();
pHis.ry() = (ep.y()/height() - m_histogramRect.top()) / m_histogramRect.height();
float frequency = m_frequencyScale.getRangeMin() + pHis.x()*m_frequencyScale.getRange();
float powerVal = m_powerScale.getRangeMax() - pHis.y()*m_powerScale.getRange();
float power = m_linear ? powerVal : CalcDb::powerFromdB(powerVal);
int fftBin = (((frequency - getDisplayedCenterFrequency()) / (float) getDisplayedSampleRate()) * m_fftSize) + (m_fftSize / 2);
if ((pHis.x() >= 0) && (pHis.x() <= 1) && (pHis.y() >= 0) && (pHis.y() <= 1))
{
if (m_histogramMarkers.size() < SpectrumHistogramMarker::m_maxNbOfMarkers)
{
m_histogramMarkers.push_back(SpectrumHistogramMarker());
m_histogramMarkers.back().m_point = pHis;
m_histogramMarkers.back().m_frequency = frequency;
m_histogramMarkers.back().m_fftBin = fftBin;
m_histogramMarkers.back().m_frequencyStr = displayScaled(
frequency,
'f',
getPrecision((getDisplayedCenterFrequency()*1000)/getDisplayedSampleRate()),
false);
m_histogramMarkers.back().m_power = power;
m_histogramMarkers.back().m_powerStr = displayPower(
powerVal,
m_linear ? 'e' : 'f',
m_linear ? 3 : 1);
if (m_histogramMarkers.size() > 1)
{
int64_t deltaFrequency = frequency - m_histogramMarkers.at(0).m_frequency;
m_histogramMarkers.back().m_deltaFrequencyStr = displayScaled(
deltaFrequency,
'f',
getPrecision(deltaFrequency/getDisplayedSampleRate()),
true);
float power0 = m_linear ?
m_histogramMarkers.at(0).m_power :
CalcDb::dbPower(m_histogramMarkers.at(0).m_power);
m_histogramMarkers.back().m_deltaPowerStr = displayPower(
power - power0,
m_linear ? 'e' : 'f',
m_linear ? 3 : 1);
}
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportHistogramMarkersChange());
}
doUpdate = true;
}
}
QPointF pWat = ep;
pWat.rx() = (ep.x()/width() - m_waterfallRect.left()) / m_waterfallRect.width();
pWat.ry() = (ep.y()/height() - m_waterfallRect.top()) / m_waterfallRect.height();
frequency = m_frequencyScale.getRangeMin() + pWat.x()*m_frequencyScale.getRange();
float time = m_timeScale.getRangeMin() + pWat.y()*m_timeScale.getRange();
if ((pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1) && !m_display3DSpectrogram)
{
if (m_waterfallMarkers.size() < SpectrumWaterfallMarker::m_maxNbOfMarkers)
{
m_waterfallMarkers.push_back(SpectrumWaterfallMarker());
m_waterfallMarkers.back().m_point = pWat;
m_waterfallMarkers.back().m_frequency = frequency;
m_waterfallMarkers.back().m_frequencyStr = displayScaled(
frequency,
'f',
getPrecision((getDisplayedCenterFrequency()*1000)/getDisplayedSampleRate()),
false);
m_waterfallMarkers.back().m_time = time;
m_waterfallMarkers.back().m_timeStr = displayScaledF(
time,
'f',
3,
true);
if (m_waterfallMarkers.size() > 1)
{
int64_t deltaFrequency = frequency - m_waterfallMarkers.at(0).m_frequency;
m_waterfallMarkers.back().m_deltaFrequencyStr = displayScaled(
deltaFrequency,
'f',
getPrecision(deltaFrequency/getDisplayedSampleRate()),
true);
m_waterfallMarkers.back().m_deltaTimeStr = displayScaledF(
time - m_waterfallMarkers.at(0).m_time,
'f',
3,
true);
}
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportWaterfallMarkersChange());
}
doUpdate = true;
}
}
if (doUpdate) {
update();
}
}
else if (event->modifiers() & Qt::AltModifier)
{
frequencyPan(event);
}
else if (event->modifiers() & Qt::ControlModifier)
{
if (!m_display3DSpectrogram && pointInWaterfallOrSpectrogram(ep))
{
QPointF pWat = ep;
pWat.rx() = (ep.x()/width() - m_waterfallRect.left()) / m_waterfallRect.width();
pWat.ry() = (ep.y()/height() - m_waterfallRect.top()) / m_waterfallRect.height();
float time = m_timeScale.getRangeMin() + pWat.y()*m_timeScale.getRange();
emit timeSelected(time);
}
return;
}
else if (m_display3DSpectrogram)
{
// Detect click and drag to rotate 3D spectrogram
if (pointInWaterfallOrSpectrogram(ep))
{
m_rotate3DSpectrogram = true;
m_mousePrevLocalPos = ep;
return;
}
}
if ((m_markersDisplay & SpectrumSettings::MarkersDisplayAnnotations) &&
(ep.y() <= m_histogramRect.top()*height() + m_annotationMarkerHeight + 2.0f))
{
QPointF pHis;
pHis.rx() = (ep.x()/width() - m_histogramRect.left()) / m_histogramRect.width();
qint64 selectedFrequency = m_frequencyScale.getRangeMin() + pHis.x() * m_frequencyScale.getRange();
bool selected = false;
for (auto iMarker = m_visibleAnnotationMarkers.rbegin(); iMarker != m_visibleAnnotationMarkers.rend(); ++iMarker)
{
if ((*iMarker)->m_show == SpectrumAnnotationMarker::Hidden) {
continue;
}
qint64 stopFrequency = (*iMarker)->m_startFrequency +
((*iMarker)->m_bandwidth == 0 ? m_frequencyScale.getRange()*0.01f : (*iMarker)->m_bandwidth);
if (((*iMarker)->m_startFrequency < selectedFrequency) && (selectedFrequency <= stopFrequency) && !selected)
{
switch ((*iMarker)->m_show)
{
case SpectrumAnnotationMarker::ShowTop:
(*iMarker)->m_show = SpectrumAnnotationMarker::ShowText;
break;
case SpectrumAnnotationMarker::ShowText:
(*iMarker)->m_show = SpectrumAnnotationMarker::ShowFull;
break;
case SpectrumAnnotationMarker::ShowFull:
(*iMarker)->m_show = SpectrumAnnotationMarker::ShowTop;
break;
case SpectrumAnnotationMarker::Hidden:
break;
}
selected = true;
}
}
}
if (m_cursorState == CSSplitter)
{
grabMouse();
m_cursorState = CSSplitterMoving;
return;
}
else if (m_cursorState == CSChannel)
{
grabMouse();
m_cursorState = CSChannelMoving;
return;
}
else if ((m_cursorState == CSNormal) &&
(m_channelMarkerStates.size() == 1) &&
!(event->modifiers() & Qt::ShiftModifier) &&
!(event->modifiers() & Qt::AltModifier) &&
!(event->modifiers() & Qt::ControlModifier) &&
(ep.y() > m_histogramRect.top()*height() + m_annotationMarkerHeight + 2.0f)) // out of annotation selection zone
{
grabMouse();
setCursor(Qt::SizeHorCursor);
m_cursorState = CSChannelMoving;
m_cursorChannel = 0;
Real freq = m_frequencyScale.getValueFromPos(event->x() - m_leftMarginPixmap.width() - 1) - getDisplayedCenterFrequency();
if (m_channelMarkerStates[m_cursorChannel]->m_channelMarker->getMovable()
&& (m_channelMarkerStates[m_cursorChannel]->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink)
&& m_channelMarkerStates[m_cursorChannel]->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
{
m_channelMarkerStates[m_cursorChannel]->m_channelMarker->setCenterFrequencyByCursor(freq);
channelMarkerChanged();
}
return;
}
}
}
void GLSpectrumView::mouseReleaseEvent(QMouseEvent*)
{
m_scrollFrequency = false;
m_pan3DSpectrogram = false;
m_rotate3DSpectrogram = false;
m_scaleZ3DSpectrogram = false;
if (m_cursorState == CSSplitterMoving)
{
releaseMouse();
m_cursorState = CSSplitter;
}
else if (m_cursorState == CSChannelMoving)
{
releaseMouse();
m_cursorState = CSChannel;
}
}
void GLSpectrumView::wheelEvent(QWheelEvent *event)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
const QPointF& ep = event->position();
#else
const QPointF& ep = event->pos();
#endif
if (m_display3DSpectrogram && pointInWaterfallOrSpectrogram(ep))
{
// Scale 3D spectrogram when mouse wheel moved
// Some mice use delta in steps of 120 for 15 degrees
// for one step of mouse wheel
// Other mice/trackpads use smaller values
int delta = event->angleDelta().y();
if (delta != 0) {
m_glShaderSpectrogram.verticalAngle(-5.0*delta/120.0);
}
repaint(); // Force repaint in case acquisition is stopped
}
else
{
if (event->modifiers() & Qt::ShiftModifier) {
channelMarkerMove(event, 100);
} else if (event->modifiers() & Qt::ControlModifier) {
channelMarkerMove(event, 10);
} else {
channelMarkerMove(event, 1);
}
}
event->accept();
}
void GLSpectrumView::zoomFactor(const QPointF& p, float factor)
{
float pwx = (p.x() - m_leftMargin) / (width() - m_leftMargin - m_rightMargin); // x position in window
if ((pwx >= 0.0f) && (pwx <= 1.0f))
{
// When we zoom, we want the frequency under the cursor to remain the same
// Determine frequency at cursor position
float zoomFreq = m_frequencyScale.getRangeMin() + pwx*m_frequencyScale.getRange();
// Calculate current centre frequency
float currentCF = (m_frequencyZoomFactor == 1) ? getDisplayedCenterFrequency() : ((m_frequencyZoomPos - 0.5) * getDisplayedSampleRate() + getDisplayedCenterFrequency());
// Calculate difference from frequency under cursor to centre frequency
float freqDiff = (currentCF - zoomFreq);
// Calculate what that difference would be if there was no zoom
float freqDiffZoom1 = freqDiff * m_frequencyZoomFactor;
m_frequencyZoomFactor *= factor;
m_frequencyZoomFactor = std::min(m_frequencyZoomFactor, m_maxFrequencyZoom);
m_frequencyZoomFactor = std::max(m_frequencyZoomFactor, 1.0f);
// Calculate what frequency difference should be at new zoom
float zoomedFreqDiff = freqDiffZoom1 / m_frequencyZoomFactor;
// Then calculate what the center frequency should be
float zoomedCF = zoomFreq + zoomedFreqDiff;
// Calculate zoom position which will set the desired center frequency
float zoomPos = (zoomedCF - getDisplayedCenterFrequency()) / getDisplayedSampleRate() + 0.5;
zoomPos = std::max(0.0f, zoomPos);
zoomPos = std::min(1.0f, zoomPos);
frequencyZoom(zoomPos);
}
}
void GLSpectrumView::zoom(const QPointF& p, int y)
{
QMutexLocker mutexLocker(&m_mutex); // Lock to ensure we don't try to redraw mid calculation of zoom and fftMin/fftMax
float pwx = (p.x() - m_leftMargin) / (width() - m_leftMargin - m_rightMargin); // x position in window
if ((pwx >= 0.0f) && (pwx <= 1.0f))
{
// When we zoom, we want the frequency under the cursor to remain the same
// Determine frequency at cursor position
float zoomFreq = m_frequencyScale.getRangeMin() + pwx*m_frequencyScale.getRange();
// Calculate current centre frequency
int adjSampleRate = m_ssbSpectrum ? getDisplayedSampleRate()/2 : getDisplayedSampleRate();
qint64 adjCenterFrequency = getDisplayedCenterFrequency() + (m_ssbSpectrum ? getDisplayedSampleRate()/4 : 0);
float currentCF = (m_frequencyZoomFactor == 1) ?
adjCenterFrequency : (m_frequencyZoomPos - 0.5) * adjSampleRate + adjCenterFrequency;
// Calculate difference from frequency under cursor to centre frequency
float freqDiff = (currentCF - zoomFreq);
// Calculate what that difference would be if there was no zoom
float freqDiffZoom1 = freqDiff * m_frequencyZoomFactor;
if (y > 0) // zoom in
{
if (m_frequencyZoomFactor < m_maxFrequencyZoom) {
m_frequencyZoomFactor += 0.5f;
} else {
return;
}
}
else
{
if (m_frequencyZoomFactor > 1.0f) {
m_frequencyZoomFactor -= 0.5f;
} else {
return;
}
}
// Calculate what frequency difference should be at new zoom
float zoomedFreqDiff = freqDiffZoom1 / m_frequencyZoomFactor;
// Then calculate what the center frequency should be
float zoomedCF = zoomFreq + zoomedFreqDiff;
// Calculate zoom position which will set the desired center frequency
float zoomPos = (zoomedCF - adjCenterFrequency) / adjSampleRate + 0.5;
zoomPos = std::max(0.0f, zoomPos);
zoomPos = std::min(1.0f, zoomPos);
frequencyZoom(zoomPos);
}
else
{
float pwyh, pwyw;
if (m_invertedWaterfall) // histo on top
{
pwyh = (p.y() - m_topMargin) / m_histogramHeight;
pwyw = (p.y() - m_topMargin - m_histogramHeight - m_frequencyScaleHeight) / m_waterfallHeight;
}
else // waterfall on top
{
pwyw = (p.y() - m_topMargin) / m_waterfallHeight;
pwyh = (p.y() - m_topMargin - m_waterfallHeight - m_frequencyScaleHeight) / m_histogramHeight;
}
//qDebug("GLSpectrumView::zoom: pwyh: %f pwyw: %f", pwyh, pwyw);
if ((pwyw >= 0.0f) && (pwyw <= 1.0f)) {
timeZoom(y > 0);
}
if ((pwyh >= 0.0f) && (pwyh <= 1.0f) && !m_linear) {
powerZoom(pwyh, y > 0);
}
}
}
void GLSpectrumView::frequencyZoom(float zoomPos)
{
m_frequencyZoomPos = zoomPos;
updateFFTLimits(false);
m_displayChanged = true;
}
void GLSpectrumView::frequencyPan(QMouseEvent *event)
{
if (m_frequencyZoomFactor == 1.0f) {
return;
}
const QPointF& p = event->pos();
float pw = (p.x() - m_leftMargin) / (width() - m_leftMargin - m_rightMargin); // position in window
pw = pw < 0.0f ? 0.0f : pw > 1.0f ? 1.0 : pw;
float dw = pw - 0.5f;
m_frequencyZoomPos += dw * (1.0f / m_frequencyZoomFactor);
float lim = 0.5f / m_frequencyZoomFactor;
m_frequencyZoomPos = m_frequencyZoomPos < lim ? lim : m_frequencyZoomPos > 1 - lim ? 1 - lim : m_frequencyZoomPos;
qDebug("GLSpectrumView::frequencyPan: pw: %f p: %f", pw, m_frequencyZoomPos);
updateFFTLimits(false);
}
void GLSpectrumView::timeZoom(bool zoomInElseOut)
{
if ((m_fftOverlap == 0) && !zoomInElseOut) {
return;
}
if (zoomInElseOut && (m_fftOverlap == m_fftSize - 1)) {
return;
}
m_fftOverlap = m_fftOverlap + (zoomInElseOut ? 1 : -1);
m_changesPending = true;
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportFFTOverlap(m_fftOverlap));
}
}
void GLSpectrumView::powerZoom(float pw, bool zoomInElseOut)
{
m_powerRange = m_powerRange + (zoomInElseOut ? -2 : 2);
if (pw > 2.0/3.0) { // bottom
m_referenceLevel = m_referenceLevel + (zoomInElseOut ? -2 : 2);
} else if (pw > 1.0/3.0) { // middle
m_referenceLevel = m_referenceLevel + (zoomInElseOut ? -1 : 1);
} // top
m_powerRange = std::clamp(m_powerRange, m_minPowerRange, m_maxPowerRange);
m_referenceLevel = std::clamp(m_referenceLevel, m_minReferenceLevel, m_maxReferenceLevel);
m_changesPending = true;
m_displayChanged = true;
redrawWaterfallAnd3DSpectrogram();
if (m_messageQueueToGUI) {
m_messageQueueToGUI->push(new MsgReportPowerScale(m_referenceLevel, m_powerRange));
}
}
void GLSpectrumView::resetFrequencyZoom()
{
m_frequencyZoomFactor = 1.0f;
m_frequencyZoomPos = 0.5f;
updateFFTLimits(false);
}
void GLSpectrumView::updateFFTLimits(bool fftSizeChangedOnly)
{
if (!fftSizeChangedOnly)
{
if (m_messageQueueToGUI)
{
m_messageQueueToGUI->push(GLSpectrumView::MsgFrequencyZooming::create(
m_frequencyZoomFactor, m_frequencyZoomPos
));
}
}
m_fftMin = m_frequencyZoomFactor == 1.0f ? 0 : (m_frequencyZoomPos - (0.5f / m_frequencyZoomFactor)) * m_fftSize;
m_fftMax = m_frequencyZoomFactor == 1.0f ? m_fftSize : (m_frequencyZoomPos + (0.5f / m_frequencyZoomFactor)) * m_fftSize;
if (m_fftMin < 0) {
m_fftMin = 0;
}
if (m_fftMax > m_fftSize) {
m_fftMax = m_fftSize;
}
m_nbBins = m_fftMax - m_fftMin;
m_redrawAll = true;
m_changesPending = true;
}
void GLSpectrumView::setFrequencyZooming(float frequencyZoomFactor, float frequencyZoomPos)
{
m_frequencyZoomFactor = frequencyZoomFactor;
frequencyZoom(frequencyZoomPos);
}
void GLSpectrumView::setReferenceLevelRange(Real minReferenceLevel, Real maxReferenceLevel)
{
m_minReferenceLevel = minReferenceLevel;
m_maxReferenceLevel = maxReferenceLevel;
}
void GLSpectrumView::setPowerRangeRange(Real minPowerRange, Real maxPowerRange)
{
m_minPowerRange = minPowerRange;
m_maxPowerRange = maxPowerRange;
}
void GLSpectrumView::setFrequencyScale()
{
int frequencySpan;
int64_t centerFrequency;
getFrequencyZoom(centerFrequency, frequencySpan);
m_frequencyScale.setSize(width() - m_leftMargin - m_rightMargin);
m_frequencyScale.setRange(Unit::Frequency, centerFrequency - frequencySpan / 2.0, centerFrequency + frequencySpan / 2.0);
m_frequencyScale.setMakeOpposite(m_lsbDisplay);
}
void GLSpectrumView::setPowerScale(int height)
{
m_powerScale.setSize(height);
if (m_linear)
{
Real referenceLevel = m_useCalibration ? m_referenceLevel * m_calibrationGain : m_referenceLevel;
m_powerScale.setRange(Unit::Scientific, 0.0f, referenceLevel);
}
else
{
Real referenceLevel = m_useCalibration ? m_referenceLevel + m_calibrationShiftdB : m_referenceLevel;
m_powerScale.setRange(Unit::Decibel, referenceLevel - m_powerRange, referenceLevel);
}
if (m_powerScale.getScaleWidth() > m_leftMargin) {
m_leftMargin = m_powerScale.getScaleWidth();
}
}
void GLSpectrumView::getFrequencyZoom(int64_t& centerFrequency, int& frequencySpan)
{
int adjSampleRate = m_ssbSpectrum ? getDisplayedSampleRate()/2 : getDisplayedSampleRate();
qint64 adjCenterFrequency = getDisplayedCenterFrequency() + (m_ssbSpectrum ? getDisplayedSampleRate()/4 : 0);
frequencySpan = (m_frequencyZoomFactor == 1) ?
adjSampleRate : adjSampleRate * (1.0 / m_frequencyZoomFactor);
centerFrequency = (m_frequencyZoomFactor == 1) ?
adjCenterFrequency : (m_frequencyZoomPos - 0.5) * adjSampleRate + adjCenterFrequency;
}
void GLSpectrumView::channelMarkerMove(QWheelEvent *event, int mul)
{
for (int i = 0; i < m_channelMarkerStates.size(); ++i)
{
if ((m_channelMarkerStates[i]->m_channelMarker->getSourceOrSinkStream() != m_displaySourceOrSink)
|| !m_channelMarkerStates[i]->m_channelMarker->streamIndexApplies(m_displayStreamIndex))
{
continue;
}
if (m_channelMarkerStates[i]->m_rect.contains(event->position()))
{
int freq = m_channelMarkerStates[i]->m_channelMarker->getCenterFrequency();
if (event->angleDelta().y() > 0) {
freq += 10 * mul;
} else if (event->angleDelta().y() < 0) {
freq -= 10 * mul;
}
// calculate scale relative cursor position for new frequency
float x_pos = m_frequencyScale.getPosFromValue(getDisplayedCenterFrequency() + freq);
if ((x_pos >= 0.0) && (x_pos < m_frequencyScale.getSize())) // cursor must be in scale
{
m_channelMarkerStates[i]->m_channelMarker->setCenterFrequencyByCursor(freq);
m_channelMarkerStates[i]->m_channelMarker->setCenterFrequency(freq);
// cursor follow-up
int xd = x_pos + m_leftMargin;
QCursor c = cursor();
QPoint cp_a = c.pos();
QPoint cp_w = mapFromGlobal(cp_a);
cp_w.setX(xd);
cp_a = mapToGlobal(cp_w);
c.setPos(cp_a);
setCursor(c);
}
return;
}
}
zoom(event->position(), event->angleDelta().y());
}
// Return if specified point is within the bounds of the waterfall / 3D spectrogram screen area
bool GLSpectrumView::pointInWaterfallOrSpectrogram(const QPointF &point) const
{
// m_waterfallRect is normalised to [0,1]
QPointF pWat = point;
pWat.rx() = (point.x()/width() - m_waterfallRect.left()) / m_waterfallRect.width();
pWat.ry() = (point.y()/height() - m_waterfallRect.top()) / m_waterfallRect.height();
return (pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1);
}
// Return if specified point is within the bounds of the histogram screen area
bool GLSpectrumView::pointInHistogram(const QPointF &point) const
{
// m_histogramRect is normalised to [0,1]
QPointF p = point;
p.rx() = (point.x()/width() - m_histogramRect.left()) / m_histogramRect.width();
p.ry() = (point.y()/height() - m_histogramRect.top()) / m_histogramRect.height();
return (p.x() >= 0) && (p.x() <= 1) && (p.y() >= 0) && (p.y() <= 1);
}
void GLSpectrumView::enterEvent(EnterEventType* event)
{
m_mouseInside = true;
update();
QOpenGLWidget::enterEvent(event);
}
void GLSpectrumView::leaveEvent(QEvent* event)
{
m_mouseInside = false;
update();
QOpenGLWidget::leaveEvent(event);
}
void GLSpectrumView::tick()
{
// Update scroll bar to account for changes to m_spectrumBuffer and waterfall size changes
updateScrollBar();
if (m_displayChanged)
{
m_displayChanged = false;
update();
}
}
void GLSpectrumView::channelMarkerChanged()
{
QMutexLocker mutexLocker(&m_mutex);
m_changesPending = true;
update();
}
void GLSpectrumView::channelMarkerDestroyed(QObject* object)
{
removeChannelMarker((ChannelMarker*)object);
}
void GLSpectrumView::setWaterfallShare(Real waterfallShare)
{
QMutexLocker mutexLocker(&m_mutex);
if (waterfallShare < 0.1f) {
m_waterfallShare = 0.1f;
} else if (waterfallShare > 0.8f) {
m_waterfallShare = 0.8f;
} else {
m_waterfallShare = waterfallShare;
}
m_changesPending = true;
}
void GLSpectrumView::setFPSPeriodMs(int fpsPeriodMs)
{
if (fpsPeriodMs == 0)
{
disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
m_timer.stop();
}
else
{
connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
m_timer.start(fpsPeriodMs);
}
m_fpsPeriodMs = fpsPeriodMs;
}
void GLSpectrumView::cleanup()
{
//makeCurrent();
m_glShaderSimple.cleanup();
m_glShaderFrequencyScale.cleanup();
m_glShaderHistogram.cleanup();
m_glShaderLeftScale.cleanup();
m_glShaderWaterfall.cleanup();
m_glShaderTextOverlay.cleanup();
m_glShaderInfo.cleanup();
m_glShaderSpectrogram.cleanup();
m_glShaderSpectrogramTimeScale.cleanup();
m_glShaderSpectrogramPowerScale.cleanup();
//doneCurrent();
}
// Display number with full precision, group separators and eng. unit suffixes
// E.g:
// -1.505,123,304G
// 456.034,123M
// 300.345k
// 789
QString GLSpectrumView::displayFull(int64_t value)
{
if (value == 0) {
return "0";
}
int64_t absValue = std::abs(value);
QString digits = QString::number(absValue);
int cnt = digits.size();
QString point = QLocale::system().decimalPoint();
QString group = QLocale::system().groupSeparator();
int i;
for (i = cnt - 3; i >= 4; i -= 3)
{
digits = digits.insert(i, group);
}
if (absValue >= 1000) {
digits = digits.insert(i, point);
}
if (cnt > 9) {
digits = digits.append("G");
} else if (cnt > 6) {
digits = digits.append("M");
} else if (cnt > 3) {
digits = digits.append("k");
}
if (value < 0) {
digits = digits.insert(0, "-");
}
return digits;
}
QString GLSpectrumView::displayScaled(int64_t value, char type, int precision, bool showMult)
{
int64_t posValue = (value < 0) ? -value : value;
if (posValue < 1000) {
return tr("%1").arg(QString::number(value, type, precision));
} else if (posValue < 1000000) {
return tr("%1%2").arg(QString::number(value / 1000.0, type, precision)).arg(showMult ? "k" : "");
} else if (posValue < 1000000000) {
return tr("%1%2").arg(QString::number(value / 1000000.0, type, precision)).arg(showMult ? "M" : "");
} else if (posValue < 1000000000000) {
return tr("%1%2").arg(QString::number(value / 1000000000.0, type, precision)).arg(showMult ? "G" : "");
} else {
return tr("%1").arg(QString::number(value, 'e', precision));
}
}
QString GLSpectrumView::displayPower(float value, char type, int precision)
{
return tr("%1").arg(QString::number(value, type, precision));
}
QString GLSpectrumView::displayScaledF(float value, char type, int precision, bool showMult)
{
float posValue = (value < 0) ? -value : value;
if (posValue == 0)
{
return tr("%1").arg(QString::number(value, 'f', precision));
}
else if (posValue < 1)
{
if (posValue > 0.001) {
return tr("%1%2").arg(QString::number(value * 1000.0, type, precision)).arg(showMult ? "m" : "");
} else if (posValue > 0.000001) {
return tr("%1%2").arg(QString::number(value * 1000000.0, type, precision)).arg(showMult ? "u" : "");
} else if (posValue > 1e-9) {
return tr("%1%2").arg(QString::number(value * 1e9, type, precision)).arg(showMult ? "n" : "");
} else if (posValue > 1e-12) {
return tr("%1%2").arg(QString::number(value * 1e12, type, precision)).arg(showMult ? "p" : "");
} else {
return tr("%1").arg(QString::number(value, 'e', precision));
}
}
else
{
if (posValue < 1e3) {
return tr("%1").arg(QString::number(value, type, precision));
} else if (posValue < 1e6) {
return tr("%1%2").arg(QString::number(value / 1000.0, type, precision)).arg(showMult ? "k" : "");
} else if (posValue < 1e9) {
return tr("%1%2").arg(QString::number(value / 1000000.0, type, precision)).arg(showMult ? "M" : "");
} else if (posValue < 1e12) {
return tr("%1%2").arg(QString::number(value / 1000000000.0, type, precision)).arg(showMult ? "G" : "");
} else {
return tr("%1").arg(QString::number(value, 'e', precision));
}
}
}
int GLSpectrumView::getPrecision(int value)
{
int posValue = (value < 0) ? -value : value;
if (posValue < 1000) {
return 3;
} else if (posValue < 10000) {
return 4;
} else if (posValue < 100000) {
return 5;
} else {
return 6;
}
}
// Draw text right justified in top info bar - currently unused
void GLSpectrumView::drawTextRight(const QString &text, const QString &value, const QString &max, const QString &units)
{
drawTextsRight({text}, {value}, {max}, {units});
}
void GLSpectrumView::drawTextsRight(const QStringList &text, const QStringList &value, const QStringList &max, const QStringList &units)
{
QFontMetrics fm(font());
m_infoPixmap.fill(Qt::transparent);
QPainter painter(&m_infoPixmap);
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::black);
painter.setBrush(Qt::transparent);
painter.drawRect(m_leftMargin, 0, width() - m_leftMargin, m_infoHeight);
painter.setPen(QColor(0xf0, 0xf0, 0xff));
painter.setFont(font());
int x = width() - m_rightMargin;
int y = fm.height() + fm.ascent() / 2 - 2;
int textWidth, maxWidth;
for (int i = text.length() - 1; i >= 0; i--)
{
if (!text[i].isEmpty() || !value[i].isEmpty())
{
textWidth = fm.horizontalAdvance(units[i]);
painter.drawText(QPointF(x - textWidth, y), units[i]);
x -= textWidth;
textWidth = fm.horizontalAdvance(value[i]);
maxWidth = fm.horizontalAdvance(max[i]);
painter.drawText(QPointF(x - textWidth, y), value[i]);
x -= maxWidth;
textWidth = fm.horizontalAdvance(text[i]);
painter.drawText(QPointF(x - textWidth, y), text[i]);
x -= textWidth;
}
}
m_glShaderTextOverlay.initTexture(m_infoPixmap.toImage());
GLfloat vtx1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
m_glShaderTextOverlay.drawSurface(m_glInfoBoxMatrix, tex1, vtx1, 4);
}
void GLSpectrumView::drawTextOverlayCentered (
const QString &text,
const QColor &color,
const QFont& font,
float shiftX,
float shiftY,
const QRectF &glRect)
{
if (text.isEmpty()) {
return;
}
QFontMetricsF metrics(font);
QRectF textRect = metrics.boundingRect(text);
QRectF overlayRect(0, 0, textRect.width() * 1.05f + 4.0f, textRect.height());
QPixmap channelOverlayPixmap = QPixmap(overlayRect.width(), overlayRect.height());
channelOverlayPixmap.fill(Qt::transparent);
QPainter painter(&channelOverlayPixmap);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing, false);
painter.fillRect(overlayRect, QColor(0, 0, 0, 0x80));
QColor textColor(color);
textColor.setAlpha(0xC0);
painter.setPen(textColor);
painter.setFont(font);
painter.drawText(QPointF(2.0f, overlayRect.height() - 4.0f), text);
painter.end();
m_glShaderTextOverlay.initTexture(channelOverlayPixmap.toImage());
{
GLfloat vtx1[] = {
0, 1,
1, 1,
1, 0,
0, 0};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0};
float rectX = glRect.x() + shiftX - ((overlayRect.width()/2)/width());
float rectY = glRect.y() + shiftY + (4.0f / height()) - ((overlayRect.height()+5)/height());
float rectW = overlayRect.width() / (float) width();
float rectH = overlayRect.height() / (float) height();
QMatrix4x4 mat;
mat.setToIdentity();
mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY);
mat.scale(2.0f * rectW, -2.0f * rectH);
m_glShaderTextOverlay.drawSurface(mat, tex1, vtx1, 4);
}
}
void GLSpectrumView::drawTextOverlay(
const QString &text,
const QColor &color,
const QFont& font,
float shiftX,
float shiftY,
bool leftHalf,
bool topHalf,
const QRectF &glRect)
{
if (text.isEmpty()) {
return;
}
QFontMetricsF metrics(font);
QRectF textRect = metrics.boundingRect(text);
QRectF overlayRect(0, 0, textRect.width() * 1.05f + 4.0f, textRect.height());
QPixmap channelOverlayPixmap = QPixmap(overlayRect.width(), overlayRect.height());
channelOverlayPixmap.fill(Qt::transparent);
QPainter painter(&channelOverlayPixmap);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing, false);
painter.fillRect(overlayRect, QColor(0, 0, 0, 0x80));
QColor textColor(color);
textColor.setAlpha(0xC0);
painter.setPen(textColor);
painter.setFont(font);
painter.drawText(QPointF(2.0f, overlayRect.height() - 4.0f), text);
painter.end();
m_glShaderTextOverlay.initTexture(channelOverlayPixmap.toImage());
{
GLfloat vtx1[] = {
0, 1,
1, 1,
1, 0,
0, 0};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0};
// float shiftX = glRect.width() - ((overlayRect.width() + 4.0f) / width());
// float shiftY = 4.0f / height();
float rectX = glRect.x() + shiftX - (leftHalf ? 0 : (overlayRect.width()+1)/width());
float rectY = glRect.y() + shiftY + (4.0f / height()) - (topHalf ? 0 : (overlayRect.height()+5)/height());
float rectW = overlayRect.width() / (float) width();
float rectH = overlayRect.height() / (float) height();
QMatrix4x4 mat;
mat.setToIdentity();
mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY);
mat.scale(2.0f * rectW, -2.0f * rectH);
m_glShaderTextOverlay.drawSurface(mat, tex1, vtx1, 4);
}
}
void GLSpectrumView::formatTextInfo(QString& info)
{
QString spacing = " ";
if (m_useCalibration) {
info.append(tr("CAL:%1dB%2").arg(QString::number(m_calibrationShiftdB, 'f', 1)).arg(spacing));
}
if (m_frequencyZoomFactor != 1.0f) {
info.append(tr("%1x%2").arg(QString::number(m_frequencyZoomFactor, 'f', 1)).arg(spacing));
}
if (getDisplayedSampleRate() == 0)
{
info.append(tr("CF:%1 SP:%2%3").arg(getDisplayedCenterFrequency()).arg(getDisplayedSampleRate()).arg(spacing));
}
else
{
int64_t centerFrequency;
int frequencySpan;
getFrequencyZoom(centerFrequency, frequencySpan);
info.append(tr("CF:%1%2").arg(displayScaled(centerFrequency, 'f', getPrecision(centerFrequency/frequencySpan), true)).arg(spacing));
info.append(tr("SP:%1%2").arg(displayScaled(frequencySpan, 'f', 3, true)).arg(spacing));
}
if (m_displayRBW)
{
float rbw = getDisplayedSampleRate() / (float) m_fftSize;
info.append(tr("RBW:%1%2").arg(displayScaled(rbw, 'f', 3, true)).arg(spacing));
}
}
// Paint right side of status line, which contains peak power/freq and power/freq under cursor
void GLSpectrumView::paintStatusLineRight()
{
QString cursorInfo;
QString cursorPower;
QString cursorFreq;
QString cursorPowerUnits;
QString cursorFreqUnits;
if (m_displayCursorStats && m_cursorOverSpectrum && m_currentSpectrum && (m_cursorFFTBin < m_fftSize))
{
Real power = m_currentSpectrum[m_cursorFFTBin];
cursorInfo = tr("Cur: ");
cursorPower = displayPower(power, m_linear ? 'e' : 'f', m_linear ? 3 : 1);
cursorFreq = displayFull(m_cursorFrequency);
cursorPowerUnits = m_peakPowerUnits;
cursorFreqUnits = "Hz";
}
QString peakInfo;
QString peakPower;
QString peakFreq;
QString peakPowerUnits;
QString peakFreqUnits;
if (m_displayPeakStats && m_currentSpectrum)
{
float peakPowerValue;
float peakFreqValue;
findPeak(&m_currentSpectrum[m_fftMin], peakPowerValue, peakFreqValue);
peakInfo = tr("Pk: ");
peakPower = displayPower(peakPowerValue, m_linear ? 'e' : 'f', m_linear ? 3 : 1);
peakFreq = displayFull(peakFreqValue);
peakPowerUnits = m_peakPowerUnits;
peakFreqUnits = "Hz";
}
if (m_displayCursorStats || m_displayPeakStats)
{
QString spacing = m_displayCursorStats && m_displayPeakStats ? " " : "";
drawTextsRight(
{
cursorInfo,
"",
spacing,
peakInfo,
""
},
{
cursorPower,
cursorFreq,
"",
peakPower,
peakFreq
},
{
m_peakPowerMaxStr,
m_peakFrequencyMaxStr,
"",
m_peakPowerMaxStr,
m_peakFrequencyMaxStr
},
{
cursorPowerUnits,
cursorFreqUnits,
"",
peakPowerUnits,
peakFreqUnits
}
);
}
}
bool GLSpectrumView::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
switch (keyEvent->key())
{
case Qt::Key_Up:
if (keyEvent->modifiers() & Qt::ShiftModifier) {
m_glShaderSpectrogram.lightRotateX(-5.0f);
} else if (keyEvent->modifiers() & Qt::AltModifier) {
m_glShaderSpectrogram.lightTranslateY(0.05);
} else if (keyEvent->modifiers() & Qt::ControlModifier) {
m_glShaderSpectrogram.translateY(0.05);
} else {
m_glShaderSpectrogram.rotateX(-5.0f);
}
break;
case Qt::Key_Down:
if (keyEvent->modifiers() & Qt::ShiftModifier) {
m_glShaderSpectrogram.lightRotateX(5.0f);
} else if (keyEvent->modifiers() & Qt::AltModifier) {
m_glShaderSpectrogram.lightTranslateY(-0.05);
} else if (keyEvent->modifiers() & Qt::ControlModifier) {
m_glShaderSpectrogram.translateY(-0.05);
} else {
m_glShaderSpectrogram.rotateX(5.0f);
}
break;
case Qt::Key_Left:
if (keyEvent->modifiers() & Qt::ShiftModifier) {
m_glShaderSpectrogram.lightRotateZ(5.0f);
} else if (keyEvent->modifiers() & Qt::AltModifier) {
m_glShaderSpectrogram.lightTranslateX(-0.05);
} else if (keyEvent->modifiers() & Qt::ControlModifier) {
m_glShaderSpectrogram.translateX(-0.05);
} else {
m_glShaderSpectrogram.rotateZ(5.0f);
}
break;
case Qt::Key_Right:
if (keyEvent->modifiers() & Qt::ShiftModifier) {
m_glShaderSpectrogram.lightRotateZ(-5.0f);
} else if (keyEvent->modifiers() & Qt::AltModifier) {
m_glShaderSpectrogram.lightTranslateX(0.05);
} else if (keyEvent->modifiers() & Qt::ControlModifier) {
m_glShaderSpectrogram.translateX(0.05);
} else {
m_glShaderSpectrogram.rotateZ(-5.0f);
}
break;
case Qt::Key_Equal: // So you don't need to press shift
case Qt::Key_Plus:
if (keyEvent->modifiers() & Qt::ControlModifier) {
m_glShaderSpectrogram.userScaleZ(1.1f);
} else {
m_glShaderSpectrogram.verticalAngle(-1.0f);
}
break;
case Qt::Key_Minus:
if (keyEvent->modifiers() & Qt::ControlModifier) {
m_glShaderSpectrogram.userScaleZ(0.9f);
} else {
m_glShaderSpectrogram.verticalAngle(1.0f);
}
break;
case Qt::Key_R:
m_glShaderSpectrogram.reset();
break;
case Qt::Key_F:
// Project straight down and scale to view, so it's a bit like 2D
m_glShaderSpectrogram.reset();
m_glShaderSpectrogram.rotateX(45.0f);
m_glShaderSpectrogram.verticalAngle(-9.0f);
m_glShaderSpectrogram.userScaleZ(0.0f);
break;
}
repaint(); // Force repaint in case acquisition is stopped
return true;
}
else
{
return QOpenGLWidget::eventFilter(object, event);
}
}
void GLSpectrumView::setMemory(int memoryIdx, const SpectrumSettings::SpectrumMemory &memory)
{
QMutexLocker mutexLocker(&m_mutex);
m_spectrumMemory[memoryIdx] = memory;
m_displayChanged = true;
}
void GLSpectrumView::getDisplayedSpectrumCopy(std::vector<Real>& copy, bool zoomed)
{
QMutexLocker mutexLocker(&m_mutex);
if (m_currentSpectrum)
{
if (zoomed) {
copy.assign(m_currentSpectrum + m_fftMin, m_currentSpectrum + m_fftMax);
} else {
copy.assign(m_currentSpectrum, m_currentSpectrum + m_fftSize);
}
}
}