@ -50,6 +50,7 @@
|
||||
#include "mainwindow.h"
|
||||
#include "remotetcpsinkstarter.h"
|
||||
#include "dsp/dsptypes.h"
|
||||
#include "util/profiler.h"
|
||||
#include "crashhandler.h"
|
||||
|
||||
static void logExceptionStackTrace()
|
||||
@ -340,6 +341,10 @@ int main(int argc, char* argv[])
|
||||
installCrashHandler(logger);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_PROFILER
|
||||
GlobalProfileData::resetProfileData(); // Start timer
|
||||
#endif
|
||||
|
||||
int res = runQtApplication(argc, argv, logger);
|
||||
|
||||
if (logger) {
|
||||
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 3.8 KiB |
BIN
doc/img/MainWindow_spectrum_gui_G.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
doc/img/MainWindow_spectrum_gui_G.xcf
Normal file
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 258 KiB After Width: | Height: | Size: 306 KiB |
BIN
doc/img/Spectrum_Display_Settings.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
doc/img/Spectrum_Measurement_Mask_Test.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 4.6 KiB |
@ -20,6 +20,7 @@
|
||||
using namespace qtwebapp;
|
||||
|
||||
BufferLogger::BufferLogger(int maxSize, QObject *parent) :
|
||||
Logger(parent),
|
||||
m_maxSize(maxSize)
|
||||
{
|
||||
}
|
||||
@ -41,7 +42,7 @@ QString BufferLogger::getLog() const
|
||||
{
|
||||
QString log;
|
||||
|
||||
for (const auto s : m_messages) {
|
||||
for (const auto& s : m_messages) {
|
||||
log.append(s);
|
||||
}
|
||||
|
||||
|
||||
@ -1067,7 +1067,7 @@ ILSDemodGUI::ILSDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
|
||||
|
||||
ui->glSpectrum->setCenterFrequency(0);
|
||||
ui->glSpectrum->setSampleRate(ILSDemodSettings::ILSDEMOD_SPECTRUM_SAMPLE_RATE);
|
||||
ui->glSpectrum->setMeasurementParams(SpectrumSettings::MeasurementPeaks, 0, 1000, 90, 150, 1, 5, true, 1);
|
||||
ui->glSpectrum->setMeasurementParams(SpectrumSettings::MeasurementPeaks, 0, 1000, 90, 150, 1, 5, true, 1, 0);
|
||||
ui->glSpectrum->setMeasurementsVisible(true);
|
||||
|
||||
m_channelMarker.setColor(Qt::yellow);
|
||||
|
||||
@ -2596,7 +2596,7 @@ void MeshtasticDemodGUI::replayDechirpSnapshot(const DechirpSnapshot& snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
ui->glSpectrum->newSpectrum(line.data(), fftSize, fftSize);
|
||||
ui->glSpectrum->newSpectrum(line.data(), fftSize);
|
||||
};
|
||||
|
||||
// Prime the GL spectrum so pending size/layout changes are applied before replay.
|
||||
|
||||
@ -487,6 +487,7 @@ set(sdrbase_HEADERS
|
||||
util/azel.h
|
||||
util/baudot.h
|
||||
util/callsign.h
|
||||
util/circularbuffer.h
|
||||
util/colormap.h
|
||||
util/coordinates.h
|
||||
util/corsproxy.h
|
||||
|
||||
@ -2167,6 +2167,8 @@ void DeviceOpener::deviceSetAdded(int index, DeviceAPI *device)
|
||||
|
||||
void DeviceOpener::deviceChanged(int index)
|
||||
{
|
||||
(void) index;
|
||||
|
||||
// Apply device settings
|
||||
QString errorMessage;
|
||||
if (200 != m_device->getSampleSource()->webapiSettingsPutPatch(false, m_settingsKeys, *m_response, errorMessage)) {
|
||||
|
||||
@ -65,6 +65,7 @@ DSPDeviceSourceEngine *DSPEngine::addDeviceSourceEngine()
|
||||
{
|
||||
auto *deviceSourceEngine = new DSPDeviceSourceEngine(m_deviceSourceEnginesUIDSequence);
|
||||
auto *deviceThread = new QThread();
|
||||
deviceThread->setObjectName(QString("DSPDeviceSourceEngine thread %1").arg(m_deviceSourceEnginesUIDSequence));
|
||||
m_deviceSourceEnginesUIDSequence++;
|
||||
m_deviceSourceEngines.push_back(deviceSourceEngine);
|
||||
m_deviceEngineReferences.push_back(DeviceEngineReference{0, m_deviceSourceEngines.back(), nullptr, nullptr, deviceThread});
|
||||
@ -107,6 +108,7 @@ DSPDeviceSinkEngine *DSPEngine::addDeviceSinkEngine()
|
||||
{
|
||||
auto *deviceSinkEngine = new DSPDeviceSinkEngine(m_deviceSinkEnginesUIDSequence);
|
||||
auto *deviceThread = new QThread();
|
||||
deviceThread->setObjectName(QString("DSPDeviceSinkEngine thread %1").arg(m_deviceSinkEnginesUIDSequence));
|
||||
m_deviceSinkEnginesUIDSequence++;
|
||||
m_deviceSinkEngines.push_back(deviceSinkEngine);
|
||||
m_deviceEngineReferences.push_back(DeviceEngineReference{1, nullptr, m_deviceSinkEngines.back(), nullptr, deviceThread});
|
||||
@ -149,6 +151,7 @@ DSPDeviceMIMOEngine *DSPEngine::addDeviceMIMOEngine()
|
||||
{
|
||||
auto *deviceMIMOEngine = new DSPDeviceMIMOEngine(m_deviceMIMOEnginesUIDSequence);
|
||||
auto *deviceThread = new QThread();
|
||||
deviceThread->setObjectName(QString("DSPDeviceMIMOEngine thread %1").arg(m_deviceMIMOEnginesUIDSequence));
|
||||
m_deviceMIMOEnginesUIDSequence++;
|
||||
m_deviceMIMOEngines.push_back(deviceMIMOEngine);
|
||||
m_deviceEngineReferences.push_back(DeviceEngineReference{2, nullptr, nullptr, m_deviceMIMOEngines.back(), deviceThread});
|
||||
|
||||
@ -28,10 +28,9 @@ class GLSpectrumInterface
|
||||
public:
|
||||
GLSpectrumInterface() {}
|
||||
virtual ~GLSpectrumInterface() {}
|
||||
virtual void newSpectrum(const Real* spectrum, int nbBins, int fftSize)
|
||||
virtual void newSpectrum(const Real* spectrum, int fftSize)
|
||||
{
|
||||
(void) spectrum;
|
||||
(void) nbBins;
|
||||
(void) fftSize;
|
||||
}
|
||||
};
|
||||
|
||||
@ -27,11 +27,13 @@ bool NCO::m_tableInitialized = false;
|
||||
|
||||
void NCO::initTable()
|
||||
{
|
||||
if(m_tableInitialized)
|
||||
if (m_tableInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < TableSize; i++)
|
||||
for (unsigned i = 0; i < TableSize; i++) {
|
||||
m_table[i] = cos((2.0 * M_PI * i) / TableSize);
|
||||
}
|
||||
|
||||
m_tableInitialized = true;
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ void SpectrumSettings::resetToDefaults()
|
||||
m_decay = 1;
|
||||
m_decayDivisor = 1;
|
||||
m_histogramStroke = 30;
|
||||
m_displayGridIntensity = 5;
|
||||
m_displayGridIntensity = 15;
|
||||
m_displayTraceIntensity = 50;
|
||||
m_waterfallShare = 0.5;
|
||||
m_displayCurrent = true;
|
||||
@ -83,12 +83,46 @@ void SpectrumSettings::resetToDefaults()
|
||||
m_measurementPrecision = 1;
|
||||
m_findHistogramPeaks = false;
|
||||
#ifdef ANDROID
|
||||
m_showAllControls = false;
|
||||
m_showControls = ShowMinimum;
|
||||
#else
|
||||
m_showAllControls = true;
|
||||
m_showControls = ShowStandard;
|
||||
#endif
|
||||
m_frequencyZoomFactor = 1.0f;
|
||||
m_frequencyZoomPos = 0.5f;
|
||||
m_waterfallTimeUnits = TimeOffset;
|
||||
m_waterfallTimeFormat = "hh:mm:ss";
|
||||
m_scrollBar = false;
|
||||
m_scrollLength = 100000;
|
||||
m_mathMode = MathModeNone;
|
||||
m_mathAvgCount = 100;
|
||||
m_measurementMemMasks = 0x1;
|
||||
m_displayRBW = false;
|
||||
m_displayCursorStats = false;
|
||||
m_displayPeakStats = false;
|
||||
|
||||
m_spectrumMemory.clear();
|
||||
while (m_spectrumMemory.size() < m_maxSpectrumMemories)
|
||||
{
|
||||
SpectrumMemory memory;
|
||||
memory.m_display = false;
|
||||
memory.m_color = QColor(Qt::cyan).darker().rgb();
|
||||
memory.m_label = "";
|
||||
m_spectrumMemory.append(memory);
|
||||
}
|
||||
|
||||
m_spectrumColor = qRgb(255, 255, 63);
|
||||
}
|
||||
|
||||
// Ensure we have settings for each spectrum memory
|
||||
void SpectrumSettings::validateSpectrumMemories()
|
||||
{
|
||||
while (m_spectrumMemory.size() < m_maxSpectrumMemories)
|
||||
{
|
||||
SpectrumMemory memory;
|
||||
memory.m_display = false;
|
||||
memory.m_color = QColor(Qt::cyan).darker().rgb();
|
||||
m_spectrumMemory.append(memory);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray SpectrumSettings::serialize() const
|
||||
@ -141,9 +175,23 @@ QByteArray SpectrumSettings::serialize() const
|
||||
s.writeS32(46, m_measurementCenterFrequencyOffset);
|
||||
s.writeBool(47, m_findHistogramPeaks);
|
||||
s.writeBool(48, m_truncateFreqScale);
|
||||
s.writeBool(49, m_showAllControls);
|
||||
// 49 was showAllControls - replaced by m_showControls
|
||||
s.writeFloat(50, m_frequencyZoomFactor);
|
||||
s.writeFloat(51, m_frequencyZoomPos);
|
||||
s.writeS32(52, (int) m_waterfallTimeUnits);
|
||||
s.writeString(53, m_waterfallTimeFormat);
|
||||
s.writeBool(54, m_scrollBar);
|
||||
s.writeS32(55, m_scrollLength);
|
||||
s.writeS32(56, (int) m_mathMode);
|
||||
s.writeU32(57, m_mathAvgCount);
|
||||
s.writeU32(58, m_measurementMemMasks);
|
||||
s.writeBool(59, m_displayRBW);
|
||||
s.writeBool(60, m_displayCursorStats);
|
||||
s.writeBool(61, m_displayPeakStats);
|
||||
s.writeList(62, m_spectrumMemory);
|
||||
s.writeS32(63, (int) m_showControls);
|
||||
s.writeU32(64, m_spectrumColor);
|
||||
|
||||
s.writeS32(100, m_histogramMarkers.size());
|
||||
|
||||
for (int i = 0; i < m_histogramMarkers.size(); i++) {
|
||||
@ -207,14 +255,14 @@ bool SpectrumSettings::deserialize(const QByteArray& data)
|
||||
d.readBool(9, &m_displayHistogram, false);
|
||||
d.readS32(10, &m_decay, 1);
|
||||
d.readBool(11, &m_displayGrid, false);
|
||||
d.readS32(13, &m_displayGridIntensity, 5);
|
||||
d.readS32(13, &m_displayGridIntensity, 15);
|
||||
d.readS32(14, &m_decayDivisor, 1);
|
||||
d.readS32(15, &m_histogramStroke, 30);
|
||||
d.readBool(16, &m_displayCurrent, true);
|
||||
d.readS32(17, &m_displayTraceIntensity, 50);
|
||||
d.readReal(18, &m_waterfallShare, 0.66);
|
||||
d.readS32(19, &tmp, 0);
|
||||
m_averagingMode = tmp < 0 ? AvgModeNone : tmp > 3 ? AvgModeMax : (AveragingMode) tmp;
|
||||
m_averagingMode = tmp < 0 ? AvgModeNone : tmp > 4 ? AvgModeMin : (AveragingMode) tmp;
|
||||
d.readS32(20, &tmp, 0);
|
||||
m_averagingIndex = getAveragingIndex(tmp, m_averagingMode);
|
||||
m_averagingValue = getAveragingValue(m_averagingIndex, m_averagingMode);
|
||||
@ -248,13 +296,26 @@ bool SpectrumSettings::deserialize(const QByteArray& data)
|
||||
d.readS32(46, &m_measurementCenterFrequencyOffset, 0);
|
||||
d.readBool(47, &m_findHistogramPeaks, false);
|
||||
d.readBool(48, &m_truncateFreqScale, false);
|
||||
#ifdef ANDROID
|
||||
d.readBool(49, &m_showAllControls, false);
|
||||
#else
|
||||
d.readBool(49, &m_showAllControls, true);
|
||||
#endif
|
||||
d.readFloat(50, &m_frequencyZoomFactor, 1.0f);
|
||||
d.readFloat(51, &m_frequencyZoomPos, 0.5f);
|
||||
d.readS32(52, (int *) &m_waterfallTimeUnits, TimeOffset);
|
||||
d.readString(53, &m_waterfallTimeFormat, "hh:mm:ss");
|
||||
d.readBool(54, &m_scrollBar, false);
|
||||
d.readS32(55, &m_scrollLength, 100000);
|
||||
d.readS32(56, (int *) &m_mathMode, (int) MathModeNone);
|
||||
d.readU32(57, &m_mathAvgCount, 100);
|
||||
d.readU32(58, &m_measurementMemMasks, 0x1);
|
||||
d.readBool(59, &m_displayRBW, false);
|
||||
d.readBool(60, &m_displayCursorStats, false);
|
||||
d.readBool(61, &m_displayPeakStats, false);
|
||||
d.readList(62, &m_spectrumMemory);
|
||||
validateSpectrumMemories();
|
||||
#ifdef ANDROID
|
||||
d.readS32(63, (int *) &m_showControls, ShowMinimum);
|
||||
#else
|
||||
d.readS32(63, (int *) &m_showControls, ShowStandard);
|
||||
#endif
|
||||
d.readU32(64, &m_spectrumColor, qRgb(255, 255, 63));
|
||||
|
||||
int histogramMarkersSize;
|
||||
d.readS32(100, &histogramMarkersSize, 0);
|
||||
@ -585,7 +646,7 @@ void SpectrumSettings::updateFrom(const QStringList& keys, const SWGSDRangel::SW
|
||||
|
||||
int SpectrumSettings::getAveragingMaxScale(AveragingMode averagingMode)
|
||||
{
|
||||
if (averagingMode == AvgModeMoving) {
|
||||
if (averagingMode == AvgModeMoving) {
|
||||
return 3; // max 10k
|
||||
} else {
|
||||
return 5; // max 1M
|
||||
@ -671,3 +732,73 @@ QColor SpectrumSettings::intToQColor(int intColor)
|
||||
int b = bg / 256;
|
||||
return QColor(r, g, b);
|
||||
}
|
||||
|
||||
bool SpectrumSettings::mathAverageUsed() const
|
||||
{
|
||||
return (m_mathMode == SpectrumSettings::MathModeXMinusAvg)
|
||||
|| (m_mathMode == SpectrumSettings::MathModeXMinusAvgDB)
|
||||
|| (m_mathMode == SpectrumSettings::MathModeXMinusAvgPlusMinAvgDB)
|
||||
|| (m_mathMode == SpectrumSettings::MathModeAbsXMinusAvgDB);
|
||||
}
|
||||
|
||||
bool SpectrumSettings::mathUsesDB() const
|
||||
{
|
||||
return (m_mathMode == SpectrumSettings::MathModeXMinusAvgDB)
|
||||
|| (m_mathMode == SpectrumSettings::MathModeXMinusAvgPlusMinAvgDB)
|
||||
|| (m_mathMode == SpectrumSettings::MathModeAbsXMinusAvgDB)
|
||||
|| (m_mathMode == SpectrumSettings::MathModeXMinusM1DB)
|
||||
|| (m_mathMode == SpectrumSettings::MathModeAbsXMinusM1DB)
|
||||
|| (m_mathMode == SpectrumSettings::MathModeXMinusM2DB)
|
||||
|| (m_mathMode == SpectrumSettings::MathModeAbsXMinusM2DB)
|
||||
;
|
||||
}
|
||||
|
||||
QByteArray SpectrumSettings::SpectrumMemory::serialize() const
|
||||
{
|
||||
SimpleSerializer s(1);
|
||||
|
||||
s.writeList(1, m_spectrum);
|
||||
s.writeBool(2, m_display);
|
||||
s.writeU32(3, m_color);
|
||||
s.writeString(4, m_label);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
|
||||
bool SpectrumSettings::SpectrumMemory::deserialize(const QByteArray& data)
|
||||
{
|
||||
SimpleDeserializer d(data);
|
||||
|
||||
if (!d.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (d.getVersion() == 1)
|
||||
{
|
||||
d.readList(1, &m_spectrum);
|
||||
d.readBool(2, &m_display, false);
|
||||
d.readU32(3, &m_color, QColor(Qt::cyan).darker().rgb());
|
||||
d.readString(4, &m_label);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const SpectrumSettings::SpectrumMemory& settings)
|
||||
{
|
||||
out << settings.serialize();
|
||||
return out;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& in, SpectrumSettings::SpectrumMemory& settings)
|
||||
{
|
||||
QByteArray data;
|
||||
in >> data;
|
||||
settings.deserialize(data);
|
||||
return in;
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// Copyright (C) 2015-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// Copyright (C) 2022-2026 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
@ -34,12 +34,29 @@
|
||||
class SDRBASE_API SpectrumSettings : public Serializable
|
||||
{
|
||||
public:
|
||||
|
||||
enum AveragingMode
|
||||
{
|
||||
AvgModeNone,
|
||||
AvgModeMoving,
|
||||
AvgModeFixed,
|
||||
AvgModeMax
|
||||
AvgModeMax,
|
||||
AvgModeMin
|
||||
};
|
||||
|
||||
enum MathMode
|
||||
{
|
||||
MathModeNone,
|
||||
MathModeXMinusAvg,
|
||||
MathModeXMinusAvgDB,
|
||||
MathModeXMinusAvgPlusMinAvgDB,
|
||||
MathModeAbsXMinusAvgDB,
|
||||
MathModeXMinusM1,
|
||||
MathModeXMinusM1DB,
|
||||
MathModeAbsXMinusM1DB,
|
||||
MathModeXMinusM2,
|
||||
MathModeXMinusM2DB,
|
||||
MathModeAbsXMinusM2DB
|
||||
};
|
||||
|
||||
// Bitmask for which selection of markers to display
|
||||
@ -81,7 +98,8 @@ public:
|
||||
MeasurementAdjacentChannelPower,
|
||||
MeasurementOccupiedBandwidth,
|
||||
Measurement3dBBandwidth,
|
||||
MeasurementSNR
|
||||
MeasurementSNR,
|
||||
MeasurementMask
|
||||
};
|
||||
|
||||
enum MeasurementsPosition {
|
||||
@ -91,6 +109,28 @@ public:
|
||||
PositionRight
|
||||
};
|
||||
|
||||
enum WaterfallTimeUnits {
|
||||
TimeOffset,
|
||||
LocalTime,
|
||||
UTCTime
|
||||
};
|
||||
|
||||
enum ShowControls {
|
||||
ShowMinimum,
|
||||
ShowStandard,
|
||||
ShowAll
|
||||
};
|
||||
|
||||
struct SpectrumMemory {
|
||||
QList<Real> m_spectrum;
|
||||
bool m_display;
|
||||
QRgb m_color;
|
||||
QString m_label;
|
||||
|
||||
QByteArray serialize() const;
|
||||
bool deserialize(const QByteArray& data);
|
||||
};
|
||||
|
||||
int m_fftSize;
|
||||
int m_fftOverlap;
|
||||
FFTWindow::Function m_fftWindow;
|
||||
@ -141,9 +181,23 @@ public:
|
||||
bool m_measurementHighlight;
|
||||
MeasurementsPosition m_measurementsPosition;
|
||||
int m_measurementPrecision;
|
||||
bool m_showAllControls;
|
||||
enum ShowControls m_showControls;
|
||||
float m_frequencyZoomFactor;
|
||||
float m_frequencyZoomPos;
|
||||
WaterfallTimeUnits m_waterfallTimeUnits;
|
||||
QString m_waterfallTimeFormat; //!< E.g: "hh:mm:ss.zzz"
|
||||
bool m_scrollBar;
|
||||
int m_scrollLength;
|
||||
MathMode m_mathMode;
|
||||
unsigned int m_mathAvgCount;
|
||||
unsigned int m_measurementMemMasks; // Bitmask for each memory to be used as a mask
|
||||
bool m_displayRBW;
|
||||
bool m_displayCursorStats;
|
||||
bool m_displayPeakStats;
|
||||
QList<SpectrumMemory> m_spectrumMemory;
|
||||
QRgb m_spectrumColor;
|
||||
|
||||
static const int m_maxSpectrumMemories = 2; // As we only have two buttons in GUI
|
||||
|
||||
static const int m_log2FFTSizeMin = 6; // 64
|
||||
static const int m_log2FFTSizeMax = 15; // 32k
|
||||
@ -151,6 +205,7 @@ public:
|
||||
SpectrumSettings();
|
||||
virtual ~SpectrumSettings();
|
||||
void resetToDefaults();
|
||||
void validateSpectrumMemories();
|
||||
|
||||
virtual QByteArray serialize() const;
|
||||
virtual bool deserialize(const QByteArray& data);
|
||||
@ -160,6 +215,8 @@ public:
|
||||
QList<SpectrumHistogramMarker>& getHistogramMarkers() { return m_histogramMarkers; }
|
||||
QList<SpectrumWaterfallMarker>& getWaterfallMarkers() { return m_waterfallMarkers; }
|
||||
bool getHistogramFindPeaks() { return m_findHistogramPeaks; }
|
||||
bool mathAverageUsed() const;
|
||||
bool mathUsesDB() const;
|
||||
|
||||
static int getAveragingMaxScale(AveragingMode averagingMode); //!< Max power of 10 multiplier to 2,5,10 base ex: 2 -> 2,5,10,20,50,100,200,500,1000
|
||||
static int getAveragingValue(int averagingIndex, AveragingMode averagingMode);
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
|
||||
// Copyright (C) 2023 Arne Jünemann <das-iro@das-iro.de> //
|
||||
// Copyright (C) 2023 Vladimir Pleskonjic //
|
||||
// Copyright (C) 2026 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
@ -30,6 +31,7 @@
|
||||
#include "dspengine.h"
|
||||
#include "fftfactory.h"
|
||||
#include "util/messagequeue.h"
|
||||
#include "util/profiler.h"
|
||||
|
||||
#include "spectrumvis.h"
|
||||
|
||||
@ -38,7 +40,6 @@ MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureScalingFactor, Message)
|
||||
MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureWSpectrumOpenClose, Message)
|
||||
MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureWSpectrum, Message)
|
||||
MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgStartStop, Message)
|
||||
MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgFrequencyZooming, Message)
|
||||
|
||||
const Real SpectrumVis::m_mult = (10.0f / log2(10.0f));
|
||||
|
||||
@ -49,7 +50,7 @@ SpectrumVis::SpectrumVis(Real scalef) :
|
||||
m_fftEngineSequence(0),
|
||||
m_fftBuffer(4096),
|
||||
m_powerSpectrum(4096),
|
||||
m_psd(4096),
|
||||
m_mathMemory(4096),
|
||||
m_fftBufferFill(0),
|
||||
m_needMoreSamples(false),
|
||||
m_scalef(scalef),
|
||||
@ -57,8 +58,7 @@ SpectrumVis::SpectrumVis(Real scalef) :
|
||||
m_specMax(0.0f),
|
||||
m_centerFrequency(0),
|
||||
m_sampleRate(48000),
|
||||
m_ofs(0),
|
||||
m_powFFTDiv(1.0),
|
||||
m_powFFTMul(1.0f),
|
||||
m_guiMessageQueue(nullptr)
|
||||
{
|
||||
setObjectName("SpectrumVis");
|
||||
@ -107,206 +107,15 @@ void SpectrumVis::feedTriggered(const SampleVector::const_iterator& triggerPoint
|
||||
|
||||
void SpectrumVis::feed(const Complex *begin, unsigned int length)
|
||||
{
|
||||
if (!m_glSpectrum && !m_wsSpectrum.socketOpened()) {
|
||||
return;
|
||||
}
|
||||
if (!m_glSpectrum && !m_wsSpectrum.socketOpened()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_mutex.tryLock(0)) { // prevent conflicts with configuration process
|
||||
return;
|
||||
}
|
||||
|
||||
Complex c;
|
||||
Real v;
|
||||
int fftMin = (m_settings.m_frequencyZoomFactor == 1.0f) ?
|
||||
0 : (m_settings.m_frequencyZoomPos - (0.5f / m_settings.m_frequencyZoomFactor)) * m_settings.m_fftSize;
|
||||
int fftMax = (m_settings.m_frequencyZoomFactor == 1.0f) ?
|
||||
m_settings.m_fftSize : (m_settings.m_frequencyZoomPos + (0.5f / m_settings.m_frequencyZoomFactor)) * m_settings.m_fftSize;
|
||||
|
||||
if (m_settings.m_averagingMode == SpectrumSettings::AvgModeNone)
|
||||
{
|
||||
for (int i = 0; i < m_settings.m_fftSize; i++)
|
||||
{
|
||||
if (i < (int) length) {
|
||||
c = begin[i];
|
||||
} else {
|
||||
c = Complex{0,0};
|
||||
}
|
||||
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
m_psd[i] = v/m_powFFTDiv;
|
||||
v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs;
|
||||
m_powerSpectrum[i] = v;
|
||||
}
|
||||
|
||||
// send new data to visualisation
|
||||
if (m_glSpectrum)
|
||||
{
|
||||
m_glSpectrum->newSpectrum(
|
||||
&m_powerSpectrum.data()[fftMin],
|
||||
fftMax - fftMin,
|
||||
m_settings.m_fftSize
|
||||
);
|
||||
}
|
||||
|
||||
// web socket spectrum connections
|
||||
if (m_wsSpectrum.socketOpened())
|
||||
{
|
||||
m_wsSpectrum.newSpectrum(
|
||||
m_powerSpectrum,
|
||||
m_settings.m_fftSize,
|
||||
m_centerFrequency,
|
||||
m_sampleRate,
|
||||
m_settings.m_linear,
|
||||
m_settings.m_ssb,
|
||||
m_settings.m_usb
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeMoving)
|
||||
{
|
||||
for (int i = 0; i < m_settings.m_fftSize; i++)
|
||||
{
|
||||
if (i < (int) length) {
|
||||
c = begin[i];
|
||||
} else {
|
||||
c = Complex{0,0};
|
||||
}
|
||||
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v = m_movingAverage.storeAndGetAvg(v, i);
|
||||
m_psd[i] = v/m_powFFTDiv;
|
||||
v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs;
|
||||
m_powerSpectrum[i] = v;
|
||||
}
|
||||
|
||||
// send new data to visualisation
|
||||
if (m_glSpectrum)
|
||||
{
|
||||
m_glSpectrum->newSpectrum(
|
||||
&m_powerSpectrum.data()[fftMin],
|
||||
fftMax - fftMin,
|
||||
m_settings.m_fftSize
|
||||
);
|
||||
}
|
||||
|
||||
// web socket spectrum connections
|
||||
if (m_wsSpectrum.socketOpened())
|
||||
{
|
||||
m_wsSpectrum.newSpectrum(
|
||||
m_powerSpectrum,
|
||||
m_settings.m_fftSize,
|
||||
m_centerFrequency,
|
||||
m_sampleRate,
|
||||
m_settings.m_linear,
|
||||
m_settings.m_ssb,
|
||||
m_settings.m_usb
|
||||
);
|
||||
}
|
||||
|
||||
m_movingAverage.nextAverage();
|
||||
}
|
||||
else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeFixed)
|
||||
{
|
||||
double avg;
|
||||
|
||||
for (int i = 0; i < m_settings.m_fftSize; i++)
|
||||
{
|
||||
if (i < (int) length) {
|
||||
c = begin[i];
|
||||
} else {
|
||||
c = Complex{0,0};
|
||||
}
|
||||
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
|
||||
// result available
|
||||
if (m_fixedAverage.storeAndGetAvg(avg, v, i))
|
||||
{
|
||||
m_psd[i] = avg/m_powFFTDiv;
|
||||
avg = m_settings.m_linear ? avg/m_powFFTDiv : m_mult * log2fapprox(avg) + m_ofs;
|
||||
m_powerSpectrum[i] = avg;
|
||||
}
|
||||
}
|
||||
|
||||
// result available
|
||||
if (m_fixedAverage.nextAverage())
|
||||
{
|
||||
// send new data to visualisation
|
||||
if (m_glSpectrum)
|
||||
{
|
||||
m_glSpectrum->newSpectrum(
|
||||
&m_powerSpectrum.data()[fftMin],
|
||||
fftMax - fftMin,
|
||||
m_settings.m_fftSize
|
||||
);
|
||||
}
|
||||
|
||||
// web socket spectrum connections
|
||||
if (m_wsSpectrum.socketOpened())
|
||||
{
|
||||
m_wsSpectrum.newSpectrum(
|
||||
m_powerSpectrum,
|
||||
m_settings.m_fftSize,
|
||||
m_centerFrequency,
|
||||
m_sampleRate,
|
||||
m_settings.m_linear,
|
||||
m_settings.m_ssb,
|
||||
m_settings.m_usb
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeMax)
|
||||
{
|
||||
double max;
|
||||
|
||||
for (int i = 0; i < m_settings.m_fftSize; i++)
|
||||
{
|
||||
if (i < (int) length) {
|
||||
c = begin[i];
|
||||
} else {
|
||||
c = Complex{0,0};
|
||||
}
|
||||
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
|
||||
// result available
|
||||
if (m_max.storeAndGetMax(max, v, i))
|
||||
{
|
||||
m_psd[i] = max/m_powFFTDiv;
|
||||
max = m_settings.m_linear ? max/m_powFFTDiv : m_mult * log2fapprox(max) + m_ofs;
|
||||
m_powerSpectrum[i] = max;
|
||||
}
|
||||
}
|
||||
|
||||
// result available
|
||||
if (m_max.nextMax())
|
||||
{
|
||||
// send new data to visualisation
|
||||
if (m_glSpectrum)
|
||||
{
|
||||
m_glSpectrum->newSpectrum(
|
||||
&m_powerSpectrum.data()[fftMin],
|
||||
fftMax - fftMin,
|
||||
m_settings.m_fftSize
|
||||
);
|
||||
}
|
||||
|
||||
// web socket spectrum connections
|
||||
if (m_wsSpectrum.socketOpened())
|
||||
{
|
||||
m_wsSpectrum.newSpectrum(
|
||||
m_powerSpectrum,
|
||||
m_settings.m_fftSize,
|
||||
m_centerFrequency,
|
||||
m_sampleRate,
|
||||
m_settings.m_linear,
|
||||
m_settings.m_ssb,
|
||||
m_settings.m_usb
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
processFFT(begin, false, false, length);
|
||||
|
||||
m_mutex.unlock();
|
||||
}
|
||||
@ -339,7 +148,7 @@ void SpectrumVis::feed(const ComplexVector::const_iterator& cbegin, const Comple
|
||||
std::copy(begin, begin + samplesNeeded, m_fftBuffer.begin() + m_fftBufferFill);
|
||||
begin += samplesNeeded;
|
||||
|
||||
processFFT(positiveOnly);
|
||||
performFFT(positiveOnly);
|
||||
|
||||
// advance buffer respecting the fft overlap factor
|
||||
// undefined behavior if the memory regions overlap, valid code for 50% overlap
|
||||
@ -393,7 +202,7 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV
|
||||
*it++ = Complex(begin->real() / m_scalef, begin->imag() / m_scalef);
|
||||
}
|
||||
|
||||
processFFT(positiveOnly);
|
||||
performFFT(positiveOnly);
|
||||
|
||||
// advance buffer respecting the fft overlap factor
|
||||
// undefined behavior if the memory regions overlap, valid code for 50% overlap
|
||||
@ -418,13 +227,61 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void SpectrumVis::processFFT(bool positiveOnly)
|
||||
// Compute math operation on linear spectrum values
|
||||
void SpectrumVis::mathLinear(std::vector<Real> &spectrum)
|
||||
{
|
||||
int fftMin = (m_settings.m_frequencyZoomFactor == 1.0f) ?
|
||||
0 : (m_settings.m_frequencyZoomPos - (0.5f / m_settings.m_frequencyZoomFactor)) * m_settings.m_fftSize;
|
||||
int fftMax = (m_settings.m_frequencyZoomFactor == 1.0f) ?
|
||||
m_settings.m_fftSize : (m_settings.m_frequencyZoomPos + (0.5f / m_settings.m_frequencyZoomFactor)) * m_settings.m_fftSize;
|
||||
if (m_settings.m_mathMode == SpectrumSettings::MathModeXMinusAvg)
|
||||
{
|
||||
for (std::size_t i = 0; i < spectrum.size(); i++) {
|
||||
spectrum[i] = std::max(spectrum[i] - (Real) m_mathMovingAverage.storeAndGetAvg(spectrum[i], i), 0.0f);
|
||||
}
|
||||
}
|
||||
else if ((m_settings.m_mathMode == SpectrumSettings::MathModeXMinusM1) || (m_settings.m_mathMode == SpectrumSettings::MathModeXMinusM2))
|
||||
{
|
||||
for (std::size_t i = 0; i < spectrum.size(); i++) {
|
||||
spectrum[i] = std::max(spectrum[i] - m_mathMemory[i], 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute math operation on dB spectrum values
|
||||
void SpectrumVis::mathDB(std::vector<Real> &spectrum)
|
||||
{
|
||||
if (m_settings.m_mathMode == SpectrumSettings::MathModeXMinusAvgDB)
|
||||
{
|
||||
for (std::size_t i = 0; i < spectrum.size(); i++) {
|
||||
spectrum[i] -= m_mathMovingAverage.storeAndGetAvg(spectrum[i], i);
|
||||
}
|
||||
}
|
||||
else if (m_settings.m_mathMode == SpectrumSettings::MathModeXMinusAvgPlusMinAvgDB)
|
||||
{
|
||||
Real minAvg = m_mathMovingAverage.getMin();
|
||||
for (std::size_t i = 0; i < spectrum.size(); i++) {
|
||||
spectrum[i] = spectrum[i] - m_mathMovingAverage.storeAndGetAvg(spectrum[i], i) + minAvg;
|
||||
}
|
||||
}
|
||||
else if (m_settings.m_mathMode == SpectrumSettings::MathModeAbsXMinusAvgDB)
|
||||
{
|
||||
for (std::size_t i = 0; i < spectrum.size(); i++) {
|
||||
spectrum[i] = abs(spectrum[i] - m_mathMovingAverage.storeAndGetAvg(spectrum[i], i));
|
||||
}
|
||||
}
|
||||
else if ((m_settings.m_mathMode == SpectrumSettings::MathModeXMinusM1DB) || (m_settings.m_mathMode == SpectrumSettings::MathModeXMinusM2DB))
|
||||
{
|
||||
for (std::size_t i = 0; i < spectrum.size(); i++) {
|
||||
spectrum[i] -= m_mathMemory[i];
|
||||
}
|
||||
}
|
||||
else if ((m_settings.m_mathMode == SpectrumSettings::MathModeAbsXMinusM1DB) || (m_settings.m_mathMode == SpectrumSettings::MathModeAbsXMinusM2DB))
|
||||
{
|
||||
for (std::size_t i = 0; i < spectrum.size(); i++) {
|
||||
spectrum[i] = abs(spectrum[i] - m_mathMemory[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumVis::performFFT(bool positiveOnly)
|
||||
{
|
||||
// apply fft window (and copy from m_fftBuffer to m_fftIn)
|
||||
m_window.apply(&m_fftBuffer[0], m_fft->in());
|
||||
|
||||
@ -432,313 +289,357 @@ void SpectrumVis::processFFT(bool positiveOnly)
|
||||
m_fft->transform();
|
||||
|
||||
// extract power spectrum and reorder buckets
|
||||
const Complex* fftOut = m_fft->out();
|
||||
processFFT(m_fft->out(), true, positiveOnly, m_settings.m_fftSize);
|
||||
}
|
||||
|
||||
void SpectrumVis::processFFT(const Complex* fftOut, bool reorder, bool positiveOnly, int fftSize)
|
||||
{
|
||||
PROFILER_START();
|
||||
|
||||
Complex c;
|
||||
Real v;
|
||||
std::size_t halfSize = m_settings.m_fftSize / 2;
|
||||
int halfSize = fftSize / 2;
|
||||
bool ready = false;
|
||||
|
||||
if (m_settings.m_averagingMode == SpectrumSettings::AvgModeNone)
|
||||
{
|
||||
m_specMax = 0.0f;
|
||||
|
||||
if ( positiveOnly )
|
||||
if (positiveOnly)
|
||||
{
|
||||
for (std::size_t i = 0; i < halfSize; i++)
|
||||
for (int i = 0; i < halfSize; i++)
|
||||
{
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
m_psd[i] = v/m_powFFTDiv;
|
||||
m_specMax = v > m_specMax ? v : m_specMax;
|
||||
v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs;
|
||||
v *= m_powFFTMul;
|
||||
m_powerSpectrum[i * 2] = v;
|
||||
m_powerSpectrum[i * 2 + 1] = v;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (reorder)
|
||||
{
|
||||
for (std::size_t i = 0; i < halfSize; i++)
|
||||
for (int i = 0; i < halfSize; i++)
|
||||
{
|
||||
c = fftOut[i + halfSize];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
m_psd[i] = v/m_powFFTDiv;
|
||||
m_specMax = v > m_specMax ? v : m_specMax;
|
||||
v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs;
|
||||
v *= m_powFFTMul;
|
||||
m_powerSpectrum[i] = v;
|
||||
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
m_psd[i + halfSize] = v/m_powFFTDiv;
|
||||
m_specMax = v > m_specMax ? v : m_specMax;
|
||||
v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs;
|
||||
v *= m_powFFTMul;
|
||||
m_powerSpectrum[i + halfSize] = v;
|
||||
}
|
||||
}
|
||||
|
||||
// send new data to visualisation
|
||||
if (m_glSpectrum)
|
||||
else
|
||||
{
|
||||
m_glSpectrum->newSpectrum(
|
||||
&m_powerSpectrum.data()[fftMin],
|
||||
fftMax - fftMin,
|
||||
m_settings.m_fftSize
|
||||
);
|
||||
for (int i = 0; i < fftSize; i++)
|
||||
{
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
m_powerSpectrum[i] = v;
|
||||
}
|
||||
}
|
||||
|
||||
// web socket spectrum connections
|
||||
if (m_wsSpectrum.socketOpened())
|
||||
{
|
||||
m_wsSpectrum.newSpectrum(
|
||||
m_powerSpectrum,
|
||||
m_settings.m_fftSize,
|
||||
m_centerFrequency,
|
||||
m_sampleRate,
|
||||
m_settings.m_linear,
|
||||
m_settings.m_ssb,
|
||||
m_settings.m_usb
|
||||
);
|
||||
}
|
||||
ready = true;
|
||||
}
|
||||
else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeMoving)
|
||||
{
|
||||
m_specMax = 0.0f;
|
||||
double avg;
|
||||
|
||||
if ( positiveOnly )
|
||||
if (positiveOnly)
|
||||
{
|
||||
for (std::size_t i = 0; i < halfSize; i++)
|
||||
for (int i = 0; i < halfSize; i++)
|
||||
{
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v = m_movingAverage.storeAndGetAvg(v, i);
|
||||
m_psd[i] = v/m_powFFTDiv;
|
||||
m_specMax = v > m_specMax ? v : m_specMax;
|
||||
v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs;
|
||||
m_powerSpectrum[i * 2] = v;
|
||||
m_powerSpectrum[i * 2 + 1] = v;
|
||||
v *= m_powFFTMul;
|
||||
avg = m_movingAverage.storeAndGetAvg(v, i);
|
||||
m_powerSpectrum[i * 2] = (Real) avg;
|
||||
m_powerSpectrum[i * 2 + 1] = (Real) avg;
|
||||
}
|
||||
}
|
||||
else if (reorder)
|
||||
{
|
||||
for (int i = 0; i < halfSize; i++)
|
||||
{
|
||||
c = fftOut[i + halfSize];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
avg = m_movingAverage.storeAndGetAvg(v, i+halfSize);
|
||||
m_powerSpectrum[i] = (Real) avg;
|
||||
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
avg = m_movingAverage.storeAndGetAvg(v, i);
|
||||
m_powerSpectrum[i + halfSize] = (Real) avg;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::size_t i = 0; i < halfSize; i++)
|
||||
for (int i = 0; i < fftSize; i++)
|
||||
{
|
||||
c = fftOut[i + halfSize];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v = m_movingAverage.storeAndGetAvg(v, i+halfSize);
|
||||
m_psd[i] = v/m_powFFTDiv;
|
||||
m_specMax = v > m_specMax ? v : m_specMax;
|
||||
v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs;
|
||||
m_powerSpectrum[i] = v;
|
||||
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v = m_movingAverage.storeAndGetAvg(v, i);
|
||||
m_psd[i + halfSize] = v/m_powFFTDiv;
|
||||
m_specMax = v > m_specMax ? v : m_specMax;
|
||||
v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs;
|
||||
m_powerSpectrum[i + halfSize] = v;
|
||||
v *= m_powFFTMul;
|
||||
avg = m_movingAverage.storeAndGetAvg(v, i);
|
||||
m_powerSpectrum[i] = (Real) avg;
|
||||
}
|
||||
}
|
||||
|
||||
// send new data to visualisation
|
||||
if (m_glSpectrum)
|
||||
{
|
||||
m_glSpectrum->newSpectrum(
|
||||
&m_powerSpectrum.data()[fftMin],
|
||||
fftMax - fftMin,
|
||||
m_settings.m_fftSize
|
||||
);
|
||||
}
|
||||
|
||||
// web socket spectrum connections
|
||||
if (m_wsSpectrum.socketOpened())
|
||||
{
|
||||
m_wsSpectrum.newSpectrum(
|
||||
m_powerSpectrum,
|
||||
m_settings.m_fftSize,
|
||||
m_centerFrequency,
|
||||
m_sampleRate,
|
||||
m_settings.m_linear,
|
||||
m_settings.m_ssb,
|
||||
m_settings.m_usb
|
||||
);
|
||||
}
|
||||
|
||||
m_movingAverage.nextAverage();
|
||||
ready = true;
|
||||
}
|
||||
else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeFixed)
|
||||
{
|
||||
double avg;
|
||||
Real specMax = 0.0f;
|
||||
|
||||
if ( positiveOnly )
|
||||
if (positiveOnly)
|
||||
{
|
||||
for (std::size_t i = 0; i < halfSize; i++)
|
||||
for (int i = 0; i < halfSize; i++)
|
||||
{
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
|
||||
// result available
|
||||
if (m_fixedAverage.storeAndGetAvg(avg, v, i))
|
||||
{
|
||||
m_psd[i] = avg/m_powFFTDiv;
|
||||
specMax = avg > specMax ? avg : specMax;
|
||||
avg = m_settings.m_linear ? avg/m_powFFTDiv : m_mult * log2fapprox(avg) + m_ofs;
|
||||
m_powerSpectrum[i * 2] = avg;
|
||||
m_powerSpectrum[i * 2 + 1] = avg;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (reorder)
|
||||
{
|
||||
for (std::size_t i = 0; i < halfSize; i++)
|
||||
for (int i = 0; i < halfSize; i++)
|
||||
{
|
||||
c = fftOut[i + halfSize];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
|
||||
// result available
|
||||
if (m_fixedAverage.storeAndGetAvg(avg, v, i+halfSize))
|
||||
{
|
||||
m_psd[i] = avg/m_powFFTDiv;
|
||||
specMax = avg > specMax ? avg : specMax;
|
||||
avg = m_settings.m_linear ? avg/m_powFFTDiv : m_mult * log2fapprox(avg) + m_ofs;
|
||||
if (m_fixedAverage.storeAndGetAvg(avg, v, i+halfSize)) {
|
||||
m_powerSpectrum[i] = avg;
|
||||
}
|
||||
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
|
||||
// result available
|
||||
if (m_fixedAverage.storeAndGetAvg(avg, v, i))
|
||||
{
|
||||
m_psd[i + halfSize] = avg/m_powFFTDiv;
|
||||
specMax = avg > specMax ? avg : specMax;
|
||||
avg = m_settings.m_linear ? avg/m_powFFTDiv : m_mult * log2fapprox(avg) + m_ofs;
|
||||
if (m_fixedAverage.storeAndGetAvg(avg, v, i)) {
|
||||
m_powerSpectrum[i + halfSize] = avg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// result available
|
||||
if (m_fixedAverage.nextAverage())
|
||||
{
|
||||
m_specMax = specMax;
|
||||
|
||||
// send new data to visualisation
|
||||
if (m_glSpectrum)
|
||||
{
|
||||
m_glSpectrum->newSpectrum(
|
||||
&m_powerSpectrum.data()[fftMin],
|
||||
fftMax - fftMin,
|
||||
m_settings.m_fftSize
|
||||
);
|
||||
}
|
||||
|
||||
// web socket spectrum connections
|
||||
if (m_wsSpectrum.socketOpened())
|
||||
{
|
||||
m_wsSpectrum.newSpectrum(
|
||||
m_powerSpectrum,
|
||||
m_settings.m_fftSize,
|
||||
m_centerFrequency,
|
||||
m_sampleRate,
|
||||
m_settings.m_linear,
|
||||
m_settings.m_ssb,
|
||||
m_settings.m_usb
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeMax)
|
||||
{
|
||||
double max;
|
||||
Real specMax = 0.0f;
|
||||
|
||||
if ( positiveOnly )
|
||||
{
|
||||
for (std::size_t i = 0; i < halfSize; i++)
|
||||
{
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
|
||||
// result available
|
||||
if (m_max.storeAndGetMax(max, v, i))
|
||||
{
|
||||
m_psd[i] = max/m_powFFTDiv;
|
||||
specMax = max > specMax ? max : specMax;
|
||||
max = m_settings.m_linear ? max/m_powFFTDiv : m_mult * log2fapprox(max) + m_ofs;
|
||||
m_powerSpectrum[i * 2] = max;
|
||||
m_powerSpectrum[i * 2 + 1] = max;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::size_t i = 0; i < halfSize; i++)
|
||||
for (int i = 0; i < fftSize; i++)
|
||||
{
|
||||
c = fftOut[i + halfSize];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
|
||||
// result available
|
||||
if (m_max.storeAndGetMax(max, v, i+halfSize))
|
||||
{
|
||||
m_psd[i] = max/m_powFFTDiv;
|
||||
specMax = max > specMax ? max : specMax;
|
||||
max = m_settings.m_linear ? max/m_powFFTDiv : m_mult * log2fapprox(max) + m_ofs;
|
||||
m_powerSpectrum[i] = max;
|
||||
}
|
||||
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
|
||||
// result available
|
||||
if (m_max.storeAndGetMax(max, v, i))
|
||||
{
|
||||
m_psd[i + halfSize] = max/m_powFFTDiv;
|
||||
specMax = max > specMax ? max : specMax;
|
||||
max = m_settings.m_linear ? max/m_powFFTDiv : m_mult * log2fapprox(max) + m_ofs;
|
||||
m_powerSpectrum[i + halfSize] = max;
|
||||
if (m_fixedAverage.storeAndGetAvg(avg, v, i)) {
|
||||
m_powerSpectrum[i] = avg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// result available
|
||||
if (m_max.nextMax())
|
||||
{
|
||||
m_specMax = specMax;
|
||||
|
||||
// send new data to visualisation
|
||||
if (m_glSpectrum)
|
||||
{
|
||||
m_glSpectrum->newSpectrum(
|
||||
&m_powerSpectrum.data()[fftMin],
|
||||
fftMax - fftMin,
|
||||
m_settings.m_fftSize
|
||||
);
|
||||
}
|
||||
|
||||
// web socket spectrum connections
|
||||
if (m_wsSpectrum.socketOpened())
|
||||
{
|
||||
m_wsSpectrum.newSpectrum(
|
||||
m_powerSpectrum,
|
||||
m_settings.m_fftSize,
|
||||
m_centerFrequency,
|
||||
m_sampleRate,
|
||||
m_settings.m_linear,
|
||||
m_settings.m_ssb,
|
||||
m_settings.m_usb
|
||||
);
|
||||
}
|
||||
if (m_fixedAverage.nextAverage()) {
|
||||
ready = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeMax)
|
||||
{
|
||||
Real max;
|
||||
|
||||
if (positiveOnly)
|
||||
{
|
||||
for (int i = 0; i < halfSize; i++)
|
||||
{
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
|
||||
// result available
|
||||
if (m_max.storeAndGetMax(max, v, i))
|
||||
{
|
||||
m_powerSpectrum[i * 2] = max;
|
||||
m_powerSpectrum[i * 2 + 1] = max;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (reorder)
|
||||
{
|
||||
for (int i = 0; i < halfSize; i++)
|
||||
{
|
||||
c = fftOut[i + halfSize];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
|
||||
// result available
|
||||
if (m_max.storeAndGetMax(max, v, i+halfSize)) {
|
||||
m_powerSpectrum[i] = max;
|
||||
}
|
||||
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
|
||||
// result available
|
||||
if (m_max.storeAndGetMax(max, v, i)) {
|
||||
m_powerSpectrum[i + halfSize] = max;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < fftSize; i++)
|
||||
{
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
|
||||
// result available
|
||||
if (m_max.storeAndGetMax(max, v, i)) {
|
||||
m_powerSpectrum[i] = max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// result available
|
||||
if (m_max.nextMax()) {
|
||||
ready = true;
|
||||
}
|
||||
}
|
||||
else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeMin)
|
||||
{
|
||||
Real min;
|
||||
|
||||
if (positiveOnly)
|
||||
{
|
||||
for (int i = 0; i < halfSize; i++)
|
||||
{
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
|
||||
if (m_min.storeAndGetMin(min, v, i))
|
||||
{
|
||||
m_powerSpectrum[i * 2] = min;
|
||||
m_powerSpectrum[i * 2 + 1] = min;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (reorder)
|
||||
{
|
||||
for (int i = 0; i < halfSize; i++)
|
||||
{
|
||||
c = fftOut[i + halfSize];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
|
||||
// result available
|
||||
if (m_min.storeAndGetMin(min, v, i+halfSize)) {
|
||||
m_powerSpectrum[i] = min;
|
||||
}
|
||||
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
|
||||
// result available
|
||||
if (m_min.storeAndGetMin(min, v, i)) {
|
||||
m_powerSpectrum[i + halfSize] = min;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < fftSize; i++)
|
||||
{
|
||||
c = fftOut[i];
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v *= m_powFFTMul;
|
||||
|
||||
if (m_min.storeAndGetMin(min, v, i)) {
|
||||
m_powerSpectrum[i] = min;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// result available
|
||||
if (m_min.nextMin()) {
|
||||
ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ready)
|
||||
{
|
||||
for (int i = fftSize; i < m_settings.m_fftSize; i++) {
|
||||
m_powerSpectrum[i] = 0.0f;
|
||||
}
|
||||
|
||||
// Calculate maximum value in spectrum
|
||||
m_specMax = *std::max_element(&m_powerSpectrum[0], &m_powerSpectrum[m_settings.m_fftSize]);
|
||||
|
||||
// Perform math operation on linear value
|
||||
if (m_settings.m_mathMode != SpectrumSettings::MathModeNone) {
|
||||
mathLinear(m_powerSpectrum);
|
||||
}
|
||||
|
||||
// Convert to dB
|
||||
if (!m_settings.m_linear)
|
||||
{
|
||||
for (int i = 0; i < m_settings.m_fftSize; i++) {
|
||||
m_powerSpectrum[i] = m_mult * log2fapprox(m_powerSpectrum[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Perform math operation on dB value
|
||||
if (m_settings.m_mathMode != SpectrumSettings::MathModeNone) {
|
||||
mathDB(m_powerSpectrum);
|
||||
}
|
||||
|
||||
if (m_settings.mathAverageUsed()) {
|
||||
m_mathMovingAverage.nextAverage();
|
||||
}
|
||||
|
||||
// Stop profiling before newSpectrum, as we profile that separately
|
||||
PROFILER_STOP("processFFT");
|
||||
|
||||
// send new data to visualisation
|
||||
if (m_glSpectrum)
|
||||
{
|
||||
m_glSpectrum->newSpectrum(
|
||||
&m_powerSpectrum.data()[0],
|
||||
m_settings.m_fftSize
|
||||
);
|
||||
}
|
||||
|
||||
// web socket spectrum connections
|
||||
if (m_wsSpectrum.socketOpened())
|
||||
{
|
||||
m_wsSpectrum.newSpectrum(
|
||||
m_powerSpectrum,
|
||||
m_settings.m_fftSize,
|
||||
m_centerFrequency,
|
||||
m_sampleRate,
|
||||
m_settings.m_linear,
|
||||
m_settings.m_ssb,
|
||||
m_settings.m_usb
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PROFILER_STOP("processFFT");
|
||||
}
|
||||
|
||||
void SpectrumVis::getZoomedPSDCopy(std::vector<Real>& copy) const
|
||||
{
|
||||
int fftMin = (m_settings.m_frequencyZoomFactor == 1.0f) ?
|
||||
0 : (m_settings.m_frequencyZoomPos - (0.5f / m_settings.m_frequencyZoomFactor)) * m_settings.m_fftSize;
|
||||
int fftMax = (m_settings.m_frequencyZoomFactor == 1.0f) ?
|
||||
m_settings.m_fftSize : (m_settings.m_frequencyZoomPos + (0.5f / m_settings.m_frequencyZoomFactor)) * m_settings.m_fftSize;
|
||||
copy.assign(m_psd.begin() + fftMin, m_psd.begin() + fftMax);
|
||||
}
|
||||
|
||||
void SpectrumVis::start()
|
||||
@ -826,13 +727,6 @@ bool SpectrumVis::handleMessage(const Message& message)
|
||||
MsgStartStop& cmd = (MsgStartStop&) message;
|
||||
setRunning(cmd.getStartStop());
|
||||
return true;
|
||||
}
|
||||
else if (MsgFrequencyZooming::match(message))
|
||||
{
|
||||
MsgFrequencyZooming& cmd = (MsgFrequencyZooming&) message;
|
||||
m_settings.m_frequencyZoomFactor = cmd.getFrequencyZoomFactor();
|
||||
m_settings.m_frequencyZoomPos = cmd.getFrequencyZoomPos();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -876,14 +770,13 @@ void SpectrumVis::applySettings(const SpectrumSettings& settings, bool force)
|
||||
}
|
||||
|
||||
m_fftEngineSequence = fftFactory->getEngine(fftSize, false, &m_fft);
|
||||
m_ofs = 20.0f * log10f(1.0f / fftSize);
|
||||
m_powFFTDiv = fftSize * fftSize;
|
||||
m_powFFTMul = 1.0f / (fftSize * fftSize);
|
||||
|
||||
if (fftSize > m_settings.m_fftSize)
|
||||
{
|
||||
m_fftBuffer.resize(fftSize);
|
||||
m_powerSpectrum.resize(fftSize);
|
||||
m_psd.resize(fftSize);
|
||||
m_mathMemory.resize(fftSize);
|
||||
}
|
||||
}
|
||||
|
||||
@ -912,6 +805,17 @@ void SpectrumVis::applySettings(const SpectrumSettings& settings, bool force)
|
||||
m_movingAverage.resize(fftSize, averagingValue);
|
||||
m_fixedAverage.resize(fftSize, averagingValue);
|
||||
m_max.resize(fftSize, averagingValue);
|
||||
m_min.resize(fftSize, averagingValue);
|
||||
}
|
||||
|
||||
if ((fftSize != m_settings.m_fftSize)
|
||||
|| (settings.m_mathAvgCount != m_settings.m_mathAvgCount) || force)
|
||||
{
|
||||
m_mathMovingAverage.resize(fftSize, settings.m_mathAvgCount);
|
||||
}
|
||||
|
||||
if (settings.m_mathMode != m_settings.m_mathMode) {
|
||||
m_mathMovingAverage.clear();
|
||||
}
|
||||
|
||||
if ((settings.m_wsSpectrumAddress != m_settings.m_wsSpectrumAddress)
|
||||
@ -1085,11 +989,10 @@ void SpectrumVis::webapiUpdateSpectrumSettings(
|
||||
}
|
||||
|
||||
// To calculate power, the usual equation:
|
||||
// 10*log10(V1/V2), where V2=fftSize^2
|
||||
// 10*log10(v=V1/V2), where V2=fftSize^2
|
||||
// is calculated using log2 instead, with:
|
||||
// ofs=20.0f * log10f(1.0f / fftSize)
|
||||
// mult=(10.0f / log2(10.0f))
|
||||
// dB = m_mult * log2f(v) + m_ofs
|
||||
// mult = 10.0f / log2(10.0f)
|
||||
// dB = m_mult * log2f(v)
|
||||
// However, while the gcc version of log2f is twice as fast as log10f,
|
||||
// MSVC version is 6x slower.
|
||||
// Also, we don't need full accuracy of log2f for calculating the power for the spectrum,
|
||||
@ -1128,3 +1031,23 @@ float SpectrumVis::log2fapprox(float x) const
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
void SpectrumVis::setMathMemory(const QList<Real> &values)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
int s = std::min((int) values.size(), m_settings.m_fftSize);
|
||||
|
||||
for (int i = 0; i < s; i++) {
|
||||
m_mathMemory[i] = values[i];
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumVis::getMathMovingAverageCopy(QList<Real>& copy)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
std::vector<Real> averages;
|
||||
m_mathMovingAverage.getAverages<Real>(averages);
|
||||
copy = QList<Real>(averages.begin(), averages.end());
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
// written by Christian Daniel //
|
||||
// Copyright (C) 2015-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
|
||||
// Copyright (C) 2026 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
@ -34,6 +35,7 @@
|
||||
#include "util/movingaverage2d.h"
|
||||
#include "util/fixedaverage2d.h"
|
||||
#include "util/max2d.h"
|
||||
#include "util/min2d.h"
|
||||
#include "websockets/wsspectrum.h"
|
||||
|
||||
class GLSpectrumInterface;
|
||||
@ -108,35 +110,6 @@ public:
|
||||
{}
|
||||
};
|
||||
|
||||
class SDRBASE_API MsgFrequencyZooming : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
float getFrequencyZoomFactor() const { return m_frequencyZoomFactor; }
|
||||
float getFrequencyZoomPos() const { return m_frequencyZoomPos; }
|
||||
|
||||
static MsgFrequencyZooming* create(float frequencyZoomFactor, float frequencyZoomPos) {
|
||||
return new MsgFrequencyZooming(frequencyZoomFactor, frequencyZoomPos);
|
||||
}
|
||||
|
||||
private:
|
||||
float m_frequencyZoomFactor;
|
||||
float m_frequencyZoomPos;
|
||||
|
||||
MsgFrequencyZooming(float frequencyZoomFactor, float frequencyZoomPos) :
|
||||
Message(),
|
||||
m_frequencyZoomFactor(frequencyZoomFactor),
|
||||
m_frequencyZoomPos(frequencyZoomPos)
|
||||
{ }
|
||||
};
|
||||
|
||||
enum AvgMode
|
||||
{
|
||||
AvgModeNone,
|
||||
AvgModeMovingAvg,
|
||||
AvgModeFixedAvg,
|
||||
AvgModeMax
|
||||
};
|
||||
|
||||
SpectrumVis(Real scalef);
|
||||
virtual ~SpectrumVis();
|
||||
@ -148,19 +121,19 @@ public:
|
||||
void setScalef(Real scalef);
|
||||
void configureWSSpectrum(const QString& address, uint16_t port);
|
||||
const SpectrumSettings& getSettings() const { return m_settings; }
|
||||
Real getSpecMax() const { return m_specMax / m_powFFTDiv; }
|
||||
void getPowerSpectrumCopy(std::vector<Real>& copy) { copy.assign(m_powerSpectrum.begin(), m_powerSpectrum.end()); }
|
||||
void getPSDCopy(std::vector<Real>& copy) const { copy.assign(m_psd.begin(), m_psd.begin() + m_settings.m_fftSize); }
|
||||
void getZoomedPSDCopy(std::vector<Real>& copy) const;
|
||||
Real getSpecMax() const { return m_specMax; }
|
||||
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
|
||||
void setMathMemory(const QList<Real> &values);
|
||||
void getMathMovingAverageCopy(QList<Real>& copy);
|
||||
|
||||
void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) override;
|
||||
void feed(const ComplexVector::const_iterator& begin, const ComplexVector::const_iterator& end, bool positiveOnly);
|
||||
virtual void feed(const Complex *begin, unsigned int length); //!< direct FFT feed
|
||||
void feed(const Complex *begin, unsigned int length) override; //!< feed output of FFT
|
||||
void feedTriggered(const SampleVector::const_iterator& triggerPoint, const SampleVector::const_iterator& end, bool positiveOnly);
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
virtual void pushMessage(Message *msg);
|
||||
virtual QString getSinkName();
|
||||
void start() override;
|
||||
void stop() override;
|
||||
void pushMessage(Message *msg) override;
|
||||
QString getSinkName() override;
|
||||
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
|
||||
|
||||
void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; }
|
||||
@ -220,7 +193,7 @@ private:
|
||||
|
||||
std::vector<Complex> m_fftBuffer;
|
||||
std::vector<Real> m_powerSpectrum; //!< displayable power spectrum
|
||||
std::vector<Real> m_psd; //!< real PSD
|
||||
std::vector<Real> m_mathMemory;
|
||||
|
||||
SpectrumSettings m_settings;
|
||||
int m_overlapSize;
|
||||
@ -233,22 +206,24 @@ private:
|
||||
WSSpectrum m_wsSpectrum;
|
||||
MovingAverage2D<double> m_movingAverage;
|
||||
FixedAverage2D<double> m_fixedAverage;
|
||||
Max2D<double> m_max;
|
||||
Max2D<Real> m_max;
|
||||
Min2D<Real> m_min;
|
||||
Real m_specMax;
|
||||
MovingAverage2D<double> m_mathMovingAverage;
|
||||
|
||||
uint64_t m_centerFrequency;
|
||||
int m_sampleRate;
|
||||
|
||||
Real m_ofs;
|
||||
Real m_powFFTDiv;
|
||||
static const Real m_mult;
|
||||
Real m_powFFTMul; //!< 1/fftSize^2
|
||||
static const Real m_mult; //!< 10/log2(10)
|
||||
|
||||
MessageQueue m_inputMessageQueue;
|
||||
MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI
|
||||
|
||||
QRecursiveMutex m_mutex;
|
||||
|
||||
void processFFT(bool positiveOnly);
|
||||
void performFFT(bool positiveOnly);
|
||||
void processFFT(const Complex* fftOut, bool reorder, bool positiveOnly, int fftSize);
|
||||
void setRunning(bool running) { m_running = running; }
|
||||
void applySettings(const SpectrumSettings& settings, bool force = false);
|
||||
bool handleMessage(const Message& message);
|
||||
@ -257,6 +232,8 @@ private:
|
||||
void handleWSOpenClose(bool openClose);
|
||||
void handleConfigureWSSpectrum(const QString& address, uint16_t port);
|
||||
float log2fapprox(float x) const;
|
||||
void mathLinear(std::vector<Real> &spectrum);
|
||||
void mathDB(std::vector<Real> &spectrum);
|
||||
|
||||
static void webapiFormatSpectrumSettings(SWGSDRangel::SWGGLSpectrum& response, const SpectrumSettings& settings);
|
||||
static void webapiUpdateSpectrumSettings(
|
||||
|
||||
200
sdrbase/util/circularbuffer.h
Normal file
@ -0,0 +1,200 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2026 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_CIRCULARBUFFER_H_
|
||||
#define INCLUDE_CIRCULARBUFFER_H_
|
||||
|
||||
#include <iterator>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
// Iterates from oldest item to newest
|
||||
template<typename T>
|
||||
class CircularBufferIterator {
|
||||
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = T;
|
||||
using pointer = T *;
|
||||
using const_pointer = const T *;
|
||||
using reference = T &;
|
||||
using const_reference = const T &;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
public:
|
||||
CircularBufferIterator(std::vector<value_type> *b, size_t tail, size_t p) :
|
||||
m_buf(b),
|
||||
m_tail(tail),
|
||||
m_pos(p)
|
||||
{
|
||||
}
|
||||
|
||||
value_type &operator*() { return (*m_buf)[(m_tail + m_pos) % m_buf->size()]; }
|
||||
value_type *operator->() { return &(operator*()); }
|
||||
|
||||
CircularBufferIterator<value_type> &operator++()
|
||||
{
|
||||
m_pos++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend bool operator!= (const CircularBufferIterator<value_type>& a, const CircularBufferIterator<value_type>& b) { return a.m_buf != b.m_buf || a.m_tail != b.m_tail || a.m_pos != b.m_pos; };
|
||||
|
||||
private:
|
||||
std::vector<value_type> *m_buf;
|
||||
size_type m_tail;
|
||||
size_type m_pos;
|
||||
};
|
||||
|
||||
// Circular buffer with fixed capacity, that overwrites oldest item when full.
|
||||
// Items are stored in a contiguous block of memory, so can be accessed by index or iterated over.
|
||||
// Index of 0 is the oldest item, index of size()-1 is the newest item.
|
||||
template <class T>
|
||||
class CircularBuffer
|
||||
{
|
||||
using value_type = T;
|
||||
using pointer = T *;
|
||||
using const_pointer = const T *;
|
||||
using reference = T &;
|
||||
using const_reference = const T &;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
std::vector<value_type> m_buffer;
|
||||
size_type m_head;
|
||||
size_type m_tail;
|
||||
size_type m_count;
|
||||
|
||||
public:
|
||||
|
||||
CircularBuffer(size_type size) :
|
||||
m_buffer(size),
|
||||
m_head(0),
|
||||
m_tail(0),
|
||||
m_count(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool isFull() const
|
||||
{
|
||||
return (m_count != 0) && (m_head == m_tail);
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return m_count == 0;
|
||||
}
|
||||
|
||||
size_type size() const
|
||||
{
|
||||
return m_count;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_head = 0;
|
||||
m_tail = 0;
|
||||
m_count = 0;
|
||||
}
|
||||
|
||||
void append(const_reference item)
|
||||
{
|
||||
size_type next = (m_head + 1) % m_buffer.size();
|
||||
if (isFull()) {
|
||||
m_tail = next; // Overwrite oldest item
|
||||
} else {
|
||||
m_count++;
|
||||
}
|
||||
m_buffer[m_head] = item;
|
||||
m_head = next;
|
||||
}
|
||||
|
||||
void removeFirst()
|
||||
{
|
||||
m_tail = (m_tail + 1) % m_buffer.size();
|
||||
m_count--;
|
||||
}
|
||||
|
||||
reference takeFirst()
|
||||
{
|
||||
reference item = m_buffer[m_tail];
|
||||
removeFirst();
|
||||
return item;
|
||||
}
|
||||
|
||||
// newSize of <= 1 not supported
|
||||
void resize(size_type newSize)
|
||||
{
|
||||
// Reduce count to newSize, by removing oldest items first
|
||||
while (size() > newSize) {
|
||||
removeFirst();
|
||||
}
|
||||
|
||||
// Shuffle data to the beginning of the buffer, so we can free up or allocate space at the end
|
||||
if (m_head < m_tail)
|
||||
{
|
||||
std::rotate(m_buffer.begin(), m_buffer.begin() + m_tail, m_buffer.end());
|
||||
m_head = m_count;
|
||||
m_tail = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::rotate(m_buffer.begin(), m_buffer.begin() + m_tail, m_buffer.begin() + m_head);
|
||||
m_head = m_count;
|
||||
m_tail = 0;
|
||||
}
|
||||
|
||||
m_buffer.resize(newSize);
|
||||
|
||||
if (m_head >= newSize) {
|
||||
m_head -= newSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Index of 0 is the oldest item, index of size()-1 is the newest item
|
||||
reference operator[] (size_type index)
|
||||
{
|
||||
return m_buffer[(m_tail + index) % m_buffer.size()];
|
||||
}
|
||||
|
||||
const_reference operator[] (size_type index) const
|
||||
{
|
||||
return m_buffer[(m_tail + index) % m_buffer.size()];
|
||||
}
|
||||
|
||||
const_reference at(size_type index) const
|
||||
{
|
||||
return m_buffer.at((m_tail + index) % m_buffer.size());
|
||||
}
|
||||
|
||||
using iterator = CircularBufferIterator<T>;
|
||||
|
||||
iterator begin()
|
||||
{
|
||||
return iterator(&m_buffer, m_tail, 0);
|
||||
}
|
||||
|
||||
iterator end()
|
||||
{
|
||||
return iterator(&m_buffer, m_tail, m_count);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif /* INCLUDE_CIRCULARBUFFER_H_ */
|
||||
108
sdrbase/util/min2d.h
Normal file
@ -0,0 +1,108 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2026 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRBASE_UTIL_MIN2D_H_
|
||||
#define SDRBASE_UTIL_MIN2D_H_
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
template<typename T>
|
||||
class Min2D
|
||||
{
|
||||
public:
|
||||
Min2D() : m_min(0), m_maxSize(0), m_width(0), m_size(0), m_index(0) {}
|
||||
|
||||
~Min2D()
|
||||
{
|
||||
if (m_min) {
|
||||
delete[] m_min;
|
||||
}
|
||||
}
|
||||
|
||||
void resize(unsigned int width, unsigned int size)
|
||||
{
|
||||
if (width > m_maxSize)
|
||||
{
|
||||
m_maxSize = width;
|
||||
if (m_min) {
|
||||
delete[] m_min;
|
||||
}
|
||||
m_min = new T[m_maxSize];
|
||||
}
|
||||
|
||||
m_width = width;
|
||||
m_size = size;
|
||||
|
||||
std::fill(m_min, m_min+m_width, 0);
|
||||
m_index = 0;
|
||||
}
|
||||
|
||||
bool storeAndGetMin(T& min, T v, unsigned int index)
|
||||
{
|
||||
if (m_size <= 1)
|
||||
{
|
||||
min = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_index == 0)
|
||||
{
|
||||
m_min[index] = v;
|
||||
return false;
|
||||
}
|
||||
else if (m_index == m_size - 1)
|
||||
{
|
||||
m_min[index] = std::min(m_min[index], v);
|
||||
min = m_min[index];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_min[index] = std::min(m_min[index], v);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool nextMin()
|
||||
{
|
||||
if (m_size <= 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_index == m_size - 1)
|
||||
{
|
||||
m_index = 0;
|
||||
std::fill(m_min, m_min+m_width, 0);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_index++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
T *m_min;
|
||||
unsigned int m_maxSize;
|
||||
unsigned int m_width;
|
||||
unsigned int m_size;
|
||||
unsigned int m_index;
|
||||
};
|
||||
|
||||
#endif /* SDRBASE_UTIL_MIN2D_H_ */
|
||||
@ -24,7 +24,7 @@ template<typename T>
|
||||
class MovingAverage2D
|
||||
{
|
||||
public:
|
||||
MovingAverage2D() : m_data(0), m_sum(0), m_dataSize(0), m_sumSize(0), m_width(0), m_depth(0), m_avgIndex(0) {}
|
||||
MovingAverage2D() : m_data(0), m_sum(0), m_dataSize(0), m_sumSize(0), m_width(0), m_depth(0), m_avgIndex(0), m_count(0) {}
|
||||
|
||||
~MovingAverage2D()
|
||||
{
|
||||
@ -37,6 +37,15 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
std::fill(m_data, m_data+(m_width*m_depth), 0.0);
|
||||
std::fill(m_sum, m_sum+m_width, 0.0);
|
||||
|
||||
m_avgIndex = 0;
|
||||
m_count = 1;
|
||||
}
|
||||
|
||||
void resize(unsigned int width, unsigned int depth)
|
||||
{
|
||||
if (width*depth > m_dataSize)
|
||||
@ -60,10 +69,7 @@ public:
|
||||
m_width = width;
|
||||
m_depth = depth;
|
||||
|
||||
std::fill(m_data, m_data+(m_width*m_depth), 0.0);
|
||||
std::fill(m_sum, m_sum+m_width, 0.0);
|
||||
|
||||
m_avgIndex = 0;
|
||||
clear();
|
||||
}
|
||||
|
||||
T storeAndGetAvg(T v, unsigned int index)
|
||||
@ -77,7 +83,7 @@ public:
|
||||
T first = m_data[m_avgIndex*m_width+index];
|
||||
m_sum[index] += (v - first);
|
||||
m_data[m_avgIndex*m_width+index] = v;
|
||||
return m_sum[index] / m_depth;
|
||||
return m_sum[index] / m_count;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -104,8 +110,31 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void nextAverage() {
|
||||
void nextAverage()
|
||||
{
|
||||
m_avgIndex = m_avgIndex == m_depth-1 ? 0 : m_avgIndex+1;
|
||||
m_count = m_count == m_depth ? m_count : m_count+1;
|
||||
}
|
||||
|
||||
template<typename R>
|
||||
void getAverages(std::vector<R> &values) const
|
||||
{
|
||||
values.resize(m_width);
|
||||
for (unsigned int index = 0; index < m_width; index++) {
|
||||
values[index] = (R) (m_sum[index] / m_count);
|
||||
}
|
||||
}
|
||||
|
||||
T getMin() const
|
||||
{
|
||||
T minSum = *std::min_element(m_sum, m_sum + m_width);
|
||||
return minSum / m_count;
|
||||
}
|
||||
|
||||
T getMax() const
|
||||
{
|
||||
T maxSum = *std::max_element(m_sum, m_sum + m_width);
|
||||
return maxSum / m_count;
|
||||
}
|
||||
|
||||
private:
|
||||
@ -116,6 +145,7 @@ private:
|
||||
unsigned int m_width;
|
||||
unsigned int m_depth;
|
||||
unsigned int m_avgIndex;
|
||||
unsigned int m_count;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
|
||||
QHash<QString, ProfileData> GlobalProfileData::m_profileData;
|
||||
QMutex GlobalProfileData::m_mutex;
|
||||
QElapsedTimer GlobalProfileData::m_startTimer;
|
||||
|
||||
QHash<QString, ProfileData>& GlobalProfileData::getProfileData()
|
||||
{
|
||||
@ -38,5 +39,6 @@ void GlobalProfileData::resetProfileData()
|
||||
{
|
||||
m_mutex.lock();
|
||||
m_profileData.clear();
|
||||
m_startTimer.start();
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
@ -68,19 +68,22 @@ public:
|
||||
ProfileData() :
|
||||
m_numSamples(0),
|
||||
m_last(0),
|
||||
m_total(0)
|
||||
m_total(0),
|
||||
m_max(0)
|
||||
{ }
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_numSamples = 0;
|
||||
m_total = 0;
|
||||
m_max = 0;
|
||||
}
|
||||
|
||||
void add(qint64 sample)
|
||||
{
|
||||
m_last = sample;
|
||||
m_total += sample;
|
||||
m_max = std::max(m_max, sample);
|
||||
m_numSamples++;
|
||||
}
|
||||
|
||||
@ -95,12 +98,14 @@ public:
|
||||
|
||||
qint64 getTotal() const { return m_total; }
|
||||
qint64 getLast() const { return m_last; }
|
||||
qint64 getMax() const { return m_max; }
|
||||
quint64 getNumSamples() const { return m_numSamples; }
|
||||
|
||||
private:
|
||||
quint64 m_numSamples;
|
||||
qint64 m_last;
|
||||
qint64 m_total;
|
||||
qint64 m_max;
|
||||
};
|
||||
|
||||
// Global thread-safe profile data that can be displayed in the GUI
|
||||
@ -112,11 +117,13 @@ public:
|
||||
static QHash<QString, ProfileData>& getProfileData();
|
||||
static void releaseProfileData();
|
||||
static void resetProfileData();
|
||||
static qint64 getMSSinceStart() { return m_startTimer.elapsed(); }
|
||||
|
||||
private:
|
||||
|
||||
static QHash<QString, ProfileData> m_profileData;
|
||||
static QMutex m_mutex;
|
||||
static QElapsedTimer m_startTimer;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2020-2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// Copyright (C) 2020-2026 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
@ -176,15 +176,15 @@ public:
|
||||
if (dms.captureCount() >= 1) {
|
||||
neg = match.capturedTexts()[1] == "-";
|
||||
}
|
||||
if (dms.captureCount() >= 3) {
|
||||
if ((dms.captureCount() >= 3) && (match.capturedTexts().size() >= 3)) {
|
||||
d = match.capturedTexts()[2].toFloat();
|
||||
}
|
||||
float m = 0.0f;
|
||||
if (dms.captureCount() >= 5) {
|
||||
if ((dms.captureCount() >= 5) && (match.capturedTexts().size() >= 5)) {
|
||||
m = match.capturedTexts()[4].toFloat();
|
||||
}
|
||||
float s = 0.0f;
|
||||
if (dms.captureCount() >= 7) {
|
||||
if ((dms.captureCount() >= 7) && (match.capturedTexts().size() >= 7)) {
|
||||
s = match.capturedTexts()[6].toFloat();
|
||||
}
|
||||
degrees = d + m/60.0 + s/(60.0*60.0);
|
||||
@ -382,7 +382,6 @@ public:
|
||||
int raMins = match.capturedTexts()[2].toInt();
|
||||
float raSecs = match.capturedTexts()[3].toFloat();
|
||||
ra = raHours + raMins / 60.0f + raSecs / (60.0f * 60.0f);
|
||||
qDebug() << ra << raHours << raMins << raSecs;
|
||||
int decDegs = match.capturedTexts()[5].toInt();
|
||||
int decMins = match.capturedTexts()[6].toInt();
|
||||
float decSecs = match.capturedTexts()[7].toFloat();
|
||||
@ -478,6 +477,68 @@ public:
|
||||
return T(10.0) * std::log10(tempK/refTempK+T(1.0));
|
||||
}
|
||||
|
||||
// Convert number using engineering units to double precision. E.g: 10.3M 0.3m -5k 3.4
|
||||
static double engUnitsToDouble(const QString& string, double defaultValue = 0.0, bool *ok = nullptr)
|
||||
{
|
||||
static const QChar micro(0xb5);
|
||||
static const QString pattern = QString(R"((-?)([\d]+)?(\.[\d]+)?([n%1umkMG]?))").arg(micro);
|
||||
static const QMap<QChar, double> map = {
|
||||
{'n', 1e-9},
|
||||
{micro, 1e-6},
|
||||
{'u', 1e-6},
|
||||
{'m', 1e-3},
|
||||
{'k', 1e3},
|
||||
{'M', 1e6},
|
||||
{'G', 1e9}
|
||||
};
|
||||
|
||||
QRegularExpression re(QRegularExpression::anchoredPattern(pattern));
|
||||
QRegularExpressionMatch match;
|
||||
|
||||
match = re.match(string);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
double value = (match.capturedTexts()[1] + match.capturedTexts()[2] + match.capturedTexts()[3]).toDouble();
|
||||
QString units = match.capturedTexts()[4];
|
||||
if (!units.isEmpty()) {
|
||||
value = value * map.value(units[0]);
|
||||
}
|
||||
if (ok) {
|
||||
*ok = true;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ok) {
|
||||
*ok = false;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert number using engineering units to positive integer
|
||||
static int engUnitsToPosInt(const QString& string, int defaultValue = 0, bool *ok = nullptr)
|
||||
{
|
||||
bool doubleOk;
|
||||
double doubleValue = Units::engUnitsToDouble(string, 0.0, &doubleOk);
|
||||
|
||||
if (doubleOk && (doubleValue >= 0.0) && (trunc(doubleValue) == doubleValue))
|
||||
{
|
||||
if (ok) {
|
||||
*ok = true;
|
||||
}
|
||||
return (int) doubleValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ok) {
|
||||
*ok = false;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // INCLUDE_UNITS_H
|
||||
|
||||
@ -87,6 +87,7 @@ set(sdrgui_SOURCES
|
||||
gui/scidoublespinbox.cpp
|
||||
gui/sdrangelsplash.cpp
|
||||
gui/spectrumcalibrationpointsdialog.cpp
|
||||
gui/spectrumdisplaysettingsdialog.cpp
|
||||
gui/spectrummarkersdialog.cpp
|
||||
gui/spectrummeasurementsdialog.cpp
|
||||
gui/spectrummeasurements.cpp
|
||||
@ -221,6 +222,7 @@ set(sdrgui_HEADERS
|
||||
gui/scidoublespinbox.h
|
||||
gui/sdrangelsplash.h
|
||||
gui/spectrumcalibrationpointsdialog.h
|
||||
gui/spectrumdisplaysettingsdialog.h
|
||||
gui/spectrummarkersdialog.h
|
||||
gui/spectrummeasurementsdialog.h
|
||||
gui/spectrummeasurements.h
|
||||
@ -301,6 +303,7 @@ set(sdrgui_FORMS
|
||||
gui/spectrummarkersdialog.ui
|
||||
gui/spectrummeasurementsdialog.ui
|
||||
gui/spectrumcalibrationpointsdialog.ui
|
||||
gui/spectrumdisplaysettingsdialog.ui
|
||||
gui/myposdialog.ui
|
||||
gui/transverterdialog.ui
|
||||
gui/loggingdialog.ui
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QLayout>
|
||||
#include <QTableWidget>
|
||||
#include <QScrollBar>
|
||||
|
||||
#include "framelesswindowresizer.h"
|
||||
|
||||
@ -55,6 +56,11 @@ void FramelessWindowResizer::enableChildMouseTracking()
|
||||
table->viewport()->setMouseTracking(true);
|
||||
table->viewport()->installEventFilter(this);
|
||||
}
|
||||
// Likewise for scroll bars, such as in GLSpectrum
|
||||
QList<QScrollBar *> scrollBars = m_widget->findChildren<QScrollBar *>();
|
||||
for (auto scrollBar : scrollBars) {
|
||||
scrollBar->installEventFilter(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool FramelessWindowResizer::mouseOnTopBorder(QPoint pos) const
|
||||
|
||||
@ -77,7 +77,7 @@ GLShaderSpectrogram::GLShaderSpectrogram() :
|
||||
m_lightRotX(0.0),
|
||||
m_lightRotY(0.0),
|
||||
m_lightRotZ(0.0),
|
||||
m_gridElements(1024)
|
||||
m_gridElements(0)
|
||||
{
|
||||
}
|
||||
|
||||
@ -154,7 +154,11 @@ void GLShaderSpectrogram::initializeGL(int majorVersion, int minorVersion)
|
||||
|
||||
void GLShaderSpectrogram::initGrid(int elements)
|
||||
{
|
||||
m_gridElements = std::min(elements, 4096); // Limit to keep memory requirements realistic
|
||||
int gridElements = std::min(elements, 4096); // Limit to keep memory requirements realistic
|
||||
if (gridElements == m_gridElements) {
|
||||
return;
|
||||
}
|
||||
m_gridElements = gridElements;
|
||||
qDebug() << "GLShaderSpectrogram::initGrid: requested: " << elements << " actual: " << m_gridElements;
|
||||
int e1 = m_gridElements+1;
|
||||
|
||||
|
||||
@ -33,11 +33,21 @@
|
||||
GLSpectrum::GLSpectrum(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
m_splitter = new QSplitter(Qt::Vertical);
|
||||
m_spectrumContainer = new QWidget();
|
||||
QHBoxLayout *hLayout = new QHBoxLayout(m_spectrumContainer);
|
||||
m_spectrum = new GLSpectrumView();
|
||||
m_scrollBar = new QScrollBar(Qt::Vertical);
|
||||
m_spectrum->setScrollBar(m_scrollBar);
|
||||
hLayout->addWidget(m_spectrum);
|
||||
hLayout->addWidget(m_scrollBar);
|
||||
hLayout->setContentsMargins(0, 0, 0, 0);
|
||||
hLayout->setSpacing(0);
|
||||
|
||||
m_measurements = new SpectrumMeasurements();
|
||||
m_spectrum->setMeasurements(m_measurements);
|
||||
m_splitter->addWidget(m_spectrum);
|
||||
|
||||
m_splitter = new QSplitter(Qt::Vertical);
|
||||
m_splitter->addWidget(m_spectrumContainer);
|
||||
m_splitter->addWidget(m_measurements);
|
||||
m_position = SpectrumSettings::PositionBelow;
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
@ -63,7 +73,7 @@ void GLSpectrum::setMeasurementsPosition(SpectrumSettings::MeasurementsPosition
|
||||
break;
|
||||
case SpectrumSettings::PositionBelow:
|
||||
m_splitter->setOrientation(Qt::Vertical);
|
||||
m_splitter->insertWidget(0, m_spectrum);
|
||||
m_splitter->insertWidget(0, m_spectrumContainer);
|
||||
break;
|
||||
case SpectrumSettings::PositionLeft:
|
||||
m_splitter->setOrientation(Qt::Horizontal);
|
||||
@ -71,7 +81,7 @@ void GLSpectrum::setMeasurementsPosition(SpectrumSettings::MeasurementsPosition
|
||||
break;
|
||||
case SpectrumSettings::PositionRight:
|
||||
m_splitter->setOrientation(Qt::Horizontal);
|
||||
m_splitter->insertWidget(0, m_spectrum);
|
||||
m_splitter->insertWidget(0, m_spectrumContainer);
|
||||
break;
|
||||
}
|
||||
m_position = position;
|
||||
@ -79,9 +89,9 @@ void GLSpectrum::setMeasurementsPosition(SpectrumSettings::MeasurementsPosition
|
||||
|
||||
void GLSpectrum::setMeasurementParams(SpectrumSettings::Measurement measurement,
|
||||
int centerFrequencyOffset, int bandwidth, int chSpacing, int adjChBandwidth,
|
||||
int harmonics, int peaks, bool highlight, int precision)
|
||||
int harmonics, int peaks, bool highlight, int precision, unsigned memoryMask)
|
||||
{
|
||||
m_spectrum->setMeasurementParams(measurement, centerFrequencyOffset, bandwidth, chSpacing, adjChBandwidth, harmonics, peaks, highlight, precision);
|
||||
m_spectrum->setMeasurementParams(measurement, centerFrequencyOffset, bandwidth, chSpacing, adjChBandwidth, harmonics, peaks, highlight, precision, memoryMask);
|
||||
// Resize splitter so there's just enough space for the measurements table
|
||||
// But don't use more than 50%
|
||||
QList<int> sizes = m_splitter->sizes();
|
||||
@ -148,3 +158,9 @@ void GLSpectrum::setMeasurementParams(SpectrumSettings::Measurement measurement,
|
||||
m_splitter->setSizes(sizes);
|
||||
//resize(size().expandedTo(minimumSizeHint()));
|
||||
}
|
||||
|
||||
void GLSpectrum::resetMeasurements()
|
||||
{
|
||||
m_measurements->reset();
|
||||
m_spectrum->resetMeasurements();
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
class QSplitter;
|
||||
class SpectrumMeasurements;
|
||||
|
||||
// Combines GLSpectrumView with SpectrumMeasurements in a QSplitter
|
||||
// Combines GLSpectrumView with SpectrumMeasurements in a QSplitter and optional QScrollBar
|
||||
class SDRGUI_API GLSpectrum : public QWidget, public GLSpectrumInterface {
|
||||
Q_OBJECT
|
||||
|
||||
@ -50,13 +50,16 @@ public:
|
||||
void setTimingRate(qint32 timingRate) { m_spectrum->setTimingRate(timingRate); }
|
||||
void setFFTOverlap(int overlap) { m_spectrum->setFFTOverlap(overlap); }
|
||||
void setReferenceLevel(Real referenceLevel) { m_spectrum->setReferenceLevel(referenceLevel); }
|
||||
void setReferenceLevelRange(Real minReferenceLevel, Real maxReferenceLevel) { m_spectrum->setReferenceLevelRange(minReferenceLevel, maxReferenceLevel); }
|
||||
void setPowerRange(Real powerRange){ m_spectrum->setPowerRange(powerRange); }
|
||||
void setPowerRangeRange(Real minPowerRange, Real maxPowerRange) { m_spectrum->setPowerRangeRange(minPowerRange, maxPowerRange); }
|
||||
void setDecay(int decay) { m_spectrum->setDecay(decay); }
|
||||
void setDecayDivisor(int decayDivisor) { m_spectrum->setDecayDivisor(decayDivisor); }
|
||||
void setHistoStroke(int stroke) { m_spectrum->setHistoStroke(stroke); }
|
||||
void setDisplayWaterfall(bool display) { m_spectrum->setDisplayWaterfall(display); }
|
||||
void setDisplay3DSpectrogram(bool display) { m_spectrum->setDisplay3DSpectrogram(display); }
|
||||
void set3DSpectrogramStyle(SpectrumSettings::SpectrogramStyle style) { m_spectrum->set3DSpectrogramStyle(style); }
|
||||
void setSpectrumColor(QRgb color) { m_spectrum->setSpectrumColor(color); }
|
||||
void setColorMapName(const QString &colorMapName) { m_spectrum->setColorMapName(colorMapName); }
|
||||
void setSpectrumStyle(SpectrumSettings::SpectrumStyle style) { m_spectrum->setSpectrumStyle(style); }
|
||||
void setSsbSpectrum(bool ssbSpectrum) { m_spectrum->setSsbSpectrum(ssbSpectrum); }
|
||||
@ -73,12 +76,13 @@ public:
|
||||
void setUseCalibration(bool useCalibration) { m_spectrum->setUseCalibration(useCalibration); }
|
||||
void setMeasurementParams(SpectrumSettings::Measurement measurement,
|
||||
int centerFrequencyOffset, int bandwidth, int chSpacing, int adjChBandwidth,
|
||||
int harmonics, int peaks, bool highlight, int precision);
|
||||
int harmonics, int peaks, bool highlight, int precision, unsigned memoryMask);
|
||||
void resetMeasurements();
|
||||
qint32 getSampleRate() const { return m_spectrum->getSampleRate(); }
|
||||
void addChannelMarker(ChannelMarker* channelMarker) { m_spectrum->addChannelMarker(channelMarker); }
|
||||
void removeChannelMarker(ChannelMarker* channelMarker) { m_spectrum->removeChannelMarker(channelMarker); }
|
||||
void setMessageQueueToGUI(MessageQueue* messageQueue) { m_spectrum->setMessageQueueToGUI(messageQueue); }
|
||||
void newSpectrum(const Real* spectrum, int nbBins, int fftSize) { m_spectrum->newSpectrum(spectrum, nbBins, fftSize); }
|
||||
void newSpectrum(const Real* spectrum, int fftSize) { m_spectrum->newSpectrum(spectrum, fftSize); }
|
||||
void clearSpectrumHistogram() { m_spectrum->clearSpectrumHistogram(); }
|
||||
Real getWaterfallShare() const { return m_spectrum->getWaterfallShare(); }
|
||||
void setWaterfallShare(Real waterfallShare) { m_spectrum->setWaterfallShare(waterfallShare); }
|
||||
@ -111,12 +115,22 @@ public:
|
||||
void setIsDeviceSpectrum(bool isDeviceSpectrum) { m_spectrum->setIsDeviceSpectrum(isDeviceSpectrum); }
|
||||
bool isDeviceSpectrum() const { return m_spectrum->isDeviceSpectrum(); }
|
||||
void setFrequencyZooming(float frequencyZoomFactor, float frequencyZoomPos) { m_spectrum->setFrequencyZooming(frequencyZoomFactor, frequencyZoomPos); }
|
||||
void setScrolling(bool enabled, int length) { m_spectrum->setScrolling(enabled, length); }
|
||||
void setWaterfallTimeFormat(SpectrumSettings::WaterfallTimeUnits waterfallTimeUnits, const QString& format) { m_spectrum->setWaterfallTimeFormat(waterfallTimeUnits, format); }
|
||||
void setStatusLine(bool displayRBW, bool displayCursorStats, bool displayPeakStats) { m_spectrum->setStatusLine(displayRBW, displayCursorStats, displayPeakStats); }
|
||||
void readCSV(QTextStream &in, bool append, QString &error) { m_spectrum->readCSV(in, append, error); }
|
||||
void writeCSV(QTextStream &out) { m_spectrum->writeCSV(out); }
|
||||
bool writeImage(const QString& filename) { return m_spectrum->writeImage(filename); }
|
||||
void getDisplayedSpectrumCopy(std::vector<Real>& copy, bool zoomed) const { m_spectrum->getDisplayedSpectrumCopy(copy, zoomed); }
|
||||
void setMemory(int memoryIdx, const SpectrumSettings::SpectrumMemory &memory) { m_spectrum->setMemory(memoryIdx, memory); }
|
||||
|
||||
private:
|
||||
QSplitter *m_splitter;
|
||||
GLSpectrumView *m_spectrum;
|
||||
SpectrumMeasurements *m_measurements;
|
||||
SpectrumSettings::MeasurementsPosition m_position;
|
||||
QWidget *m_spectrumContainer;
|
||||
QScrollBar *m_scrollBar;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
#include <QLineEdit>
|
||||
#include <QToolTip>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QScreen>
|
||||
|
||||
@ -42,11 +43,14 @@
|
||||
#include "gui/spectrumcalibrationpointsdialog.h"
|
||||
#include "gui/spectrummeasurementsdialog.h"
|
||||
#include "gui/spectrummeasurements.h"
|
||||
#include "gui/spectrumdisplaysettingsdialog.h"
|
||||
#include "gui/flowlayout.h"
|
||||
#include "gui/dialogpositioner.h"
|
||||
#include "gui/dialpopup.h"
|
||||
#include "util/colormap.h"
|
||||
#include "util/db.h"
|
||||
#include "util/csv.h"
|
||||
#include "util/units.h"
|
||||
#include "ui_glspectrumgui.h"
|
||||
|
||||
const int GLSpectrumGUI::m_fpsMs[] = {500, 200, 100, 50, 20, 10, 5};
|
||||
@ -58,11 +62,13 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) :
|
||||
m_glSpectrum(nullptr),
|
||||
m_doApplySettings(true),
|
||||
m_calibrationShiftdB(0.0),
|
||||
m_markersDialog(nullptr)
|
||||
m_markersDialog(nullptr),
|
||||
m_contextMenu(nullptr)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
// Use the custom flow layout for the 3 main horizontal layouts (lines)
|
||||
ui->verticalLayout->removeItem(ui->Line7Layout);
|
||||
ui->verticalLayout->removeItem(ui->Line6Layout);
|
||||
ui->verticalLayout->removeItem(ui->Line5Layout);
|
||||
ui->verticalLayout->removeItem(ui->Line4Layout);
|
||||
@ -76,6 +82,7 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) :
|
||||
flowLayout->addItem(ui->Line4Layout);
|
||||
flowLayout->addItem(ui->Line5Layout);
|
||||
flowLayout->addItem(ui->Line6Layout);
|
||||
flowLayout->addItem(ui->Line7Layout);
|
||||
ui->verticalLayout->addItem(flowLayout);
|
||||
|
||||
on_linscale_toggled(false);
|
||||
@ -101,6 +108,14 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) :
|
||||
CRightClickEnabler *calibrationPointsRightClickEnabler = new CRightClickEnabler(ui->calibration);
|
||||
connect(calibrationPointsRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(openCalibrationPointsDialog(const QPoint &)));
|
||||
|
||||
m_contextMenu = new QMenu(this);
|
||||
ui->mem1->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->mem1, &QWidget::customContextMenuRequested, this, &GLSpectrumGUI::mem1ContextMenu);
|
||||
ui->mem2->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->mem2, &QWidget::customContextMenuRequested, this, &GLSpectrumGUI::mem2ContextMenu);
|
||||
|
||||
connect(ui->mathAvgCount->lineEdit(), &QLineEdit::editingFinished, this, &GLSpectrumGUI::on_mathAvgCount_editingFinished);
|
||||
|
||||
DialPopup::addPopupsToChildDials(this);
|
||||
|
||||
displaySettings();
|
||||
@ -178,7 +193,8 @@ void GLSpectrumGUI::updateSettings()
|
||||
void GLSpectrumGUI::displaySettings()
|
||||
{
|
||||
blockApplySettings(true);
|
||||
ui->showAllControls->setChecked(m_settings.m_showAllControls);
|
||||
ui->showControls->setCurrentIndex((int) m_settings.m_showControls);
|
||||
setPowerAndRefRange();
|
||||
ui->refLevel->setValue(m_settings.m_refLevel + m_calibrationShiftdB);
|
||||
ui->levelRange->setValue(m_settings.m_powerRange);
|
||||
ui->decay->setSliderPosition(m_settings.m_decay);
|
||||
@ -187,7 +203,7 @@ void GLSpectrumGUI::displaySettings()
|
||||
ui->waterfall->setChecked(m_settings.m_displayWaterfall);
|
||||
ui->spectrogram->setChecked(m_settings.m_display3DSpectrogram);
|
||||
ui->spectrogramStyle->setCurrentIndex((int) m_settings.m_3DSpectrogramStyle);
|
||||
ui->spectrogramStyle->setVisible(m_settings.m_display3DSpectrogram && m_settings.m_showAllControls);
|
||||
ui->spectrogramStyle->setVisible(m_settings.m_display3DSpectrogram && (m_settings.m_showControls == SpectrumSettings::ShowAll));
|
||||
ui->colorMap->setCurrentText(m_settings.m_colorMap);
|
||||
ui->currentLine->blockSignals(true);
|
||||
ui->currentFill->blockSignals(true);
|
||||
@ -215,6 +231,8 @@ void GLSpectrumGUI::displaySettings()
|
||||
ui->averaging->blockSignals(true);
|
||||
ui->averagingMode->blockSignals(true);
|
||||
ui->linscale->blockSignals(true);
|
||||
ui->mathMode->blockSignals(true);
|
||||
ui->mathAvgCount->blockSignals(true);
|
||||
|
||||
ui->fftWindow->setCurrentIndex(m_settings.m_fftWindow);
|
||||
|
||||
@ -245,15 +263,28 @@ void GLSpectrumGUI::displaySettings()
|
||||
ui->averagingMode->setCurrentIndex((int) m_settings.m_averagingMode);
|
||||
ui->linscale->setChecked(m_settings.m_linear);
|
||||
setAveragingToolitp();
|
||||
ui->mathMode->setCurrentIndex((int) m_settings.m_mathMode);
|
||||
int idx = findEquivalentText(ui->mathAvgCount, m_settings.m_mathAvgCount);
|
||||
if (idx >= 0) {
|
||||
ui->mathAvgCount->setCurrentIndex(idx);
|
||||
} else {
|
||||
ui->mathAvgCount->setCurrentText(QString::number(m_settings.m_mathAvgCount));
|
||||
}
|
||||
ui->mathAvgCount->setVisible(m_settings.mathAverageUsed());
|
||||
ui->calibration->setChecked(m_settings.m_useCalibration);
|
||||
ui->resetMeasurements->setVisible(m_settings.m_measurement >= SpectrumSettings::MeasurementChannelPower);
|
||||
displayGotoMarkers();
|
||||
displayControls();
|
||||
|
||||
ui->mem1->setChecked(m_settings.m_spectrumMemory[0].m_display);
|
||||
ui->mem2->setChecked(m_settings.m_spectrumMemory[1].m_display);
|
||||
|
||||
ui->fftWindow->blockSignals(false);
|
||||
ui->averaging->blockSignals(false);
|
||||
ui->averagingMode->blockSignals(false);
|
||||
ui->linscale->blockSignals(false);
|
||||
ui->mathMode->blockSignals(false);
|
||||
ui->mathAvgCount->blockSignals(false);
|
||||
blockApplySettings(false);
|
||||
|
||||
updateMeasurements();
|
||||
@ -261,33 +292,40 @@ void GLSpectrumGUI::displaySettings()
|
||||
|
||||
void GLSpectrumGUI::displayControls()
|
||||
{
|
||||
ui->grid->setVisible(m_settings.m_showAllControls);
|
||||
ui->gridIntensity->setVisible(m_settings.m_showAllControls);
|
||||
ui->truncateScale->setVisible(m_settings.m_showAllControls);
|
||||
ui->clearSpectrum->setVisible(m_settings.m_showAllControls);
|
||||
ui->histogram->setVisible(m_settings.m_showAllControls);
|
||||
ui->maxHold->setVisible(m_settings.m_showAllControls);
|
||||
ui->decay->setVisible(m_settings.m_showAllControls);
|
||||
ui->decayDivisor->setVisible(m_settings.m_showAllControls);
|
||||
ui->stroke->setVisible(m_settings.m_showAllControls);
|
||||
ui->currentLine->setVisible(m_settings.m_showAllControls);
|
||||
ui->currentFill->setVisible(m_settings.m_showAllControls);
|
||||
ui->currentGradient->setVisible(m_settings.m_showAllControls);
|
||||
ui->traceIntensity->setVisible(m_settings.m_showAllControls);
|
||||
ui->colorMap->setVisible(m_settings.m_showAllControls);
|
||||
ui->invertWaterfall->setVisible(m_settings.m_showAllControls);
|
||||
ui->waterfall->setVisible(m_settings.m_showAllControls);
|
||||
ui->spectrogram->setVisible(m_settings.m_showAllControls);
|
||||
ui->spectrogramStyle->setVisible(m_settings.m_showAllControls);
|
||||
ui->fftWindow->setVisible(m_settings.m_showAllControls);
|
||||
ui->fftSize->setVisible(m_settings.m_showAllControls);
|
||||
ui->fftOverlap->setVisible(m_settings.m_showAllControls);
|
||||
ui->fps->setVisible(m_settings.m_showAllControls);
|
||||
ui->linscale->setVisible(m_settings.m_showAllControls);
|
||||
ui->save->setVisible(m_settings.m_showAllControls);
|
||||
ui->wsSpectrum->setVisible(m_settings.m_showAllControls);
|
||||
ui->calibration->setVisible(m_settings.m_showAllControls);
|
||||
ui->markers->setVisible(m_settings.m_showAllControls);
|
||||
ui->grid->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->gridIntensity->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->truncateScale->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll);
|
||||
ui->clearSpectrum->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll);
|
||||
ui->histogram->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->maxHold->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->decay->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->decayDivisor->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->stroke->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->currentLine->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->currentFill->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->currentGradient->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->traceIntensity->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->colorMap->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll);
|
||||
ui->invertWaterfall->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->waterfall->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->spectrogram->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->spectrogramStyle->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll && m_settings.m_display3DSpectrogram);
|
||||
ui->fftWindow->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->fftSize->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->fftOverlap->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll);
|
||||
ui->mathMode->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll);
|
||||
ui->mathAvgCount->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll && m_settings.mathAverageUsed());
|
||||
ui->fps->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll);
|
||||
ui->linscale->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->mem1->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll);
|
||||
ui->mem2->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll);
|
||||
ui->load->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll);
|
||||
ui->save->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll);
|
||||
ui->saveImage->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll);
|
||||
ui->wsSpectrum->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowAll);
|
||||
ui->calibration->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->markers->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
ui->measure->setVisible(m_settings.m_showControls >= SpectrumSettings::ShowStandard);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::displayGotoMarkers()
|
||||
@ -348,6 +386,7 @@ void GLSpectrumGUI::applySpectrumSettings()
|
||||
m_glSpectrum->setDisplayWaterfall(m_settings.m_displayWaterfall);
|
||||
m_glSpectrum->setDisplay3DSpectrogram(m_settings.m_display3DSpectrogram);
|
||||
m_glSpectrum->set3DSpectrogramStyle(m_settings.m_3DSpectrogramStyle);
|
||||
m_glSpectrum->setSpectrumColor(m_settings.m_spectrumColor);
|
||||
m_glSpectrum->setColorMapName(m_settings.m_colorMap);
|
||||
m_glSpectrum->setSpectrumStyle(m_settings.m_spectrumStyle);
|
||||
m_glSpectrum->setInvertedWaterfall(m_settings.m_invertedWaterfall);
|
||||
@ -374,7 +413,9 @@ void GLSpectrumGUI::applySpectrumSettings()
|
||||
Real powerRange = m_settings.m_linear ? pow(10.0, m_settings.m_refLevel/10.0) : m_settings.m_powerRange;
|
||||
qDebug("GLSpectrumGUI::applySpectrumSettings: refLevel: %e powerRange: %e", refLevel, powerRange);
|
||||
m_glSpectrum->setReferenceLevel(refLevel);
|
||||
m_glSpectrum->setReferenceLevelRange(ui->refLevel->minimum(), ui->refLevel->maximum());
|
||||
m_glSpectrum->setPowerRange(powerRange);
|
||||
m_glSpectrum->setPowerRangeRange(ui->levelRange->minimum(), ui->levelRange->maximum());
|
||||
m_glSpectrum->setFPSPeriodMs(m_settings.m_fpsPeriodMs);
|
||||
m_glSpectrum->setFreqScaleTruncationMode(m_settings.m_truncateFreqScale);
|
||||
m_glSpectrum->setLinear(m_settings.m_linear);
|
||||
@ -386,6 +427,41 @@ void GLSpectrumGUI::applySpectrumSettings()
|
||||
m_glSpectrum->setMarkersDisplay(m_settings.m_markersDisplay);
|
||||
m_glSpectrum->setCalibrationPoints(m_settings.m_calibrationPoints);
|
||||
m_glSpectrum->setCalibrationInterpMode(m_settings.m_calibrationInterpMode);
|
||||
|
||||
m_glSpectrum->setScrolling(m_settings.m_scrollBar, m_settings.m_scrollLength);
|
||||
m_glSpectrum->setWaterfallTimeFormat(m_settings.m_waterfallTimeUnits, m_settings.m_waterfallTimeFormat);
|
||||
m_glSpectrum->setStatusLine(m_settings.m_displayRBW, m_settings.m_displayCursorStats, m_settings.m_displayPeakStats);
|
||||
|
||||
for (int i = 0; i < m_settings.m_spectrumMemory.size(); i++) {
|
||||
m_glSpectrum->setMemory(i, m_settings.m_spectrumMemory[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::setPowerAndRefRange()
|
||||
{
|
||||
const Real maxSignal = 120.0f; // 20-bits
|
||||
|
||||
if ( (m_settings.m_mathMode == SpectrumSettings::MathModeXMinusAvgDB)
|
||||
|| (m_settings.m_mathMode == SpectrumSettings::MathModeXMinusM1DB)
|
||||
|| (m_settings.m_mathMode == SpectrumSettings::MathModeXMinusM2DB)
|
||||
)
|
||||
{
|
||||
ui->levelRange->setRange(1.0f, 2.0f * maxSignal);
|
||||
ui->refLevel->setRange(-maxSignal, maxSignal);
|
||||
}
|
||||
else if ( (m_settings.m_mathMode == SpectrumSettings::MathModeAbsXMinusAvgDB)
|
||||
|| (m_settings.m_mathMode == SpectrumSettings::MathModeAbsXMinusM1DB)
|
||||
|| (m_settings.m_mathMode == SpectrumSettings::MathModeAbsXMinusM2DB)
|
||||
)
|
||||
{
|
||||
ui->levelRange->setRange(1.0f, maxSignal);
|
||||
ui->refLevel->setRange(0.0f, maxSignal);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->levelRange->setRange(1.0f, maxSignal);
|
||||
ui->refLevel->setRange(-maxSignal, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_fftWindow_currentIndexChanged(int index)
|
||||
@ -419,12 +495,14 @@ void GLSpectrumGUI::on_autoscale_clicked(bool checked)
|
||||
{
|
||||
(void) checked;
|
||||
|
||||
if (!m_spectrumVis) {
|
||||
// Autoscale according to currently displayed spectrum (which may be old, if scrolling enabled)
|
||||
|
||||
if (!m_glSpectrum) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Real> psd;
|
||||
m_spectrumVis->getZoomedPSDCopy(psd);
|
||||
m_glSpectrum->getDisplayedSpectrumCopy(psd, true);
|
||||
int avgRange = m_settings.m_fftSize / 32;
|
||||
|
||||
if (psd.size() < (unsigned int) avgRange) {
|
||||
@ -440,8 +518,18 @@ void GLSpectrumGUI::on_autoscale_clicked(bool checked)
|
||||
}
|
||||
|
||||
float minAvg = minSum / avgRange;
|
||||
int minLvl = CalcDb::dbPower(minAvg*2);
|
||||
int maxLvl = CalcDb::dbPower(max*10);
|
||||
int minLvl;
|
||||
int maxLvl;
|
||||
if (m_settings.m_linear)
|
||||
{
|
||||
minLvl = CalcDb::dbPower(minAvg*2);
|
||||
maxLvl = CalcDb::dbPower(max*10);
|
||||
}
|
||||
else
|
||||
{
|
||||
minLvl = minAvg + 3;
|
||||
maxLvl = max + 10;
|
||||
}
|
||||
|
||||
m_settings.m_refLevel = maxLvl;
|
||||
m_settings.m_powerRange = maxLvl - minLvl;
|
||||
@ -458,9 +546,7 @@ void GLSpectrumGUI::on_averagingMode_currentIndexChanged(int index)
|
||||
qDebug("GLSpectrumGUI::on_averagingMode_currentIndexChanged: %d", index);
|
||||
m_settings.m_averagingMode = index < 0 ?
|
||||
SpectrumSettings::AvgModeNone :
|
||||
index > 3 ?
|
||||
SpectrumSettings::AvgModeMax :
|
||||
(SpectrumSettings::AveragingMode) index;
|
||||
(SpectrumSettings::AveragingMode) index;
|
||||
|
||||
setAveragingCombo();
|
||||
applySettings();
|
||||
@ -475,6 +561,30 @@ void GLSpectrumGUI::on_averaging_currentIndexChanged(int index)
|
||||
setAveragingToolitp();
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_mathMode_currentIndexChanged(int index)
|
||||
{
|
||||
m_settings.m_mathMode = (SpectrumSettings::MathMode) index;
|
||||
ui->mathAvgCount->setVisible((m_settings.m_showControls >= SpectrumSettings::ShowAll) && m_settings.mathAverageUsed());
|
||||
setMathMemory();
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_mathAvgCount_currentIndexChanged(int index)
|
||||
{
|
||||
(void) index;
|
||||
|
||||
int value = Units::engUnitsToPosInt(ui->mathAvgCount->currentText(), 2);
|
||||
m_settings.m_mathAvgCount = std::min(std::max(2, value), 1000000);
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_mathAvgCount_editingFinished()
|
||||
{
|
||||
int value = Units::engUnitsToPosInt(ui->mathAvgCount->currentText(), 2);
|
||||
m_settings.m_mathAvgCount = std::min(std::max(2, value), 1000000);
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_linscale_toggled(bool checked)
|
||||
{
|
||||
qDebug("GLSpectrumGUI::on_averaging_currentIndexChanged: %s", checked ? "lin" : "log");
|
||||
@ -543,43 +653,71 @@ void GLSpectrumGUI::closeMarkersDialog()
|
||||
m_markersDialog = nullptr;
|
||||
}
|
||||
|
||||
// Load spectrum data to a CSV file
|
||||
void GLSpectrumGUI::on_load_clicked(bool checked)
|
||||
{
|
||||
(void) checked;
|
||||
|
||||
QString filename = QFileDialog::getSaveFileName(this, "Select file to read data from", "", "*.csv");
|
||||
if (!filename.isEmpty())
|
||||
{
|
||||
QFile file(filename);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
QTextStream in(&file);
|
||||
QString error;
|
||||
|
||||
// Read in all data
|
||||
m_glSpectrum->readCSV(in, false, error);
|
||||
|
||||
if (!error.isEmpty()) {
|
||||
QMessageBox::critical(this, "Spectrum", QString("Failed to read file %1: %2").arg(filename).arg(error));
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, "Spectrum", QString("Failed to open file %1").arg(filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save spectrum data to a CSV file
|
||||
void GLSpectrumGUI::on_save_clicked(bool checked)
|
||||
{
|
||||
(void) checked;
|
||||
|
||||
// Get filename to write
|
||||
QFileDialog fileDialog(nullptr, "Select file to save data to", "", "*.csv");
|
||||
fileDialog.setDefaultSuffix("csv");
|
||||
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
if (fileDialog.exec())
|
||||
QString filename = QFileDialog::getSaveFileName(this, "Select file to save data to", "", "*.csv");
|
||||
if (!filename.isEmpty())
|
||||
{
|
||||
QStringList fileNames = fileDialog.selectedFiles();
|
||||
if (fileNames.size() > 0)
|
||||
QFile file(filename);
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
{
|
||||
// Get spectrum data (This vector can be larger than fftSize)
|
||||
std::vector<Real> spectrum;
|
||||
m_spectrumVis->getPowerSpectrumCopy(spectrum);
|
||||
QTextStream out(&file);
|
||||
|
||||
// Write to text file
|
||||
QFile file(fileNames[0]);
|
||||
if (file.open(QIODevice::WriteOnly))
|
||||
{
|
||||
QTextStream out(&file);
|
||||
float frequency = m_glSpectrum->getCenterFrequency() - (m_glSpectrum->getSampleRate() / 2.0f);
|
||||
float rbw = m_glSpectrum->getSampleRate() / (float)m_settings.m_fftSize;
|
||||
out << "\"Frequency\",\"Power\"\n";
|
||||
for (int i = 0; i < m_settings.m_fftSize; i++)
|
||||
{
|
||||
out << frequency << "," << spectrum[i] << "\n";
|
||||
frequency += rbw;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, "Spectrum", QString("Failed to open file %1").arg(fileNames[0]));
|
||||
}
|
||||
// Write out all data in scroll buffer
|
||||
m_glSpectrum->writeCSV(out);
|
||||
|
||||
file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, "Spectrum", QString("Failed to open file %1").arg(filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save spectrum/waterfall image to file
|
||||
void GLSpectrumGUI::on_saveImage_clicked(bool checked)
|
||||
{
|
||||
(void) checked;
|
||||
|
||||
QString filename = QFileDialog::getSaveFileName(this, "Select file to save image to", "", "*.jpg *.png");
|
||||
if (!filename.isEmpty())
|
||||
{
|
||||
if (!m_glSpectrum->writeImage(filename)) {
|
||||
QMessageBox::critical(this, "Spectrum", QString("Failed to save image to file %1").arg(filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -658,7 +796,7 @@ void GLSpectrumGUI::on_spectrogram_toggled(bool checked)
|
||||
ui->waterfall->setChecked(false);
|
||||
blockApplySettings(false);
|
||||
}
|
||||
ui->spectrogramStyle->setVisible(m_settings.m_display3DSpectrogram && m_settings.m_showAllControls);
|
||||
ui->spectrogramStyle->setVisible(m_settings.m_display3DSpectrogram && (m_settings.m_showControls >= SpectrumSettings::ShowAll));
|
||||
applySettings();
|
||||
}
|
||||
|
||||
@ -719,9 +857,9 @@ void GLSpectrumGUI::on_invertWaterfall_toggled(bool checked)
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_showAllControls_toggled(bool checked)
|
||||
void GLSpectrumGUI::on_showControls_currentIndexChanged(int index)
|
||||
{
|
||||
m_settings.m_showAllControls = checked;
|
||||
m_settings.m_showControls = (SpectrumSettings::ShowControls) index;
|
||||
displayControls();
|
||||
applySettings();
|
||||
}
|
||||
@ -1000,9 +1138,9 @@ bool GLSpectrumGUI::handleMessage(const Message& message)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (SpectrumVis::MsgFrequencyZooming::match(message))
|
||||
else if (GLSpectrumView::MsgFrequencyZooming::match(message))
|
||||
{
|
||||
const SpectrumVis::MsgFrequencyZooming& report = (const SpectrumVis::MsgFrequencyZooming&) message;
|
||||
const GLSpectrumView::MsgFrequencyZooming& report = (const GLSpectrumView::MsgFrequencyZooming&) message;
|
||||
m_settings.m_frequencyZoomFactor = report.getFrequencyZoomFactor();
|
||||
m_settings.m_frequencyZoomPos = report.getFrequencyZoomPos();
|
||||
return true;
|
||||
@ -1133,7 +1271,7 @@ void GLSpectrumGUI::on_resetMeasurements_clicked(bool checked)
|
||||
(void) checked;
|
||||
|
||||
if (m_glSpectrum) {
|
||||
m_glSpectrum->getMeasurements()->reset();
|
||||
m_glSpectrum->resetMeasurements();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1153,7 +1291,385 @@ void GLSpectrumGUI::updateMeasurements()
|
||||
m_settings.m_measurementHarmonics,
|
||||
m_settings.m_measurementPeaks,
|
||||
m_settings.m_measurementHighlight,
|
||||
m_settings.m_measurementPrecision
|
||||
m_settings.m_measurementPrecision,
|
||||
m_settings.m_measurementMemMasks
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_showSettingsDialog_clicked(bool checked)
|
||||
{
|
||||
(void) checked;
|
||||
|
||||
SpectrumDisplaySettingsDialog dialog(m_glSpectrum, &m_settings, m_glSpectrum->getSampleRate(), this);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
applySpectrumSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_mem1_clicked(bool checked)
|
||||
{
|
||||
m_settings.m_spectrumMemory[0].m_display = checked;
|
||||
if (m_glSpectrum) {
|
||||
m_glSpectrum->setMemory(0, m_settings.m_spectrumMemory[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_mem2_clicked(bool checked)
|
||||
{
|
||||
m_settings.m_spectrumMemory[1].m_display = checked;
|
||||
if (m_glSpectrum) {
|
||||
m_glSpectrum->setMemory(1, m_settings.m_spectrumMemory[1]);
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::mem1ContextMenu(const QPoint &p)
|
||||
{
|
||||
memContextMenu(0, ui->mem1->mapToGlobal(p));
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::mem2ContextMenu(const QPoint &p)
|
||||
{
|
||||
memContextMenu(1, ui->mem2->mapToGlobal(p));
|
||||
}
|
||||
|
||||
// Show memory context menu
|
||||
void GLSpectrumGUI::memContextMenu(int memoryIdx, const QPoint &p)
|
||||
{
|
||||
if (m_glSpectrum && m_spectrumVis)
|
||||
{
|
||||
m_contextMenu->clear();
|
||||
|
||||
bool memNotEmpty = m_settings.m_spectrumMemory[memoryIdx].m_spectrum.size() > 0;
|
||||
|
||||
// Clear memory
|
||||
QAction* clearAction = new QAction(QString("Clear M%1").arg(memoryIdx+1), m_contextMenu);
|
||||
connect(clearAction, &QAction::triggered, this, [this, memoryIdx]()->void {
|
||||
m_settings.m_spectrumMemory[memoryIdx].m_spectrum.clear();
|
||||
updateMem(memoryIdx);
|
||||
});
|
||||
m_contextMenu->addAction(clearAction);
|
||||
clearAction->setEnabled(memNotEmpty);
|
||||
|
||||
// Copy currently displayed spectrum to memory
|
||||
QAction* copyCurrentSpectrumAction = new QAction(QString("Set M%1 to current spectrum").arg(memoryIdx+1), m_contextMenu);
|
||||
connect(copyCurrentSpectrumAction, &QAction::triggered, this, [this, memoryIdx]()->void {
|
||||
std::vector<Real> copy;
|
||||
m_glSpectrum->getDisplayedSpectrumCopy(copy, false);
|
||||
m_settings.m_spectrumMemory[memoryIdx].m_spectrum = QList<Real>(copy.begin(), copy.end());
|
||||
updateMem(memoryIdx);
|
||||
});
|
||||
m_contextMenu->addAction(copyCurrentSpectrumAction);
|
||||
|
||||
// Copy math moving average to memory
|
||||
QAction* copyMovingAverageAction = new QAction(QString("Set M%1 to moving average").arg(memoryIdx+1), m_contextMenu);
|
||||
connect(copyMovingAverageAction, &QAction::triggered, this, [this, memoryIdx]()->void {
|
||||
QList<Real> copy;
|
||||
bool mathDB = m_settings.mathUsesDB();
|
||||
m_spectrumVis->getMathMovingAverageCopy(copy);
|
||||
convertSpectrum(copy, mathDB, !m_settings.m_linear);
|
||||
m_settings.m_spectrumMemory[memoryIdx].m_spectrum = QList<Real>(copy.begin(), copy.end());
|
||||
updateMem(memoryIdx);
|
||||
});
|
||||
m_contextMenu->addAction(copyMovingAverageAction);
|
||||
copyMovingAverageAction->setEnabled(m_settings.mathAverageUsed());
|
||||
|
||||
// Add offset
|
||||
QAction* applyOffsetAction = new QAction(QString("Add offset to M%1").arg(memoryIdx+1), m_contextMenu);
|
||||
connect(applyOffsetAction, &QAction::triggered, this, [this, memoryIdx]()->void {
|
||||
applyOffsetToMem(memoryIdx);
|
||||
});
|
||||
m_contextMenu->addAction(applyOffsetAction);
|
||||
applyOffsetAction->setEnabled(memNotEmpty);
|
||||
|
||||
// Smooth
|
||||
QAction* smoothAction = new QAction(QString("Smooth M%1").arg(memoryIdx+1), m_contextMenu);
|
||||
connect(smoothAction, &QAction::triggered, this, [this, memoryIdx]()->void {
|
||||
smoothMem(memoryIdx);
|
||||
});
|
||||
m_contextMenu->addAction(smoothAction);
|
||||
smoothAction->setEnabled(memNotEmpty);
|
||||
|
||||
bool memAAndBNotEmpty = (m_settings.m_spectrumMemory[0].m_spectrum.size() > 0)
|
||||
&& (m_settings.m_spectrumMemory[1].m_spectrum.size() > 0);
|
||||
|
||||
// Add mems
|
||||
QAction* addMemAction = new QAction(QString("Set M%1 to M1+M2").arg(memoryIdx+1), m_contextMenu);
|
||||
connect(addMemAction, &QAction::triggered, this, [this, memoryIdx]()->void {
|
||||
addMem(memoryIdx);
|
||||
});
|
||||
m_contextMenu->addAction(addMemAction);
|
||||
addMemAction->setEnabled(memAAndBNotEmpty);
|
||||
|
||||
// Diff mems
|
||||
QAction* diffMemAction = new QAction(QString("Set M%1 to M1-M2").arg(memoryIdx+1), m_contextMenu);
|
||||
connect(diffMemAction, &QAction::triggered, this, [this, memoryIdx]()->void {
|
||||
diffMem(memoryIdx);
|
||||
});
|
||||
m_contextMenu->addAction(diffMemAction);
|
||||
diffMemAction->setEnabled(memAAndBNotEmpty);
|
||||
|
||||
m_contextMenu->addSeparator();
|
||||
|
||||
// Load memory from .csv
|
||||
QAction* loadSpectrumAction = new QAction(QString("Load M%1 from .csv").arg(memoryIdx+1), m_contextMenu);
|
||||
connect(loadSpectrumAction, &QAction::triggered, this, [this, memoryIdx]()->void {
|
||||
loadMem(memoryIdx);
|
||||
});
|
||||
m_contextMenu->addAction(loadSpectrumAction);
|
||||
|
||||
// Save memory to .csv
|
||||
QAction* saveSpectrumAction = new QAction(QString("Save M%1 to .csv").arg(memoryIdx+1), m_contextMenu);
|
||||
connect(saveSpectrumAction, &QAction::triggered, this, [this, memoryIdx]()->void {
|
||||
saveMem(memoryIdx);
|
||||
});
|
||||
m_contextMenu->addAction(saveSpectrumAction);
|
||||
saveSpectrumAction->setEnabled(memNotEmpty);
|
||||
|
||||
m_contextMenu->addSeparator();
|
||||
|
||||
QAction* infoAction = new QAction(QString("M%1 has %2 elements").arg(memoryIdx+1).arg(m_settings.m_spectrumMemory[memoryIdx].m_spectrum.size()), m_contextMenu);
|
||||
m_contextMenu->addAction(infoAction);
|
||||
infoAction->setEnabled(false);
|
||||
|
||||
m_contextMenu->popup(p);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert spectrum from dB to linear or vice-versa
|
||||
void GLSpectrumGUI::convertSpectrum(QList<Real> &spectrum, bool fromDB, bool toDB) const
|
||||
{
|
||||
if (toDB && !fromDB)
|
||||
{
|
||||
for (int i = 0; i < spectrum.size(); i++) {
|
||||
spectrum[i] = CalcDb::dbPower(spectrum[i]);
|
||||
}
|
||||
}
|
||||
else if (!toDB && fromDB)
|
||||
{
|
||||
for (int i = 0; i < spectrum.size(); i++) {
|
||||
spectrum[i] = CalcDb::powerFromdB(spectrum[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set math memory in SpectrumVis to selected memory
|
||||
void GLSpectrumGUI::setMathMemory()
|
||||
{
|
||||
if (m_spectrumVis)
|
||||
{
|
||||
QList<Real> spectrum;
|
||||
|
||||
if ( (m_settings.m_mathMode == SpectrumSettings::MathModeXMinusM1)
|
||||
|| (m_settings.m_mathMode == SpectrumSettings::MathModeXMinusM1DB)
|
||||
|| (m_settings.m_mathMode == SpectrumSettings::MathModeAbsXMinusM1DB)
|
||||
)
|
||||
{
|
||||
spectrum = m_settings.m_spectrumMemory[0].m_spectrum;
|
||||
} else if ( (m_settings.m_mathMode == SpectrumSettings::MathModeXMinusM2)
|
||||
|| (m_settings.m_mathMode == SpectrumSettings::MathModeXMinusM2DB)
|
||||
|| (m_settings.m_mathMode == SpectrumSettings::MathModeAbsXMinusM2DB)
|
||||
)
|
||||
{
|
||||
spectrum = m_settings.m_spectrumMemory[1].m_spectrum;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
convertSpectrum(spectrum, !m_settings.m_linear, m_settings.mathUsesDB());
|
||||
m_spectrumVis->setMathMemory(spectrum);
|
||||
}
|
||||
}
|
||||
|
||||
// Load from .csv file in to specified memory
|
||||
void GLSpectrumGUI::loadMem(int memoryIdx)
|
||||
{
|
||||
QFileDialog fileDialog(nullptr, "Select file to load data from", "", "*.csv");
|
||||
fileDialog.setDefaultSuffix("csv");
|
||||
fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
if (fileDialog.exec())
|
||||
{
|
||||
QStringList fileNames = fileDialog.selectedFiles();
|
||||
if (fileNames.size() > 0)
|
||||
{
|
||||
// Read from csv file
|
||||
QFile file(fileNames[0]);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
QTextStream in(&file);
|
||||
QString error;
|
||||
QHash<QString, int> colIndexes = CSV::readHeader(in, {"Power"}, error);
|
||||
QList<Real> spectrum;
|
||||
|
||||
if (error.isEmpty())
|
||||
{
|
||||
int powerCol = colIndexes.value("Power");
|
||||
int maxCol = std::max({powerCol});
|
||||
QStringList cols;
|
||||
|
||||
while (CSV::readRow(in, &cols) && error.isEmpty())
|
||||
{
|
||||
if (cols.size() > maxCol)
|
||||
{
|
||||
bool ok;
|
||||
float value = cols[powerCol].toFloat(&ok);
|
||||
if (ok) {
|
||||
spectrum.push_back(value);
|
||||
} else {
|
||||
error = QString("Failed to convert %1 to float").arg(cols[powerCol]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (error.isEmpty())
|
||||
{
|
||||
m_settings.m_spectrumMemory[memoryIdx].m_spectrum = spectrum;
|
||||
updateMem(memoryIdx);
|
||||
|
||||
if (spectrum.size() != m_settings.m_fftSize) {
|
||||
QMessageBox::warning(this, "Spectrum", QString("Loaded data size (%1) does not match FFT size (%2)").arg(spectrum.size()).arg(m_settings.m_fftSize));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, "Spectrum", QString("Failed to read file %1\n%2").arg(fileNames[0]).arg(error));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, "Spectrum", QString("Failed to open file %1").arg(fileNames[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save from specified memory to .csv file
|
||||
void GLSpectrumGUI::saveMem(int memoryIdx)
|
||||
{
|
||||
// Get filename to write
|
||||
QFileDialog fileDialog(nullptr, "Select file to save data to", "", "*.csv");
|
||||
|
||||
fileDialog.setDefaultSuffix("csv");
|
||||
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
if (fileDialog.exec())
|
||||
{
|
||||
QStringList fileNames = fileDialog.selectedFiles();
|
||||
|
||||
if (fileNames.size() > 0)
|
||||
{
|
||||
// Write to csv file
|
||||
QFile file(fileNames[0]);
|
||||
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
{
|
||||
QTextStream out(&file);
|
||||
|
||||
out << "\"Power\"\n";
|
||||
for (int i = 0; i < m_settings.m_spectrumMemory[memoryIdx].m_spectrum.size(); i++) {
|
||||
out << m_settings.m_spectrumMemory[memoryIdx].m_spectrum[i] << "\n";
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, "Spectrum", QString("Failed to open file %1").arg(fileNames[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::applyOffsetToMem(int memoryIdx)
|
||||
{
|
||||
double offset = QInputDialog::getDouble(this, "Enter offset to apply", "Offset");
|
||||
|
||||
for (int i = 0; i < m_settings.m_spectrumMemory[memoryIdx].m_spectrum.size(); i++) {
|
||||
m_settings.m_spectrumMemory[memoryIdx].m_spectrum[i] += offset;
|
||||
}
|
||||
|
||||
updateMem(memoryIdx);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::smoothMem(int memoryIdx)
|
||||
{
|
||||
int n = 5;
|
||||
int l = n / 2;
|
||||
std::size_t size1 = m_settings.m_spectrumMemory[memoryIdx].m_spectrum.size();
|
||||
std::size_t size2 = size1 + (n - 1);
|
||||
Real *temp = new Real[size2];
|
||||
|
||||
// Copy and replicate sample at each end
|
||||
for (int i = 0; i < l; i++)
|
||||
{
|
||||
temp[i] = m_settings.m_spectrumMemory[memoryIdx].m_spectrum[0];
|
||||
temp[size2-1-i] = m_settings.m_spectrumMemory[memoryIdx].m_spectrum[size1 - 1];
|
||||
}
|
||||
std::copy(m_settings.m_spectrumMemory[memoryIdx].m_spectrum.begin(), m_settings.m_spectrumMemory[memoryIdx].m_spectrum.end(), &temp[l]);
|
||||
|
||||
// Average n consequtive samples
|
||||
for (std::size_t i = 0; i < size1; i++)
|
||||
{
|
||||
Real sum = 0.0;
|
||||
for (int j = 0; j < n; j++) {
|
||||
sum += temp[i+j];
|
||||
}
|
||||
Real avg = sum / (Real) n;
|
||||
m_settings.m_spectrumMemory[memoryIdx].m_spectrum[i] = avg;
|
||||
}
|
||||
|
||||
delete[] temp;
|
||||
|
||||
updateMem(memoryIdx);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::addMem(int memoryIdx)
|
||||
{
|
||||
std::size_t s = std::min(m_settings.m_spectrumMemory[0].m_spectrum.size(), m_settings.m_spectrumMemory[1].m_spectrum.size());
|
||||
|
||||
for (std::size_t i = 0; i < s; i++)
|
||||
{
|
||||
m_settings.m_spectrumMemory[memoryIdx].m_spectrum[i] =
|
||||
m_settings.m_spectrumMemory[0].m_spectrum[i]
|
||||
+ m_settings.m_spectrumMemory[1].m_spectrum[i];
|
||||
}
|
||||
|
||||
updateMem(memoryIdx);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::diffMem(int memoryIdx)
|
||||
{
|
||||
std::size_t s = std::min(m_settings.m_spectrumMemory[0].m_spectrum.size(), m_settings.m_spectrumMemory[1].m_spectrum.size());
|
||||
|
||||
for (std::size_t i = 0; i < s; i++)
|
||||
{
|
||||
m_settings.m_spectrumMemory[memoryIdx].m_spectrum[i] =
|
||||
m_settings.m_spectrumMemory[0].m_spectrum[i]
|
||||
- m_settings.m_spectrumMemory[1].m_spectrum[i];
|
||||
}
|
||||
|
||||
updateMem(memoryIdx);
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::updateMem(int memoryIdx)
|
||||
{
|
||||
// Update in GLSpectrum
|
||||
m_glSpectrum->setMemory(memoryIdx, m_settings.m_spectrumMemory[memoryIdx]);
|
||||
// Update in SpectrumVIS
|
||||
setMathMemory();
|
||||
}
|
||||
|
||||
// Look through items of numerical values using engineering units in a combo box, looking for text with equivalent value. E.g. 1000 == 1k
|
||||
int GLSpectrumGUI::findEquivalentText(QComboBox *combo, int value)
|
||||
{
|
||||
for (int i = 0; i < combo->count(); i++)
|
||||
{
|
||||
int itemValue = Units::engUnitsToPosInt(combo->itemText(i));
|
||||
if (itemValue == value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -26,6 +26,8 @@
|
||||
#define INCLUDE_GLSPECTRUMGUI_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
#include <QMenu>
|
||||
|
||||
#include "dsp/dsptypes.h"
|
||||
#include "dsp/spectrumsettings.h"
|
||||
@ -45,13 +47,6 @@ class SDRGUI_API GLSpectrumGUI : public QWidget, public Serializable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum AveragingMode
|
||||
{
|
||||
AvgModeNone,
|
||||
AvgModeMoving,
|
||||
AvgModeFixed,
|
||||
AvgModeMax
|
||||
};
|
||||
|
||||
explicit GLSpectrumGUI(QWidget* parent = NULL);
|
||||
~GLSpectrumGUI();
|
||||
@ -77,10 +72,12 @@ private:
|
||||
Real m_calibrationShiftdB;
|
||||
static const int m_fpsMs[];
|
||||
SpectrumMarkersDialog *m_markersDialog;
|
||||
QMenu *m_contextMenu;
|
||||
|
||||
void blockApplySettings(bool block);
|
||||
void applySettings();
|
||||
void applySpectrumSettings();
|
||||
void setPowerAndRefRange();
|
||||
void displaySettings();
|
||||
void displayControls();
|
||||
void setAveragingCombo();
|
||||
@ -92,6 +89,17 @@ private:
|
||||
bool handleMessage(const Message& message);
|
||||
void displayGotoMarkers();
|
||||
QString displayScaled(int64_t value, char type, int precision, bool showMult);
|
||||
void setMathMemory();
|
||||
void convertSpectrum(QList<Real> &spectrum, bool fromDB, bool toDB) const;
|
||||
void memContextMenu(int memoryIdx, const QPoint &p);
|
||||
void loadMem(int memoryIdx);
|
||||
void saveMem(int memoryIdx);
|
||||
void updateMem(int memoryIdx);
|
||||
void applyOffsetToMem(int memoryIdx);
|
||||
void smoothMem(int memoryIdx);
|
||||
void addMem(int memoryIdx);
|
||||
void diffMem(int memoryIdx);
|
||||
int findEquivalentText(QComboBox *combo, int value);
|
||||
|
||||
private slots:
|
||||
void on_fftWindow_currentIndexChanged(int index);
|
||||
@ -111,10 +119,15 @@ private slots:
|
||||
void on_traceIntensity_valueChanged(int index);
|
||||
void on_averagingMode_currentIndexChanged(int index);
|
||||
void on_averaging_currentIndexChanged(int index);
|
||||
void on_linscale_toggled(bool checked);
|
||||
void on_mathMode_currentIndexChanged(int index);
|
||||
void on_mathAvgCount_currentIndexChanged(int index);
|
||||
void on_mathAvgCount_editingFinished();
|
||||
void on_linscale_toggled(bool checked);
|
||||
void on_wsSpectrum_toggled(bool checked);
|
||||
void on_markers_clicked(bool checked);
|
||||
void on_load_clicked(bool checked);
|
||||
void on_save_clicked(bool checked);
|
||||
void on_saveImage_clicked(bool checked);
|
||||
|
||||
void on_waterfall_toggled(bool checked);
|
||||
void on_spectrogram_toggled(bool checked);
|
||||
@ -129,7 +142,7 @@ private slots:
|
||||
void on_freeze_toggled(bool checked);
|
||||
void on_calibration_toggled(bool checked);
|
||||
void on_gotoMarker_currentIndexChanged(int index);
|
||||
void on_showAllControls_toggled(bool checked);
|
||||
void on_showControls_currentIndexChanged(int index);
|
||||
|
||||
void on_measure_clicked(bool checked);
|
||||
void on_resetMeasurements_clicked(bool checked);
|
||||
@ -146,6 +159,12 @@ private slots:
|
||||
void updateMeasurements();
|
||||
void closeMarkersDialog();
|
||||
|
||||
void on_showSettingsDialog_clicked(bool checked=false);
|
||||
void on_mem1_clicked(bool checked=false);
|
||||
void on_mem2_clicked(bool checked=false);
|
||||
void mem1ContextMenu(const QPoint &p);
|
||||
void mem2ContextMenu(const QPoint &p);
|
||||
|
||||
signals:
|
||||
// Emitted when user selects an annotation marker
|
||||
void requestCenterFrequency(qint64 frequency);
|
||||
|
||||
@ -112,7 +112,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="clearSpectrum">
|
||||
<widget class="QToolButton" name="clearSpectrum">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@ -533,7 +533,7 @@
|
||||
<string>FFT window function</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
<enum>QComboBox::SizeAdjustPolicy::AdjustToContents</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
@ -606,7 +606,7 @@
|
||||
<string>FFT size</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
<enum>QComboBox::SizeAdjustPolicy::AdjustToContents</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
@ -665,14 +665,20 @@
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>FFT overlap</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
@ -719,6 +725,11 @@
|
||||
<string>Max</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Min</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@ -835,6 +846,169 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="mathMode">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>88</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Math function to apply to spectrum data</string>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>No</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>x-μ</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>x-μ dB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>x-μ+∧μ dB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>|x-μ| dB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>x-M1</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>x-M1 dB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>|x-M1| dB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>x-M2</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>x-M2 dB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>|x-M2| dB</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="mathAvgCount">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>55</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Number of samples in moving average. [2,1M]</string>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>5</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>10</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>20</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>50</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>100</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>200</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>500</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1k</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2k</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>5k</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>10k</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>20k</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>50k</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="fillLabel1">
|
||||
<property name="sizePolicy">
|
||||
@ -895,13 +1069,13 @@
|
||||
<string>Reference level (dB)</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>-200</number>
|
||||
<number>-120</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>40</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -923,13 +1097,13 @@
|
||||
<string>Range (dB)</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
<number>120</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -1031,6 +1205,90 @@
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="Line6Layout">
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="mem1">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Left: Toggle display of memory 1 - Right: Show menu</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>M1</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="mem2">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Left: Toggle display of memory 2 - Right: Show menu</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>M2</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="load">
|
||||
<property name="toolTip">
|
||||
<string>Load spectrum data from .csv file</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/res.qrc">
|
||||
<normaloff>:/load.png</normaloff>:/load.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="save">
|
||||
<property name="toolTip">
|
||||
<string>Save spectrum data to .csv file</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/res.qrc">
|
||||
<normaloff>:/save.png</normaloff>:/save.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="saveImage">
|
||||
<property name="toolTip">
|
||||
<string>Save display to image file (*.png / *.jpg)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/res.qrc">
|
||||
<normaloff>:/picture.png</normaloff>:/picture.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="Line7Layout">
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="freeze">
|
||||
<property name="toolTip">
|
||||
@ -1046,20 +1304,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="save">
|
||||
<property name="toolTip">
|
||||
<string>Save spectrum data to file</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/res.qrc">
|
||||
<normaloff>:/save.png</normaloff>:/save.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="wsSpectrum">
|
||||
<property name="toolTip">
|
||||
@ -1076,7 +1320,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="markers">
|
||||
<widget class="QToolButton" name="markers">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@ -1097,7 +1341,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="measure">
|
||||
<widget class="QToolButton" name="measure">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@ -1145,26 +1389,51 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="showAllControls">
|
||||
<widget class="QToolButton" name="showSettingsDialog">
|
||||
<property name="toolTip">
|
||||
<string>Toggle all controls</string>
|
||||
<string>Show settings dialog</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Grid</string>
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/res.qrc">
|
||||
<normaloff>:/listing.png</normaloff>:/listing.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="showControls">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>52</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Which controls to show (Minimum / Standard / All)</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Min</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Std</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>All</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
// written by Christian Daniel //
|
||||
// Copyright (C) 2015-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2018 beta-tester <alpha-beta-release@gmx.net> //
|
||||
// Copyright (C) 2022-2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// Copyright (C) 2022-2026 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
|
||||
// //
|
||||
// OpenGL interface modernization. //
|
||||
@ -34,6 +34,8 @@
|
||||
#include <QPoint>
|
||||
#include <QOpenGLWidget>
|
||||
#include <QOpenGLDebugLogger>
|
||||
#include <QScrollBar>
|
||||
#include <QDateTime>
|
||||
#include "gui/qtcompatibility.h"
|
||||
#include "gui/scaleengine.h"
|
||||
#include "gui/glshadersimple.h"
|
||||
@ -47,6 +49,7 @@
|
||||
#include "export.h"
|
||||
#include "util/incrementalarray.h"
|
||||
#include "util/message.h"
|
||||
#include "util/circularbuffer.h"
|
||||
#include "util/colormap.h"
|
||||
#include "util/peakfinder.h"
|
||||
|
||||
@ -56,7 +59,7 @@ class SpectrumVis;
|
||||
class QOpenGLDebugLogger;
|
||||
class SpectrumMeasurements;
|
||||
|
||||
class SDRGUI_API GLSpectrumView : public QOpenGLWidget, public GLSpectrumInterface {
|
||||
class SDRGUI_API GLSpectrumView : public QOpenGLWidget, public GLSpectrumInterface, public ScaleEngine::TickFormatter {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@ -155,6 +158,28 @@ public:
|
||||
{}
|
||||
};
|
||||
|
||||
class MsgFrequencyZooming : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
float getFrequencyZoomFactor() const { return m_frequencyZoomFactor; }
|
||||
float getFrequencyZoomPos() const { return m_frequencyZoomPos; }
|
||||
|
||||
static MsgFrequencyZooming* create(float frequencyZoomFactor, float frequencyZoomPos) {
|
||||
return new MsgFrequencyZooming(frequencyZoomFactor, frequencyZoomPos);
|
||||
}
|
||||
|
||||
private:
|
||||
float m_frequencyZoomFactor;
|
||||
float m_frequencyZoomPos;
|
||||
|
||||
MsgFrequencyZooming(float frequencyZoomFactor, float frequencyZoomPos) :
|
||||
Message(),
|
||||
m_frequencyZoomFactor(frequencyZoomFactor),
|
||||
m_frequencyZoomPos(frequencyZoomPos)
|
||||
{ }
|
||||
};
|
||||
|
||||
GLSpectrumView(QWidget* parent = nullptr);
|
||||
virtual ~GLSpectrumView();
|
||||
|
||||
@ -166,13 +191,16 @@ public:
|
||||
void setTimingRate(qint32 timingRate);
|
||||
void setFFTOverlap(int overlap);
|
||||
void setReferenceLevel(Real referenceLevel);
|
||||
void setReferenceLevelRange(Real minReferenceLevel, Real maxReferenceLevel);
|
||||
void setPowerRange(Real powerRange);
|
||||
void setPowerRangeRange(Real minPowerRange, Real maxPowerRange);
|
||||
void setDecay(int decay);
|
||||
void setDecayDivisor(int decayDivisor);
|
||||
void setHistoStroke(int stroke);
|
||||
void setDisplayWaterfall(bool display);
|
||||
void setDisplay3DSpectrogram(bool display);
|
||||
void set3DSpectrogramStyle(SpectrumSettings::SpectrogramStyle style);
|
||||
void setSpectrumColor(QRgb color);
|
||||
void setColorMapName(const QString &colorMapName);
|
||||
void setSpectrumStyle(SpectrumSettings::SpectrumStyle style);
|
||||
void setSsbSpectrum(bool ssbSpectrum);
|
||||
@ -190,14 +218,15 @@ public:
|
||||
void setMeasurements(SpectrumMeasurements *measurements) { m_measurements = measurements; }
|
||||
void setMeasurementParams(SpectrumSettings::Measurement measurement,
|
||||
int centerFrequencyOffset, int bandwidth, int chSpacing, int adjChBandwidth,
|
||||
int harmonics, int peaks, bool highlight, int precision);
|
||||
int harmonics, int peaks, bool highlight, int precision, unsigned memoryMask);
|
||||
void resetMeasurements();
|
||||
qint32 getSampleRate() const { return m_sampleRate; }
|
||||
|
||||
void addChannelMarker(ChannelMarker* channelMarker);
|
||||
void removeChannelMarker(ChannelMarker* channelMarker);
|
||||
void setMessageQueueToGUI(MessageQueue* messageQueue) { m_messageQueueToGUI = messageQueue; }
|
||||
|
||||
virtual void newSpectrum(const Real* spectrum, int nbBins, int fftSize);
|
||||
virtual void newSpectrum(const Real* spectrum, int fftSize);
|
||||
void clearSpectrumHistogram();
|
||||
|
||||
Real getWaterfallShare() const { return m_waterfallShare; }
|
||||
@ -237,6 +266,17 @@ public:
|
||||
void setIsDeviceSpectrum(bool isDeviceSpectrum) { m_isDeviceSpectrum = isDeviceSpectrum; }
|
||||
bool isDeviceSpectrum() const { return m_isDeviceSpectrum; }
|
||||
void setFrequencyZooming(float frequencyZoomFactor, float frequencyZoomPos);
|
||||
void setWaterfallTimeFormat(SpectrumSettings::WaterfallTimeUnits waterfallTimeUnits, const QString& format);
|
||||
void setStatusLine(bool displayRBW, bool displayCursorStats, bool displayPeakStats);
|
||||
void setScrolling(bool enabled, int length);
|
||||
void setScrollBar(QScrollBar* scrollBar);
|
||||
void readCSV(QTextStream &in, bool append, QString &error);
|
||||
void writeCSV(QTextStream &out);
|
||||
bool writeImage(const QString& filename);
|
||||
void getDisplayedSpectrumCopy(std::vector<Real>& copy, bool zoomed);
|
||||
void setMemory(int memoryIdx, const SpectrumSettings::SpectrumMemory &memory);
|
||||
|
||||
QString formatTick(double value) const override;
|
||||
|
||||
private:
|
||||
struct ChannelMarkerState {
|
||||
@ -282,10 +322,15 @@ private:
|
||||
QMutex m_mutex;
|
||||
bool m_mouseInside;
|
||||
bool m_changesPending;
|
||||
bool m_redrawAll; // Call redrawSpectrum/redrawWaterfallAnd3DSpectrogram in applyChanges()
|
||||
|
||||
qint64 m_centerFrequency;
|
||||
Real m_referenceLevel;
|
||||
Real m_minReferenceLevel;
|
||||
Real m_maxReferenceLevel;
|
||||
Real m_powerRange;
|
||||
Real m_minPowerRange;
|
||||
Real m_maxPowerRange;
|
||||
bool m_linear;
|
||||
int m_decay;
|
||||
quint32 m_sampleRate;
|
||||
@ -294,6 +339,8 @@ private:
|
||||
|
||||
int m_fftSize; //!< FFT size in number of bins
|
||||
int m_nbBins; //!< Number of visible FFT bins (zoom support)
|
||||
int m_fftMin; //!< First bin when zooming in
|
||||
int m_fftMax; //!< Last bin when zooming in
|
||||
|
||||
bool m_displayGrid;
|
||||
int m_displayGridIntensity;
|
||||
@ -315,6 +362,8 @@ private:
|
||||
int m_histogramHeight;
|
||||
int m_waterfallHeight;
|
||||
int m_bottomMargin;
|
||||
int m_waterfallTop;
|
||||
int m_histogramTop;
|
||||
QFont m_textOverlayFont;
|
||||
QPixmap m_leftMarginPixmap;
|
||||
QPixmap m_frequencyPixmap;
|
||||
@ -357,6 +406,7 @@ private:
|
||||
QPixmap m_spectrogramTimePixmap;
|
||||
QPixmap m_spectrogramPowerPixmap;
|
||||
SpectrumSettings::SpectrogramStyle m_3DSpectrogramStyle;
|
||||
QColor m_spectrumColor;
|
||||
QString m_colorMapName;
|
||||
SpectrumSettings::SpectrumStyle m_spectrumStyle;
|
||||
const float *m_colorMap;
|
||||
@ -426,16 +476,56 @@ private:
|
||||
int m_measurementPeaks;
|
||||
bool m_measurementHighlight;
|
||||
int m_measurementPrecision;
|
||||
int m_measurementMemMasks;
|
||||
std::vector<qint64> m_maskTestCount;
|
||||
std::vector<qint64> m_maskFailCount;
|
||||
std::vector<std::vector<Real>> m_maskFails;
|
||||
static const QVector4D m_measurementLightMarkerColor;
|
||||
static const QVector4D m_measurementDarkMarkerColor;
|
||||
|
||||
bool m_displayRBW;
|
||||
bool m_displayCursorStats;
|
||||
bool m_displayPeakStats;
|
||||
bool m_cursorOverSpectrum;
|
||||
float m_cursorFrequency;
|
||||
int m_cursorFFTBin;
|
||||
|
||||
std::vector<Real> m_spectrumNoBuffer; // Copy of most recent spectrum, when scroll buffer disabled
|
||||
|
||||
// Spectrum scroll buffer
|
||||
|
||||
struct Spectrum {
|
||||
Real *m_spectrum;
|
||||
quint32 m_sampleRate;
|
||||
qint64 m_centerFrequency;
|
||||
QDateTime m_dateTime;
|
||||
};
|
||||
|
||||
CircularBuffer<Spectrum> m_spectrumBuffer;
|
||||
int m_spectrumBufferFFTSize;
|
||||
int m_spectrumBufferMaxSize;
|
||||
QScrollBar* m_scrollBar;
|
||||
bool m_scrollBarEnabled;
|
||||
int m_scrollBarValue;
|
||||
|
||||
SpectrumSettings::WaterfallTimeUnits m_waterfallTimeUnits;
|
||||
QString m_waterfallTimeFormat;
|
||||
|
||||
QVector<SpectrumSettings::SpectrumMemory> m_spectrumMemory;
|
||||
|
||||
#ifdef ENABLE_PROFILER
|
||||
QString m_profileName;
|
||||
#endif
|
||||
|
||||
void updateWaterfall(const Real *spectrum);
|
||||
void update3DSpectrogram(const Real *spectrum);
|
||||
void updateHistogram(const Real *spectrum);
|
||||
void newSpectrum(const Real* spectrum, int fftSize, quint32 sampleRate, qint64 centerFrequency, const QDateTime &dateTime);
|
||||
void updateSpectrumNoBuffer(const Real *spectrum, int fftSize);
|
||||
void clearSpectrumBuffer();
|
||||
void updateSpectrumBuffer(const Real *spectrum, int fftSize, quint32 sampleRate, qint64 centerFrequency, const QDateTime &dateTime);
|
||||
void clearWaterfallRow(int nbBins);
|
||||
void updateWaterfall(const Real *spectrum, int fftSize, int fftMin, int nbBins);
|
||||
void clear3DSpectrogramRow(int nbBins);
|
||||
void update3DSpectrogram(const Real *spectrum, int fftSize, int fftMin, int nbBins);
|
||||
void updateHistogram(const Real *spectrum, int fftMin, int nbBins);
|
||||
|
||||
void initializeGL();
|
||||
void resizeGL(int width, int height);
|
||||
@ -446,18 +536,19 @@ private:
|
||||
void drawSpectrumMarkers();
|
||||
void drawAnnotationMarkers();
|
||||
|
||||
void measurePeak();
|
||||
void measurePeaks();
|
||||
void measureChannelPower();
|
||||
void measureAdjacentChannelPower();
|
||||
void measureOccupiedBandwidth();
|
||||
void measure3dBBandwidth();
|
||||
void measureSNR();
|
||||
void measureSFDR();
|
||||
float calcChannelPower(int64_t centerFrequency, int channelBandwidth) const;
|
||||
void measure(const Real *spectrum, bool updateGUI);
|
||||
void measurePeaks(const Real *spectrum);
|
||||
void measureChannelPower(const Real *spectrum, bool updateGUI);
|
||||
void measureAdjacentChannelPower(const Real *spectrum, bool updateGUI);
|
||||
void measureOccupiedBandwidth(const Real *spectrum, bool updateGUI);
|
||||
void measure3dBBandwidth(const Real *spectrum, bool updateGUI);
|
||||
void measureSNR(const Real *spectrum, bool updateGUI);
|
||||
void measureSFDR(const Real *spectrum, bool updateGUI);
|
||||
void measureMask(const Real *spectrum, int fftSize, bool updateGUI);
|
||||
float calcChannelPower(const Real *spectrum, int64_t centerFrequency, int channelBandwidth) const;
|
||||
float calPower(float power) const;
|
||||
int findPeakBin(const Real *spectrum) const;
|
||||
void findPeak(float &power, float &frequency) const;
|
||||
void findPeak(const Real *spectrum, float &power, float &frequency) const;
|
||||
void peakWidth(const Real *spectrum, int center, int &left, int &right, int maxLeft, int maxRight) const;
|
||||
int frequencyToBin(int64_t frequency) const;
|
||||
int64_t binToFrequency(int bin) const;
|
||||
@ -478,7 +569,7 @@ private:
|
||||
void timeZoom(bool zoomInElseOut);
|
||||
void powerZoom(float pw, bool zoomInElseOut);
|
||||
void resetFrequencyZoom();
|
||||
void updateFFTLimits();
|
||||
void updateFFTLimits(bool fftSizeOnly);
|
||||
void setFrequencyScale();
|
||||
void setPowerScale(int height);
|
||||
void getFrequencyZoom(int64_t& centerFrequency, int& frequencySpan);
|
||||
@ -515,6 +606,16 @@ private:
|
||||
void updateSortedAnnotationMarkers();
|
||||
void queueRequestCenterFrequency(qint64 frequency);
|
||||
|
||||
void setTimeScaleRange();
|
||||
void redrawSpectrum();
|
||||
void redrawWaterfallAnd3DSpectrogram();
|
||||
void paintLeftScales();
|
||||
void paintStatusLineRight();
|
||||
void updateScrollBar();
|
||||
int scrollBarValue() const;
|
||||
qint64 getDisplayedCenterFrequency() const;
|
||||
quint32 getDisplayedSampleRate() const;
|
||||
|
||||
static bool annotationDisplayLessThan(const SpectrumAnnotationMarker *m1, const SpectrumAnnotationMarker *m2)
|
||||
{
|
||||
if (m1->m_bandwidth == m2->m_bandwidth) {
|
||||
@ -529,6 +630,21 @@ private:
|
||||
return m1.m_frequency < m2.m_frequency;
|
||||
}
|
||||
|
||||
inline Real clampPower(Real value) const
|
||||
{
|
||||
return std::clamp(value, -m_powerRange, 0.0f);
|
||||
}
|
||||
|
||||
inline int clampWaterfall(int value) const
|
||||
{
|
||||
return std::clamp(value, 0, 239);
|
||||
}
|
||||
|
||||
inline int clampPixel(int value) const
|
||||
{
|
||||
return std::clamp(value, 0, 255);
|
||||
}
|
||||
|
||||
private slots:
|
||||
void cleanup();
|
||||
void tick();
|
||||
@ -536,6 +652,7 @@ private slots:
|
||||
void channelMarkerDestroyed(QObject* object);
|
||||
void openGLDebug(const QOpenGLDebugMessage &debugMessage);
|
||||
bool eventFilter(QObject *object, QEvent *event);
|
||||
void scrollBarValueChanged(int value);
|
||||
|
||||
signals:
|
||||
// Emitted when user tries to scroll to frequency currently out of range
|
||||
|
||||
@ -59,6 +59,7 @@ void ProfileDialog::resizeTable()
|
||||
ui->table->setItem(row, COL_TOTAL, new QTableWidgetItem("1000.000 ms"));
|
||||
ui->table->setItem(row, COL_AVERAGE, new QTableWidgetItem("1000.000 ns/frame"));
|
||||
ui->table->setItem(row, COL_LAST, new QTableWidgetItem("1000.000 ms"));
|
||||
ui->table->setItem(row, COL_MAX, new QTableWidgetItem("1000.000 ms"));
|
||||
ui->table->setItem(row, COL_NUM_SAMPLES, new QTableWidgetItem("1000000000"));
|
||||
ui->table->resizeColumnsToContents();
|
||||
ui->table->setRowCount(row);
|
||||
@ -79,6 +80,7 @@ void ProfileDialog::updateData()
|
||||
double totalTime = data.getTotal();
|
||||
double averageTime = data.getAverage();
|
||||
double lastTime = data.getLast();
|
||||
double maxTime = data.getMax();
|
||||
|
||||
int i = 0;
|
||||
for (; i < ui->table->rowCount(); i++)
|
||||
@ -90,6 +92,7 @@ void ProfileDialog::updateData()
|
||||
ui->table->item(i, COL_TOTAL)->setData(Qt::DisplayRole, totalTime);
|
||||
ui->table->item(i, COL_AVERAGE)->setData(Qt::DisplayRole, averageTime);
|
||||
ui->table->item(i, COL_LAST)->setData(Qt::DisplayRole, lastTime);
|
||||
ui->table->item(i, COL_MAX)->setData(Qt::DisplayRole, maxTime);
|
||||
ui->table->item(i, COL_NUM_SAMPLES)->setData(Qt::DisplayRole, data.getNumSamples());
|
||||
break;
|
||||
}
|
||||
@ -105,31 +108,47 @@ void ProfileDialog::updateData()
|
||||
QTableWidgetItem *total = new QTableWidgetItem();
|
||||
QTableWidgetItem *average = new QTableWidgetItem();
|
||||
QTableWidgetItem *last = new QTableWidgetItem();
|
||||
QTableWidgetItem *max = new QTableWidgetItem();
|
||||
QTableWidgetItem *numSamples = new QTableWidgetItem();
|
||||
|
||||
ui->table->setItem(row, COL_NAME, name);
|
||||
ui->table->setItem(row, COL_TOTAL, total);
|
||||
ui->table->setItem(row, COL_AVERAGE, average);
|
||||
ui->table->setItem(row, COL_LAST, last);
|
||||
ui->table->setItem(row, COL_MAX, max);
|
||||
ui->table->setItem(row, COL_NUM_SAMPLES, numSamples);
|
||||
|
||||
total->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
average->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
last->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
max->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
numSamples->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
|
||||
total->setData(Qt::DisplayRole, totalTime);
|
||||
average->setData(Qt::DisplayRole, averageTime);
|
||||
last->setData(Qt::DisplayRole, lastTime);
|
||||
max->setData(Qt::DisplayRole, maxTime);
|
||||
numSamples->setData(Qt::DisplayRole, data.getNumSamples());
|
||||
|
||||
ui->table->setItemDelegateForColumn(COL_TOTAL, new NanoSecondsDelegate());
|
||||
ui->table->setItemDelegateForColumn(COL_AVERAGE, new NanoSecondsDelegate());
|
||||
ui->table->setItemDelegateForColumn(COL_LAST, new NanoSecondsDelegate());
|
||||
ui->table->setItemDelegateForColumn(COL_MAX, new NanoSecondsDelegate());
|
||||
|
||||
ui->table->setSortingEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
GlobalProfileData::releaseProfileData();
|
||||
|
||||
qint64 msecSinceStart = GlobalProfileData::getMSSinceStart();
|
||||
QString s;
|
||||
|
||||
if (msecSinceStart < 1e3) {
|
||||
s = QString("%1 ms").arg(msecSinceStart);
|
||||
} else {
|
||||
s = QString("%1 s").arg(msecSinceStart/1e3, 0, 'f', 3);
|
||||
}
|
||||
ui->time->setText(s);
|
||||
|
||||
}
|
||||
|
||||
@ -52,6 +52,7 @@ private:
|
||||
COL_TOTAL,
|
||||
COL_AVERAGE,
|
||||
COL_LAST,
|
||||
COL_MAX,
|
||||
COL_NUM_SAMPLES
|
||||
};
|
||||
|
||||
|
||||
@ -24,7 +24,41 @@
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="layout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="timeLabel">
|
||||
<property name="text">
|
||||
<string>Runtime</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="time">
|
||||
<property name="toolTip">
|
||||
<string>Elapsed run-time since reset button pressed</string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="table">
|
||||
<column>
|
||||
@ -59,6 +93,14 @@
|
||||
<string>Time for last execution of the code</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Max</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Maximum length of time taken to execute the code</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Samples</string>
|
||||
@ -74,10 +116,10 @@
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close|QDialogButtonBox::Reset</set>
|
||||
<set>QDialogButtonBox::StandardButton::Close|QDialogButtonBox::StandardButton::Reset</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@ -34,7 +34,10 @@ static double trunc(double d)
|
||||
|
||||
QString ScaleEngine::formatTick(double value, int decimalPlaces)
|
||||
{
|
||||
if (m_physicalUnit != Unit::TimeHMS)
|
||||
if (m_tickFormatter) {
|
||||
return m_tickFormatter->formatTick(value);
|
||||
}
|
||||
else if (m_physicalUnit != Unit::TimeHMS)
|
||||
{
|
||||
if (m_physicalUnit == Unit::Scientific) {
|
||||
return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'e', m_fixedDecimalPlaces);
|
||||
@ -862,7 +865,8 @@ ScaleEngine::ScaleEngine() :
|
||||
m_makeOpposite(false),
|
||||
m_truncateMode(false),
|
||||
m_truncated(false),
|
||||
m_truncationValue(0.0)
|
||||
m_truncationValue(0.0),
|
||||
m_tickFormatter(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@ -39,6 +39,11 @@ public:
|
||||
};
|
||||
typedef QList<Tick> TickList;
|
||||
|
||||
class TickFormatter {
|
||||
public:
|
||||
virtual QString formatTick(double value) const = 0;
|
||||
};
|
||||
|
||||
ScaleEngine();
|
||||
|
||||
void setOrientation(Qt::Orientation orientation);
|
||||
@ -59,6 +64,9 @@ public:
|
||||
|
||||
float getScaleWidth();
|
||||
|
||||
void setTickFormatter(TickFormatter *tickFormatter) { m_tickFormatter = tickFormatter; }
|
||||
void requestReCalc() { m_recalc = true; }
|
||||
|
||||
private:
|
||||
// base configuration
|
||||
Qt::Orientation m_orientation;
|
||||
@ -87,6 +95,8 @@ private:
|
||||
bool m_truncated; //!< true if upper digits are truncated
|
||||
double m_truncationValue; //!< value to subreact from tick display values
|
||||
|
||||
TickFormatter *m_tickFormatter;
|
||||
|
||||
QString formatTick(double value, int decimalPlaces);
|
||||
void calcCharSize();
|
||||
void calcScaleFactor();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<h1>Spectrum component</h1>
|
||||
<h1>Spectrum component</h1>
|
||||
|
||||
This page details the spectrum component that takes part of the main spectrum display and is also used in some channel and feature plugins.
|
||||
|
||||
@ -28,6 +28,9 @@ A status line is displayed at the left of the top margin. It displays the follow
|
||||
- if frequency zooming is active the zooming factor
|
||||
- `CF:` followed by the Center Frequency of the displayed spectrum possibly with multiplier suffix (G, M, k)
|
||||
- `SP:` followed by the frequency SPan of the displayed spectrum possibly with multiplier suffix (M, k)
|
||||
- `RBW:` followed by the RBW (Resolution BandWidth) of the displayed spectrum possibly with multiplier suffix (k). This is optional and must be enabled in the Spectrum Display Settings dialog.
|
||||
- `Cur:` followed by the power in dB and frequency under the cursor. This is optional and must be enabled in the Spectrum Display Settings dialog.
|
||||
- `Pk:` followed by the power in dB and frequency of the highest peak in the displayed spectrum. This is optional and must be enabled in the Spectrum Display Settings dialog.
|
||||
|
||||
<h3>Spectrum markers</h3>
|
||||
|
||||
@ -93,9 +96,11 @@ When the mouse is inside the time scale (waterfall) the overlap is increased by
|
||||
|
||||
<h2>B. Spectrum controls</h2>
|
||||
|
||||
Controls are organized in 6 blocks arranged in a flow layout so that the size of the control area can adapt to the width of the spectrum arranging the blocks from 4 to 1 line as the spectrum widens. The buttons and various controls in each block remain at the same place.
|
||||
Not all controls are visible by default. See B.7.8 for how to select which controls are visible.
|
||||
|
||||
Narrow (4 lines):
|
||||
Controls are organized in 7 blocks arranged in a flow layout so that the size of the control area can adapt to the width of the spectrum arranging the blocks from 5 to 1 line as the spectrum widens. The buttons and various controls in each block remain at the same place.
|
||||
|
||||
Narrow (5 lines):
|
||||
|
||||

|
||||
|
||||
@ -103,7 +108,7 @@ Wide (1 line):
|
||||
|
||||

|
||||
|
||||
The 6 blocks are detailed next:
|
||||
The 7 blocks are detailed next:
|
||||
|
||||

|
||||
|
||||
@ -254,7 +259,8 @@ Use this combo to select which averaging mode is applied:
|
||||
- **Mov**: moving average. This is a sliding average over the amount of samples specified next (B.2.5). There is one complete FFT line produced at every FFT sampling period
|
||||
- **Fix**: fixed average. Average is done over the amount of samples specified next (B.2.5) and a result is produced at the end of the corresponding period then the next block of averaged samples is processed. There is one complete FFT line produced every FFT sampling period multiplied by the number of averaged samples (4.6). The time scale on the waterfall display is updated accordingly.
|
||||
- **Max**: this is not an averaging but a max hold. It will retain the maximum value over the amount of samples specified next (B.2.5). Similarly to the fixed average a result is produced at the end of the corresponding period which results in slowing down the waterfall display. The point of this mode is to make outlying short bursts within the "averaging" period stand out. With averaging they would only cause a modest increase and could be missed out.
|
||||
|
||||
- **Min**: It will retain the minimum value over the amount of samples specified next (B.2.5). Similarly to the fixed average a result is produced at the end of the corresponding period which results in slowing down the waterfall display. This mode is useful to measure the noise floor by retaining the minimum value over the averaging period.
|
||||
|
||||
<h4>B.4.5: Number of averaged samples</h4>
|
||||
|
||||
Each FFT bin (squared magnitude) is averaged or max'ed over a number of samples. This combo allows selecting the number of samples between these values: 1 (no averaging), 2, 5, 10, 20, 50, 100, 200, 500, 1k (1000) for all modes and in addition 2k, 5k, 10k, 20k, 50k, 1e5 (100000), 2e5, 5e5, 1M (1000000) for "fixed" and "max" modes. Averaging reduces the noise variance and can be used to better detect weak continuous signals. The fixed averaging mode allows long time monitoring on the waterfall. The max mode helps showing short bursts that may appear during the "averaging" period.
|
||||
@ -263,6 +269,26 @@ The resulting spectrum refresh period appears in the tooltip taking sample rate,
|
||||
|
||||
Period = ((((FFT_size ÷ 2) - overlap) × 2) ÷ sample_rate) × averaging_size
|
||||
|
||||
<h4>B.4.6:Math mode</h4>
|
||||
|
||||
Use this combo to select which mathematical operation is applied to the spectrum:
|
||||
- **No**: no math operation.
|
||||
- **x-μ**: difference between current spectrum and a moving average.
|
||||
- **x-μ dB**: difference between current spectrum and a moving average after values are converted to dB (so x/μ).
|
||||
- **x-μ+∧μ dB**: difference between current spectrum and a moving average after values are converted to dB (so x/μ) and then adding the average back in dB (∧μ) to keep the overall level of the spectrum. This is useful to make outlying short bursts within the "averaging" period stand out while keeping the overall level of the spectrum unchanged. With "x-μ dB" the resulting spectrum would be centered around zero which may not be desirable.
|
||||
- **x-μ dB**: absolute value of difference between current spectrum and a moving average after values are converted to dB (so |x/μ|).
|
||||
- **x-M1**: difference between current spectrum and the spectrum stored in memory M1.
|
||||
- **x-M1 dB**: difference between current spectrum and the spectrum stored in memory M1 after values are converted to dB (so x/M1).
|
||||
- **|x-M1| dB**: absolute value of difference between current spectrum and the spectrum stored in memory M1 after values are converted to dB (so |x/M1|).
|
||||
- **x-M2**: difference between current spectrum and the spectrum stored in memory M2.
|
||||
- **x-M2 dB**: difference between current spectrum and the spectrum stored in memory M2 after values are converted to dB (so x/M1).
|
||||
- **|x-M2| dB**: absolute value of difference between current spectrum and the spectrum stored in memory M1 after values are converted to dB (so |x/M1|).
|
||||
|
||||
<h4>B.4.7: Math moving average length</h4>
|
||||
|
||||
When the math mode (B.4.6) is set to a mode that requires a moving average (x-μ, x-μ dB, x-μ+∧μ dB, |x-μ| dB) this combo allows selecting the number of samples for the moving average.
|
||||
One of the predefined values can be selected or a user-defined value can be entered between 2 and 1M.
|
||||
|
||||
<h3>B.5: Spectrum display controls - block #5</h3>
|
||||
|
||||

|
||||
@ -298,17 +324,56 @@ Use this toggle button to switch between spectrum logarithmic and linear scale d
|
||||
|
||||
When in linear mode the range control (B.3.3) has no effect because the actual range is between 0 and the reference level. The reference level in dB (B.3.2) still applies but is translated to a linear value e.g -40 dB is 1e-4. In linear mode the scale numbers are formatted using scientific notation so that they always occupy the same space.
|
||||
|
||||
<h3>B.6: Spectrum miscellaneous controls - block #6</h3>
|
||||
<h3>B.6: Spectrum memory controls - block #6</h3>
|
||||
|
||||

|
||||

|
||||
|
||||
<h4>B.6.1: Play/Pause spectrum</h4>
|
||||
|
||||
<h4>B.6.1: Memory 1</h4>
|
||||
|
||||
Left clicking M1 toggles display of the spectrum stored in memory M1.
|
||||
|
||||
Right clicking M1 displays a menu with the following options:
|
||||
- **Clear M1**: clears the spectrum stored in memory M1.
|
||||
- **Set M1 to current spectrum**: stores the current spectrum in memory M1.
|
||||
- **Set M1 to moving average**: stores the moving average in memory M1. This option is only available when the Math mode (B.4.6) is set to a mode that uses the moving average.
|
||||
- **Add offset to M1**: displays a dialog that allows the user to enter a value that will be added to each value in M1. This allows the spectrum to be moved up or down.
|
||||
- **Smooth M1**: smooths the values in M1. Values are set to the average of the neighbouring 5 samples. This can be applied repeatedly to further smooth the spectrum.
|
||||
- **Set M1 to M1+M2**: adds M1 to M2, storing the result in M1.
|
||||
- **Set M1 to M1-M2**: subtracts M2 from M1, storing the result in M1.
|
||||
- **Load M1 from .csv**: loads M1 from a .csv file. The .csv should have a single column named Power, with FFT size rows.
|
||||
- **Save M1 to .csv**: saves M1 to a .csv file.
|
||||
|
||||
Colour and label for the M1 spectrum can be set in the Spectrum Display Settings dialog.
|
||||
|
||||
<h4>B.6.2: Memory 2</h4>
|
||||
|
||||
Left clicking M2 toggles display of the spectrum stored in memory M2.
|
||||
|
||||
Right clicking M2 display a menu as described above for M1.
|
||||
|
||||
<h4>B.6.3: Load spectrum to CSV file</h4>
|
||||
|
||||
Click to specify the name of a .csv file that will be loaded to the current spectrum or spectrum scroll buffer.
|
||||
|
||||
<h4>B.6.4: Save spectrum to CSV file</h4>
|
||||
|
||||
Click to specify the name of a .csv file that will have the current spectrum or spectrum scroll buffer saved to.
|
||||
|
||||
<h4>B.6.5: Save spectrum/waterfall to image file</h4>
|
||||
|
||||
Click to specify the name of a .png or .jpg file to save the current display (spectrum and waterfall) to.
|
||||
|
||||
<h3>B.7: Spectrum miscellaneous controls - block #7</h3>
|
||||
|
||||

|
||||
|
||||
|
||||
<h4>B.7.1: Play/Pause spectrum</h4>
|
||||
|
||||
Use this button to freeze the spectrum update. Useful when making measurements with the markers.
|
||||
|
||||
<h4>B.6.2: Save spectrum to CSV file</h4>
|
||||
|
||||
<h4>B.6.3: Spectrum server control</h4>
|
||||
<h4>B.7.2: Spectrum server control</h4>
|
||||
|
||||
A websockets based server can be used to send spectrum data to clients. An example of such client can be found in the [SDRangelSpectrum](https://github.com/f4exb/sdrangelspectrum) project.
|
||||
|
||||
@ -368,24 +433,104 @@ The server only sends data. Control including FFT details is done via the REST A
|
||||
|
||||
</table>
|
||||
|
||||
<h4>B.6.4: Spectrum markers dialog</h4>
|
||||
<h4>B.7.3: Spectrum markers dialog</h4>
|
||||
|
||||
Opens the [spectrum markers dialog](spectrummarkers.md)
|
||||
|
||||
<h4>B.6.5: Spectrum measurements dialog</h4>
|
||||
<h4>B.7.4: Spectrum measurements dialog</h4>
|
||||
|
||||
Opens the [Spectrum measurement control dialog](spectrummeasurements.md) Check this link for details on the available measurements.
|
||||
|
||||
<h4>B.6.6: Spectrum calibration</h4>
|
||||
<h4>B.7.5: Spectrum calibration</h4>
|
||||
|
||||
Use the toggle button to switch between relative and calibrated power readings.
|
||||
|
||||
Right click to open the [calibration management dialog](spectrumcalibration.md)
|
||||
|
||||
<h4>B.6.7: Go to annotation marker</h4>
|
||||
<h4>B.7.6: Spectrum Display Settings dialog</h4>
|
||||
|
||||
Click to open the Spectrum Display Settings dialog. See below.
|
||||
|
||||
<h4>B.7.7: Spectrum controls display</h4>
|
||||
|
||||
Selects which of these spectrum controls are displayed. This allows a user to hide less frequently used controls.
|
||||
|
||||
- **Min**: displays a minimum set of controls.
|
||||
- **Std**: displays the standard set of controls.
|
||||
- **All**: displays all controls.
|
||||
|
||||
<h4>B.7.8: Go to annotation marker</h4>
|
||||
|
||||
This combo only appears if the spectrum display is the spectrum of a device (i.e. main spectrum) and if there are visible annotation markers. It allows to set the device center frequency to the frequency of the selected annotation marker.
|
||||
|
||||
<h2>C. Spectrum Display Settings dialog</h2>
|
||||
|
||||
The Spectrum Display Settings dialog contains settings that do not have dedicated controls below the spectrum.
|
||||
|
||||

|
||||
|
||||
<h3>C.1: Waterfall Scrolling</h3>
|
||||
|
||||
When enabled, a scroll bar will be displayed on the right hand side of the spectrum and spectra will be stored in memory that can be larger than the displayed waterfall.
|
||||
The scroll bar can then be used to scroll through and display any spectra in memory. The number of spectra that can be stored in memory can be set via the Length (Spectra) field.
|
||||
The amount of RAM required and total time duration of all spectra are displayed underneath.
|
||||
|
||||
<h3>C.2: Waterfall Axis</h3>
|
||||
|
||||
<h4>C.2.1: Time units</h4>
|
||||
|
||||
Specifies what units will be used for the vertical time axis for the waterfall.
|
||||
|
||||
- **Time offset**: displays a time offset.
|
||||
- **Local time**: displays local time. Only available when Waterfall Scrolling is enabled.
|
||||
- **UTC time**: displays UTC time. Only available when Waterfall Scrolling is enabled.
|
||||
|
||||
<h4>C.2.2: Time format</h4>
|
||||
|
||||
When local time or UTC time is used for the time axis, the Time format field species how that time will be formatted. The default is hh:mm:ss. Other examples include:
|
||||
|
||||
- **dd.MM.yyyy** - 21.05.2001
|
||||
- **ddd MMMM d yy** - Tue May 21 01
|
||||
- **hh:mm:ss.zzz** - 14:13:09.120
|
||||
- **hh:mm:ss.z** - 14:13:09.12
|
||||
- **h:m:s ap** - 2:13:9 pm
|
||||
|
||||
<h3>C.3: Status Line</h3>
|
||||
|
||||
Allows customizing which information is displayed in the status line.
|
||||
|
||||
<h4>C.3.1: Display RBW</h4>
|
||||
|
||||
When checked, the Resolution Bandwidth (RBW) will be displayed in the status line.
|
||||
|
||||
<h4>C.3.2: Display power/frequency under cursor</h4>
|
||||
|
||||
When checked, the power and frequency of the FFT bin under the cursor will be displayed in the status line.
|
||||
|
||||
<h4>C.3.3: Display peak power/frequency</h4>
|
||||
|
||||
When checked, the power and frequency of the highest peak will be displayed in the status line.
|
||||
|
||||
<h3>C.4: Spectrum</h3>
|
||||
|
||||
<h4>C.4.1: Colour</h4>
|
||||
|
||||
Specifies the color to draw the spectrum when line (B.2.1) or fill (B.2.2) style is selected.
|
||||
|
||||
<h3>C.5: Spectrum Memories</h3>
|
||||
|
||||
<h4>C.5.1: Memory</h4>
|
||||
|
||||
Selects which memory settings will be displayed for (M1 or M2).
|
||||
|
||||
<h4>C.5.2: Label</h4>
|
||||
|
||||
Specifies a text label that will be displayed to the left hand side of the spectrum held in the memory.
|
||||
|
||||
<h4>C.5.3: Color</h4>
|
||||
|
||||
Specifies the color to draw the spectrum held in the memory.
|
||||
|
||||
<h2>3D Spectrogram Controls</h2>
|
||||
|
||||

|
||||
|
||||
201
sdrgui/gui/spectrumdisplaysettingsdialog.cpp
Normal file
@ -0,0 +1,201 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2026 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation 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 <QColorDialog>
|
||||
|
||||
#include "spectrumdisplaysettingsdialog.h"
|
||||
#include "glspectrum.h"
|
||||
|
||||
#include "ui_spectrumdisplaysettingsdialog.h"
|
||||
|
||||
static QString rgbToColor(quint32 rgb)
|
||||
{
|
||||
QColor color = QColor::fromRgba(rgb);
|
||||
return QString("%1,%2,%3").arg(color.red()).arg(color.green()).arg(color.blue());
|
||||
}
|
||||
|
||||
static QString backgroundCSS(quint32 rgb)
|
||||
{
|
||||
// Must specify a border, otherwise we end up with a gradient instead of solid background
|
||||
return QString("QToolButton { background-color: rgb(%1); border: none; }").arg(rgbToColor(rgb));
|
||||
}
|
||||
|
||||
SpectrumDisplaySettingsDialog::SpectrumDisplaySettingsDialog(GLSpectrum *glSpectrum, SpectrumSettings *settings, int sampleRate, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::SpectrumDisplaySettingsDialog),
|
||||
m_glSpectrum(glSpectrum),
|
||||
m_settings(settings),
|
||||
m_sampleRate(sampleRate)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
m_spectrumColor = m_settings->m_spectrumColor;
|
||||
ui->spectrumColor->setStyleSheet(backgroundCSS(m_spectrumColor));
|
||||
|
||||
for (int i = 0; i < settings->m_spectrumMemory.size(); i++)
|
||||
{
|
||||
MemorySettings memorySetting = {settings->m_spectrumMemory[i].m_color, settings->m_spectrumMemory[i].m_label};
|
||||
m_memorySettings.append(memorySetting);
|
||||
}
|
||||
|
||||
ui->waterfallScrollBar->setChecked(m_settings->m_scrollBar);
|
||||
ui->scrollLength->setValue(m_settings->m_scrollLength);
|
||||
|
||||
ui->waterfallVerticalAxisUnits->setCurrentIndex((int) m_settings->m_waterfallTimeUnits);
|
||||
ui->waterfallVerticalAxisFormat->setText(m_settings->m_waterfallTimeFormat);
|
||||
|
||||
ui->displayRBW->setChecked(m_settings->m_displayRBW);
|
||||
ui->displayCursorStats->setChecked(m_settings->m_displayCursorStats);
|
||||
ui->displayPeakStats->setChecked(m_settings->m_displayPeakStats);
|
||||
|
||||
displaySettings();
|
||||
}
|
||||
|
||||
SpectrumDisplaySettingsDialog::~SpectrumDisplaySettingsDialog()
|
||||
{}
|
||||
|
||||
void SpectrumDisplaySettingsDialog::accept()
|
||||
{
|
||||
m_settings->m_waterfallTimeUnits = (SpectrumSettings::WaterfallTimeUnits) ui->waterfallVerticalAxisUnits->currentIndex();
|
||||
m_settings->m_waterfallTimeFormat = ui->waterfallVerticalAxisFormat->text();
|
||||
m_settings->m_scrollBar = ui->waterfallScrollBar->isChecked();
|
||||
m_settings->m_scrollLength = ui->scrollLength->value();
|
||||
m_settings->m_displayRBW = ui->displayRBW->isChecked();
|
||||
m_settings->m_displayCursorStats = ui->displayCursorStats->isChecked();
|
||||
m_settings->m_displayPeakStats = ui->displayPeakStats->isChecked();
|
||||
m_settings->m_spectrumColor = m_spectrumColor;
|
||||
|
||||
for (int i = 0; i < m_settings->m_spectrumMemory.size(); i++)
|
||||
{
|
||||
m_settings->m_spectrumMemory[i].m_color = m_memorySettings[i].m_color;
|
||||
m_settings->m_spectrumMemory[i].m_label = m_memorySettings[i].m_label;
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void SpectrumDisplaySettingsDialog::displaySettings()
|
||||
{
|
||||
bool enabled = ui->waterfallScrollBar->isChecked();
|
||||
ui->scrollLengthLabel->setEnabled(enabled);
|
||||
ui->scrollLength->setEnabled(enabled);
|
||||
ui->scrollRAMLabel->setEnabled(enabled);
|
||||
ui->scrollRAM->setEnabled(enabled);
|
||||
ui->scrollTimeLabel->setEnabled(enabled);
|
||||
ui->scrollTime->setEnabled(enabled);
|
||||
|
||||
ui->waterfallVerticalAxisUnitsLabel->setEnabled(enabled);
|
||||
ui->waterfallVerticalAxisUnits->setEnabled(enabled);
|
||||
|
||||
bool formatEnabled = ui->waterfallScrollBar->isChecked() && (ui->waterfallVerticalAxisUnits->currentIndex() != (int)SpectrumSettings::WaterfallTimeUnits::TimeOffset);
|
||||
if (!formatEnabled) {
|
||||
ui->waterfallVerticalAxisUnits->setCurrentIndex((int) SpectrumSettings::WaterfallTimeUnits::TimeOffset);
|
||||
}
|
||||
ui->waterfallVerticalAxisFormatLabel->setEnabled(formatEnabled);
|
||||
ui->waterfallVerticalAxisFormat->setEnabled(formatEnabled);
|
||||
|
||||
// Calculate RAM usage
|
||||
|
||||
std::size_t ram = ui->scrollLength->value() * (m_settings->m_fftSize * sizeof(float) + sizeof(Real *) + sizeof(int) + sizeof(qint64) + sizeof(QDateTime));
|
||||
|
||||
ui->scrollRAM->setText(tr("%1 MB").arg(ram / 1024 / 1024));
|
||||
|
||||
// Calculate time duration of complete scroll history
|
||||
|
||||
float fftRate = m_sampleRate / (float) m_settings->m_fftSize * (100.0f - m_settings->m_fftOverlap) / 100.0f;
|
||||
if (m_settings->m_averagingMode == SpectrumSettings::AvgModeFixed) {
|
||||
fftRate /= m_settings->getAveragingValue(m_settings->m_averagingIndex, m_settings->m_averagingMode);
|
||||
}
|
||||
int secs = ui->scrollLength->value() / fftRate;
|
||||
int hours = secs / 3600;
|
||||
secs -= hours * 3600;
|
||||
int minutes = secs / 60;
|
||||
secs -= minutes * 60;
|
||||
|
||||
ui->scrollTime->setText(QString("%1:%2:%3")
|
||||
.arg(hours, 2, 10, QChar('0'))
|
||||
.arg(minutes, 2, 10, QChar('0'))
|
||||
.arg(secs, 2, 10, QChar('0')));
|
||||
|
||||
displayMemorySettings();
|
||||
}
|
||||
|
||||
void SpectrumDisplaySettingsDialog::displayMemorySettings()
|
||||
{
|
||||
int idx = ui->memIdx->currentIndex();
|
||||
ui->memColor->setStyleSheet(backgroundCSS(m_memorySettings[idx].m_color));
|
||||
ui->memLabel->setText(m_memorySettings[idx].m_label);
|
||||
}
|
||||
|
||||
void SpectrumDisplaySettingsDialog::on_waterfallVerticalAxisUnits_currentIndexChanged(int index)
|
||||
{
|
||||
(void) index;
|
||||
|
||||
displaySettings();
|
||||
}
|
||||
|
||||
void SpectrumDisplaySettingsDialog::on_waterfallScrollBar_clicked(bool checked)
|
||||
{
|
||||
(void) checked;
|
||||
|
||||
displaySettings();
|
||||
}
|
||||
|
||||
void SpectrumDisplaySettingsDialog::on_scrollLength_valueChanged(int value)
|
||||
{
|
||||
(void) value;
|
||||
|
||||
displaySettings();
|
||||
}
|
||||
|
||||
void SpectrumDisplaySettingsDialog::on_spectrumColor_clicked(bool checked)
|
||||
{
|
||||
(void) checked;
|
||||
|
||||
QColorDialog dialog(QColor::fromRgba(m_spectrumColor), ui->spectrumColor);
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
QRgb color = dialog.selectedColor().rgba();
|
||||
ui->spectrumColor->setStyleSheet(backgroundCSS(color));
|
||||
m_spectrumColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumDisplaySettingsDialog::on_memIdx_currentIndexChanged(int index)
|
||||
{
|
||||
(void) index;
|
||||
|
||||
displayMemorySettings();
|
||||
}
|
||||
|
||||
void SpectrumDisplaySettingsDialog::on_memColor_clicked(bool checked)
|
||||
{
|
||||
(void) checked;
|
||||
|
||||
QColorDialog dialog(QColor::fromRgba(m_memorySettings[ui->memIdx->currentIndex()].m_color), ui->memColor);
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
QRgb color = dialog.selectedColor().rgba();
|
||||
ui->memColor->setStyleSheet(backgroundCSS(color));
|
||||
m_memorySettings[ui->memIdx->currentIndex()].m_color = color;
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumDisplaySettingsDialog::on_memLabel_editingFinished()
|
||||
{
|
||||
m_memorySettings[ui->memIdx->currentIndex()].m_label = ui->memLabel->text();
|
||||
}
|
||||
66
sdrgui/gui/spectrumdisplaysettingsdialog.h
Normal file
@ -0,0 +1,66 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2026 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRBASE_GUI_SPECTRUMDISPLAYSETTINGSDIALOG_H_
|
||||
#define SDRBASE_GUI_SPECTRUMDISPLAYSETTINGSDIALOG_H_
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "dsp/spectrumsettings.h"
|
||||
#include "export.h"
|
||||
|
||||
namespace Ui {
|
||||
class SpectrumDisplaySettingsDialog;
|
||||
}
|
||||
|
||||
class GLSpectrum;
|
||||
|
||||
class SDRGUI_API SpectrumDisplaySettingsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
struct MemorySettings {
|
||||
QRgb m_color;
|
||||
QString m_label;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit SpectrumDisplaySettingsDialog(GLSpectrum *glSpectrum, SpectrumSettings *settings, int sampleRate, QWidget *parent = nullptr);
|
||||
~SpectrumDisplaySettingsDialog();
|
||||
|
||||
private:
|
||||
void displaySettings();
|
||||
void displayMemorySettings();
|
||||
|
||||
Ui::SpectrumDisplaySettingsDialog *ui;
|
||||
GLSpectrum *m_glSpectrum;
|
||||
SpectrumSettings *m_settings;
|
||||
int m_sampleRate;
|
||||
QRgb m_spectrumColor;
|
||||
QList<MemorySettings> m_memorySettings;
|
||||
|
||||
private slots:
|
||||
void on_waterfallVerticalAxisUnits_currentIndexChanged(int index);
|
||||
void on_waterfallScrollBar_clicked(bool checked=false);
|
||||
void on_scrollLength_valueChanged(int value);
|
||||
void on_spectrumColor_clicked(bool checked=false);
|
||||
void on_memIdx_currentIndexChanged(int index);
|
||||
void on_memColor_clicked(bool checked=false);
|
||||
void on_memLabel_editingFinished();
|
||||
void accept() override;
|
||||
};
|
||||
|
||||
#endif // SDRBASE_GUI_SPECTRUMDISPLAYSETTINGSDIALOG_H_
|
||||
424
sdrgui/gui/spectrumdisplaysettingsdialog.ui
Normal file
@ -0,0 +1,424 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SpectrumDisplaySettingsDialog</class>
|
||||
<widget class="QDialog" name="SpectrumDisplaySettingsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>420</width>
|
||||
<height>613</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>250</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Sans</family>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Spectrum Display Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="layout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Waterfall Scrolling</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="scrollBarLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enabled</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="waterfallScrollBar">
|
||||
<property name="toolTip">
|
||||
<string>Whether scrolling of the waterfall / 3D spectrogram is supported</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="scrollLengthLabel">
|
||||
<property name="text">
|
||||
<string>Length (Spectra)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="scrollLength">
|
||||
<property name="toolTip">
|
||||
<string>Length of scroll buffer / how many spectra will be stored in RAM</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="scrollRAMLabel">
|
||||
<property name="text">
|
||||
<string>RAM usage</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="scrollRAM">
|
||||
<property name="toolTip">
|
||||
<string>How much RAM will be used by the scroll buffer</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10 M</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="scrollTimeLabel">
|
||||
<property name="text">
|
||||
<string>Time duration</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="scrollTime">
|
||||
<property name="toolTip">
|
||||
<string>Time duration of scroll buffer in hh:mm:ss</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>00:00:00</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="waterfallGroup">
|
||||
<property name="title">
|
||||
<string>Waterfall Axis</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="waterfallVerticalAxisUnitsLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Time units</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="waterfallVerticalAxisUnits">
|
||||
<property name="toolTip">
|
||||
<string>Values for waterfall/3D spectrogram vertical axis. Scolling needs to be enabled to display system time</string>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Time offset</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Local time</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>UTC time</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="waterfallVerticalAxisFormatLabel">
|
||||
<property name="text">
|
||||
<string>Time format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="waterfallVerticalAxisFormat">
|
||||
<property name="toolTip">
|
||||
<string>Format string for time.
|
||||
|
||||
dd.MM.yyyy - 21.05.2001
|
||||
ddd MMMM d yy - Tue May 21 01
|
||||
hh:mm:ss.zzz - 14:13:09.120
|
||||
hh:mm:ss.z - 14:13:09.12
|
||||
h:m:s ap - 2:13:9 pm</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>hh:mm:ss</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="statusLineGroup">
|
||||
<property name="title">
|
||||
<string>Status Line</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="displayRBW">
|
||||
<property name="toolTip">
|
||||
<string>Display RBW (Resolution Bandwidth) in status line</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Display RBW</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="displayCursorStats">
|
||||
<property name="toolTip">
|
||||
<string>Display power/frequency under cursor in status line</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Display power/frequency under cursor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="displayPeakStats">
|
||||
<property name="text">
|
||||
<string>Display peak power/frequency</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="spectrumGroup">
|
||||
<property name="title">
|
||||
<string>Spectrum</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="spectrumColorLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Color</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QToolButton" name="spectrumColor">
|
||||
<property name="toolTip">
|
||||
<string>Spectrum color</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="memoryGroup">
|
||||
<property name="title">
|
||||
<string>Spectrum Memories</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Memory</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="memIdxLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="memIdx">
|
||||
<property name="toolTip">
|
||||
<string>Select which memory to display settings for</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>M1</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>M2</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="memIdxSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="memLabelLabel">
|
||||
<property name="text">
|
||||
<string>Label</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="memLabel"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="memColorLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Color</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="memColorLayout">
|
||||
<item>
|
||||
<widget class="QToolButton" name="memColor">
|
||||
<property name="toolTip">
|
||||
<string>Memory color</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="memColorSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SpectrumDisplaySettingsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>257</x>
|
||||
<y>194</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>203</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SpectrumDisplaySettingsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>314</x>
|
||||
<y>194</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>203</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@ -198,7 +198,8 @@ SpectrumMeasurements::SpectrumMeasurements(QWidget *parent) :
|
||||
m_measurement(SpectrumSettings::MeasurementPeaks),
|
||||
m_precision(1),
|
||||
m_table(nullptr),
|
||||
m_peakTable(nullptr)
|
||||
m_peakTable(nullptr),
|
||||
m_maskTable(nullptr)
|
||||
{
|
||||
m_textBrush.setColor(Qt::white); // Should get this from the style sheet?
|
||||
m_redBrush.setColor(Qt::red);
|
||||
@ -263,6 +264,10 @@ void SpectrumMeasurements::createMeasurementsTable(const QStringList &rows, cons
|
||||
// Cell context menu
|
||||
m_table->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_table, &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::tableContextMenu);
|
||||
|
||||
// Enable mouse tracking for FramelessWindowResizer, as table is created after FramelessWindowResizer::enableChildMouseTracking is called
|
||||
m_table->setMouseTracking(true);
|
||||
m_table->viewport()->setMouseTracking(true);
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::createPeakTable(int peaks)
|
||||
@ -306,6 +311,10 @@ void SpectrumMeasurements::createPeakTable(int peaks)
|
||||
// Cell context menu
|
||||
m_peakTable->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_peakTable, &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::peakTableContextMenu);
|
||||
|
||||
// Enable mouse tracking for FramelessWindowResizer, as table is created after FramelessWindowResizer::enableChildMouseTracking is called
|
||||
m_peakTable->setMouseTracking(true);
|
||||
m_peakTable->viewport()->setMouseTracking(true);
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::createTableMenus()
|
||||
@ -371,6 +380,52 @@ void SpectrumMeasurements::createSNRTable()
|
||||
createMeasurementsTable(rows, units);
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::createMaskTable(unsigned memMask)
|
||||
{
|
||||
int memories = qPopulationCount(memMask);
|
||||
|
||||
m_maskTable = new SpectrumMeasurementsTable();
|
||||
m_maskTable->horizontalHeader()->setSectionsMovable(true);
|
||||
|
||||
QStringList columns = QStringList{"Tests", "Fails", ""};
|
||||
|
||||
m_maskTable->setColumnCount(columns.size());
|
||||
m_maskTable->setRowCount(memories);
|
||||
|
||||
for (int i = 0; i < columns.size(); i++) {
|
||||
m_maskTable->setHorizontalHeaderItem(i, new QTableWidgetItem(columns[i]));
|
||||
}
|
||||
m_maskTable->horizontalHeader()->setStretchLastSection(true);
|
||||
|
||||
int row = 0;
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
if ((memMask & (1 << i)) != 0)
|
||||
{
|
||||
m_maskTable->setVerticalHeaderItem(row, new QTableWidgetItem(QString("M%1").arg(i + 1)));
|
||||
|
||||
for (int j = 0; j < 2; j++)
|
||||
{
|
||||
QTableWidgetItem *item = new QTableWidgetItem();
|
||||
item->setFlags(Qt::ItemIsEnabled);
|
||||
m_maskTable->setItem(row, j, item);
|
||||
}
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
m_maskTable->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||
m_maskTable->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
|
||||
|
||||
// Cell context menu
|
||||
m_maskTable->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_maskTable, &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::maskTableContextMenu);
|
||||
|
||||
// Enable mouse tracking for FramelessWindowResizer, as table is created after FramelessWindowResizer::enableChildMouseTracking is called
|
||||
m_maskTable->setMouseTracking(true);
|
||||
m_maskTable->viewport()->setMouseTracking(true);
|
||||
}
|
||||
|
||||
// Create column select menu item
|
||||
QAction *SpectrumMeasurements::createCheckableItem(QString &text, int idx, bool checked, bool row)
|
||||
{
|
||||
@ -458,12 +513,34 @@ void SpectrumMeasurements::peakTableContextMenu(QPoint pos)
|
||||
connect(copyAction, &QAction::triggered, this, [text]()->void {
|
||||
QClipboard *clipboard = QGuiApplication::clipboard();
|
||||
clipboard->setText(text);
|
||||
});
|
||||
});
|
||||
tableContextMenu->addAction(copyAction);
|
||||
tableContextMenu->addSeparator();
|
||||
|
||||
tableContextMenu->popup(m_peakTable->viewport()->mapToGlobal(pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::maskTableContextMenu(QPoint pos)
|
||||
{
|
||||
QTableWidgetItem *item = m_maskTable->itemAt(pos);
|
||||
if (item)
|
||||
{
|
||||
QMenu* tableContextMenu = new QMenu(m_maskTable);
|
||||
connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater);
|
||||
|
||||
// Copy current cell
|
||||
QAction* copyAction = new QAction("Copy", tableContextMenu);
|
||||
const QString text = item->text();
|
||||
connect(copyAction, &QAction::triggered, this, [text]()->void {
|
||||
QClipboard *clipboard = QGuiApplication::clipboard();
|
||||
clipboard->setText(text);
|
||||
});
|
||||
tableContextMenu->addAction(copyAction);
|
||||
tableContextMenu->addSeparator();
|
||||
|
||||
tableContextMenu->popup(m_peakTable->viewport()->mapToGlobal(pos));
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::resizeMeasurementsTable()
|
||||
@ -495,10 +572,11 @@ void SpectrumMeasurements::resizePeakTable()
|
||||
m_peakTable->removeRow(row);
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::setMeasurementParams(SpectrumSettings::Measurement measurement, int peaks, int precision)
|
||||
void SpectrumMeasurements::setMeasurementParams(SpectrumSettings::Measurement measurement, int peaks, int precision, unsigned memMasks)
|
||||
{
|
||||
if ( (measurement != m_measurement)
|
||||
|| (m_precision != precision)
|
||||
|| (m_memMask != memMasks)
|
||||
|| ((m_peakTable == nullptr) && (m_table == nullptr))
|
||||
|| ((m_peakTable != nullptr) && (peaks != m_peakTable->rowCount()))
|
||||
)
|
||||
@ -508,9 +586,12 @@ void SpectrumMeasurements::setMeasurementParams(SpectrumSettings::Measurement me
|
||||
m_peakTable = nullptr;
|
||||
delete m_table;
|
||||
m_table = nullptr;
|
||||
delete m_maskTable;
|
||||
m_maskTable = nullptr;
|
||||
|
||||
m_measurement = measurement;
|
||||
m_precision = precision;
|
||||
m_memMask = memMasks;
|
||||
|
||||
switch (measurement)
|
||||
{
|
||||
@ -543,6 +624,11 @@ void SpectrumMeasurements::setMeasurementParams(SpectrumSettings::Measurement me
|
||||
createSNRTable();
|
||||
layout()->addWidget(m_table);
|
||||
break;
|
||||
case SpectrumSettings::MeasurementMask:
|
||||
reset();
|
||||
createMaskTable(m_memMask);
|
||||
layout()->addWidget(m_maskTable);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -558,6 +644,11 @@ void SpectrumMeasurements::setMeasurementParams(SpectrumSettings::Measurement me
|
||||
m_table->show();
|
||||
resize(sizeHint());
|
||||
}
|
||||
else if (m_maskTable)
|
||||
{
|
||||
m_maskTable->show();
|
||||
resize(sizeHint());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -578,6 +669,16 @@ void SpectrumMeasurements::reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_maskTable)
|
||||
{
|
||||
for (int i = 0; i < m_maskTable->rowCount(); i++)
|
||||
{
|
||||
for (int j = COL_MASK_TESTS; j <= COL_MASK_FAILS; j++)
|
||||
{
|
||||
m_maskTable->item(i, j)->setData(Qt::DisplayRole, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the value meets the user-defined specification
|
||||
@ -614,74 +715,88 @@ bool SpectrumMeasurements::checkSpec(const QString &spec, double value) const
|
||||
return false;
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::updateMeasurement(int row, float value)
|
||||
void SpectrumMeasurements::updateMeasurement(int row, float value, bool updateGUI)
|
||||
{
|
||||
m_measurements[row].add(value);
|
||||
double mean = m_measurements[row].mean();
|
||||
|
||||
m_table->item(row, COL_CURRENT)->setData(Qt::DisplayRole, value);
|
||||
m_table->item(row, COL_MEAN)->setData(Qt::DisplayRole, mean);
|
||||
m_table->item(row, COL_MIN)->setData(Qt::DisplayRole, m_measurements[row].m_min);
|
||||
m_table->item(row, COL_MAX)->setData(Qt::DisplayRole, m_measurements[row].m_max);
|
||||
m_table->item(row, COL_RANGE)->setData(Qt::DisplayRole, m_measurements[row].m_max - m_measurements[row].m_min);
|
||||
m_table->item(row, COL_STD_DEV)->setData(Qt::DisplayRole, m_measurements[row].stdDev());
|
||||
m_table->item(row, COL_COUNT)->setData(Qt::DisplayRole, m_measurements[row].m_values.size());
|
||||
|
||||
QString spec = m_table->item(row, COL_SPEC)->text();
|
||||
bool valueOK = checkSpec(spec, value);
|
||||
bool meanOK = checkSpec(spec, mean);
|
||||
bool minOK = checkSpec(spec, m_measurements[row].m_min);
|
||||
bool mmaxOK = checkSpec(spec, m_measurements[row].m_max);
|
||||
|
||||
if (!valueOK)
|
||||
if (!updateGUI)
|
||||
{
|
||||
m_measurements[row].m_fails++;
|
||||
m_table->item(row, 8)->setData(Qt::DisplayRole, m_measurements[row].m_fails);
|
||||
m_measurements[row].add(value);
|
||||
|
||||
double mean = m_measurements[row].mean();
|
||||
|
||||
QString spec = m_table->item(row, COL_SPEC)->text();
|
||||
bool valueOK = checkSpec(spec, value);
|
||||
checkSpec(spec, mean);
|
||||
checkSpec(spec, m_measurements[row].m_min);
|
||||
checkSpec(spec, m_measurements[row].m_max);
|
||||
|
||||
if (!valueOK) {
|
||||
m_measurements[row].m_fails++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double mean = m_measurements[row].mean();
|
||||
|
||||
// item->setForeground doesn't work, perhaps as we have style sheet applied?
|
||||
m_table->item(row, COL_CURRENT)->setData(Qt::ForegroundRole, valueOK ? m_textBrush : m_redBrush);
|
||||
m_table->item(row, COL_MEAN)->setData(Qt::ForegroundRole, meanOK ? m_textBrush : m_redBrush);
|
||||
m_table->item(row, COL_MIN)->setData(Qt::ForegroundRole, minOK ? m_textBrush : m_redBrush);
|
||||
m_table->item(row, COL_MAX)->setData(Qt::ForegroundRole, mmaxOK ? m_textBrush : m_redBrush);
|
||||
m_table->item(row, COL_CURRENT)->setData(Qt::DisplayRole, value);
|
||||
m_table->item(row, COL_MEAN)->setData(Qt::DisplayRole, mean);
|
||||
m_table->item(row, COL_MIN)->setData(Qt::DisplayRole, m_measurements[row].m_min);
|
||||
m_table->item(row, COL_MAX)->setData(Qt::DisplayRole, m_measurements[row].m_max);
|
||||
m_table->item(row, COL_RANGE)->setData(Qt::DisplayRole, m_measurements[row].m_max - m_measurements[row].m_min);
|
||||
m_table->item(row, COL_STD_DEV)->setData(Qt::DisplayRole, m_measurements[row].stdDev());
|
||||
m_table->item(row, COL_COUNT)->setData(Qt::DisplayRole, m_measurements[row].m_values.size());
|
||||
|
||||
QString spec = m_table->item(row, COL_SPEC)->text();
|
||||
bool valueOK = checkSpec(spec, value);
|
||||
bool meanOK = checkSpec(spec, mean);
|
||||
bool minOK = checkSpec(spec, m_measurements[row].m_min);
|
||||
bool mmaxOK = checkSpec(spec, m_measurements[row].m_max);
|
||||
|
||||
m_table->item(row, 8)->setData(Qt::DisplayRole, m_measurements[row].m_fails);
|
||||
|
||||
// item->setForeground doesn't work, perhaps as we have style sheet applied?
|
||||
m_table->item(row, COL_CURRENT)->setData(Qt::ForegroundRole, valueOK ? m_textBrush : m_redBrush);
|
||||
m_table->item(row, COL_MEAN)->setData(Qt::ForegroundRole, meanOK ? m_textBrush : m_redBrush);
|
||||
m_table->item(row, COL_MIN)->setData(Qt::ForegroundRole, minOK ? m_textBrush : m_redBrush);
|
||||
m_table->item(row, COL_MAX)->setData(Qt::ForegroundRole, mmaxOK ? m_textBrush : m_redBrush);
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::setSNR(float snr, float snfr, float thd, float thdpn, float sinad)
|
||||
void SpectrumMeasurements::setSNR(float snr, float snfr, float thd, float thdpn, float sinad, bool updateGUI)
|
||||
{
|
||||
updateMeasurement(0, snr);
|
||||
updateMeasurement(1, snfr);
|
||||
updateMeasurement(2, thd);
|
||||
updateMeasurement(3, thdpn);
|
||||
updateMeasurement(4, sinad);
|
||||
updateMeasurement(0, snr, updateGUI);
|
||||
updateMeasurement(1, snfr, updateGUI);
|
||||
updateMeasurement(2, thd, updateGUI);
|
||||
updateMeasurement(3, thdpn, updateGUI);
|
||||
updateMeasurement(4, sinad, updateGUI);
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::setSFDR(float sfdr)
|
||||
void SpectrumMeasurements::setSFDR(float sfdr, bool updateGUI)
|
||||
{
|
||||
updateMeasurement(5, sfdr);
|
||||
updateMeasurement(5, sfdr, updateGUI);
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::setChannelPower(float power)
|
||||
void SpectrumMeasurements::setChannelPower(float power, bool updateGUI)
|
||||
{
|
||||
updateMeasurement(0, power);
|
||||
updateMeasurement(0, power, updateGUI);
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::setAdjacentChannelPower(float left, float leftACPR, float center, float right, float rightACPR)
|
||||
void SpectrumMeasurements::setAdjacentChannelPower(float left, float leftACPR, float center, float right, float rightACPR, bool updateGUI)
|
||||
{
|
||||
updateMeasurement(0, left);
|
||||
updateMeasurement(1, leftACPR);
|
||||
updateMeasurement(2, center);
|
||||
updateMeasurement(3, right);
|
||||
updateMeasurement(4, rightACPR);
|
||||
updateMeasurement(0, left, updateGUI);
|
||||
updateMeasurement(1, leftACPR, updateGUI);
|
||||
updateMeasurement(2, center, updateGUI);
|
||||
updateMeasurement(3, right, updateGUI);
|
||||
updateMeasurement(4, rightACPR, updateGUI);
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::setOccupiedBandwidth(float occupiedBandwidth)
|
||||
void SpectrumMeasurements::setOccupiedBandwidth(float occupiedBandwidth, bool updateGUI)
|
||||
{
|
||||
updateMeasurement(0, occupiedBandwidth);
|
||||
updateMeasurement(0, occupiedBandwidth, updateGUI);
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::set3dBBandwidth(float bandwidth)
|
||||
void SpectrumMeasurements::set3dBBandwidth(float bandwidth, bool updateGUI)
|
||||
{
|
||||
updateMeasurement(0, bandwidth);
|
||||
updateMeasurement(0, bandwidth, updateGUI);
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::setPeak(int peak, int64_t frequency, float power)
|
||||
@ -696,3 +811,28 @@ void SpectrumMeasurements::setPeak(int peak, int64_t frequency, float power)
|
||||
qDebug() << "SpectrumMeasurements::setPeak: Attempt to set peak " << peak << " when only " << m_peakTable->rowCount() << " rows in peak table";
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumMeasurements::setMaskTestResult(int memoryIdx, qint64 count, qint64 fails)
|
||||
{
|
||||
int row = 0;
|
||||
|
||||
for (int i = 0; i < memoryIdx; i++)
|
||||
{
|
||||
if (m_memMask & (1 << i)) {
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
if (row < m_maskTable->rowCount())
|
||||
{
|
||||
QTableWidgetItem *testsItem = m_maskTable->item(row, COL_MASK_TESTS);
|
||||
QTableWidgetItem *failsItem = m_maskTable->item(row, COL_MASK_FAILS);
|
||||
|
||||
testsItem->setData(Qt::DisplayRole, count);
|
||||
failsItem->setData(Qt::DisplayRole, fails);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "SpectrumMeasurements::addMaskTestResult: Result for memory " << memoryIdx << " when only " << m_maskTable->rowCount() << " rows in mask table";
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,14 +91,15 @@ class SDRGUI_API SpectrumMeasurements : public QWidget {
|
||||
|
||||
public:
|
||||
SpectrumMeasurements(QWidget *parent = nullptr);
|
||||
void setMeasurementParams(SpectrumSettings::Measurement measurement, int peaks, int precision);
|
||||
void setSNR(float snr, float snfr, float thd, float thdpn, float sinad);
|
||||
void setSFDR(float sfdr);
|
||||
void setChannelPower(float power);
|
||||
void setAdjacentChannelPower(float left, float leftACPR, float center, float right, float rightACPR);
|
||||
void setOccupiedBandwidth(float occupiedBandwidth);
|
||||
void set3dBBandwidth(float bandwidth);
|
||||
void setMeasurementParams(SpectrumSettings::Measurement measurement, int peaks, int precision, unsigned memMasks);
|
||||
void setSNR(float snr, float snfr, float thd, float thdpn, float sinad, bool updateGUI);
|
||||
void setSFDR(float sfdr, bool updateGUI);
|
||||
void setChannelPower(float power, bool updateGUI);
|
||||
void setAdjacentChannelPower(float left, float leftACPR, float center, float right, float rightACPR, bool updateGUI);
|
||||
void setOccupiedBandwidth(float occupiedBandwidth, bool updateGUI);
|
||||
void set3dBBandwidth(float bandwidth, bool updateGUI);
|
||||
void setPeak(int peak, int64_t frequency, float power);
|
||||
void setMaskTestResult(int memoryIdx, qint64 count, qint64 fails);
|
||||
void reset();
|
||||
|
||||
private:
|
||||
@ -110,8 +111,10 @@ private:
|
||||
void createOccupiedBandwidthTable();
|
||||
void create3dBBandwidthTable();
|
||||
void createSNRTable();
|
||||
void createMaskTable(unsigned memMask);
|
||||
void tableContextMenu(QPoint pos);
|
||||
void peakTableContextMenu(QPoint pos);
|
||||
void maskTableContextMenu(QPoint pos);
|
||||
void rowSelectMenu(QPoint pos);
|
||||
void rowSelectMenuChecked(bool checked);
|
||||
void columnSelectMenu(QPoint pos);
|
||||
@ -119,11 +122,12 @@ private:
|
||||
QAction *createCheckableItem(QString &text, int idx, bool checked, bool row);
|
||||
void resizeMeasurementsTable();
|
||||
void resizePeakTable();
|
||||
void updateMeasurement(int row, float value);
|
||||
void updateMeasurement(int row, float value, bool updateGUI);
|
||||
bool checkSpec(const QString &spec, double value) const;
|
||||
|
||||
SpectrumSettings::Measurement m_measurement;
|
||||
int m_precision;
|
||||
unsigned m_memMask;
|
||||
|
||||
SpectrumMeasurementsTable *m_table;
|
||||
QMenu *m_rowMenu;
|
||||
@ -134,6 +138,8 @@ private:
|
||||
QBrush m_textBrush;
|
||||
QBrush m_redBrush;
|
||||
|
||||
SpectrumMeasurementsTable *m_maskTable;
|
||||
|
||||
enum MeasurementsCol {
|
||||
COL_CURRENT,
|
||||
COL_MEAN,
|
||||
@ -153,6 +159,11 @@ private:
|
||||
COL_PEAK_EMPTY
|
||||
};
|
||||
|
||||
enum MaskTableCol {
|
||||
COL_MASK_TESTS,
|
||||
COL_MASK_FAILS
|
||||
};
|
||||
|
||||
static const QStringList m_measurementColumns;
|
||||
static const QStringList m_tooltips;
|
||||
|
||||
|
||||
@ -116,6 +116,13 @@ SINAD is measured as per SNR, but the result is the ratio of the fundamental to
|
||||
|
||||
SFDR is a measurement of the difference in power from the largest peak (the fundamental) to the second largest peak (the strongest spurious signal).
|
||||
|
||||
<h2>Mask Test</h2>
|
||||
|
||||
The mask test measurement checks whether the spectrum exceeds a user-defined mask. The mask is defined by a set of frequency and power points, held in memory M1 or M2.
|
||||
Any points where the spectrum exceeds the mask are highlighted in red and held. The measurement results table shows the number of spectrum that fail the mask test.
|
||||
|
||||

|
||||
|
||||
<h2>Specifications</h2>
|
||||
|
||||
The measurements table has a Spec column that allows entry of user-defined specifications for the Current, Mean, Min and Max values to be checked against.
|
||||
|
||||
@ -57,6 +57,9 @@ SpectrumMeasurementsDialog::SpectrumMeasurementsDialog(GLSpectrum *glSpectrum, S
|
||||
ui->harmonics->setValue(m_settings->m_measurementHarmonics);
|
||||
ui->peaks->setValue(m_settings->m_measurementPeaks);
|
||||
|
||||
ui->m1Mask->setChecked(m_settings->m_measurementMemMasks & 1);
|
||||
ui->m2Mask->setChecked(m_settings->m_measurementMemMasks & 2);
|
||||
|
||||
displaySettings();
|
||||
}
|
||||
|
||||
@ -66,11 +69,12 @@ SpectrumMeasurementsDialog::~SpectrumMeasurementsDialog()
|
||||
void SpectrumMeasurementsDialog::displaySettings()
|
||||
{
|
||||
bool show = m_settings->m_measurement != SpectrumSettings::MeasurementNone;
|
||||
bool mask = m_settings->m_measurement == SpectrumSettings::MeasurementMask;
|
||||
|
||||
ui->positionLabel->setVisible(show);
|
||||
ui->position->setVisible(show);
|
||||
ui->precisionLabel->setVisible(show);
|
||||
ui->precision->setVisible(show);
|
||||
ui->precisionLabel->setVisible(show && !mask);
|
||||
ui->precision->setVisible(show && !mask);
|
||||
ui->highlightLabel->setVisible(show);
|
||||
ui->highlight->setVisible(show);
|
||||
|
||||
@ -98,8 +102,11 @@ void SpectrumMeasurementsDialog::displaySettings()
|
||||
bool peaks = (m_settings->m_measurement == SpectrumSettings::MeasurementPeaks);
|
||||
ui->peaksLabel->setVisible(peaks && show);
|
||||
ui->peaks->setVisible(peaks && show);
|
||||
}
|
||||
|
||||
ui->maskLabel->setVisible(mask);
|
||||
ui->m1Mask->setVisible(mask);
|
||||
ui->m2Mask->setVisible(mask);
|
||||
}
|
||||
|
||||
void SpectrumMeasurementsDialog::on_measurement_currentIndexChanged(int index)
|
||||
{
|
||||
@ -131,7 +138,7 @@ void SpectrumMeasurementsDialog::on_resetMeasurements_clicked(bool checked)
|
||||
(void) checked;
|
||||
|
||||
if (m_glSpectrum) {
|
||||
m_glSpectrum->getMeasurements()->reset();
|
||||
m_glSpectrum->resetMeasurements();
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,3 +177,17 @@ void SpectrumMeasurementsDialog::on_peaks_valueChanged(int value)
|
||||
m_settings->m_measurementPeaks = value;
|
||||
emit updateMeasurements();
|
||||
}
|
||||
|
||||
void SpectrumMeasurementsDialog::on_m1Mask_toggled(bool checked)
|
||||
{
|
||||
m_settings->m_measurementMemMasks &= ~1;
|
||||
m_settings->m_measurementMemMasks |= checked ? 1 : 0;
|
||||
emit updateMeasurements();
|
||||
}
|
||||
|
||||
void SpectrumMeasurementsDialog::on_m2Mask_toggled(bool checked)
|
||||
{
|
||||
m_settings->m_measurementMemMasks &= ~2;
|
||||
m_settings->m_measurementMemMasks |= checked ? 2 : 0;
|
||||
emit updateMeasurements();
|
||||
}
|
||||
|
||||
@ -58,6 +58,8 @@ private slots:
|
||||
void on_adjChBandwidth_changed(qint64 value);
|
||||
void on_harmonics_valueChanged(int value);
|
||||
void on_peaks_valueChanged(int value);
|
||||
void on_m1Mask_toggled(bool checked);
|
||||
void on_m2Mask_toggled(bool checked);
|
||||
|
||||
signals:
|
||||
void updateMeasurements();
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>302</height>
|
||||
<height>315</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
@ -28,105 +28,6 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="highlightLabel">
|
||||
<property name="text">
|
||||
<string>Highlight on spectrum</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QSpinBox" name="harmonics">
|
||||
<property name="toolTip">
|
||||
<string>Number of harmonics</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>20</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="ValueDialZ" name="centerFrequencyOffset" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>DejaVu Sans Mono</family>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Center frequency offset (Hz)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="chSpacingLabel">
|
||||
<property name="text">
|
||||
<string>Channel spacing</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="position">
|
||||
<property name="toolTip">
|
||||
<string>Where to display the measurements result table</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Above spectrum</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Below spectrum</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Left of spectrum</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Right of spectrum</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="peaksLabel">
|
||||
<property name="text">
|
||||
<string>Peaks</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="centerFrequencyOffsetLabel">
|
||||
<property name="text">
|
||||
<string>Center frequency offset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="measurement">
|
||||
<property name="toolTip">
|
||||
@ -170,119 +71,11 @@
|
||||
<string>SNR</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="positionLabel">
|
||||
<property name="text">
|
||||
<string>Display results table</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="bandwidthLabel">
|
||||
<property name="text">
|
||||
<string>Channel bandwidth</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="ValueDialZ" name="chSpacing" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>DejaVu Sans Mono</family>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Channel spacing (Hz)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="precisionLabel">
|
||||
<property name="text">
|
||||
<string>Results precision</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="ValueDialZ" name="bandwidth" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>DejaVu Sans Mono</family>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Channel bandwidth (Hz)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="measurementLabel">
|
||||
<property name="text">
|
||||
<string>Measurement</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="highlight">
|
||||
<property name="toolTip">
|
||||
<string>Whether to highlight the measurements in the spectrum</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="harmonicsLabel">
|
||||
<property name="text">
|
||||
<string>Harmonics</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Mask test</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
@ -298,26 +91,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="adjChBandwidthLabel">
|
||||
<property name="text">
|
||||
<string>Adjacent channel bandwidth</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="precision">
|
||||
<property name="toolTip">
|
||||
<string>Precision of results (number of decimal places)</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>9</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="ValueDialZ" name="adjChBandwidth" native="true">
|
||||
<property name="sizePolicy">
|
||||
@ -342,19 +115,301 @@
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
<enum>Qt::FocusPolicy::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Adjacent channel bandwidth (Hz)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="precisionLabel">
|
||||
<property name="text">
|
||||
<string>Results precision</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="harmonicsLabel">
|
||||
<property name="text">
|
||||
<string>Harmonics</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="precision">
|
||||
<property name="toolTip">
|
||||
<string>Precision of results (number of decimal places)</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>9</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<layout class="QHBoxLayout" name="maskLayout">
|
||||
<item>
|
||||
<widget class="QToolButton" name="m1Mask">
|
||||
<property name="toolTip">
|
||||
<string>Use memory 2 as mask</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>M1</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="m2Mask">
|
||||
<property name="toolTip">
|
||||
<string>Use memory 1 as mask</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>M2</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QSpinBox" name="harmonics">
|
||||
<property name="toolTip">
|
||||
<string>Number of harmonics</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>20</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="highlight">
|
||||
<property name="toolTip">
|
||||
<string>Whether to highlight the measurements in the spectrum</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="highlightLabel">
|
||||
<property name="text">
|
||||
<string>Highlight on spectrum</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="peaksLabel">
|
||||
<property name="text">
|
||||
<string>Peaks</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="centerFrequencyOffsetLabel">
|
||||
<property name="text">
|
||||
<string>Center frequency offset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="ValueDialZ" name="centerFrequencyOffset" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>DejaVu Sans Mono</family>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::FocusPolicy::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Center frequency offset (Hz)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="bandwidthLabel">
|
||||
<property name="text">
|
||||
<string>Channel bandwidth</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="ValueDialZ" name="bandwidth" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>DejaVu Sans Mono</family>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::FocusPolicy::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Channel bandwidth (Hz)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="chSpacingLabel">
|
||||
<property name="text">
|
||||
<string>Channel spacing</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="measurementLabel">
|
||||
<property name="text">
|
||||
<string>Measurement</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="position">
|
||||
<property name="toolTip">
|
||||
<string>Where to display the measurements result table</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Above spectrum</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Below spectrum</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Left of spectrum</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Right of spectrum</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="positionLabel">
|
||||
<property name="text">
|
||||
<string>Display results table</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="ValueDialZ" name="chSpacing" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>DejaVu Sans Mono</family>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::FocusPolicy::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Channel spacing (Hz)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="maskLabel">
|
||||
<property name="text">
|
||||
<string>Memories to use as masks</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="adjChBandwidthLabel">
|
||||
<property name="text">
|
||||
<string>Adjacent channel bandwidth</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@ -379,10 +434,10 @@
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
<set>QDialogButtonBox::StandardButton::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@ -3423,7 +3423,7 @@ void MainWindow::startAll()
|
||||
startAllDevices(workspace);
|
||||
}
|
||||
// Start all features
|
||||
for (int featureSetIndex = 0; featureSetIndex < m_featureUIs.size(); featureSetIndex++)
|
||||
for (std::size_t featureSetIndex = 0; featureSetIndex < m_featureUIs.size(); featureSetIndex++)
|
||||
{
|
||||
for (int featureIndex = 0; featureIndex < m_featureUIs[featureSetIndex]->getNumberOfFeatures(); featureIndex++) {
|
||||
FeatureWebAPIUtils::run(featureSetIndex, featureIndex);
|
||||
|
||||