2022-09-29 13:48:04 +01:00
///////////////////////////////////////////////////////////////////////////////////
2023-11-19 13:31:45 +01:00
// 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> //
2022-09-29 13:48:04 +01:00
// //
// 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>
2026-03-25 16:30:13 +00:00
# include <QtAlgorithms>
2022-09-29 13:48:04 +01:00
# include <QMouseEvent>
# include <QOpenGLShaderProgram>
# include <QOpenGLFunctions>
# include <QPainter>
# include <QFontDatabase>
# include <QWindow>
2022-12-20 14:39:39 +00:00
# include <QGestureEvent>
# include <QPanGesture>
# include <QPinchGesture>
2022-09-29 13:48:04 +01:00
# 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"
2023-08-13 23:52:57 +01:00
# include "util/profiler.h"
2026-03-25 16:30:13 +00:00
# include "util/csv.h"
2022-09-29 13:48:04 +01:00
# 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 )
2022-10-01 20:58:21 +02:00
MESSAGE_CLASS_DEFINITION ( GLSpectrumView : : MsgReportHistogramMarkersChange , Message )
MESSAGE_CLASS_DEFINITION ( GLSpectrumView : : MsgReportWaterfallMarkersChange , Message )
2026-03-25 16:30:13 +00:00
MESSAGE_CLASS_DEFINITION ( GLSpectrumView : : MsgFrequencyZooming , Message )
2022-09-29 13:48:04 +01:00
2026-02-12 15:13:52 +00:00
const float GLSpectrumView : : m_maxFrequencyZoom = 50.0f ;
2022-09-29 13:48:04 +01:00
const float GLSpectrumView : : m_annotationMarkerHeight = 20.0f ;
GLSpectrumView : : GLSpectrumView ( QWidget * parent ) :
QOpenGLWidget ( parent ) ,
m_markersDisplay ( SpectrumSettings : : MarkersDisplaySpectrum ) ,
2022-10-03 00:23:47 +02:00
m_histogramFindPeaks ( false ) ,
2022-09-29 13:48:04 +01:00
m_cursorState ( CSNormal ) ,
m_cursorChannel ( 0 ) ,
m_spectrumVis ( nullptr ) ,
m_fpsPeriodMs ( 50 ) ,
m_mouseInside ( false ) ,
m_changesPending ( true ) ,
2026-03-25 16:30:13 +00:00
m_redrawAll ( false ) ,
2022-09-29 13:48:04 +01:00
m_centerFrequency ( 100000000 ) ,
2026-03-25 16:30:13 +00:00
m_referenceLevel ( 0.0f ) ,
m_minReferenceLevel ( - 120.0f ) ,
m_maxReferenceLevel ( 0.0f ) ,
m_powerRange ( 100.0 ) ,
m_minPowerRange ( 1.0f ) ,
m_maxPowerRange ( 120.0f ) ,
2022-09-29 13:48:04 +01:00
m_linear ( false ) ,
m_decay ( 1 ) ,
m_sampleRate ( 500000 ) ,
m_timingRate ( 1 ) ,
m_fftOverlap ( 0 ) ,
m_fftSize ( 512 ) ,
m_nbBins ( 512 ) ,
2026-03-25 16:30:13 +00:00
m_fftMin ( 0 ) ,
m_fftMax ( 512 ) ,
2022-09-29 13:48:04 +01:00
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 ) ,
2026-03-25 16:30:13 +00:00
m_spectrumColor ( 255 , 255 , 63 ) ,
2022-09-29 13:48:04 +01:00
m_colorMapName ( " Angel " ) ,
m_scrollFrequency ( false ) ,
m_scrollStartCenterFreq ( 0 ) ,
2022-12-20 14:39:39 +00:00
m_pinching ( false ) ,
m_pinching3D ( false ) ,
m_frequencyRequested ( false ) ,
m_nextFrequencyValid ( false ) ,
2022-09-29 13:48:04 +01:00
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 ) ,
2026-03-25 16:30:13 +00:00
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 )
2022-09-29 13:48:04 +01:00
{
// 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 ) ;
2022-12-20 14:39:39 +00:00
grabGesture ( Qt : : PinchGesture ) ;
2022-09-29 13:48:04 +01:00
}
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 ;
}
2026-03-25 16:30:13 +00:00
clearSpectrumBuffer ( ) ;
2022-09-29 13:48:04 +01:00
}
2022-12-20 14:39:39 +00:00
void GLSpectrumView : : queueRequestCenterFrequency ( qint64 frequency )
{
if ( ! m_frequencyRequested )
{
m_frequencyRequested = true ;
m_requestedFrequency = frequency ;
emit requestCenterFrequency ( frequency ) ;
}
else
{
m_nextFrequencyValid = true ;
m_nextFrequency = frequency ;
}
}
2022-09-29 13:48:04 +01:00
void GLSpectrumView : : setCenterFrequency ( qint64 frequency )
{
m_mutex . lock ( ) ;
m_centerFrequency = frequency ;
2022-12-20 14:39:39 +00:00
// Handle queued frequency requests
if ( m_frequencyRequested & & ( frequency = = m_requestedFrequency ) )
{
m_frequencyRequested = false ;
if ( m_nextFrequencyValid )
{
m_nextFrequencyValid = false ;
queueRequestCenterFrequency ( m_nextFrequency ) ;
}
}
2022-09-29 13:48:04 +01:00
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 ;
2026-03-25 16:30:13 +00:00
redrawWaterfallAnd3DSpectrogram ( ) ;
2022-09-29 13:48:04 +01:00
m_mutex . unlock ( ) ;
update ( ) ;
}
void GLSpectrumView : : setPowerRange ( Real powerRange )
{
m_mutex . lock ( ) ;
m_powerRange = powerRange ;
m_changesPending = true ;
2026-03-25 16:30:13 +00:00
redrawWaterfallAnd3DSpectrogram ( ) ;
2022-09-29 13:48:04 +01:00
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 ;
2022-10-01 20:58:21 +02:00
if ( ! display )
{
2022-09-29 13:48:04 +01:00
m_waterfallMarkers . clear ( ) ;
2022-10-01 20:58:21 +02:00
if ( m_messageQueueToGUI ) {
m_messageQueueToGUI - > push ( new MsgReportWaterfallMarkersChange ( ) ) ;
}
2022-09-29 13:48:04 +01:00
}
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 ( ) ;
}
2026-03-25 16:30:13 +00:00
void GLSpectrumView : : setSpectrumColor ( QRgb color )
{
m_mutex . lock ( ) ;
m_spectrumColor = QColor ( color ) ;
m_changesPending = true ;
m_redrawAll = true ;
m_mutex . unlock ( ) ;
update ( ) ;
}
2022-09-29 13:48:04 +01:00
void GLSpectrumView : : setColorMapName ( const QString & colorMapName )
{
m_mutex . lock ( ) ;
m_colorMapName = colorMapName ;
m_changesPending = true ;
2026-03-25 16:30:13 +00:00
m_redrawAll = true ;
2022-09-29 13:48:04 +01:00
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 ( ) ;
2026-03-25 16:30:13 +00:00
if ( m_scrollBar ) {
m_scrollBar - > setInvertedAppearance ( ! inv ) ;
}
2022-09-29 13:48:04 +01:00
m_mutex . unlock ( ) ;
update ( ) ;
}
void GLSpectrumView : : setDisplayMaxHold ( bool display )
{
m_mutex . lock ( ) ;
m_displayMaxHold = display ;
2022-10-01 20:58:21 +02:00
if ( ! m_displayMaxHold & & ! m_displayCurrent & & ! m_displayHistogram )
{
2022-09-29 13:48:04 +01:00
m_histogramMarkers . clear ( ) ;
2022-10-01 20:58:21 +02:00
if ( m_messageQueueToGUI ) {
m_messageQueueToGUI - > push ( new MsgReportHistogramMarkersChange ( ) ) ;
}
2022-09-29 13:48:04 +01:00
}
m_changesPending = true ;
stopDrag ( ) ;
m_mutex . unlock ( ) ;
update ( ) ;
}
void GLSpectrumView : : setDisplayCurrent ( bool display )
{
m_mutex . lock ( ) ;
m_displayCurrent = display ;
2022-10-01 20:58:21 +02:00
if ( ! m_displayMaxHold & & ! m_displayCurrent & & ! m_displayHistogram )
{
2022-09-29 13:48:04 +01:00
m_histogramMarkers . clear ( ) ;
2022-10-01 20:58:21 +02:00
if ( m_messageQueueToGUI ) {
m_messageQueueToGUI - > push ( new MsgReportHistogramMarkersChange ( ) ) ;
}
2022-09-29 13:48:04 +01:00
}
m_changesPending = true ;
stopDrag ( ) ;
m_mutex . unlock ( ) ;
update ( ) ;
}
void GLSpectrumView : : setDisplayHistogram ( bool display )
{
m_mutex . lock ( ) ;
m_displayHistogram = display ;
2022-10-01 20:58:21 +02:00
if ( ! m_displayMaxHold & & ! m_displayCurrent & & ! m_displayHistogram )
{
2022-09-29 13:48:04 +01:00
m_histogramMarkers . clear ( ) ;
2022-10-01 20:58:21 +02:00
if ( m_messageQueueToGUI ) {
m_messageQueueToGUI - > push ( new MsgReportHistogramMarkersChange ( ) ) ;
}
2022-09-29 13:48:04 +01:00
}
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 ( ) ;
}
2022-10-16 04:48:07 +02:00
void GLSpectrumView : : setFreqScaleTruncationMode ( bool mode )
{
m_frequencyScale . setTruncateMode ( mode ) ;
update ( ) ;
}
2022-09-29 13:48:04 +01:00
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 ( ) ;
}
2024-07-13 13:01:32 +02:00
void GLSpectrumView : : setMeasurementParams (
SpectrumSettings : : Measurement measurement ,
int centerFrequencyOffset ,
int bandwidth ,
int chSpacing ,
int adjChBandwidth ,
int harmonics ,
int peaks ,
bool highlight ,
2026-03-25 16:30:13 +00:00
int precision ,
unsigned memoryMask
2024-07-13 13:01:32 +02:00
)
2022-09-29 13:48:04 +01:00
{
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 ;
2026-03-25 16:30:13 +00:00
m_measurementMemMasks = memoryMask ;
2022-09-29 13:48:04 +01:00
m_changesPending = true ;
if ( m_measurements ) {
2026-03-25 16:30:13 +00:00
m_measurements - > setMeasurementParams ( measurement , peaks , precision , memoryMask ) ;
2022-09-29 13:48:04 +01:00
}
m_mutex . unlock ( ) ;
update ( ) ;
}
2026-03-25 16:30:13 +00:00
void GLSpectrumView : : resetMeasurements ( )
{
m_mutex . lock ( ) ;
2026-03-25 18:58:43 +00:00
for ( std : : size_t i = 0 ; i < m_maskFails . size ( ) ; + + i )
2026-03-25 16:30:13 +00:00
{
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 ( ) ;
}
2022-09-29 13:48:04 +01:00
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 ( ) ;
}
2026-03-25 16:30:13 +00:00
void GLSpectrumView : : setWaterfallTimeFormat ( SpectrumSettings : : WaterfallTimeUnits waterfallTimeUnits , const QString & format )
2022-09-29 13:48:04 +01:00
{
QMutexLocker mutexLocker ( & m_mutex ) ;
2026-03-25 16:30:13 +00:00
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 ;
2022-09-29 13:48:04 +01:00
m_displayChanged = true ;
2026-03-25 16:30:13 +00:00
}
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 )
2022-09-29 13:48:04 +01:00
{
2026-03-25 16:30:13 +00:00
if ( ! m_spectrumBuffer . isEmpty ( ) ) {
clearSpectrumBuffer ( ) ;
}
}
else
{
2026-03-25 18:58:43 +00:00
while ( m_spectrumBuffer . size ( ) > ( std : : size_t ) length )
2026-03-25 16:30:13 +00:00
{
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 " ;
2026-03-25 18:58:43 +00:00
for ( std : : size_t j = 0 ; j < m_spectrumBuffer . size ( ) ; j + + )
2026-03-25 16:30:13 +00:00
{
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
{
2026-03-25 19:06:11 +00:00
int idx = m_spectrumBuffer . size ( ) - 1 - scrollBarValue ( ) ;
2026-03-25 16:30:13 +00:00
2026-03-25 19:06:11 +00:00
if ( ( idx > = 0 ) & & ( idx < ( int ) m_spectrumBuffer . size ( ) ) ) {
2026-03-25 16:30:13 +00:00
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
{
2026-03-25 19:06:11 +00:00
int idx = m_spectrumBuffer . size ( ) - 1 - scrollBarValue ( ) ;
2026-03-25 16:30:13 +00:00
2026-03-25 19:06:11 +00:00
if ( ( idx > = 0 ) & & ( idx < ( int ) m_spectrumBuffer . size ( ) ) ) {
2026-03-25 16:30:13 +00:00
return m_spectrumBuffer [ idx ] . m_sampleRate ;
} else {
return m_sampleRate ;
}
}
void GLSpectrumView : : redrawSpectrum ( )
{
if ( m_spectrumBuffer . size ( ) > 0 )
{
2026-03-25 19:06:11 +00:00
int idx = m_spectrumBuffer . size ( ) - 1 - scrollBarValue ( ) ;
2026-03-25 18:58:43 +00:00
2026-03-25 19:06:11 +00:00
if ( idx > = 0 & & idx < ( int ) m_spectrumBuffer . size ( ) )
2026-03-25 16:30:13 +00:00
{
2026-03-25 19:06:11 +00:00
updateHistogram ( m_spectrumBuffer [ idx ] . m_spectrum , m_fftMin , m_nbBins ) ;
2026-03-25 16:30:13 +00:00
m_currentSpectrum = m_spectrumBuffer [ idx ] . m_spectrum ;
}
}
}
void GLSpectrumView : : redrawWaterfallAnd3DSpectrogram ( )
{
if ( m_waterfallBuffer & & m_spectrumBuffer . size ( ) > 0 )
{
2026-03-25 19:06:11 +00:00
int idx = m_spectrumBuffer . size ( ) - 1 - m_waterfallBuffer - > height ( ) - scrollBarValue ( ) ;
2026-03-25 16:30:13 +00:00
m_waterfallBufferPos = 0 ;
m_waterfallTexturePos = 0 ;
m_3DSpectrogramBufferPos = 0 ;
m_3DSpectrogramTexturePos = 0 ;
for ( int i = 0 ; i < m_waterfallBuffer - > height ( ) ; i + + )
{
2026-03-25 19:08:30 +00:00
if ( ( idx > = 0 ) & & ( idx < ( int ) m_spectrumBuffer . size ( ) ) )
2026-03-25 16:30:13 +00:00
{
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 ;
2022-09-29 13:48:04 +01:00
}
2026-03-25 16:30:13 +00:00
}
// 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 ;
2022-09-29 13:48:04 +01:00
2026-03-25 16:30:13 +00:00
if ( fftSize ! = m_fftSize )
2022-09-29 13:48:04 +01:00
{
m_fftSize = fftSize ;
2026-03-25 16:30:13 +00:00
updateFFTLimits ( true ) ;
2022-09-29 13:48:04 +01:00
m_changesPending = true ;
2026-03-25 16:30:13 +00:00
}
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 ) {
2022-09-29 13:48:04 +01:00
return ;
}
2026-03-25 16:30:13 +00:00
if ( offset = = 0 )
{
updateWaterfall ( spectrum , m_fftSize , m_fftMin , m_nbBins ) ;
update3DSpectrogram ( spectrum , m_fftSize , m_fftMin , m_nbBins ) ;
2026-03-25 19:06:11 +00:00
updateHistogram ( spectrum , m_fftMin , m_nbBins ) ;
2026-03-25 16:30:13 +00:00
}
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 ) ;
2026-03-25 19:06:11 +00:00
updateHistogram ( m_spectrumBuffer [ idx ] . m_spectrum , m_fftMin , m_nbBins ) ;
2026-03-25 16:30:13 +00:00
}
}
void GLSpectrumView : : updateSpectrumNoBuffer ( const Real * spectrum , int fftSize )
{
2026-03-25 18:58:43 +00:00
if ( m_spectrumNoBuffer . size ( ) ! = ( std : : size_t ) fftSize ) {
2026-03-25 16:30:13 +00:00
m_spectrumNoBuffer . resize ( fftSize ) ;
}
std : : copy ( spectrum , spectrum + fftSize , m_spectrumNoBuffer . begin ( ) ) ;
2022-09-29 13:48:04 +01:00
}
2026-03-25 16:30:13 +00:00
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 ;
2026-03-25 18:58:43 +00:00
if ( m_spectrumBuffer . size ( ) > = ( std : : size_t ) m_spectrumBufferMaxSize ) {
2026-03-25 16:30:13 +00:00
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 )
2022-09-29 13:48:04 +01:00
{
if ( m_waterfallBufferPos < m_waterfallBuffer - > height ( ) )
{
quint32 * pix = ( quint32 * ) m_waterfallBuffer - > scanLine ( m_waterfallBufferPos ) ;
2026-03-25 16:30:13 +00:00
for ( int i = 0 ; i < = nbBins ; i + + ) {
* pix + + = m_waterfallPalette [ 0 ] ;
}
m_waterfallBufferPos + + ;
}
}
2022-09-29 13:48:04 +01:00
2026-03-25 16:30:13 +00:00
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 ) ;
2022-09-29 13:48:04 +01:00
2026-03-25 16:30:13 +00:00
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 ) ;
2022-09-29 13:48:04 +01:00
* pix + + = m_waterfallPalette [ ( int ) v ] ;
}
2026-03-25 16:30:13 +00:00
int lastIdx = ( fftMin + nbBins ) % fftSize ;
int v = ( int ) ( ( spectrum [ lastIdx ] - m_referenceLevel ) * 2.4 * 100.0 / m_powerRange + 240.0 ) ;
v = clampWaterfall ( v ) ;
2026-02-14 12:28:15 +00:00
* pix + + = m_waterfallPalette [ ( int ) v ] ;
2022-09-29 13:48:04 +01:00
m_waterfallBufferPos + + ;
}
}
2026-03-25 16:30:13 +00:00
void GLSpectrumView : : clear3DSpectrogramRow ( int nbBins )
2022-09-29 13:48:04 +01:00
{
if ( m_3DSpectrogramBufferPos < m_3DSpectrogramBuffer - > height ( ) )
{
quint8 * pix = ( quint8 * ) m_3DSpectrogramBuffer - > scanLine ( m_3DSpectrogramBufferPos ) ;
2026-03-25 16:30:13 +00:00
for ( int i = 0 ; i < = nbBins ; i + + ) {
* pix + + = 0 ;
}
m_3DSpectrogramBufferPos + + ;
}
}
2022-09-29 13:48:04 +01:00
2026-03-25 16:30:13 +00:00
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 ) ;
2022-09-29 13:48:04 +01:00
2026-03-25 16:30:13 +00:00
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 ) ;
2022-09-29 13:48:04 +01:00
* pix + + = v ;
}
2026-03-25 16:30:13 +00:00
int lastIdx = ( fftMin + nbBins ) % fftSize ;
int v = ( int ) ( ( spectrum [ lastIdx ] - m_referenceLevel ) * 2.4 * 100.0 / m_powerRange + 240.0 ) ;
v = clampPixel ( v ) ;
2026-02-14 12:28:15 +00:00
* pix + + = v ;
2022-09-29 13:48:04 +01:00
m_3DSpectrogramBufferPos + + ;
}
}
2026-03-25 19:06:11 +00:00
void GLSpectrumView : : updateHistogram ( const Real * spectrum , int fftMin , int nbBins )
2022-09-29 13:48:04 +01:00
{
quint8 * b = m_histogram ;
2026-03-25 16:30:13 +00:00
int fftMulSize = 100 * nbBins ;
2022-09-29 13:48:04 +01:00
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
2026-03-25 16:30:13 +00:00
for ( int i = 0 ; i < nbBins ; i + + )
2022-09-29 13:48:04 +01:00
{
2026-03-25 16:30:13 +00:00
int v = ( int ) ( ( spectrum [ fftMin + i ] - m_referenceLevel ) * 100.0 / m_powerRange + 100.0 ) ;
2022-09-29 13:48:04 +01:00
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 ( )
{
2023-08-13 23:52:57 +01:00
PROFILER_START ( )
2026-03-25 16:30:13 +00:00
if ( ! m_mutex . tryLock ( 3 ) ) { // Give time for newSpectrum to complete
2022-09-29 13:48:04 +01:00
return ;
}
2026-03-25 16:30:13 +00:00
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 ( ) ) ;
}
}
2022-09-29 13:48:04 +01:00
if ( m_changesPending )
{
applyChanges ( ) ;
m_changesPending = false ;
}
if ( m_nbBins < = 0 )
{
m_mutex . unlock ( ) ;
return ;
}
QOpenGLFunctions * glFunctions = QOpenGLContext : : currentContext ( ) - > functions ( ) ;
2023-07-31 21:43:15 +01:00
glFunctions - > glClearColor ( 0.0f , 0.0f , 0.0f , 1.0f ) ;
2022-09-29 13:48:04 +01:00
glFunctions - > glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ) ;
QMatrix4x4 spectrogramGridMatrix ;
2022-12-20 14:39:39 +00:00
float devicePixelRatio ;
2022-09-29 13:48:04 +01:00
if ( m_display3DSpectrogram )
{
m_glShaderSpectrogram . applyTransform ( spectrogramGridMatrix ) ;
// paint 3D spectrogram
if ( m_3DSpectrogramTexturePos + m_3DSpectrogramBufferPos < m_3DSpectrogramTextureHeight )
{
2026-02-14 12:28:15 +00:00
m_glShaderSpectrogram . subTexture ( 0 , m_3DSpectrogramTexturePos , m_nbBins + 1 , m_3DSpectrogramBufferPos , m_3DSpectrogramBuffer - > scanLine ( 0 ) ) ;
2022-09-29 13:48:04 +01:00
m_3DSpectrogramTexturePos + = m_3DSpectrogramBufferPos ;
}
else
{
int breakLine = m_3DSpectrogramTextureHeight - m_3DSpectrogramTexturePos ;
int linesLeft = m_3DSpectrogramTexturePos + m_3DSpectrogramBufferPos - m_3DSpectrogramTextureHeight ;
2026-02-14 12:28:15 +00:00
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 ) ) ;
2022-09-29 13:48:04 +01:00
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 {
2022-12-20 14:39:39 +00:00
devicePixelRatio = 1.0f ;
2022-09-29 13:48:04 +01:00
}
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 )
{
2026-02-14 12:28:15 +00:00
m_glShaderWaterfall . subTexture ( 0 , m_waterfallTexturePos , m_nbBins + 1 , m_waterfallBufferPos , m_waterfallBuffer - > scanLine ( 0 ) ) ;
2022-09-29 13:48:04 +01:00
m_waterfallTexturePos + = m_waterfallBufferPos ;
}
else
{
int breakLine = m_waterfallTextureHeight - m_waterfallTexturePos ;
int linesLeft = m_waterfallTexturePos + m_waterfallBufferPos - m_waterfallTextureHeight ;
2026-02-14 12:28:15 +00:00
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 ) ) ;
2022-09-29 13:48:04 +01:00
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 ) ;
}
}
2026-03-25 16:30:13 +00:00
// When zoomed, use the next sample off screen. When not, replicate the Nyquist bin
const int lastSampleIdx = ( m_fftMin + m_nbBins ) % m_fftSize ;
2022-09-29 13:48:04 +01:00
// 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 [ ] = {
2024-07-13 13:01:32 +02:00
- l , - h ,
1.0f + r , - h ,
1.0f + r , 0.0f ,
- l , 0.0f
2022-09-29 13:48:04 +01:00
} ;
GLfloat tex1 [ ] = {
2024-07-13 13:01:32 +02:00
0 , 1 ,
1 , 1 ,
1 , 0 ,
0 , 0
2022-09-29 13:48:04 +01:00
} ;
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 [ ] = {
2024-07-13 13:01:32 +02:00
- w , 0.0f - h ,
0.0f , 0.0f - h ,
0.0f , 1.0f + h ,
- w , 1.0f + h
2022-09-29 13:48:04 +01:00
} ;
GLfloat tex1 [ ] = {
2024-07-13 13:01:32 +02:00
0 , 1 ,
1 , 1 ,
1 , 0 ,
0 , 0
2022-09-29 13:48:04 +01:00
} ;
m_glShaderSpectrogramTimeScale . drawSurface ( spectrogramGridMatrix , tex1 , vtx1 , 4 ) ;
}
{
GLfloat w = m_spectrogramPowerPixmap . width ( ) / ( GLfloat ) width ( ) ;
GLfloat h = m_topMargin / ( GLfloat ) m_spectrogramPowerPixmap . height ( ) ;
GLfloat vtx1 [ ] = {
2024-07-13 13:01:32 +02:00
- w , 1.0f , 0.0f ,
0.0f , 1.0f , 0.0f ,
0.0f , 1.0f , 1.0f + h ,
- w , 1.0f , 1.0f + h ,
2022-09-29 13:48:04 +01:00
} ;
GLfloat tex1 [ ] = {
2024-07-13 13:01:32 +02:00
0 , 1 ,
1 , 1 ,
1 , 0 ,
0 , 0
2022-09-29 13:48:04 +01:00
} ;
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 + + )
{
2026-03-25 16:30:13 +00:00
Real v = clampPower ( m_maxHold [ i ] - m_referenceLevel ) ;
2022-09-29 13:48:04 +01:00
q3 [ 4 * i ] = ( GLfloat ) i ;
q3 [ 4 * i + 1 ] = - m_powerRange ;
q3 [ 4 * i + 2 ] = ( GLfloat ) i ;
q3 [ 4 * i + 3 ] = v ;
}
2022-10-03 10:55:20 +01:00
q3 [ 4 * m_nbBins ] = ( GLfloat ) m_nbBins ;
2026-03-25 16:30:13 +00:00
q3 [ 4 * m_nbBins + 1 ] = - m_powerRange ;
2022-10-03 10:55:20 +01:00
q3 [ 4 * m_nbBins + 2 ] = ( GLfloat ) m_nbBins ;
2026-03-25 16:30:13 +00:00
q3 [ 4 * m_nbBins + 3 ] = clampPower ( m_maxHold [ lastSampleIdx ] - m_referenceLevel ) ;
2022-09-29 13:48:04 +01:00
QVector4D color ( 0.5f , 0.0f , 0.0f , ( float ) m_displayTraceIntensity / 100.0f ) ;
2022-10-03 10:55:20 +01:00
m_glShaderSimple . drawSurfaceStrip ( m_glHistogramSpectrumMatrix , color , q3 , 2 * ( m_nbBins + 1 ) ) ;
2022-09-29 13:48:04 +01:00
}
// Max hold line
{
GLfloat * q3 = m_q3FFT . m_array ;
for ( int i = 0 ; i < m_nbBins ; i + + )
{
2026-03-25 16:30:13 +00:00
Real v = clampPower ( m_maxHold [ i ] - m_referenceLevel ) ;
2022-09-29 13:48:04 +01:00
q3 [ 2 * i ] = ( Real ) i ;
q3 [ 2 * i + 1 ] = v ;
}
2022-10-03 10:55:20 +01:00
q3 [ 2 * m_nbBins ] = ( GLfloat ) m_nbBins ;
2026-03-25 16:30:13 +00:00
q3 [ 2 * m_nbBins + 1 ] = clampPower ( m_maxHold [ lastSampleIdx ] - m_referenceLevel ) ;
2022-09-29 13:48:04 +01:00
QVector4D color ( 1.0f , 0.0f , 0.0f , ( float ) m_displayTraceIntensity / 100.0f ) ;
2022-10-03 10:55:20 +01:00
m_glShaderSimple . drawPolyline ( m_glHistogramSpectrumMatrix , color , q3 , m_nbBins + 1 ) ;
2022-09-29 13:48:04 +01:00
}
}
2026-03-25 16:30:13 +00:00
// 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 )
2026-03-25 18:58:43 +00:00
& & ( m_maskFails [ m ] . size ( ) = = ( std : : size_t ) m_fftSize )
2026-03-25 16:30:13 +00:00
& & ( ( 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 ) ;
}
}
}
2022-09-29 13:48:04 +01:00
// paint current spectrum line on top of histogram
2022-10-03 00:23:47 +02:00
if ( m_displayCurrent & & m_currentSpectrum )
2022-09-29 13:48:04 +01:00
{
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 + + )
{
2026-03-25 16:30:13 +00:00
Real v = clampPower ( m_currentSpectrum [ m_fftMin + i ] - m_referenceLevel ) ;
2022-09-29 13:48:04 +01:00
q3 [ 4 * i ] = ( GLfloat ) i ;
q3 [ 4 * i + 1 ] = bottom ;
q3 [ 4 * i + 2 ] = ( GLfloat ) i ;
q3 [ 4 * i + 3 ] = v ;
}
2026-03-25 16:30:13 +00:00
2022-10-03 10:55:20 +01:00
q3 [ 4 * m_nbBins ] = ( GLfloat ) m_nbBins ;
2026-03-25 16:30:13 +00:00
q3 [ 4 * m_nbBins + 1 ] = bottom ;
2022-10-03 10:55:20 +01:00
q3 [ 4 * m_nbBins + 2 ] = ( GLfloat ) m_nbBins ;
2026-03-25 16:30:13 +00:00
q3 [ 4 * m_nbBins + 3 ] = clampPower ( m_currentSpectrum [ lastSampleIdx ] - m_referenceLevel ) ;
2022-09-29 13:48:04 +01:00
2026-03-25 16:30:13 +00:00
QVector4D color ( m_spectrumColor . redF ( ) , m_spectrumColor . greenF ( ) , m_spectrumColor . blueF ( ) , ( float ) m_displayTraceIntensity / 100.0f ) ;
2022-09-29 13:48:04 +01:00
if ( m_spectrumStyle = = SpectrumSettings : : Gradient ) {
2022-10-03 10:55:20 +01:00
m_glShaderColorMap . drawSurfaceStrip ( m_glHistogramSpectrumMatrix , q3 , 2 * ( m_nbBins + 1 ) , bottom , 0.75f ) ;
2022-09-29 13:48:04 +01:00
} else {
2022-10-03 10:55:20 +01:00
m_glShaderSimple . drawSurfaceStrip ( m_glHistogramSpectrumMatrix , color , q3 , 2 * ( m_nbBins + 1 ) ) ;
2022-09-29 13:48:04 +01:00
}
}
{
2022-10-03 00:23:47 +02:00
if ( m_histogramFindPeaks ) {
2026-03-25 16:30:13 +00:00
m_peakFinder . init ( m_currentSpectrum [ m_fftMin ] ) ;
2022-10-03 00:23:47 +02:00
}
2022-09-29 13:48:04 +01:00
// Draw line
q3 = m_q3FFT . m_array ;
for ( int i = 0 ; i < m_nbBins ; i + + )
{
2026-03-25 16:30:13 +00:00
Real v = clampPower ( m_currentSpectrum [ m_fftMin + i ] - m_referenceLevel ) ;
2022-09-29 13:48:04 +01:00
q3 [ 2 * i ] = ( Real ) i ;
q3 [ 2 * i + 1 ] = v ;
2022-10-03 00:23:47 +02:00
if ( m_histogramFindPeaks & & ( i > 0 ) ) {
2026-03-25 16:30:13 +00:00
m_peakFinder . push ( m_currentSpectrum [ m_fftMin + i ] , i = = m_nbBins - 1 ) ;
2022-10-03 00:23:47 +02:00
}
2022-09-29 13:48:04 +01:00
}
2026-03-25 16:30:13 +00:00
2022-10-03 10:55:20 +01:00
q3 [ 2 * m_nbBins ] = ( GLfloat ) m_nbBins ;
2026-03-25 16:30:13 +00:00
q3 [ 2 * m_nbBins + 1 ] = clampPower ( m_currentSpectrum [ lastSampleIdx ] - m_referenceLevel ) ;
2022-09-29 13:48:04 +01:00
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 {
2026-03-25 16:30:13 +00:00
color = QVector4D ( m_spectrumColor . redF ( ) , m_spectrumColor . greenF ( ) , m_spectrumColor . blueF ( ) , ( float ) m_displayTraceIntensity / 100.0f ) ;
2022-09-29 13:48:04 +01:00
}
2022-10-03 10:55:20 +01:00
m_glShaderSimple . drawPolyline ( m_glHistogramSpectrumMatrix , color , q3 , m_nbBins + 1 ) ;
2022-10-03 00:23:47 +02:00
if ( m_histogramFindPeaks ) {
m_peakFinder . sortPeaks ( ) ;
}
2022-09-29 13:48:04 +01:00
}
}
2022-10-03 00:23:47 +02:00
if ( m_displayCurrent & & m_currentSpectrum & & ( m_markersDisplay & SpectrumSettings : : MarkersDisplaySpectrum ) )
{
if ( m_histogramFindPeaks ) {
updateHistogramPeaks ( ) ;
}
2022-09-29 13:48:04 +01:00
drawSpectrumMarkers ( ) ;
}
2022-10-03 00:23:47 +02:00
2022-09-29 13:48:04 +01:00
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 ) ;
}
}
2022-10-03 10:55:20 +01:00
// 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 ) ;
}
}
2022-09-29 13:48:04 +01:00
// 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 ) ;
}
2026-03-25 16:30:13 +00:00
if ( m_displayCursorStats | | m_displayPeakStats ) {
paintStatusLineRight ( ) ;
}
if ( m_currentSpectrum ) {
measure ( & m_currentSpectrum [ m_fftMin ] , true ) ;
2022-09-29 13:48:04 +01:00
}
m_mutex . unlock ( ) ;
2023-08-13 23:52:57 +01:00
# 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 )
2022-10-03 00:23:47 +02:00
} // paintGL
2022-09-29 13:48:04 +01:00
2024-07-10 22:59:13 +02:00
// Highlight power band for SFDR
2022-09-29 13:48:04 +01:00
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 ) ;
}
2024-07-10 22:59:13 +02:00
// Highlight bandwidth being measured
2022-09-29 13:48:04 +01:00
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 ) ;
}
2024-07-10 22:59:13 +02:00
// Highlight peak being measured. Note that the peak isn't always at the center
2022-09-29 13:48:04 +01:00
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 ?
2026-03-25 16:30:13 +00:00
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 ) ;
2022-09-29 13:48:04 +01:00
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 )
{
2026-03-25 16:30:13 +00:00
float power = m_currentSpectrum [ m_fftMin + m_histogramMarkers . at ( i ) . m_fftBin ] ;
2022-09-29 13:48:04 +01:00
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 ) {
2026-03-25 16:30:13 +00:00
power0 = m_currentSpectrum [ m_fftMin + m_histogramMarkers . at ( 0 ) . m_fftBin ] ;
2022-09-29 13:48:04 +01:00
} 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 ) {
2026-03-25 16:30:13 +00:00
poweri = m_currentSpectrum [ m_fftMin + m_histogramMarkers . at ( i ) . m_fftBin ] ;
2022-09-29 13:48:04 +01:00
} 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 ,
2026-03-25 16:30:13 +00:00
true ,
m_histogramRect ) ;
2024-07-13 13:01:32 +02:00
}
2022-09-29 13:48:04 +01:00
}
}
// Find and display peaks
2026-03-25 16:30:13 +00:00
void GLSpectrumView : : measurePeaks ( const Real * spectrum )
2022-09-29 13:48:04 +01:00
{
// Copy current spectrum so we can modify it
2026-03-25 16:30:13 +00:00
Real * spectrumCopy = new Real [ m_nbBins ] ;
std : : copy ( spectrum , spectrum + m_nbBins , spectrumCopy ) ;
2022-09-29 13:48:04 +01:00
for ( int i = 0 ; i < m_measurementPeaks ; i + + )
{
// Find peak
2026-03-25 16:30:13 +00:00
int peakBin = findPeakBin ( spectrumCopy ) ;
2022-09-29 13:48:04 +01:00
int left , right ;
2026-03-25 16:30:13 +00:00
peakWidth ( spectrumCopy , peakBin , left , right , 0 , m_nbBins ) ;
2022-09-29 13:48:04 +01:00
left + + ;
right - - ;
float power = m_linear ?
2026-03-25 16:30:13 +00:00
spectrumCopy [ peakBin ] * ( m_useCalibration ? m_calibrationGain : 1.0f ) :
spectrumCopy [ peakBin ] + ( m_useCalibration ? m_calibrationShiftdB : 0.0f ) ;
2022-09-29 13:48:04 +01:00
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 + + ) {
2026-03-25 16:30:13 +00:00
spectrumCopy [ j ] = - std : : numeric_limits < float > : : max ( ) ;
2022-09-29 13:48:04 +01:00
}
}
2026-03-25 16:30:13 +00:00
delete [ ] spectrumCopy ;
2022-09-29 13:48:04 +01:00
}
// Calculate and display channel power
2026-03-25 16:30:13 +00:00
void GLSpectrumView : : measureChannelPower ( const Real * spectrum , bool updateGUI )
2022-09-29 13:48:04 +01:00
{
float power ;
2026-03-25 16:30:13 +00:00
qint64 centerFrequency = getDisplayedCenterFrequency ( ) ;
2022-09-29 13:48:04 +01:00
2026-03-25 16:30:13 +00:00
power = calcChannelPower ( spectrum , centerFrequency + m_measurementCenterFrequencyOffset , m_measurementBandwidth ) ;
2022-09-29 13:48:04 +01:00
if ( m_measurements ) {
2026-03-25 16:30:13 +00:00
m_measurements - > setChannelPower ( power , updateGUI ) ;
2022-09-29 13:48:04 +01:00
}
2026-03-25 16:30:13 +00:00
if ( m_measurementHighlight & & updateGUI ) {
drawBandwidthMarkers ( centerFrequency + m_measurementCenterFrequencyOffset , m_measurementBandwidth , m_measurementLightMarkerColor ) ;
2022-09-29 13:48:04 +01:00
}
}
// Calculate and display channel power and adjacent channel power
2026-03-25 16:30:13 +00:00
void GLSpectrumView : : measureAdjacentChannelPower ( const Real * spectrum , bool updateGUI )
2022-09-29 13:48:04 +01:00
{
float power , powerLeft , powerRight ;
2026-03-25 16:30:13 +00:00
qint64 centerFrequency = getDisplayedCenterFrequency ( ) ;
2022-09-29 13:48:04 +01:00
2026-03-25 16:30:13 +00:00
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 ) ;
2022-09-29 13:48:04 +01:00
float leftDiff = powerLeft - power ;
float rightDiff = powerRight - power ;
if ( m_measurements ) {
2026-03-25 16:30:13 +00:00
m_measurements - > setAdjacentChannelPower ( powerLeft , leftDiff , power , powerRight , rightDiff , updateGUI ) ;
2022-09-29 13:48:04 +01:00
}
2026-03-25 16:30:13 +00:00
if ( m_measurementHighlight & & updateGUI )
2022-09-29 13:48:04 +01:00
{
2026-03-25 16:30:13 +00:00
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 ) ;
2022-09-29 13:48:04 +01:00
}
}
2022-10-03 16:02:24 +01:00
// Measure bandwidth that has 99% of power
2026-03-25 16:30:13 +00:00
void GLSpectrumView : : measureOccupiedBandwidth ( const Real * spectrum , bool updateGUI )
2022-10-03 16:02:24 +01:00
{
2026-03-25 16:30:13 +00:00
float hzPerBin = getDisplayedSampleRate ( ) / ( float ) m_fftSize ;
qint64 centerFrequency = getDisplayedCenterFrequency ( ) ;
int start = frequencyToBin ( centerFrequency + m_measurementCenterFrequencyOffset ) ;
2022-10-03 16:02:24 +01:00
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 ;
2026-03-25 16:30:13 +00:00
totalPower = CalcDb : : powerFromdB ( calcChannelPower ( spectrum , centerFrequency + m_measurementCenterFrequencyOffset , m_measurementBandwidth ) ) ;
2022-10-03 16:02:24 +01:00
do
{
if ( ( idx > = 0 ) & & ( idx < m_nbBins ) )
{
if ( m_linear ) {
2026-03-25 16:30:13 +00:00
power + = spectrum [ idx ] * gain ;
2022-10-03 16:02:24 +01:00
} else {
2026-03-25 16:30:13 +00:00
power + = CalcDb : : powerFromdB ( spectrum [ idx ] ) + shift ;
2022-10-03 16:02:24 +01:00
}
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 ) {
2026-03-25 16:30:13 +00:00
m_measurements - > setOccupiedBandwidth ( occupiedBandwidth , updateGUI ) ;
2022-10-03 16:02:24 +01:00
}
2026-03-25 16:30:13 +00:00
if ( m_measurementHighlight & & updateGUI )
2022-10-03 16:02:24 +01:00
{
2026-03-25 16:30:13 +00:00
qint64 centerFrequency = getDisplayedCenterFrequency ( ) ;
drawBandwidthMarkers ( centerFrequency + m_measurementCenterFrequencyOffset , m_measurementBandwidth , m_measurementDarkMarkerColor ) ;
drawBandwidthMarkers ( centerFrequency + m_measurementCenterFrequencyOffset , occupiedBandwidth , m_measurementLightMarkerColor ) ;
2022-10-03 16:02:24 +01:00
}
}
// Measure bandwidth -3dB from peak
2026-03-25 16:30:13 +00:00
void GLSpectrumView : : measure3dBBandwidth ( const Real * spectrum , bool updateGUI )
2022-10-03 16:02:24 +01:00
{
// Find max peak and it's power in dB
2026-03-25 16:30:13 +00:00
int peakBin = findPeakBin ( spectrum ) ;
float peakPower = m_linear ? CalcDb : : dbPower ( spectrum [ peakBin ] ) : spectrum [ peakBin ] ;
2022-10-03 16:02:24 +01:00
// Search right until 3dB from peak
int rightBin = peakBin ;
for ( int i = peakBin + 1 ; i < m_nbBins ; i + + )
{
2026-03-25 16:30:13 +00:00
float power = m_linear ? CalcDb : : dbPower ( spectrum [ i ] ) : spectrum [ i ] ;
2022-10-03 16:02:24 +01:00
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 - - )
{
2026-03-25 16:30:13 +00:00
float power = m_linear ? CalcDb : : dbPower ( spectrum [ i ] ) : spectrum [ i ] ;
2022-10-03 16:02:24 +01:00
if ( peakPower - power > 3.0f )
{
leftBin = i + 1 ;
break ;
}
}
2024-07-10 22:59:13 +02:00
// Calculate bandwidth
2022-10-03 16:02:24 +01:00
int bins = rightBin - leftBin - 1 ;
2022-10-03 16:18:38 +01:00
bins = std : : max ( 1 , bins ) ;
2026-03-25 16:30:13 +00:00
float hzPerBin = getDisplayedSampleRate ( ) / ( float ) m_fftSize ;
2022-10-03 16:02:24 +01:00
float bandwidth = bins * hzPerBin ;
int centerBin = leftBin + ( rightBin - leftBin ) / 2 ;
float centerFrequency = binToFrequency ( centerBin ) ;
if ( m_measurements ) {
2026-03-25 16:30:13 +00:00
m_measurements - > set3dBBandwidth ( bandwidth , updateGUI ) ;
2022-10-03 16:02:24 +01:00
}
2026-03-25 16:30:13 +00:00
if ( m_measurementHighlight & & updateGUI ) {
2022-10-03 16:02:24 +01:00
drawBandwidthMarkers ( centerFrequency , bandwidth , m_measurementLightMarkerColor ) ;
}
}
2022-09-29 13:48:04 +01:00
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 ;
2024-10-31 12:42:46 +00:00
// Use <= as SSB spectrums have duplicated values
while ( ( left > maxLeft ) & & ( spectrum [ left ] < = prevLeft ) & & ( right < maxRight ) & & ( spectrum [ right ] < = prevRight ) )
2022-09-29 13:48:04 +01:00
{
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
{
2026-03-25 16:30:13 +00:00
float rbw = ( m_ssbSpectrum ? ( getDisplayedSampleRate ( ) / 2 ) : getDisplayedSampleRate ( ) ) / ( float ) m_fftSize ;
2022-09-29 13:48:04 +01:00
return ( frequency - m_frequencyScale . getRangeMin ( ) ) / rbw ;
}
int64_t GLSpectrumView : : binToFrequency ( int bin ) const
{
2026-03-25 16:30:13 +00:00
float rbw = ( m_ssbSpectrum ? ( getDisplayedSampleRate ( ) / 2 ) : getDisplayedSampleRate ( ) ) / ( float ) m_fftSize ;
2022-09-29 13:48:04 +01:00
return m_frequencyScale . getRangeMin ( ) + bin * rbw ;
}
// Find a peak and measure SNR / THD / SINAD
2026-03-25 16:30:13 +00:00
void GLSpectrumView : : measureSNR ( const Real * spectrum , bool updateGUI )
2022-09-29 13:48:04 +01:00
{
// Find bin with max peak - that will be our signal
2026-03-25 16:30:13 +00:00
int sig = findPeakBin ( spectrum ) ;
2022-09-29 13:48:04 +01:00
int sigLeft , sigRight ;
2026-03-25 16:30:13 +00:00
peakWidth ( spectrum , sig , sigLeft , sigRight , 0 , m_nbBins ) ;
2022-09-29 13:48:04 +01:00
int sigBins = sigRight - sigLeft - 1 ;
int binsLeft = sig - sigLeft ;
int binsRight = sigRight - sig ;
// Highlight the signal
float sigFreq = binToFrequency ( sig ) ;
2026-03-25 16:30:13 +00:00
if ( m_measurementHighlight & & updateGUI ) {
2022-09-29 13:48:04 +01:00
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
2026-03-25 16:30:13 +00:00
if ( spectrum [ hBin - 1 ] > spectrum [ hBin ] ) {
2022-09-29 13:48:04 +01:00
hBin - - ;
2026-03-25 16:30:13 +00:00
} else if ( spectrum [ hBin + 1 ] > spectrum [ hBin ] ) {
2022-09-29 13:48:04 +01:00
hBin + + ;
}
hFreq = binToFrequency ( hBin ) ;
int hLeft , hRight ;
2026-03-25 16:30:13 +00:00
peakWidth ( spectrum , hBin , hLeft , hRight , hBin - binsLeft , hBin + binsRight ) ;
2022-09-29 13:48:04 +01:00
int hBins = hRight - hLeft - 1 ;
2026-03-25 16:30:13 +00:00
if ( m_measurementHighlight & & updateGUI ) {
2022-09-29 13:48:04 +01:00
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 ) {
2026-03-25 16:30:13 +00:00
power = spectrum [ i ] * gain ;
2022-09-29 13:48:04 +01:00
} else {
2026-03-25 16:30:13 +00:00
power = CalcDb : : powerFromdB ( spectrum [ i ] ) + shift ;
2022-09-29 13:48:04 +01:00
}
// 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 ) ) ;
2026-03-25 16:30:13 +00:00
m_measurements - > setSNR ( snr , snfr , thdDB , thdpn , sinad , updateGUI ) ;
2022-09-29 13:48:04 +01:00
}
}
2026-03-25 16:30:13 +00:00
void GLSpectrumView : : measureSFDR ( const Real * spectrum , bool updateGUI )
2022-09-29 13:48:04 +01:00
{
// Find first peak which is our signal
2026-03-25 16:30:13 +00:00
int peakBin = findPeakBin ( spectrum ) ;
2022-09-29 13:48:04 +01:00
int peakLeft , peakRight ;
2026-03-25 16:30:13 +00:00
peakWidth ( spectrum , peakBin , peakLeft , peakRight , 0 , m_nbBins ) ;
2022-09-29 13:48:04 +01:00
// 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 ) )
{
2026-03-25 16:30:13 +00:00
if ( spectrum [ i ] > nextPeakPower )
2022-09-29 13:48:04 +01:00
{
nextPeakBin = i ;
2026-03-25 16:30:13 +00:00
nextPeakPower = spectrum [ i ] ;
2022-09-29 13:48:04 +01:00
}
}
}
if ( nextPeakBin ! = - 1 )
{
// Calculate SFDR in dB from difference between two peaks
2026-03-25 16:30:13 +00:00
float peakPower = calPower ( spectrum [ peakBin ] ) ;
float nextPeakPower = calPower ( spectrum [ nextPeakBin ] ) ;
2022-09-29 13:48:04 +01:00
float peakPowerDB = CalcDb : : dbPower ( peakPower ) ;
float nextPeakPowerDB = CalcDb : : dbPower ( nextPeakPower ) ;
float sfdr = peakPowerDB - nextPeakPowerDB ;
// Display
if ( m_measurements ) {
2026-03-25 16:30:13 +00:00
m_measurements - > setSFDR ( sfdr , updateGUI ) ;
2022-09-29 13:48:04 +01:00
}
2026-03-25 16:30:13 +00:00
if ( m_measurementHighlight & & updateGUI )
2022-09-29 13:48:04 +01:00
{
if ( m_linear ) {
drawPowerBandMarkers ( peakPower , nextPeakPower , m_measurementDarkMarkerColor ) ;
} else {
drawPowerBandMarkers ( peakPowerDB , nextPeakPowerDB , m_measurementDarkMarkerColor ) ;
}
}
}
}
// Find power and frequency of max peak in current spectrum
2026-03-25 16:30:13 +00:00
void GLSpectrumView : : findPeak ( const Real * spectrum , float & power , float & frequency ) const
2022-09-29 13:48:04 +01:00
{
int bin ;
bin = 0 ;
2026-03-25 16:30:13 +00:00
power = spectrum [ 0 ] ;
2022-09-29 13:48:04 +01:00
for ( int i = 1 ; i < m_nbBins ; i + + )
{
2026-03-25 16:30:13 +00:00
if ( spectrum [ i ] > power )
2022-09-29 13:48:04 +01:00
{
2026-03-25 16:30:13 +00:00
power = spectrum [ i ] ;
2022-09-29 13:48:04 +01:00
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
2026-03-25 16:30:13 +00:00
float GLSpectrumView : : calcChannelPower ( const Real * spectrum , int64_t centerFrequency , int channelBandwidth ) const
2022-09-29 13:48:04 +01:00
{
2026-03-25 16:30:13 +00:00
float hzPerBin = getDisplayedSampleRate ( ) / ( float ) m_fftSize ;
2022-09-29 13:48:04 +01:00
int bins = channelBandwidth / hzPerBin ;
int start = frequencyToBin ( centerFrequency ) - ( bins / 2 ) ;
int end = start + bins ;
float power = 0.0 ;
2022-10-03 16:02:24 +01:00
start = std : : max ( start , 0 ) ;
end = std : : min ( end , m_nbBins ) ;
2022-09-29 13:48:04 +01:00
if ( m_linear )
{
float gain = m_useCalibration ? m_calibrationGain : 1.0f ;
2022-10-03 16:02:24 +01:00
for ( int i = start ; i < end ; i + + ) {
2026-03-25 16:30:13 +00:00
power + = spectrum [ i ] * gain ;
2022-09-29 13:48:04 +01:00
}
}
else
{
float shift = m_useCalibration ? m_calibrationShiftdB : 0.0f ;
2022-10-03 16:02:24 +01:00
for ( int i = start ; i < end ; i + + ) {
2026-03-25 16:30:13 +00:00
power + = CalcDb : : powerFromdB ( spectrum [ i ] ) + shift ;
2022-09-29 13:48:04 +01:00
}
}
return CalcDb : : dbPower ( power ) ;
}
2026-03-25 16:30:13 +00:00
// 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 )
{
2026-03-25 18:58:43 +00:00
if ( m_maskFails [ m ] . size ( ) < ( std : : size_t ) fftSize ) {
2026-03-25 16:30:13 +00:00
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 ] ) ;
}
}
}
}
}
2022-09-29 13:48:04 +01:00
void GLSpectrumView : : stopDrag ( )
{
if ( m_cursorState ! = CSNormal )
{
if ( ( m_cursorState = = CSSplitterMoving ) | | ( m_cursorState = = CSChannelMoving ) ) {
releaseMouse ( ) ;
}
setCursor ( Qt : : ArrowCursor ) ;
m_cursorState = CSNormal ;
}
}
2026-03-25 16:30:13 +00:00
// 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
{
2026-03-25 19:06:11 +00:00
int idx = value - scrollBarValue ( ) + m_spectrumBuffer . size ( ) - 1 - m_waterfallHeight ;
2026-03-25 16:30:13 +00:00
2026-03-25 19:06:11 +00:00
if ( ( idx > = 0 ) & & ( idx < ( int ) m_spectrumBuffer . size ( ) ) )
2026-03-25 16:30:13 +00:00
{
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
}
}
2022-09-29 13:48:04 +01:00
void GLSpectrumView : : applyChanges ( )
{
if ( m_nbBins < = 0 ) {
return ;
}
2026-03-25 16:30:13 +00:00
qint64 centerFrequency = getDisplayedCenterFrequency ( ) ;
2022-09-29 13:48:04 +01:00
QFontMetrics fm ( font ( ) ) ;
int M = fm . horizontalAdvance ( " - " ) ;
2026-03-25 16:30:13 +00:00
m_topMargin = fm . ascent ( ) * 2 ;
m_bottomMargin = fm . ascent ( ) * 1 ;
2022-09-29 13:48:04 +01:00
m_infoHeight = fm . height ( ) * 3 ;
2026-03-25 16:30:13 +00:00
m_waterfallTop = 0 ;
2022-09-29 13:48:04 +01:00
m_frequencyScaleHeight = fm . height ( ) * 3 ; // +1 line for marker frequency scale
int frequencyScaleTop = 0 ;
2026-03-25 16:30:13 +00:00
m_histogramTop = 0 ;
2022-09-29 13:48:04 +01:00
//int m_leftMargin;
m_rightMargin = fm . horizontalAdvance ( " 000 " ) ;
// displays both histogram and waterfall
2026-03-25 16:30:13 +00:00
if ( ( m_displayWaterfall | | m_display3DSpectrogram ) & & ( m_displayHistogram | | m_displayMaxHold | | m_displayCurrent ) )
2022-09-29 13:48:04 +01:00
{
m_waterfallHeight = height ( ) * m_waterfallShare - 1 ;
if ( m_waterfallHeight < 0 ) {
m_waterfallHeight = 0 ;
}
if ( m_invertedWaterfall )
{
2026-03-25 16:30:13 +00:00
m_histogramTop = m_topMargin ;
2022-09-29 13:48:04 +01:00
m_histogramHeight = height ( ) - m_topMargin - m_waterfallHeight - m_frequencyScaleHeight - m_bottomMargin ;
2026-03-25 16:30:13 +00:00
m_waterfallTop = m_histogramTop + m_histogramHeight + m_frequencyScaleHeight + 1 ;
frequencyScaleTop = m_histogramTop + m_histogramHeight + 1 ;
2022-09-29 13:48:04 +01:00
}
else
{
2026-03-25 16:30:13 +00:00
m_waterfallTop = m_topMargin ;
frequencyScaleTop = m_waterfallTop + m_waterfallHeight + 1 ;
m_histogramTop = m_waterfallTop + m_waterfallHeight + m_frequencyScaleHeight + 1 ;
2022-09-29 13:48:04 +01:00
m_histogramHeight = height ( ) - m_topMargin - m_waterfallHeight - m_frequencyScaleHeight - m_bottomMargin ;
}
m_timeScale . setSize ( m_waterfallHeight ) ;
2026-03-25 16:30:13 +00:00
setTimeScaleRange ( ) ;
2022-09-29 13:48:04 +01:00
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 ( ) ) ,
2026-03-25 16:30:13 +00:00
1.0f - ( ( float ) ( 2 * m_waterfallTop ) / ( float ) height ( ) )
2022-09-29 13:48:04 +01:00
) ;
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 ( ) ) ,
2026-03-25 16:30:13 +00:00
1.0f - ( ( float ) ( 2 * m_histogramTop ) / ( float ) height ( ) )
2022-09-29 13:48:04 +01:00
) ;
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 ( ) ) ,
2026-03-25 16:30:13 +00:00
1.0f - ( ( float ) ( 2 * m_histogramTop ) / ( float ) height ( ) )
2022-09-29 13:48:04 +01:00
) ;
m_glHistogramSpectrumMatrix . scale (
2022-10-03 10:55:20 +01:00
( ( float ) 2 * ( width ( ) - m_leftMargin - m_rightMargin ) ) / ( ( float ) width ( ) * ( float ) ( m_nbBins ) ) ,
2022-09-29 13:48:04 +01:00
( ( float ) 2 * m_histogramHeight / height ( ) ) / m_powerRange
) ;
// m_frequencyScaleRect = QRect(
// 0,
// frequencyScaleTop,
// width(),
// m_frequencyScaleHeight
// );
m_glFrequencyScaleBoxMatrix . setToIdentity ( ) ;
m_glFrequencyScaleBoxMatrix . translate (
- 1.0f ,
2024-07-13 13:01:32 +02:00
1.0f - ( ( float ) 2 * frequencyScaleTop / ( float ) height ( ) )
2022-09-29 13:48:04 +01:00
) ;
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 ;
2026-03-25 16:30:13 +00:00
m_histogramTop = 0 ;
2022-09-29 13:48:04 +01:00
m_bottomMargin = m_frequencyScaleHeight ;
m_waterfallHeight = height ( ) - m_topMargin - m_frequencyScaleHeight ;
2026-03-25 16:30:13 +00:00
m_waterfallTop = m_topMargin ;
2022-09-29 13:48:04 +01:00
frequencyScaleTop = m_topMargin + m_waterfallHeight + 1 ;
m_timeScale . setSize ( m_waterfallHeight ) ;
2026-03-25 16:30:13 +00:00
setTimeScaleRange ( ) ;
2022-09-29 13:48:04 +01:00
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 ( ) ) ,
2024-07-13 13:01:32 +02:00
1.0f - ( ( float ) ( 2 * m_topMargin ) / ( float ) height ( ) )
2022-09-29 13:48:04 +01:00
) ;
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 ,
2024-07-13 13:01:32 +02:00
1.0f - ( ( float ) 2 * frequencyScaleTop / ( float ) height ( ) )
2022-09-29 13:48:04 +01:00
) ;
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 ;
2026-03-25 16:30:13 +00:00
m_histogramTop = m_topMargin - 1 ;
2022-09-29 13:48:04 +01:00
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 ( ) ) ,
2026-03-25 16:30:13 +00:00
1.0f - ( ( float ) ( 2 * m_histogramTop ) / ( float ) height ( ) )
2022-09-29 13:48:04 +01:00
) ;
m_glHistogramSpectrumMatrix . scale (
2022-10-03 10:55:20 +01:00
( ( float ) 2 * ( width ( ) - m_leftMargin - m_rightMargin ) ) / ( ( float ) width ( ) * ( float ) ( m_nbBins ) ) ,
2022-09-29 13:48:04 +01:00
( ( float ) 2 * ( height ( ) - m_topMargin - m_frequencyScaleHeight ) ) / ( height ( ) * m_powerRange )
) ;
m_glHistogramBoxMatrix . setToIdentity ( ) ;
m_glHistogramBoxMatrix . translate (
- 1.0f + ( ( float ) ( 2 * m_leftMargin ) / ( float ) width ( ) ) ,
2026-03-25 16:30:13 +00:00
1.0f - ( ( float ) ( 2 * m_histogramTop ) / ( float ) height ( ) )
2022-09-29 13:48:04 +01:00
) ;
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 ,
2024-07-13 13:01:32 +02:00
1.0f - ( ( float ) 2 * frequencyScaleTop / ( float ) height ( ) )
2022-09-29 13:48:04 +01:00
) ;
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 ( ) ,
2026-03-25 16:30:13 +00:00
( float ) ( m_waterfallTop + m_waterfallHeight + m_frequencyScaleHeight ) / ( float ) height ( ) ,
2022-09-29 13:48:04 +01:00
( 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
2026-03-25 16:30:13 +00:00
int64_t centerFrequencyUnused ;
2022-09-29 13:48:04 +01:00
int frequencySpan ;
2026-03-25 16:30:13 +00:00
getFrequencyZoom ( centerFrequencyUnused , frequencySpan ) ;
2022-09-29 13:48:04 +01:00
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 ( ) ;
2026-03-25 16:30:13 +00:00
xc = centerFrequency + dv - > m_channelMarker - > getCenterFrequency ( ) ; // marker center frequency
2022-09-29 13:48:04 +01:00
dsbw = dv - > m_channelMarker - > getBandwidth ( ) ;
2024-07-13 13:01:32 +02:00
if ( sidebands = = ChannelMarker : : usb )
{
nw = dv - > m_channelMarker - > getLowCutoff ( ) + dv - > m_channelMarker - > getShift ( ) ; // negative bandwidth
2022-09-29 13:48:04 +01:00
int bw = dv - > m_channelMarker - > getBandwidth ( ) / 2 ;
2024-07-13 13:01:32 +02:00
pw = ( qreal ) bw + dv - > m_channelMarker - > getShift ( ) ; // positive bandwidth
}
else if ( sidebands = = ChannelMarker : : lsb )
{
pw = dv - > m_channelMarker - > getLowCutoff ( ) + dv - > m_channelMarker - > getShift ( ) ;
2022-09-29 13:48:04 +01:00
int bw = dv - > m_channelMarker - > getBandwidth ( ) / 2 ;
2024-07-13 13:01:32 +02:00
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 ( ) ;
2022-09-29 13:48:04 +01:00
}
// draw the DSB rectangle
QMatrix4x4 glMatrixDsb ;
glMatrixDsb . setToIdentity ( ) ;
glMatrixDsb . translate (
- 1.0f + 2.0f * ( ( m_leftMargin + m_frequencyScale . getPosFromValue ( xc - ( dsbw / 2 ) ) ) / ( float ) width ( ) ) ,
2024-07-13 13:01:32 +02:00
1.0f
2022-09-29 13:48:04 +01:00
) ;
glMatrixDsb . scale (
2.0f * ( dsbw / ( float ) frequencySpan ) ,
- 2.0f
) ;
dv - > m_glMatrixDsbWaterfall = glMatrixDsb ;
dv - > m_glMatrixDsbWaterfall . translate (
2024-07-13 13:01:32 +02:00
0.0f ,
2026-03-25 16:30:13 +00:00
( float ) m_waterfallTop / ( float ) height ( )
2022-09-29 13:48:04 +01:00
) ;
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 (
2024-07-13 13:01:32 +02:00
0.0f ,
2026-03-25 16:30:13 +00:00
( float ) m_histogramTop / ( float ) height ( )
2022-09-29 13:48:04 +01:00
) ;
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 (
2024-07-13 13:01:32 +02:00
0.0f ,
( float ) frequencyScaleTop / ( float ) height ( )
2022-09-29 13:48:04 +01:00
) ;
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 ( ) ) ,
2024-07-13 13:01:32 +02:00
1.0f
2022-09-29 13:48:04 +01:00
) ;
glMatrix . scale (
2.0f * ( ( pw - nw ) / ( float ) frequencySpan ) ,
- 2.0f
) ;
dv - > m_glMatrixWaterfall = glMatrix ;
dv - > m_glMatrixWaterfall . translate (
2024-07-13 13:01:32 +02:00
0.0f ,
2026-03-25 16:30:13 +00:00
( float ) m_waterfallTop / ( float ) height ( )
2022-09-29 13:48:04 +01:00
) ;
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 (
2024-07-13 13:01:32 +02:00
0.0f ,
2026-03-25 16:30:13 +00:00
( float ) m_histogramTop / ( float ) height ( )
2022-09-29 13:48:04 +01:00
) ;
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 (
2024-07-13 13:01:32 +02:00
0.0f ,
( float ) frequencyScaleTop / ( float ) height ( )
2022-09-29 13:48:04 +01:00
) ;
dv - > m_glMatrixFreqScale . scale (
( float ) ( width ( ) - m_leftMargin - m_rightMargin ) / ( float ) width ( ) ,
( float ) m_frequencyScaleHeight / ( float ) height ( )
) ;
/*
dv - > m_glRect . setRect (
2026-03-25 16:30:13 +00:00
m_frequencyScale . getPosFromValue ( centerFrequency + dv - > m_channelMarker - > getCenterFrequency ( ) - dv - > m_channelMarker - > getBandwidth ( ) / 2 ) / ( float ) ( width ( ) - m_leftMargin - m_rightMargin ) ,
2022-09-29 13:48:04 +01:00
0 ,
2026-03-25 16:30:13 +00:00
( dv - > m_channelMarker - > getBandwidth ( ) / ( float ) getDisplayedSampleRate ( ) ) ,
2022-09-29 13:48:04 +01:00
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 ) {
2026-03-25 16:30:13 +00:00
dv - > m_rect . setRect ( m_frequencyScale . getPosFromValue ( centerFrequency + dv - > m_channelMarker - > getCenterFrequency ( ) ) + m_leftMargin - 1 ,
2022-09-29 13:48:04 +01:00
m_topMargin ,
5 ,
height ( ) - m_topMargin - m_bottomMargin ) ;
}
*/
}
// prepare left scales (time and power)
2026-03-25 16:30:13 +00:00
paintLeftScales ( ) ;
m_glShaderLeftScale . initTexture ( m_leftMarginPixmap . toImage ( ) ) ;
2022-09-29 13:48:04 +01:00
// 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();
2026-03-25 16:30:13 +00:00
xc = centerFrequency + dv - > m_channelMarker - > getCenterFrequency ( ) ; // marker center frequency
2022-09-29 13:48:04 +01:00
QString ftext ;
switch ( dv - > m_channelMarker - > getFrequencyScaleDisplayType ( ) )
{
case ChannelMarker : : FScaleDisplay_freq :
2026-03-25 16:30:13 +00:00
ftext = QString : : number ( ( centerFrequency + dv - > m_channelMarker - > getCenterFrequency ( ) ) / 1e6 , ' f ' , 6 ) ;
2022-09-29 13:48:04 +01:00
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 :
2026-03-25 16:30:13 +00:00
ftext = QString : : number ( ( centerFrequency + dv - > m_channelMarker - > getCenterFrequency ( ) ) / 1e6 , ' f ' , 6 ) ;
2022-09-29 13:48:04 +01:00
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
2026-03-25 16:30:13 +00:00
QString minFrequencyStr = displayFull ( centerFrequency - getDisplayedSampleRate ( ) / 2 ) ; // This can be wider if negative, while max is positive
QString maxFrequencyStr = displayFull ( centerFrequency + getDisplayedSampleRate ( ) / 2 ) ;
2022-09-29 13:48:04 +01:00
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 " ;
2026-03-25 16:30:13 +00:00
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 ;
}
2022-11-04 19:11:25 +01:00
bool waterfallFFTSizeChanged = true ;
2022-09-29 13:48:04 +01:00
if ( m_waterfallBuffer ) {
2026-02-14 12:28:15 +00:00
waterfallFFTSizeChanged = m_waterfallBuffer - > width ( ) ! = m_nbBins + 1 ;
2022-09-29 13:48:04 +01:00
}
bool windowSizeChanged = m_waterfallTextureHeight ! = m_waterfallHeight ;
2022-11-04 19:11:25 +01:00
if ( waterfallFFTSizeChanged | | windowSizeChanged )
2022-09-29 13:48:04 +01:00
{
if ( m_waterfallBuffer ) {
delete m_waterfallBuffer ;
}
2026-02-14 12:28:15 +00:00
m_waterfallBuffer = new QImage ( m_nbBins + 1 , m_waterfallHeight , QImage : : Format_ARGB32 ) ;
2022-09-29 13:48:04 +01:00
m_waterfallBuffer - > fill ( qRgb ( 0x00 , 0x00 , 0x00 ) ) ;
2022-11-04 19:11:25 +01:00
2022-09-29 13:48:04 +01:00
if ( m_waterfallHeight > 0 ) {
m_glShaderWaterfall . initTexture ( * m_waterfallBuffer ) ;
}
2022-11-04 19:11:25 +01:00
2022-09-29 13:48:04 +01:00
m_waterfallBufferPos = 0 ;
if ( m_3DSpectrogramBuffer ) {
delete m_3DSpectrogramBuffer ;
}
2026-02-14 12:28:15 +00:00
m_3DSpectrogramBuffer = new QImage ( m_nbBins + 1 , m_waterfallHeight , QImage : : Format_Grayscale8 ) ;
2022-09-29 13:48:04 +01:00
m_3DSpectrogramBuffer - > fill ( qRgb ( 0x00 , 0x00 , 0x00 ) ) ;
2022-11-04 19:11:25 +01:00
2022-09-29 13:48:04 +01:00
if ( m_waterfallHeight > 0 ) {
m_glShaderSpectrogram . initTexture ( * m_3DSpectrogramBuffer ) ;
}
2022-11-04 19:11:25 +01:00
2022-09-29 13:48:04 +01:00
m_3DSpectrogramBufferPos = 0 ;
2022-11-04 19:11:25 +01:00
m_waterfallTextureHeight = m_waterfallHeight ;
m_waterfallTexturePos = 0 ;
m_3DSpectrogramTextureHeight = m_waterfallHeight ;
m_3DSpectrogramTexturePos = 0 ;
2022-09-29 13:48:04 +01:00
}
2022-11-04 19:11:25 +01:00
bool histogramFFTSizeChanged = true ;
if ( m_histogramBuffer ) {
histogramFFTSizeChanged = m_histogramBuffer - > width ( ) ! = m_nbBins ;
}
if ( histogramFFTSizeChanged )
2022-09-29 13:48:04 +01:00
{
2022-11-04 19:11:25 +01:00
if ( m_histogramBuffer ) {
2022-09-29 13:48:04 +01:00
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 ) ;
2022-11-04 19:11:25 +01:00
if ( m_histogram ) {
delete [ ] m_histogram ;
}
2022-09-29 13:48:04 +01:00
m_histogram = new quint8 [ 100 * m_nbBins ] ;
memset ( m_histogram , 0x00 , 100 * m_nbBins ) ;
2022-10-03 10:55:20 +01:00
m_q3FFT . allocate ( 2 * ( m_nbBins + 1 ) ) ;
2022-09-29 13:48:04 +01:00
2022-10-03 10:55:20 +01:00
m_q3ColorMap . allocate ( 4 * ( m_nbBins + 1 ) ) ;
std : : fill ( m_q3ColorMap . m_array , m_q3ColorMap . m_array + 4 * ( m_nbBins + 1 ) , 0.0f ) ;
2022-09-29 13:48:04 +01:00
}
2026-03-25 16:30:13 +00:00
if ( m_redrawAll )
{
redrawSpectrum ( ) ;
redrawWaterfallAnd3DSpectrogram ( ) ;
m_redrawAll = false ;
}
else if ( waterfallFFTSizeChanged | | windowSizeChanged )
{
redrawWaterfallAnd3DSpectrogram ( ) ;
}
else if ( histogramFFTSizeChanged )
{
redrawSpectrum ( ) ;
}
2022-09-29 13:48:04 +01:00
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 ( )
{
2026-03-25 16:30:13 +00:00
if ( getDisplayedSampleRate ( ) = = 0 ) {
2024-01-04 17:05:10 +00:00
return ;
}
2022-09-29 13:48:04 +01:00
int64_t centerFrequency ;
int frequencySpan ;
getFrequencyZoom ( centerFrequency , frequencySpan ) ;
2026-03-25 16:30:13 +00:00
int effFftSize = m_fftSize * ( ( float ) frequencySpan / ( float ) getDisplayedSampleRate ( ) ) ;
2022-09-29 13:48:04 +01:00
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 =
2026-03-25 16:30:13 +00:00
// (((m_histogramMarkers[i].m_frequency - centerFrequency) / (float) getDisplayedSampleRate()) + 0.5) * m_fftSize;
2022-09-29 13:48:04 +01:00
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 ' ,
2026-03-25 16:30:13 +00:00
getPrecision ( ( centerFrequency * 1000 ) / getDisplayedSampleRate ( ) ) ,
2022-09-29 13:48:04 +01:00
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 ;
2022-10-03 00:23:47 +02:00
m_histogramMarkers [ i ] . m_deltaFrequencyStr = displayScaled (
2022-09-29 13:48:04 +01:00
deltaFrequency ,
' f ' ,
2026-03-25 16:30:13 +00:00
getPrecision ( deltaFrequency / getDisplayedSampleRate ( ) ) ,
2022-09-29 13:48:04 +01:00
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 ) ;
2022-10-03 00:23:47 +02:00
m_histogramMarkers [ i ] . m_deltaPowerStr = displayPower (
2022-09-29 13:48:04 +01:00
powerI - power0 ,
m_linear ? ' e ' : ' f ' ,
m_linear ? 3 : 1 ) ;
}
}
}
2022-10-03 00:23:47 +02:00
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 ) & &
2024-07-13 13:01:32 +02:00
( m_histogramMarkers . at ( i ) . m_holdReset | | ( power > m_histogramMarkers . at ( i ) . m_powerMax ) ) ) )
2022-10-03 00:23:47 +02:00
{
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 ' ,
2026-03-25 16:30:13 +00:00
getPrecision ( ( getDisplayedCenterFrequency ( ) * 1000 ) / getDisplayedSampleRate ( ) ) ,
2022-10-03 00:23:47 +02:00
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 ' ,
2026-03-25 16:30:13 +00:00
getPrecision ( deltaFrequency / getDisplayedSampleRate ( ) ) ,
2022-10-03 00:23:47 +02:00
true
) ;
}
}
else
{
continue ;
}
j + + ;
}
}
2022-09-29 13:48:04 +01:00
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 ' ,
2026-03-25 16:30:13 +00:00
getPrecision ( ( getDisplayedCenterFrequency ( ) * 1000 ) / getDisplayedSampleRate ( ) ) ,
2022-09-29 13:48:04 +01:00
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 ' ,
2026-03-25 16:30:13 +00:00
getPrecision ( deltaFrequency / getDisplayedSampleRate ( ) ) ,
2022-09-29 13:48:04 +01:00
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 ( )
{
2023-10-03 16:10:08 +01:00
emit updateAnnotations ( ) ; // Notify other plugins we have updated annotations
2022-09-29 13:48:04 +01:00
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 /
2024-07-13 13:01:32 +02:00
m_calibrationPoints . first ( ) . m_powerRelativeReference ;
2022-09-29 13:48:04 +01:00
m_calibrationShiftdB = CalcDb : : dbPower ( m_calibrationGain ) ;
}
else
{
QList < SpectrumCalibrationPoint > sortedCalibrationPoints = m_calibrationPoints ;
std : : sort ( sortedCalibrationPoints . begin ( ) , sortedCalibrationPoints . end ( ) , calibrationPointsLessThan ) ;
2026-03-25 16:30:13 +00:00
if ( getDisplayedCenterFrequency ( ) < = sortedCalibrationPoints . first ( ) . m_frequency )
2022-09-29 13:48:04 +01:00
{
m_calibrationGain = m_calibrationPoints . first ( ) . m_powerCalibratedReference /
m_calibrationPoints . first ( ) . m_powerRelativeReference ;
m_calibrationShiftdB = CalcDb : : dbPower ( m_calibrationGain ) ;
}
2026-03-25 16:30:13 +00:00
else if ( getDisplayedCenterFrequency ( ) > = sortedCalibrationPoints . last ( ) . m_frequency )
2022-09-29 13:48:04 +01:00
{
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 + + )
{
2026-03-25 16:30:13 +00:00
if ( getDisplayedCenterFrequency ( ) < sortedCalibrationPoints [ index ] . m_frequency )
2022-09-29 13:48:04 +01:00
{
highIndex = index ;
break ;
}
else
{
lowIndex = index ;
}
}
// frequency interpolation is always linear
double deltaFrequency = sortedCalibrationPoints [ highIndex ] . m_frequency -
sortedCalibrationPoints [ lowIndex ] . m_frequency ;
2026-03-25 16:30:13 +00:00
double shiftFrequency = getDisplayedCenterFrequency ( ) - sortedCalibrationPoints [ lowIndex ] . m_frequency ;
2022-09-29 13:48:04 +01:00
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 ;
}
2022-12-20 14:39:39 +00:00
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 )
{
2026-03-25 16:30:13 +00:00
m_scrollStartCenterFreq = getDisplayedCenterFrequency ( ) ;
2022-12-20 14:39:39 +00:00
}
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 )
{
2026-03-25 16:30:13 +00:00
m_scrollStartCenterFreq = getDisplayedCenterFrequency ( ) ;
2022-12-20 14:39:39 +00:00
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 ) ;
}
2022-09-29 13:48:04 +01:00
void GLSpectrumView : : mouseMoveEvent ( QMouseEvent * event )
{
2026-03-25 16:30:13 +00:00
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 ;
}
}
2022-12-20 14:39:39 +00:00
if ( m_rotate3DSpectrogram & & ! m_pinching3D )
2022-09-29 13:48:04 +01:00
{
// 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 ( ) ) ;
2022-12-20 14:39:39 +00:00
queueRequestCenterFrequency ( frequency ) ;
2022-09-29 13:48:04 +01:00
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
2022-12-20 14:39:39 +00:00
// FIXME: This doesn't take zoom into account, so only works when zoomed out
2022-09-29 13:48:04 +01:00
Real freqAbs = m_frequencyScale . getValueFromPos ( event - > x ( ) - m_leftMarginPixmap . width ( ) - 1 ) ;
2026-03-25 16:30:13 +00:00
Real freqMin = getDisplayedCenterFrequency ( ) - getDisplayedSampleRate ( ) / 2.0f ;
Real freqMax = getDisplayedCenterFrequency ( ) + getDisplayedSampleRate ( ) / 2.0f ;
2022-09-29 13:48:04 +01:00
if ( freqAbs < freqMin ) {
2026-03-25 16:30:13 +00:00
queueRequestCenterFrequency ( getDisplayedCenterFrequency ( ) - ( freqMin - freqAbs ) ) ;
2022-09-29 13:48:04 +01:00
} else if ( freqAbs > freqMax ) {
2026-03-25 16:30:13 +00:00
queueRequestCenterFrequency ( getDisplayedCenterFrequency ( ) + ( freqAbs - freqMax ) ) ;
2022-09-29 13:48:04 +01:00
}
2026-03-25 16:30:13 +00:00
Real freq = freqAbs - getDisplayedCenterFrequency ( ) ;
2022-09-29 13:48:04 +01:00
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 ( ) )
{
2023-01-23 14:40:19 +00:00
// 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 ( ) ;
}
2022-09-29 13:48:04 +01:00
}
}
}
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 ;
2026-03-25 16:30:13 +00:00
m_scrollStartCenterFreq = getDisplayedCenterFrequency ( ) ;
2022-09-29 13:48:04 +01:00
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 ;
2022-10-01 20:58:21 +02:00
if ( m_messageQueueToGUI ) {
m_messageQueueToGUI - > push ( new MsgReportHistogramMarkersChange ( ) ) ;
}
2022-09-29 13:48:04 +01:00
}
}
else
{
if ( ( m_histogramMarkers . size ( ) > 0 ) & & ( pHis . x ( ) > = 0 ) & & ( pHis . x ( ) < = 1 ) & & ( pHis . y ( ) > = 0 ) & & ( pHis . y ( ) < = 1 ) )
{
m_histogramMarkers . pop_back ( ) ;
doUpdate = true ;
2022-10-01 20:58:21 +02:00
if ( m_messageQueueToGUI ) {
m_messageQueueToGUI - > push ( new MsgReportHistogramMarkersChange ( ) ) ;
}
2022-09-29 13:48:04 +01:00
}
}
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 ;
2022-10-01 20:58:21 +02:00
if ( m_messageQueueToGUI ) {
m_messageQueueToGUI - > push ( new MsgReportWaterfallMarkersChange ( ) ) ;
}
2022-09-29 13:48:04 +01:00
}
}
else
{
if ( ( m_waterfallMarkers . size ( ) > 0 ) & & ( pWat . x ( ) > = 0 ) & & ( pWat . x ( ) < = 1 ) & & ( pWat . y ( ) > = 0 ) & & ( pWat . y ( ) < = 1 ) )
{
m_waterfallMarkers . pop_back ( ) ;
doUpdate = true ;
2022-10-01 20:58:21 +02:00
if ( m_messageQueueToGUI ) {
m_messageQueueToGUI - > push ( new MsgReportWaterfallMarkersChange ( ) ) ;
}
2022-09-29 13:48:04 +01:00
}
}
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 ) ;
2026-03-25 16:30:13 +00:00
int fftBin = ( ( ( frequency - getDisplayedCenterFrequency ( ) ) / ( float ) getDisplayedSampleRate ( ) ) * m_fftSize ) + ( m_fftSize / 2 ) ;
2022-09-29 13:48:04 +01:00
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 ' ,
2026-03-25 16:30:13 +00:00
getPrecision ( ( getDisplayedCenterFrequency ( ) * 1000 ) / getDisplayedSampleRate ( ) ) ,
2022-09-29 13:48:04 +01:00
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 ' ,
2026-03-25 16:30:13 +00:00
getPrecision ( deltaFrequency / getDisplayedSampleRate ( ) ) ,
2022-09-29 13:48:04 +01:00
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 ) ;
}
2022-10-01 20:58:21 +02:00
if ( m_messageQueueToGUI ) {
m_messageQueueToGUI - > push ( new MsgReportHistogramMarkersChange ( ) ) ;
}
2022-09-29 13:48:04 +01:00
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 ' ,
2026-03-25 16:30:13 +00:00
getPrecision ( ( getDisplayedCenterFrequency ( ) * 1000 ) / getDisplayedSampleRate ( ) ) ,
2022-09-29 13:48:04 +01:00
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 ' ,
2026-03-25 16:30:13 +00:00
getPrecision ( deltaFrequency / getDisplayedSampleRate ( ) ) ,
2022-09-29 13:48:04 +01:00
true ) ;
m_waterfallMarkers . back ( ) . m_deltaTimeStr = displayScaledF (
time - m_waterfallMarkers . at ( 0 ) . m_time ,
' f ' ,
3 ,
true ) ;
}
2022-10-01 20:58:21 +02:00
if ( m_messageQueueToGUI ) {
m_messageQueueToGUI - > push ( new MsgReportWaterfallMarkersChange ( ) ) ;
}
2022-09-29 13:48:04 +01:00
doUpdate = true ;
}
}
if ( doUpdate ) {
update ( ) ;
}
}
else if ( event - > modifiers ( ) & Qt : : AltModifier )
{
frequencyPan ( event ) ;
}
2023-12-06 10:09:30 +00:00
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 ;
}
2022-09-29 13:48:04 +01:00
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 ;
2026-03-25 16:30:13 +00:00
Real freq = m_frequencyScale . getValueFromPos ( event - > x ( ) - m_leftMarginPixmap . width ( ) - 1 ) - getDisplayedCenterFrequency ( ) ;
2022-09-29 13:48:04 +01:00
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 ) {
2024-07-13 13:01:32 +02:00
m_glShaderSpectrogram . verticalAngle ( - 5.0 * delta / 120.0 ) ;
2022-09-29 13:48:04 +01:00
}
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 ) ;
}
}
2023-03-03 22:16:29 +00:00
event - > accept ( ) ;
2022-09-29 13:48:04 +01:00
}
2022-12-20 14:39:39 +00:00
void GLSpectrumView : : zoomFactor ( const QPointF & p , float factor )
2022-09-29 13:48:04 +01:00
{
2022-12-20 14:39:39 +00:00
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
2026-03-25 16:30:13 +00:00
float currentCF = ( m_frequencyZoomFactor = = 1 ) ? getDisplayedCenterFrequency ( ) : ( ( m_frequencyZoomPos - 0.5 ) * getDisplayedSampleRate ( ) + getDisplayedCenterFrequency ( ) ) ;
2022-09-29 13:48:04 +01:00
2022-12-20 14:39:39 +00:00
// 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
2026-03-25 16:30:13 +00:00
float zoomPos = ( zoomedCF - getDisplayedCenterFrequency ( ) ) / getDisplayedSampleRate ( ) + 0.5 ;
2022-12-20 14:39:39 +00:00
zoomPos = std : : max ( 0.0f , zoomPos ) ;
zoomPos = std : : min ( 1.0f , zoomPos ) ;
frequencyZoom ( zoomPos ) ;
}
2026-03-25 16:30:13 +00:00
}
2022-12-20 14:39:39 +00:00
void GLSpectrumView : : zoom ( const QPointF & p , int y )
{
2026-03-25 16:30:13 +00:00
QMutexLocker mutexLocker ( & m_mutex ) ; // Lock to ensure we don't try to redraw mid calculation of zoom and fftMin/fftMax
2022-09-29 13:48:04 +01:00
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
2026-03-25 16:30:13 +00:00
int adjSampleRate = m_ssbSpectrum ? getDisplayedSampleRate ( ) / 2 : getDisplayedSampleRate ( ) ;
qint64 adjCenterFrequency = getDisplayedCenterFrequency ( ) + ( m_ssbSpectrum ? getDisplayedSampleRate ( ) / 4 : 0 ) ;
2023-07-09 00:00:51 +02:00
float currentCF = ( m_frequencyZoomFactor = = 1 ) ?
adjCenterFrequency : ( m_frequencyZoomPos - 0.5 ) * adjSampleRate + adjCenterFrequency ;
2022-09-29 13:48:04 +01:00
// 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 ;
2022-12-20 14:39:39 +00:00
if ( y > 0 ) // zoom in
2022-09-29 13:48:04 +01:00
{
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
2023-07-09 00:00:51 +02:00
float zoomPos = ( zoomedCF - adjCenterFrequency ) / adjSampleRate + 0.5 ;
2022-09-29 13:48:04 +01:00
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 ) ) {
2022-12-20 14:39:39 +00:00
timeZoom ( y > 0 ) ;
2022-09-29 13:48:04 +01:00
}
if ( ( pwyh > = 0.0f ) & & ( pwyh < = 1.0f ) & & ! m_linear ) {
2022-12-20 14:39:39 +00:00
powerZoom ( pwyh , y > 0 ) ;
2022-09-29 13:48:04 +01:00
}
}
}
void GLSpectrumView : : frequencyZoom ( float zoomPos )
{
m_frequencyZoomPos = zoomPos ;
2026-03-25 16:30:13 +00:00
updateFFTLimits ( false ) ;
m_displayChanged = true ;
2022-09-29 13:48:04 +01:00
}
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 ) ;
2026-03-25 16:30:13 +00:00
updateFFTLimits ( false ) ;
2022-09-29 13:48:04 +01:00
}
void GLSpectrumView : : timeZoom ( bool zoomInElseOut )
{
if ( ( m_fftOverlap = = 0 ) & & ! zoomInElseOut ) {
return ;
}
2023-10-23 15:05:34 +02:00
if ( zoomInElseOut & & ( m_fftOverlap = = m_fftSize - 1 ) ) {
2022-09-29 13:48:04 +01:00
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
2026-03-25 16:30:13 +00:00
m_powerRange = std : : clamp ( m_powerRange , m_minPowerRange , m_maxPowerRange ) ;
m_referenceLevel = std : : clamp ( m_referenceLevel , m_minReferenceLevel , m_maxReferenceLevel ) ;
2022-09-29 13:48:04 +01:00
m_changesPending = true ;
2026-03-25 16:30:13 +00:00
m_displayChanged = true ;
redrawWaterfallAnd3DSpectrogram ( ) ;
2022-09-29 13:48:04 +01:00
if ( m_messageQueueToGUI ) {
m_messageQueueToGUI - > push ( new MsgReportPowerScale ( m_referenceLevel , m_powerRange ) ) ;
}
}
void GLSpectrumView : : resetFrequencyZoom ( )
{
m_frequencyZoomFactor = 1.0f ;
m_frequencyZoomPos = 0.5f ;
2026-03-25 16:30:13 +00:00
updateFFTLimits ( false ) ;
2022-09-29 13:48:04 +01:00
}
2026-03-25 16:30:13 +00:00
void GLSpectrumView : : updateFFTLimits ( bool fftSizeChangedOnly )
2022-09-29 13:48:04 +01:00
{
2026-03-25 16:30:13 +00:00
if ( ! fftSizeChangedOnly )
2026-02-12 15:13:52 +00:00
{
2026-03-25 16:30:13 +00:00
if ( m_messageQueueToGUI )
{
m_messageQueueToGUI - > push ( GLSpectrumView : : MsgFrequencyZooming : : create (
m_frequencyZoomFactor , m_frequencyZoomPos
) ) ;
}
2022-09-29 13:48:04 +01:00
}
2026-03-25 16:30:13 +00:00
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 ;
2026-02-12 15:13:52 +00:00
}
2026-03-25 16:30:13 +00:00
if ( m_fftMax > m_fftSize ) {
m_fftMax = m_fftSize ;
}
m_nbBins = m_fftMax - m_fftMin ;
2022-09-29 13:48:04 +01:00
2026-03-25 16:30:13 +00:00
m_redrawAll = true ;
2022-09-29 13:48:04 +01:00
m_changesPending = true ;
}
2026-02-12 15:13:52 +00:00
void GLSpectrumView : : setFrequencyZooming ( float frequencyZoomFactor , float frequencyZoomPos )
{
m_frequencyZoomFactor = frequencyZoomFactor ;
frequencyZoom ( frequencyZoomPos ) ;
}
2026-03-25 16:30:13 +00:00
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 ;
}
2022-09-29 13:48:04 +01:00
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 )
{
2026-03-25 16:30:13 +00:00
int adjSampleRate = m_ssbSpectrum ? getDisplayedSampleRate ( ) / 2 : getDisplayedSampleRate ( ) ;
qint64 adjCenterFrequency = getDisplayedCenterFrequency ( ) + ( m_ssbSpectrum ? getDisplayedSampleRate ( ) / 4 : 0 ) ;
2022-09-29 13:48:04 +01:00
frequencySpan = ( m_frequencyZoomFactor = = 1 ) ?
2023-06-18 00:43:26 +02:00
adjSampleRate : adjSampleRate * ( 1.0 / m_frequencyZoomFactor ) ;
2022-09-29 13:48:04 +01:00
centerFrequency = ( m_frequencyZoomFactor = = 1 ) ?
2023-06-18 00:43:26 +02:00
adjCenterFrequency : ( m_frequencyZoomPos - 0.5 ) * adjSampleRate + adjCenterFrequency ;
2022-09-29 13:48:04 +01:00
}
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
2026-03-25 16:30:13 +00:00
float x_pos = m_frequencyScale . getPosFromValue ( getDisplayedCenterFrequency ( ) + freq ) ;
2022-09-29 13:48:04 +01:00
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 ;
}
}
2022-12-20 14:39:39 +00:00
zoom ( event - > position ( ) , event - > angleDelta ( ) . y ( ) ) ;
2022-09-29 13:48:04 +01:00
}
// 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 ) ;
}
2022-11-17 14:41:55 +00:00
void GLSpectrumView : : enterEvent ( EnterEventType * event )
2022-09-29 13:48:04 +01:00
{
m_mouseInside = true ;
update ( ) ;
QOpenGLWidget : : enterEvent ( event ) ;
}
void GLSpectrumView : : leaveEvent ( QEvent * event )
{
m_mouseInside = false ;
update ( ) ;
2022-11-17 14:41:55 +00:00
QOpenGLWidget : : leaveEvent ( event ) ;
2022-09-29 13:48:04 +01:00
}
void GLSpectrumView : : tick ( )
{
2026-03-25 16:30:13 +00:00
// Update scroll bar to account for changes to m_spectrumBuffer and waterfall size changes
updateScrollBar ( ) ;
2022-09-29 13:48:04 +01:00
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 - - )
{
2026-03-25 16:30:13 +00:00
if ( ! text [ i ] . isEmpty ( ) | | ! value [ i ] . isEmpty ( ) )
{
textWidth = fm . horizontalAdvance ( units [ i ] ) ;
painter . drawText ( QPointF ( x - textWidth , y ) , units [ i ] ) ;
x - = textWidth ;
2022-09-29 13:48:04 +01:00
2026-03-25 16:30:13 +00:00
textWidth = fm . horizontalAdvance ( value [ i ] ) ;
maxWidth = fm . horizontalAdvance ( max [ i ] ) ;
painter . drawText ( QPointF ( x - textWidth , y ) , value [ i ] ) ;
x - = maxWidth ;
2022-09-29 13:48:04 +01:00
2026-03-25 16:30:13 +00:00
textWidth = fm . horizontalAdvance ( text [ i ] ) ;
painter . drawText ( QPointF ( x - textWidth , y ) , text [ i ] ) ;
x - = textWidth ;
}
2022-09-29 13:48:04 +01:00
}
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 )
{
2026-03-25 16:30:13 +00:00
QString spacing = " " ;
2022-09-29 13:48:04 +01:00
if ( m_useCalibration ) {
2026-03-25 16:30:13 +00:00
info . append ( tr ( " CAL:%1dB%2 " ) . arg ( QString : : number ( m_calibrationShiftdB , ' f ' , 1 ) ) . arg ( spacing ) ) ;
2022-09-29 13:48:04 +01:00
}
if ( m_frequencyZoomFactor ! = 1.0f ) {
2026-03-25 16:30:13 +00:00
info . append ( tr ( " %1x%2 " ) . arg ( QString : : number ( m_frequencyZoomFactor , ' f ' , 1 ) ) . arg ( spacing ) ) ;
2022-09-29 13:48:04 +01:00
}
2026-03-25 16:30:13 +00:00
if ( getDisplayedSampleRate ( ) = = 0 )
2022-09-29 13:48:04 +01:00
{
2026-03-25 16:30:13 +00:00
info . append ( tr ( " CF:%1 SP:%2%3 " ) . arg ( getDisplayedCenterFrequency ( ) ) . arg ( getDisplayedSampleRate ( ) ) . arg ( spacing ) ) ;
2022-09-29 13:48:04 +01:00
}
else
{
int64_t centerFrequency ;
int frequencySpan ;
getFrequencyZoom ( centerFrequency , frequencySpan ) ;
2026-03-25 16:30:13 +00:00
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
}
) ;
2022-09-29 13:48:04 +01:00
}
2026-03-25 16:30:13 +00:00
2022-09-29 13:48:04 +01:00
}
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 ;
2022-11-05 20:58:47 +00:00
case Qt : : Key_Equal : // So you don't need to press shift
2022-09-29 13:48:04 +01:00
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 ) ;
}
}
2026-03-25 16:30:13 +00:00
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 ) ;
}
}
}