diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5b07ed716..acecc559f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -127,7 +127,9 @@ set(sdrbase_SOURCES
sdrbase/dsp/basebandsamplesource.cpp
sdrbase/dsp/nullsink.cpp
sdrbase/dsp/spectrumscopecombovis.cpp
+ sdrbase/dsp/spectrumscopengcombovis.cpp
sdrbase/dsp/scopevis.cpp
+ sdrbase/dsp/scopevisng.cpp
sdrbase/dsp/spectrumvis.cpp
sdrbase/dsp/threadedbasebandsamplesink.cpp
sdrbase/dsp/threadedbasebandsamplesource.cpp
@@ -141,6 +143,8 @@ set(sdrbase_SOURCES
sdrbase/gui/cwkeyergui.cpp
sdrbase/gui/glscope.cpp
sdrbase/gui/glscopegui.cpp
+ sdrbase/gui/glscopeng.cpp
+ sdrbase/gui/glscopenggui.cpp
sdrbase/gui/glshadersimple.cpp
sdrbase/gui/glshadertextured.cpp
sdrbase/gui/glspectrum.cpp
@@ -237,7 +241,10 @@ set(sdrbase_HEADERS
sdrbase/dsp/basebandsamplesink.h
sdrbase/dsp/basebandsamplesource.h
sdrbase/dsp/nullsink.h
+ sdrbase/dsp/spectrumscopecombovis.h
+ sdrbase/dsp/spectrumscopengcombovis.h
sdrbase/dsp/scopevis.h
+ sdrbase/dsp/scopevisng.h
sdrbase/dsp/spectrumvis.h
sdrbase/dsp/threadedbasebandsamplesink.h
sdrbase/dsp/threadedbasebandsamplesource.h
@@ -251,6 +258,8 @@ set(sdrbase_HEADERS
sdrbase/gui/cwkeyergui.h
sdrbase/gui/glscope.h
sdrbase/gui/glscopegui.h
+ sdrbase/gui/glscopeng.h
+ sdrbase/gui/glscopenggui.h
sdrbase/gui/glshadersimple.h
sdrbase/gui/glshadertextured.h
sdrbase/gui/glspectrum.h
@@ -282,6 +291,7 @@ set(sdrbase_HEADERS
sdrbase/util/CRC64.h
sdrbase/util/db.h
+ sdrbase/util/doublebuffer.h
sdrbase/util/export.h
sdrbase/util/message.h
sdrbase/util/messagequeue.h
@@ -309,6 +319,7 @@ set(sdrbase_FORMS
sdrbase/gui/basicchannelsettingswidget.ui
sdrbase/gui/cwkeyergui.ui
sdrbase/gui/glscopegui.ui
+ sdrbase/gui/glscopenggui.ui
sdrbase/gui/glspectrumgui.ui
sdrbase/gui/pluginsdialog.ui
sdrbase/gui/audiodialog.ui
diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt
index 6fce4669e..757687b4d 100644
--- a/plugins/channelrx/CMakeLists.txt
+++ b/plugins/channelrx/CMakeLists.txt
@@ -2,6 +2,7 @@ project(demod)
add_subdirectory(demodlora)
add_subdirectory(demodam)
+add_subdirectory(demodatv)
add_subdirectory(demodbfm)
add_subdirectory(demodnfm)
add_subdirectory(demodssb)
@@ -9,6 +10,7 @@ add_subdirectory(tcpsrc)
add_subdirectory(udpsrc)
add_subdirectory(demodwfm)
add_subdirectory(chanalyzer)
+add_subdirectory(chanalyzerng)
if(LIBDSDCC_FOUND AND LIBMBE_FOUND)
add_subdirectory(demoddsd)
diff --git a/plugins/channelrx/chanalyzerng/CMakeLists.txt b/plugins/channelrx/chanalyzerng/CMakeLists.txt
new file mode 100644
index 000000000..efedaadc0
--- /dev/null
+++ b/plugins/channelrx/chanalyzerng/CMakeLists.txt
@@ -0,0 +1,45 @@
+project(chanalyzerng)
+
+set(chanalyzerng_SOURCES
+ chanalyzerng.cpp
+ chanalyzernggui.cpp
+ chanalyzerngplugin.cpp
+)
+
+set(chanalyzerng_HEADERS
+ chanalyzerng.h
+ chanalyzernggui.h
+ chanalyzerngplugin.h
+)
+
+set(chanalyzerng_FORMS
+ chanalyzernggui.ui
+)
+
+include_directories(
+ .
+ ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+#include(${QT_USE_FILE})
+add_definitions(${QT_DEFINITIONS})
+add_definitions(-DQT_PLUGIN)
+add_definitions(-DQT_SHARED)
+
+#qt5_wrap_cpp(chanalyzer_HEADERS_MOC ${chanalyzer_HEADERS})
+qt5_wrap_ui(chanalyzerng_FORMS_HEADERS ${chanalyzerng_FORMS})
+
+add_library(chanalyzerng SHARED
+ ${chanalyzerng_SOURCES}
+ ${chanalyzerng_HEADERS_MOC}
+ ${chanalyzerng_FORMS_HEADERS}
+)
+
+target_link_libraries(chanalyzerng
+ ${QT_LIBRARIES}
+ sdrbase
+)
+
+qt5_use_modules(chanalyzerng Core Widgets )
+
+install(TARGETS chanalyzerng DESTINATION lib/plugins/channelrx)
diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp
new file mode 100644
index 000000000..aa5a4cc76
--- /dev/null
+++ b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp
@@ -0,0 +1,206 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include "chanalyzerng.h"
+
+#include
+#include
+#include
+#include
+#include "audio/audiooutput.h"
+
+
+MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelAnalyzer, Message)
+
+ChannelAnalyzerNG::ChannelAnalyzerNG(BasebandSampleSink* sampleSink) :
+ m_sampleSink(sampleSink),
+ m_settingsMutex(QMutex::Recursive)
+{
+ m_Bandwidth = 5000;
+ m_LowCutoff = 300;
+ m_spanLog2 = 3;
+ m_sampleRate = 96000;
+ m_frequency = 0;
+ m_nco.setFreq(m_frequency, m_sampleRate);
+ m_undersampleCount = 0;
+ m_sum = 0;
+ m_usb = true;
+ m_ssb = true;
+ m_magsq = 0;
+ SSBFilter = new fftfilt(m_LowCutoff / m_sampleRate, m_Bandwidth / m_sampleRate, ssbFftLen);
+ DSBFilter = new fftfilt(m_Bandwidth / m_sampleRate, 2*ssbFftLen);
+}
+
+ChannelAnalyzerNG::~ChannelAnalyzerNG()
+{
+ if (SSBFilter) delete SSBFilter;
+ if (DSBFilter) delete DSBFilter;
+}
+
+void ChannelAnalyzerNG::configure(MessageQueue* messageQueue,
+ Real Bandwidth,
+ Real LowCutoff,
+ int spanLog2,
+ bool ssb)
+{
+ Message* cmd = MsgConfigureChannelAnalyzer::create(Bandwidth, LowCutoff, spanLog2, ssb);
+ messageQueue->push(cmd);
+}
+
+void ChannelAnalyzerNG::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly)
+{
+ fftfilt::cmplx *sideband;
+ int n_out;
+ int decim = 1<real() / 32768.0f, it->imag() / 32768.0f);
+ Complex c(it->real(), it->imag());
+ c *= m_nco.nextIQ();
+
+ if (m_ssb)
+ {
+ n_out = SSBFilter->runSSB(c, &sideband, m_usb);
+ }
+ else
+ {
+ n_out = DSBFilter->runDSB(c, &sideband);
+ }
+
+ for (int i = 0; i < n_out; i++)
+ {
+ // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display
+ // smart decimation with bit gain using float arithmetic (23 bits significand)
+
+ m_sum += sideband[i];
+
+ if (!(m_undersampleCount++ & decim_mask))
+ {
+ m_sum /= decim;
+ m_magsq = (m_sum.real() * m_sum.real() + m_sum.imag() * m_sum.imag())/ (1<<30);
+
+ if (m_ssb & !m_usb)
+ { // invert spectrum for LSB
+ //m_sampleBuffer.push_back(Sample(m_sum.imag() * 32768.0, m_sum.real() * 32768.0));
+ m_sampleBuffer.push_back(Sample(m_sum.imag(), m_sum.real()));
+ }
+ else
+ {
+ //m_sampleBuffer.push_back(Sample(m_sum.real() * 32768.0, m_sum.imag() * 32768.0));
+ m_sampleBuffer.push_back(Sample(m_sum.real(), m_sum.imag()));
+ }
+
+ m_sum = 0;
+ }
+ }
+ }
+
+ if(m_sampleSink != NULL)
+ {
+ m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), m_ssb); // m_ssb = positive only
+ }
+
+ m_sampleBuffer.clear();
+
+ m_settingsMutex.unlock();
+}
+
+void ChannelAnalyzerNG::start()
+{
+}
+
+void ChannelAnalyzerNG::stop()
+{
+}
+
+bool ChannelAnalyzerNG::handleMessage(const Message& cmd)
+{
+ float band, lowCutoff;
+
+ qDebug() << "ChannelAnalyzerNG::handleMessage";
+
+ if (DownChannelizer::MsgChannelizerNotification::match(cmd))
+ {
+ DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd;
+
+ m_sampleRate = notif.getSampleRate();
+ m_nco.setFreq(-notif.getFrequencyOffset(), m_sampleRate);
+
+ qDebug() << "ChannelAnalyzerNG::handleMessage: MsgChannelizerNotification: m_sampleRate: " << m_sampleRate
+ << " frequencyOffset: " << notif.getFrequencyOffset();
+
+ return true;
+ }
+ else if (MsgConfigureChannelAnalyzer::match(cmd))
+ {
+ MsgConfigureChannelAnalyzer& cfg = (MsgConfigureChannelAnalyzer&) cmd;
+
+ band = cfg.getBandwidth();
+ lowCutoff = cfg.getLoCutoff();
+
+ if (band < 0)
+ {
+ band = -band;
+ lowCutoff = -lowCutoff;
+ m_usb = false;
+ }
+ else
+ {
+ m_usb = true;
+ }
+
+ if (band < 100.0f)
+ {
+ band = 100.0f;
+ lowCutoff = 0;
+ }
+
+ m_settingsMutex.lock();
+
+ m_Bandwidth = band;
+ m_LowCutoff = lowCutoff;
+
+ SSBFilter->create_filter(m_LowCutoff / m_sampleRate, m_Bandwidth / m_sampleRate);
+ DSBFilter->create_dsb_filter(m_Bandwidth / m_sampleRate);
+
+ m_spanLog2 = cfg.getSpanLog2();
+ m_ssb = cfg.getSSB();
+
+ m_settingsMutex.unlock();
+
+ qDebug() << " - MsgConfigureChannelAnalyzer: m_Bandwidth: " << m_Bandwidth
+ << " m_LowCutoff: " << m_LowCutoff
+ << " m_spanLog2: " << m_spanLog2
+ << " m_ssb: " << m_ssb;
+
+ return true;
+ }
+ else
+ {
+ if (m_sampleSink != 0)
+ {
+ return m_sampleSink->handleMessage(cmd);
+ }
+ else
+ {
+ return false;
+ }
+ }
+}
diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.h b/plugins/channelrx/chanalyzerng/chanalyzerng.h
new file mode 100644
index 000000000..f6b36f20b
--- /dev/null
+++ b/plugins/channelrx/chanalyzerng/chanalyzerng.h
@@ -0,0 +1,105 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_CHANALYZERNG_H
+#define INCLUDE_CHANALYZERNG_H
+
+#include
+#include
+#include
+#include "dsp/ncof.h"
+#include "dsp/fftfilt.h"
+#include "audio/audiofifo.h"
+#include "util/message.h"
+
+#define ssbFftLen 1024
+
+class ChannelAnalyzerNG : public BasebandSampleSink {
+public:
+ ChannelAnalyzerNG(BasebandSampleSink* m_sampleSink);
+ virtual ~ChannelAnalyzerNG();
+
+ void configure(MessageQueue* messageQueue,
+ Real Bandwidth,
+ Real LowCutoff,
+ int spanLog2,
+ bool ssb);
+
+ int getSampleRate() const { return m_sampleRate; }
+ Real getMagSq() const { return m_magsq; }
+
+ virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
+ virtual void start();
+ virtual void stop();
+ virtual bool handleMessage(const Message& cmd);
+
+private:
+ class MsgConfigureChannelAnalyzer : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ Real getBandwidth() const { return m_Bandwidth; }
+ Real getLoCutoff() const { return m_LowCutoff; }
+ int getSpanLog2() const { return m_spanLog2; }
+ bool getSSB() const { return m_ssb; }
+
+ static MsgConfigureChannelAnalyzer* create(Real Bandwidth,
+ Real LowCutoff,
+ int spanLog2,
+ bool ssb)
+ {
+ return new MsgConfigureChannelAnalyzer(Bandwidth, LowCutoff, spanLog2, ssb);
+ }
+
+ private:
+ Real m_Bandwidth;
+ Real m_LowCutoff;
+ int m_spanLog2;
+ bool m_ssb;
+
+ MsgConfigureChannelAnalyzer(Real Bandwidth,
+ Real LowCutoff,
+ int spanLog2,
+ bool ssb) :
+ Message(),
+ m_Bandwidth(Bandwidth),
+ m_LowCutoff(LowCutoff),
+ m_spanLog2(spanLog2),
+ m_ssb(ssb)
+ { }
+ };
+
+ Real m_Bandwidth;
+ Real m_LowCutoff;
+ int m_spanLog2;
+ int m_undersampleCount;
+ fftfilt::cmplx m_sum;
+ int m_sampleRate;
+ int m_frequency;
+ bool m_usb;
+ bool m_ssb;
+ Real m_magsq;
+
+ NCOF m_nco;
+ fftfilt* SSBFilter;
+ fftfilt* DSBFilter;
+
+ BasebandSampleSink* m_sampleSink;
+ SampleVector m_sampleBuffer;
+ QMutex m_settingsMutex;
+};
+
+#endif // INCLUDE_CHANALYZERNG_H
diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.pro b/plugins/channelrx/chanalyzerng/chanalyzerng.pro
new file mode 100644
index 000000000..3bd87bbea
--- /dev/null
+++ b/plugins/channelrx/chanalyzerng/chanalyzerng.pro
@@ -0,0 +1,41 @@
+#--------------------------------------------------------
+#
+# Pro file for Android and Windows builds with Qt Creator
+#
+#--------------------------------------------------------
+
+TEMPLATE = lib
+CONFIG += plugin
+
+QT += core gui widgets multimedia opengl
+
+TARGET = chanalyzerng
+
+DEFINES += USE_SSE2=1
+QMAKE_CXXFLAGS += -msse2
+DEFINES += USE_SSE4_1=1
+QMAKE_CXXFLAGS += -msse4.1
+
+INCLUDEPATH += $$PWD
+INCLUDEPATH += ../../../sdrbase
+
+CONFIG(ANDROID):INCLUDEPATH += /opt/softs/boost_1_60_0
+CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0"
+CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0"
+
+CONFIG(Release):build_subdir = release
+CONFIG(Debug):build_subdir = debug
+
+SOURCES += chanalyzerng.cpp\
+ chanalyzernggui.cpp\
+ chanalyzerngplugin.cpp
+
+HEADERS += chanalyzerng.h\
+chanalyzernggui.h\
+chanalyzerngplugin.h
+
+FORMS += chanalyzernggui.ui
+
+LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase
+
+RESOURCES = ../../../sdrbase/resources/res.qrc
diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp
new file mode 100644
index 000000000..affaa6a85
--- /dev/null
+++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp
@@ -0,0 +1,485 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include "chanalyzernggui.h"
+
+#include
+#include
+#include
+#include
+
+#include "dsp/threadedbasebandsamplesink.h"
+#include "ui_chanalyzernggui.h"
+#include "dsp/spectrumscopengcombovis.h"
+#include "dsp/spectrumvis.h"
+#include "dsp/scopevis.h"
+#include "gui/glspectrum.h"
+#include "gui/glscopeng.h"
+#include "plugin/pluginapi.h"
+#include "util/simpleserializer.h"
+#include "util/db.h"
+#include "gui/basicchannelsettingswidget.h"
+#include "dsp/dspengine.h"
+#include "mainwindow.h"
+
+#include "chanalyzerng.h"
+
+const QString ChannelAnalyzerNGGUI::m_channelID = "sdrangel.channel.chanalyzerng";
+
+ChannelAnalyzerNGGUI* ChannelAnalyzerNGGUI::create(PluginAPI* pluginAPI, DeviceSourceAPI *deviceAPI)
+{
+ ChannelAnalyzerNGGUI* gui = new ChannelAnalyzerNGGUI(pluginAPI, deviceAPI);
+ return gui;
+}
+
+void ChannelAnalyzerNGGUI::destroy()
+{
+ delete this;
+}
+
+void ChannelAnalyzerNGGUI::setName(const QString& name)
+{
+ setObjectName(name);
+}
+
+QString ChannelAnalyzerNGGUI::getName() const
+{
+ return objectName();
+}
+
+qint64 ChannelAnalyzerNGGUI::getCenterFrequency() const
+{
+ return m_channelMarker.getCenterFrequency();
+}
+
+void ChannelAnalyzerNGGUI::setCenterFrequency(qint64 centerFrequency)
+{
+ m_channelMarker.setCenterFrequency(centerFrequency);
+ applySettings();
+}
+
+void ChannelAnalyzerNGGUI::resetToDefaults()
+{
+ blockApplySettings(true);
+
+ ui->BW->setValue(30);
+ ui->deltaFrequency->setValue(0);
+ ui->spanLog2->setValue(3);
+
+ blockApplySettings(false);
+ applySettings();
+}
+
+QByteArray ChannelAnalyzerNGGUI::serialize() const
+{
+ SimpleSerializer s(1);
+ s.writeS32(1, m_channelMarker.getCenterFrequency());
+ s.writeS32(2, ui->BW->value());
+ s.writeBlob(3, ui->spectrumGUI->serialize());
+ s.writeU32(4, m_channelMarker.getColor().rgb());
+ s.writeS32(5, ui->lowCut->value());
+ s.writeS32(6, ui->spanLog2->value());
+ s.writeBool(7, ui->ssb->isChecked());
+ s.writeBlob(8, ui->scopeGUI->serialize());
+ return s.final();
+}
+
+bool ChannelAnalyzerNGGUI::deserialize(const QByteArray& data)
+{
+ SimpleDeserializer d(data);
+
+ if(!d.isValid())
+ {
+ resetToDefaults();
+ return false;
+ }
+
+ if(d.getVersion() == 1)
+ {
+ QByteArray bytetmp;
+ quint32 u32tmp;
+ qint32 tmp, bw, lowCut;
+ bool tmpBool;
+
+ blockApplySettings(true);
+ m_channelMarker.blockSignals(true);
+
+ d.readS32(1, &tmp, 0);
+ m_channelMarker.setCenterFrequency(tmp);
+ d.readS32(2, &bw, 30);
+ ui->BW->setValue(bw);
+ d.readBlob(3, &bytetmp);
+ ui->spectrumGUI->deserialize(bytetmp);
+
+ if(d.readU32(4, &u32tmp))
+ {
+ m_channelMarker.setColor(u32tmp);
+ }
+
+ d.readS32(5, &lowCut, 3);
+ ui->lowCut->setValue(lowCut);
+ d.readS32(6, &tmp, 20);
+ ui->spanLog2->setValue(tmp);
+ setNewRate(tmp);
+ d.readBool(7, &tmpBool, false);
+ ui->ssb->setChecked(tmpBool);
+ d.readBlob(8, &bytetmp);
+ ui->scopeGUI->deserialize(bytetmp);
+
+ blockApplySettings(false);
+ m_channelMarker.blockSignals(false);
+
+ ui->BW->setValue(bw);
+ ui->lowCut->setValue(lowCut); // does applySettings();
+
+ return true;
+ }
+ else
+ {
+ resetToDefaults();
+ return false;
+ }
+}
+
+bool ChannelAnalyzerNGGUI::handleMessage(const Message& message)
+{
+ return false;
+}
+
+void ChannelAnalyzerNGGUI::viewChanged()
+{
+ applySettings();
+}
+
+void ChannelAnalyzerNGGUI::tick()
+{
+ Real powDb = CalcDb::dbPower(m_channelAnalyzer->getMagSq());
+ m_channelPowerDbAvg.feed(powDb);
+ ui->channelPower->setText(QString::number(m_channelPowerDbAvg.average(), 'f', 1));
+}
+
+void ChannelAnalyzerNGGUI::channelSampleRateChanged()
+{
+ setNewRate(m_spanLog2);
+}
+
+void ChannelAnalyzerNGGUI::on_deltaMinus_toggled(bool minus)
+{
+ int deltaFrequency = m_channelMarker.getCenterFrequency();
+ bool minusDelta = (deltaFrequency < 0);
+
+ if (minus ^ minusDelta) // sign change
+ {
+ m_channelMarker.setCenterFrequency(-deltaFrequency);
+ }
+}
+
+void ChannelAnalyzerNGGUI::on_deltaFrequency_changed(quint64 value)
+{
+ if (ui->deltaMinus->isChecked()) {
+ m_channelMarker.setCenterFrequency(-value);
+ } else {
+ m_channelMarker.setCenterFrequency(value);
+ }
+}
+
+void ChannelAnalyzerNGGUI::on_BW_valueChanged(int value)
+{
+ QString s = QString::number(value/10.0, 'f', 1);
+ ui->BWText->setText(tr("%1k").arg(s));
+ m_channelMarker.setBandwidth(value * 100 * 2);
+
+ if (ui->ssb->isChecked())
+ {
+ if (value < 0) {
+ m_channelMarker.setSidebands(ChannelMarker::lsb);
+ } else {
+ m_channelMarker.setSidebands(ChannelMarker::usb);
+ }
+ }
+ else
+ {
+ m_channelMarker.setSidebands(ChannelMarker::dsb);
+ }
+
+ on_lowCut_valueChanged(m_channelMarker.getLowCutoff()/100);
+}
+
+int ChannelAnalyzerNGGUI::getEffectiveLowCutoff(int lowCutoff)
+{
+ int ssbBW = m_channelMarker.getBandwidth() / 2;
+ int effectiveLowCutoff = lowCutoff;
+ const int guard = 100;
+
+ if (ssbBW < 0) {
+ if (effectiveLowCutoff < ssbBW + guard) {
+ effectiveLowCutoff = ssbBW + guard;
+ }
+ if (effectiveLowCutoff > 0) {
+ effectiveLowCutoff = 0;
+ }
+ } else {
+ if (effectiveLowCutoff > ssbBW - guard) {
+ effectiveLowCutoff = ssbBW - guard;
+ }
+ if (effectiveLowCutoff < 0) {
+ effectiveLowCutoff = 0;
+ }
+ }
+
+ return effectiveLowCutoff;
+}
+
+void ChannelAnalyzerNGGUI::on_lowCut_valueChanged(int value)
+{
+ int lowCutoff = getEffectiveLowCutoff(value * 100);
+ m_channelMarker.setLowCutoff(lowCutoff);
+ QString s = QString::number(lowCutoff/1000.0, 'f', 1);
+ ui->lowCutText->setText(tr("%1k").arg(s));
+ ui->lowCut->setValue(lowCutoff/100);
+ applySettings();
+}
+
+void ChannelAnalyzerNGGUI::on_spanLog2_valueChanged(int value)
+{
+ if (setNewRate(value)) {
+ applySettings();
+ }
+
+}
+
+void ChannelAnalyzerNGGUI::on_ssb_toggled(bool checked)
+{
+ if (checked)
+ {
+ if (ui->BW->value() < 0) {
+ m_channelMarker.setSidebands(ChannelMarker::lsb);
+ } else {
+ m_channelMarker.setSidebands(ChannelMarker::usb);
+ }
+
+ ui->glSpectrum->setCenterFrequency(m_rate/4);
+ ui->glSpectrum->setSampleRate(m_rate/2);
+ ui->glSpectrum->setSsbSpectrum(true);
+
+ on_lowCut_valueChanged(m_channelMarker.getLowCutoff()/100);
+ }
+ else
+ {
+ m_channelMarker.setSidebands(ChannelMarker::dsb);
+
+ ui->glSpectrum->setCenterFrequency(0);
+ ui->glSpectrum->setSampleRate(m_rate);
+ ui->glSpectrum->setSsbSpectrum(false);
+
+ applySettings();
+ }
+}
+
+void ChannelAnalyzerNGGUI::onWidgetRolled(QWidget* widget, bool rollDown)
+{
+ /*
+ if((widget == ui->spectrumContainer) && (m_ssbDemod != NULL))
+ m_ssbDemod->setSpectrum(m_threadedSampleSink->getMessageQueue(), rollDown);
+ */
+}
+
+void ChannelAnalyzerNGGUI::onMenuDoubleClicked()
+{
+ if(!m_basicSettingsShown) {
+ m_basicSettingsShown = true;
+ BasicChannelSettingsWidget* bcsw = new BasicChannelSettingsWidget(&m_channelMarker, this);
+ bcsw->show();
+ }
+}
+
+ChannelAnalyzerNGGUI::ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceSourceAPI *deviceAPI, QWidget* parent) :
+ RollupWidget(parent),
+ ui(new Ui::ChannelAnalyzerNGGUI),
+ m_pluginAPI(pluginAPI),
+ m_deviceAPI(deviceAPI),
+ m_channelMarker(this),
+ m_basicSettingsShown(false),
+ m_doApplySettings(true),
+ m_rate(6000),
+ m_spanLog2(3),
+ m_channelPowerDbAvg(40,0)
+{
+ ui->setupUi(this);
+ setAttribute(Qt::WA_DeleteOnClose, true);
+ connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
+ connect(this, SIGNAL(menuDoubleClickEvent()), this, SLOT(onMenuDoubleClicked()));
+
+ m_spectrumVis = new SpectrumVis(ui->glSpectrum);
+ m_scopeVis = new ScopeVisNG(ui->glScope);
+ m_spectrumScopeComboVis = new SpectrumScopeNGComboVis(m_spectrumVis, m_scopeVis);
+ m_channelAnalyzer = new ChannelAnalyzerNG(m_spectrumScopeComboVis);
+ m_channelizer = new DownChannelizer(m_channelAnalyzer);
+ m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this);
+ connect(m_channelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged()));
+ m_deviceAPI->addThreadedSink(m_threadedChannelizer);
+
+ ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::ReverseGold));
+ ui->deltaFrequency->setValueRange(7, 0U, 9999999U);
+
+ ui->glSpectrum->setCenterFrequency(m_rate/2);
+ ui->glSpectrum->setSampleRate(m_rate);
+ ui->glSpectrum->setDisplayWaterfall(true);
+ ui->glSpectrum->setDisplayMaxHold(true);
+ ui->glSpectrum->setSsbSpectrum(true);
+
+ ui->glSpectrum->connectTimer(m_pluginAPI->getMainWindow()->getMasterTimer());
+ ui->glScope->connectTimer(m_pluginAPI->getMainWindow()->getMasterTimer());
+ connect(&m_pluginAPI->getMainWindow()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
+
+ //m_channelMarker = new ChannelMarker(this);
+ m_channelMarker.setColor(Qt::gray);
+ m_channelMarker.setBandwidth(m_rate);
+ m_channelMarker.setSidebands(ChannelMarker::usb);
+ m_channelMarker.setCenterFrequency(0);
+ m_channelMarker.setVisible(true);
+
+ connect(&m_channelMarker, SIGNAL(changed()), this, SLOT(viewChanged()));
+
+ m_deviceAPI->registerChannelInstance(m_channelID, this);
+ m_deviceAPI->addChannelMarker(&m_channelMarker);
+ m_deviceAPI->addRollupWidget(this);
+
+ ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum);
+ ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope);
+
+ applySettings();
+ setNewRate(m_spanLog2);
+}
+
+ChannelAnalyzerNGGUI::~ChannelAnalyzerNGGUI()
+{
+ m_deviceAPI->removeChannelInstance(this);
+ m_deviceAPI->removeThreadedSink(m_threadedChannelizer);
+ delete m_threadedChannelizer;
+ delete m_channelizer;
+ delete m_channelAnalyzer;
+ delete m_spectrumVis;
+ delete m_scopeVis;
+ delete m_spectrumScopeComboVis;
+ //delete m_channelMarker;
+ delete ui;
+}
+
+bool ChannelAnalyzerNGGUI::setNewRate(int spanLog2)
+{
+ qDebug("ChannelAnalyzerNGGUI::setNewRate");
+
+ if ((spanLog2 < 0) || (spanLog2 > 6)) {
+ return false;
+ }
+
+ m_spanLog2 = spanLog2;
+ //m_rate = 48000 / (1<getSampleRate() / (1<BW->value() < -m_rate/200) {
+ ui->BW->setValue(-m_rate/200);
+ m_channelMarker.setBandwidth(-m_rate*2);
+ } else if (ui->BW->value() > m_rate/200) {
+ ui->BW->setValue(m_rate/200);
+ m_channelMarker.setBandwidth(m_rate*2);
+ }
+
+ if (ui->lowCut->value() < -m_rate/200) {
+ ui->lowCut->setValue(-m_rate/200);
+ m_channelMarker.setLowCutoff(-m_rate);
+ } else if (ui->lowCut->value() > m_rate/200) {
+ ui->lowCut->setValue(m_rate/200);
+ m_channelMarker.setLowCutoff(m_rate);
+ }
+
+ ui->BW->setMinimum(-m_rate/200);
+ ui->lowCut->setMinimum(-m_rate/200);
+ ui->BW->setMaximum(m_rate/200);
+ ui->lowCut->setMaximum(m_rate/200);
+
+ QString s = QString::number(m_rate/1000.0, 'f', 1);
+ ui->spanText->setText(tr("%1k").arg(s));
+
+ if (ui->ssb->isChecked())
+ {
+ if (ui->BW->value() < 0) {
+ m_channelMarker.setSidebands(ChannelMarker::lsb);
+ } else {
+ m_channelMarker.setSidebands(ChannelMarker::usb);
+ }
+
+ ui->glSpectrum->setCenterFrequency(m_rate/4);
+ ui->glSpectrum->setSampleRate(m_rate/2);
+ ui->glSpectrum->setSsbSpectrum(true);
+ }
+ else
+ {
+ m_channelMarker.setSidebands(ChannelMarker::dsb);
+
+ ui->glSpectrum->setCenterFrequency(0);
+ ui->glSpectrum->setSampleRate(m_rate);
+ ui->glSpectrum->setSsbSpectrum(false);
+ }
+
+ ui->glScope->setSampleRate(m_rate);
+ //m_scopeVis->setSampleRate(m_rate); TODO: not needed anymore?
+
+ return true;
+}
+
+void ChannelAnalyzerNGGUI::blockApplySettings(bool block)
+{
+ ui->glScope->blockSignals(block);
+ ui->glSpectrum->blockSignals(block);
+ m_doApplySettings = !block;
+}
+
+void ChannelAnalyzerNGGUI::applySettings()
+{
+ if (m_doApplySettings)
+ {
+ setTitleColor(m_channelMarker.getColor());
+ ui->deltaFrequency->setValue(abs(m_channelMarker.getCenterFrequency()));
+ ui->deltaMinus->setChecked(m_channelMarker.getCenterFrequency() < 0);
+
+ m_channelizer->configure(m_channelizer->getInputMessageQueue(),
+ m_channelizer->getInputSampleRate(),
+ m_channelMarker.getCenterFrequency());
+
+ m_channelAnalyzer->configure(m_channelAnalyzer->getInputMessageQueue(),
+ ui->BW->value() * 100.0,
+ ui->lowCut->value() * 100.0,
+ m_spanLog2,
+ ui->ssb->isChecked());
+ }
+}
+
+void ChannelAnalyzerNGGUI::leaveEvent(QEvent*)
+{
+ blockApplySettings(true);
+ m_channelMarker.setHighlighted(false);
+ blockApplySettings(false);
+}
+
+void ChannelAnalyzerNGGUI::enterEvent(QEvent*)
+{
+ blockApplySettings(true);
+ m_channelMarker.setHighlighted(true);
+ blockApplySettings(false);
+}
+
diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.h b/plugins/channelrx/chanalyzerng/chanalyzernggui.h
new file mode 100644
index 000000000..814e13676
--- /dev/null
+++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.h
@@ -0,0 +1,103 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_CHANNELANALYZERNGGUI_H
+#define INCLUDE_CHANNELANALYZERNGGUI_H
+
+#include "gui/rollupwidget.h"
+#include "plugin/plugingui.h"
+#include "dsp/channelmarker.h"
+#include "dsp/movingaverage.h"
+
+class PluginAPI;
+class DeviceSourceAPI;
+
+class ThreadedBasebandSampleSink;
+class DownChannelizer;
+class ChannelAnalyzerNG;
+class SpectrumScopeNGComboVis;
+class SpectrumVis;
+class ScopeVisNG;
+
+namespace Ui {
+ class ChannelAnalyzerNGGUI;
+}
+
+class ChannelAnalyzerNGGUI : public RollupWidget, public PluginGUI {
+ Q_OBJECT
+
+public:
+ static ChannelAnalyzerNGGUI* create(PluginAPI* pluginAPI, DeviceSourceAPI *deviceAPI);
+ void destroy();
+
+ void setName(const QString& name);
+ QString getName() const;
+ virtual qint64 getCenterFrequency() const;
+ virtual void setCenterFrequency(qint64 centerFrequency);
+
+ void resetToDefaults();
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+
+ virtual bool handleMessage(const Message& message);
+
+ static const QString m_channelID;
+
+private slots:
+ void viewChanged();
+ void channelSampleRateChanged();
+ void on_deltaFrequency_changed(quint64 value);
+ void on_deltaMinus_toggled(bool minus);
+ void on_BW_valueChanged(int value);
+ void on_lowCut_valueChanged(int value);
+ void on_spanLog2_valueChanged(int value);
+ void on_ssb_toggled(bool checked);
+ void onWidgetRolled(QWidget* widget, bool rollDown);
+ void onMenuDoubleClicked();
+ void tick();
+
+private:
+ Ui::ChannelAnalyzerNGGUI* ui;
+ PluginAPI* m_pluginAPI;
+ DeviceSourceAPI* m_deviceAPI;
+ ChannelMarker m_channelMarker;
+ bool m_basicSettingsShown;
+ bool m_doApplySettings;
+ int m_rate;
+ int m_spanLog2;
+ MovingAverage m_channelPowerDbAvg;
+
+ ThreadedBasebandSampleSink* m_threadedChannelizer;
+ DownChannelizer* m_channelizer;
+ ChannelAnalyzerNG* m_channelAnalyzer;
+ SpectrumScopeNGComboVis* m_spectrumScopeComboVis;
+ SpectrumVis* m_spectrumVis;
+ ScopeVisNG* m_scopeVis;
+
+ explicit ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceSourceAPI *deviceAPI, QWidget* parent = NULL);
+ virtual ~ChannelAnalyzerNGGUI();
+
+ int getEffectiveLowCutoff(int lowCutoff);
+ bool setNewRate(int spanLog2);
+
+ void blockApplySettings(bool block);
+ void applySettings();
+
+ void leaveEvent(QEvent*);
+ void enterEvent(QEvent*);
+};
+
+#endif // INCLUDE_CHANNELANALYZERNGGUI_H
diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui
new file mode 100644
index 000000000..3fbc03f3a
--- /dev/null
+++ b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui
@@ -0,0 +1,540 @@
+
+
+ ChannelAnalyzerNGGUI
+
+
+
+ 0
+ 0
+ 640
+ 814
+
+
+
+
+ 640
+ 0
+
+
+
+
+ Sans Serif
+ 9
+
+
+
+ Channel Analyzer NG
+
+
+
+
+ 0
+ 10
+ 301
+ 131
+
+
+
+ Settings
+
+
+
+ 3
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+ -
+
+
-
+
+
-
+
+
+ Frequency shift direction
+
+
+ ...
+
+
+
+ :/plus.png
+ :/minus.png
+
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 16
+
+
+
+
+ Monospace
+ 12
+
+
+
+ SizeVerCursor
+
+
+ Qt::StrongFocus
+
+
+ Demod shift frequency from center in Hz
+
+
+
+ -
+
+
+
+
+
+
+
+ 26
+ 26
+ 26
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 26
+ 26
+ 26
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 118
+ 118
+ 117
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+ Hz
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
-
+
+
+ Channel power
+
+
+ Qt::LeftToRight
+
+
+ 0.0
+
+
+
+ -
+
+
+ dB
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Rate
+
+
+
+ -
+
+
+ Channel sample rate
+
+
+ 0
+
+
+ 6
+
+
+ 1
+
+
+ 3
+
+
+ 3
+
+
+ Qt::Horizontal
+
+
+ true
+
+
+ true
+
+
+
+ -
+
+
+ 6.0k
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ SSB/DSB togggle
+
+
+ SSB
+
+
+
+
+
+ -
+
+
-
+
+
+ BW
+
+
+
+ -
+
+
+ Lowpass filter cutoff frequency
+
+
+ -60
+
+
+ 60
+
+
+ 1
+
+
+ 30
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+ 3.0k
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
-
+
+
+ Low cut.
+
+
+
+ -
+
+
+ Highpass filter cutoff frequency (SSB)
+
+
+ -60
+
+
+ 60
+
+
+ 1
+
+
+ 3
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+ 0.3k
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+
+
+
+
+
+ 10
+ 180
+ 636
+ 284
+
+
+
+
+ 636
+ 0
+
+
+
+ Channel Spectrum
+
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+ -
+
+
+
+ 200
+ 250
+
+
+
+
+ Monospace
+ 8
+
+
+
+
+ -
+
+
+
+
+
+
+
+ 0
+ 470
+ 636
+ 284
+
+
+
+
+ 636
+ 0
+
+
+
+ Channel Scope
+
+
+
+ 2
+
+
+ 3
+
+
+ 3
+
+
+ 3
+
+
+ 3
+
+ -
+
+
+
+ 200
+ 250
+
+
+
+
+ Monospace
+ 8
+
+
+
+
+ -
+
+
+
+
+
+
+
+ RollupWidget
+ QWidget
+
+ 1
+
+
+ ValueDial
+ QWidget
+
+ 1
+
+
+ GLSpectrum
+ QWidget
+
+ 1
+
+
+ GLSpectrumGUI
+ QWidget
+
+ 1
+
+
+ GLScopeNG
+ QWidget
+
+ 1
+
+
+ GLScopeNGGUI
+ QWidget
+
+ 1
+
+
+
+
+
+
+
diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp b/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp
new file mode 100644
index 000000000..0a5d24890
--- /dev/null
+++ b/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp
@@ -0,0 +1,64 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#include "plugin/pluginapi.h"
+#include "chanalyzerngplugin.h"
+#include "chanalyzernggui.h"
+
+const PluginDescriptor ChannelAnalyzerNGPlugin::m_pluginDescriptor = {
+ QString("Channel Analyzer NG"),
+ QString("3.2.0"),
+ QString("(c) Edouard Griffiths, F4EXB"),
+ QString("https://github.com/f4exb/sdrangel"),
+ true,
+ QString("https://github.com/f4exb/sdrangel")
+};
+
+ChannelAnalyzerNGPlugin::ChannelAnalyzerNGPlugin(QObject* parent) :
+ QObject(parent)
+{
+}
+
+const PluginDescriptor& ChannelAnalyzerNGPlugin::getPluginDescriptor() const
+{
+ return m_pluginDescriptor;
+}
+
+void ChannelAnalyzerNGPlugin::initPlugin(PluginAPI* pluginAPI)
+{
+ m_pluginAPI = pluginAPI;
+
+ // register demodulator
+ m_pluginAPI->registerRxChannel(ChannelAnalyzerNGGUI::m_channelID, this);
+}
+
+PluginGUI* ChannelAnalyzerNGPlugin::createRxChannel(const QString& channelName, DeviceSourceAPI *deviceAPI)
+{
+ if(channelName == ChannelAnalyzerNGGUI::m_channelID)
+ {
+ ChannelAnalyzerNGGUI* gui = ChannelAnalyzerNGGUI::create(m_pluginAPI, deviceAPI);
+ return gui;
+ } else {
+ return NULL;
+ }
+}
+
+void ChannelAnalyzerNGPlugin::createInstanceChannelAnalyzer(DeviceSourceAPI *deviceAPI)
+{
+ ChannelAnalyzerNGGUI* gui = ChannelAnalyzerNGGUI::create(m_pluginAPI, deviceAPI);
+}
diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.h b/plugins/channelrx/chanalyzerng/chanalyzerngplugin.h
new file mode 100644
index 000000000..65d126e4b
--- /dev/null
+++ b/plugins/channelrx/chanalyzerng/chanalyzerngplugin.h
@@ -0,0 +1,48 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_CHANALYZERNGPLUGIN_H
+#define INCLUDE_CHANALYZERNGPLUGIN_H
+
+#include
+
+#include "plugin/plugininterface.h"
+
+class DeviceSourceAPI;
+
+class ChannelAnalyzerNGPlugin : public QObject, PluginInterface {
+ Q_OBJECT
+ Q_INTERFACES(PluginInterface)
+ Q_PLUGIN_METADATA(IID "sdrangel.channel.chanalyzerng")
+
+public:
+ explicit ChannelAnalyzerNGPlugin(QObject* parent = NULL);
+
+ const PluginDescriptor& getPluginDescriptor() const;
+ void initPlugin(PluginAPI* pluginAPI);
+
+ PluginGUI* createRxChannel(const QString& channelName, DeviceSourceAPI *deviceAPI);
+
+private:
+ static const PluginDescriptor m_pluginDescriptor;
+
+ PluginAPI* m_pluginAPI;
+
+private slots:
+ void createInstanceChannelAnalyzer(DeviceSourceAPI *deviceAPI);
+};
+
+#endif // INCLUDE_CHANALYZERNGPLUGIN_H
diff --git a/sdrbase/dsp/scopevisng.cpp b/sdrbase/dsp/scopevisng.cpp
new file mode 100644
index 000000000..b2a78d20f
--- /dev/null
+++ b/sdrbase/dsp/scopevisng.cpp
@@ -0,0 +1,299 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 F4EXB //
+// written by Edouard Griffiths //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+#include "scopevisng.h"
+#include "gui/glscopeng.h"
+
+MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgConfigureScopeVisNG, Message)
+MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGAddTrigger, Message)
+MESSAGE_CLASS_DEFINITION(ScopeVisNG::MsgScopeVisNGRemoveTrigger, Message)
+
+const uint ScopeVisNG::m_traceChunkSize = 4800;
+const Real ScopeVisNG::ProjectorMagDB::mult = (10.0f / log2f(10.0f));
+
+
+ScopeVisNG::ScopeVisNG(GLScopeNG* glScope) :
+ m_glScope(glScope),
+ m_preTriggerDelay(0),
+ m_currentTriggerIndex(0),
+ m_triggerState(TriggerUntriggered),
+ m_traceSize(m_traceChunkSize),
+ m_traceStart(true),
+ m_traceFill(0),
+ m_zTraceIndex(-1),
+ m_traceCompleteCount(0)
+{
+ setObjectName("ScopeVisNG");
+ m_tracebackBuffers.resize(1);
+ m_tracebackBuffers[0].resize(4*m_traceChunkSize);
+}
+
+ScopeVisNG::~ScopeVisNG()
+{
+ std::vector::iterator it = m_triggerConditions.begin();
+
+ for (; it != m_triggerConditions.end(); ++it)
+ {
+ delete it->m_projector;
+ }
+}
+
+void ScopeVisNG::configure(MessageQueue* msgQueue,
+ uint traceSize)
+{
+ Message* cmd = MsgConfigureScopeVisNG::create(traceSize);
+ msgQueue->push(cmd);
+}
+
+
+void ScopeVisNG::feed(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, bool positiveOnly)
+{
+ uint32_t feedIndex = 0; // TODO: redefine feed interface so it can be passed a feed index
+
+ if (m_triggerState == TriggerFreeRun) {
+ m_triggerPoint = cbegin;
+ }
+ else if (m_triggerState == TriggerTriggered) {
+ m_triggerPoint = cbegin;
+ }
+ else if (m_triggerState == TriggerUntriggered) {
+ m_triggerPoint = end;
+ }
+ else if (m_triggerState == TriggerWait) {
+ m_triggerPoint = end;
+ }
+ else {
+ m_triggerPoint = cbegin;
+ }
+
+ if (m_triggerState == TriggerNewConfig)
+ {
+ m_triggerState = TriggerUntriggered;
+ return;
+ }
+
+ if ((m_triggerConditions.size() > 0) && (m_triggerState == TriggerWait)) {
+ return;
+ }
+
+ m_tracebackBuffers[feedIndex].write(cbegin, end);
+ SampleVector::const_iterator begin(cbegin);
+ TriggerCondition& triggerCondition = m_triggerConditions[m_currentTriggerIndex];
+
+ // trigger process
+ if ((m_triggerConditions.size() > 0) && (feedIndex == triggerCondition.m_inputIndex))
+ {
+ while (begin < end)
+ {
+ if (m_triggerState == TriggerUntriggered)
+ {
+ bool condition = triggerCondition.m_projector->run(*begin) > triggerCondition.m_triggerLevel;
+ bool trigger;
+
+ if (triggerCondition.m_triggerBothEdges) {
+ trigger = triggerCondition.m_prevCondition ^ condition;
+ } else {
+ trigger = condition ^ !triggerCondition.m_triggerPositiveEdge;
+ }
+
+ if (trigger)
+ {
+ if (triggerCondition.m_triggerDelay > 0)
+ {
+ triggerCondition.m_triggerDelayCount = triggerCondition.m_triggerDelay;
+ m_triggerState == TriggerDelay;
+ }
+ else
+ {
+ if (triggerCondition.m_triggerCounts > 0)
+ {
+ triggerCondition.m_triggerCounts--;
+ m_triggerState = TriggerUntriggered;
+ }
+ else
+ {
+ // next trigger
+ m_currentTriggerIndex++;
+
+ if (m_currentTriggerIndex == m_triggerConditions.size())
+ {
+ m_currentTriggerIndex = 0;
+ m_triggerState = TriggerTriggered;
+ m_triggerPoint = begin;
+ m_traceStart = true;
+ break;
+ }
+ else
+ {
+ m_triggerState = TriggerUntriggered;
+ }
+ }
+ }
+ }
+ }
+ else if (m_triggerState == TriggerDelay)
+ {
+ if (triggerCondition.m_triggerDelayCount > 0)
+ {
+ triggerCondition.m_triggerDelayCount--;
+ }
+ else
+ {
+ triggerCondition.m_triggerDelayCount = 0;
+
+ // next trigger
+ m_currentTriggerIndex++;
+
+ if (m_currentTriggerIndex == m_triggerConditions.size())
+ {
+ m_currentTriggerIndex = 0;
+ m_triggerState = TriggerTriggered;
+ m_triggerPoint = begin;
+ m_traceStart = true;
+ break;
+ }
+ else
+ {
+ // initialize a new trace
+ m_triggerState = TriggerUntriggered;
+ m_traceCompleteCount = 0;
+ m_triggerState = TriggerUntriggered;
+
+ feed(begin, end, positiveOnly); // process the rest of samples
+ }
+ }
+ }
+ else
+ {
+ break;
+ }
+
+ ++begin;
+ } // begin < end
+ }
+
+ // trace process
+ if ((m_triggerConditions.size() == 0) || (m_triggerState == TriggerTriggered))
+ {
+ // trace back
+
+ if (m_traceStart)
+ {
+ int count = begin - cbegin; // number of samples consumed since begin
+ std::vector::iterator itTrace = m_traces.begin();
+
+ for (;itTrace != m_traces.end(); ++itTrace)
+ {
+ if (itTrace->m_inputIndex == feedIndex)
+ {
+ SampleVector::const_iterator startPoint = m_tracebackBuffers[feedIndex].getCurrent() - count;
+ SampleVector::const_iterator prevPoint = m_tracebackBuffers[feedIndex].getCurrent() - count - m_preTriggerDelay - itTrace->m_traceDelay;
+ processPrevTrace(prevPoint, startPoint, itTrace);
+ }
+ }
+
+ m_traceStart = false;
+ }
+
+ // live trace
+
+ int shift = (m_timeOfsProMill / 1000.0) * m_traceSize;
+
+ while (begin < end)
+ {
+ for (std::vector::iterator itTrace = m_traces.begin(); itTrace != m_traces.end(); ++itTrace)
+ {
+ if (itTrace->m_inputIndex == feedIndex)
+ {
+ float posLimit = 1.0 / itTrace->m_amp;
+ float negLimit = -1.0 / itTrace->m_amp;
+
+ if (itTrace->m_traceCount < m_traceSize)
+ {
+ float v = itTrace->m_projector->run(*begin) * itTrace->m_amp + itTrace->m_shift;
+
+ if(v > posLimit) {
+ v = posLimit;
+ } else if (v < negLimit) {
+ v = negLimit;
+ }
+
+ itTrace->m_trace[2*(itTrace->m_traceCount)] = itTrace->m_traceCount - shift;
+ itTrace->m_trace[2*(itTrace->m_traceCount)+1] = v;
+
+ itTrace->m_traceCount++;
+ }
+ else
+ {
+ itTrace->m_traceCount = 0;
+
+ if (m_traceCompleteCount < m_traces.size())
+ {
+ m_traceCompleteCount++;
+ }
+ else
+ {
+ //m_glScope->newTraces((DisplayTraces&) m_traces); // TODO: glScopeNG new traces
+ m_traceCompleteCount = 0;
+ }
+ }
+ }
+ }
+
+ begin++;
+ }
+ }
+}
+
+void ScopeVisNG::processPrevTrace(SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, std::vector::iterator& trace)
+{
+ int shift = (m_timeOfsProMill / 1000.0) * m_traceSize;
+ float posLimit = 1.0 / trace->m_amp;
+ float negLimit = -1.0 / trace->m_amp;
+
+ while (begin < end)
+ {
+ float v = trace->m_projector->run(*begin) * trace->m_amp + trace->m_shift;
+
+ if(v > posLimit) {
+ v = posLimit;
+ } else if (v < negLimit) {
+ v = negLimit;
+ }
+
+ trace->m_trace[2*(trace->m_traceCount)] = (trace->m_traceCount - shift); // display x
+ trace->m_trace[2*(trace->m_traceCount) + 1] = v; // display y
+
+ trace->m_traceCount++;
+ begin++;
+ }
+}
+
+void ScopeVisNG::start()
+{
+}
+
+void ScopeVisNG::stop()
+{
+}
+
+bool ScopeVisNG::handleMessage(const Message& message)
+{
+ qDebug() << "ScopeVisNG::handleMessage" << message.getIdentifier();
+}
+
diff --git a/sdrbase/dsp/scopevisng.h b/sdrbase/dsp/scopevisng.h
new file mode 100644
index 000000000..0bd4bde9f
--- /dev/null
+++ b/sdrbase/dsp/scopevisng.h
@@ -0,0 +1,285 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 F4EXB //
+// written by Edouard Griffiths //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef SDRBASE_DSP_SCOPEVISNG_H_
+#define SDRBASE_DSP_SCOPEVISNG_H_
+
+#include
+#include
+#include
+#include "dsp/dsptypes.h"
+#include "dsp/basebandsamplesink.h"
+#include "util/export.h"
+#include "util/message.h"
+#include "util/doublebuffer.h"
+
+class GLScopeNG;
+
+class SDRANGEL_API ScopeVisNG : public BasebandSampleSink {
+
+public:
+ enum ProjectionType
+ {
+ ProjectionReal, //!< Extract real part
+ ProjectionImag, //!< Extract imaginary part
+ ProjectionMagLin, //!< Calculate linear magnitude or modulus
+ ProjectionMagDB, //!< Calculate logarithmic (dB) of squared magnitude
+ ProjectionPhase, //!< Calculate phase
+ ProjectionDPhase //!< Calculate phase derivative i.e. instantaneous frequency scaled to sample rate
+ };
+
+ struct DisplayTrace
+ {
+ float *m_trace; //!< Displayable trace (interleaved x,y of GLfloat)
+ ProjectionType m_projectionType; //!< Complex to real projection type
+ float m_amp; //!< Amplification factor
+ float m_ofs; //!< Offset factor
+ };
+
+ typedef std::vector DisplayTraces;
+
+ static const uint m_traceChunkSize;
+ static const uint m_nbTriggers = 10;
+
+ ScopeVisNG(GLScopeNG* glScope = 0);
+ virtual ~ScopeVisNG();
+
+ void configure(MessageQueue* msgQueue,
+ uint32_t traceSize);
+
+ virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
+ virtual void start();
+ virtual void stop();
+ virtual bool handleMessage(const Message& message);
+ SampleVector::const_iterator getTriggerPoint() const { return m_triggerPoint; }
+
+private:
+ typedef DoubleBufferSimple TraceBuffer;
+
+ class MsgConfigureScopeVisNG : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ static MsgConfigureScopeVisNG* create(
+ uint32_t traceSize)
+ {
+ return new MsgConfigureScopeVisNG(traceSize);
+ }
+
+ private:
+ uint32_t m_traceSize;
+
+ MsgConfigureScopeVisNG(uint32_t traceSize) :
+ m_traceSize(traceSize)
+ {}
+ };
+
+ class MsgScopeVisNGAddTrigger : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ static MsgScopeVisNGAddTrigger* create(
+ ProjectionType projectionType)
+ {
+ return new MsgScopeVisNGAddTrigger(projectionType);
+ }
+
+ private:
+ ProjectionType m_projectionType;
+
+ MsgScopeVisNGAddTrigger(ProjectionType projectionType) :
+ m_projectionType(projectionType)
+ {}
+ };
+
+ class MsgScopeVisNGRemoveTrigger : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ static MsgScopeVisNGRemoveTrigger* create(
+ uint32_t triggerIndex)
+ {
+ return new MsgScopeVisNGRemoveTrigger(triggerIndex);
+ }
+
+ private:
+ uint32_t m_triggerIndex;
+
+ MsgScopeVisNGRemoveTrigger(uint32_t triggerIndex) :
+ m_triggerIndex(triggerIndex)
+ {}
+ };
+
+ class Projector
+ {
+ public:
+ Projector(ProjectionType projectionType) : m_projectionType(projectionType) {}
+
+ ProjectionType getProjectionType() const { return m_projectionType; }
+ virtual Real run(const Sample& s) = 0;
+ private:
+ ProjectionType m_projectionType;
+ };
+
+ class ProjectorReal : public Projector
+ {
+ public:
+ ProjectorReal() : Projector(ProjectionReal) {}
+ virtual Real run(const Sample& s) { return s.m_real / 32768.0f; }
+ };
+
+ class ProjectorImag : public Projector
+ {
+ public:
+ ProjectorImag() : Projector(ProjectionImag) {}
+ virtual Real run(const Sample& s) { return s.m_imag / 32768.0f; }
+ };
+
+ class ProjectorMagLin : public Projector
+ {
+ public:
+ ProjectorMagLin() : Projector(ProjectionMagLin) {}
+ virtual Real run(const Sample& s)
+ {
+ uint32_t magsq = s.m_real*s.m_real + s.m_imag*s.m_imag;
+ return std::sqrt(magsq/1073741824.0f);
+ }
+ };
+
+ class ProjectorMagDB : public Projector
+ {
+ public:
+ ProjectorMagDB() : Projector(ProjectionMagDB) {}
+ virtual Real run(const Sample& s)
+ {
+ uint32_t magsq = s.m_real*s.m_real + s.m_imag*s.m_imag;
+ return mult * log2f(magsq/1073741824.0f);
+ }
+ private:
+ static const Real mult;
+ };
+
+ class ProjectorPhase : public Projector
+ {
+ public:
+ ProjectorPhase() : Projector(ProjectionPhase) {}
+ virtual Real run(const Sample& s) { return std::atan2((float) s.m_imag, (float) s.m_real) / M_PI; }
+ };
+
+ class ProjectorDPhase : public Projector
+ {
+ public:
+ ProjectorDPhase() : Projector(ProjectionDPhase), m_prevArg(0.0f) {}
+ virtual Real run(const Sample& s)
+ {
+ Real curArg = std::atan2((float) s.m_imag, (float) s.m_real) / M_PI;
+ Real dPhi = curArg - m_prevArg;
+ m_prevArg = curArg;
+
+ if (dPhi < -M_PI) {
+ dPhi += 2.0 * M_PI;
+ } else if (dPhi > M_PI) {
+ dPhi -= 2.0 * M_PI;
+ }
+
+ return dPhi;
+ }
+
+ private:
+ Real m_prevArg;
+ };
+
+ enum TriggerState
+ {
+ TriggerFreeRun, //!< Trigger is disabled
+ TriggerUntriggered, //!< Trigger is not kicked off yet (or trigger list is empty)
+ TriggerTriggered, //!< Trigger has been kicked off
+ TriggerWait, //!< In one shot mode trigger waits for manual re-enabling
+ TriggerDelay, //!< Trigger conditions have been kicked off but it is waiting for delay before final kick off
+ TriggerNewConfig, //!< Special condition when a new configuration has been received
+ };
+
+ struct TriggerCondition
+ {
+ public:
+ Projector *m_projector; //!< Projector transform from complex trace to reaL trace usable for triggering
+ uint32_t m_inputIndex; //!< Input or feed index this trigger is associated with
+ Real m_triggerLevel; //!< Level in real units
+ bool m_triggerPositiveEdge; //!< Trigger on the positive edge (else negative)
+ bool m_triggerBothEdges; //!< Trigger on both edges (else only one)
+ bool m_prevCondition; //!< Condition (above threshold) at previous sample
+ uint32_t m_triggerDelay; //!< Delay before the trigger is kicked off in number of samples
+ uint32_t m_triggerDelayCount; //!< Counter of samples for delay
+ uint32_t m_triggerCounts; //!< Number of trigger conditions before the final decisive trigger
+
+ TriggerCondition(Projector *projector) :
+ m_projector(projector),
+ m_inputIndex(0),
+ m_triggerLevel(0.0f),
+ m_triggerPositiveEdge(true),
+ m_triggerBothEdges(false),
+ m_prevCondition(false),
+ m_triggerDelay(0),
+ m_triggerDelayCount(0),
+ m_triggerCounts(0)
+ {}
+ };
+
+ struct Trace : public DisplayTrace
+ {
+ Projector *m_projector; //!< Projector transform from complex trace to real (displayable) trace
+ uint32_t m_inputIndex; //!< Input or feed index this trace is associated with
+ int m_traceDelay; //!< Trace delay in number of samples
+ int m_traceCount; //!< Count of samples processed
+ float m_amp; //!< Linear trace amplifier factor
+ float m_shift; //!< Linear trace shift
+
+ Trace(Projector *projector, Real *displayTraceBuffer) :
+ m_projector(projector),
+ m_inputIndex(0),
+ m_traceDelay(0),
+ m_traceCount(0),
+ m_amp(1.0f),
+ m_shift(0.0f)
+ {
+ m_projectionType = m_projector->getProjectionType();
+ m_trace = displayTraceBuffer;
+ }
+ };
+
+ GLScopeNG* m_glScope;
+ std::vector m_tracebackBuffers; //!< One complex (Sample type) trace buffer per input source or feed
+ DoubleBufferSimple m_traceback; //!< FIFO to handle delayed processes
+ int m_preTriggerDelay; //!< Pre-trigger delay in number of samples
+ std::vector m_triggerConditions; //!< Chain of triggers
+ int m_currentTriggerIndex; //!< Index of current index in the chain
+ TriggerState m_triggerState; //!< Current trigger state
+ std::vector m_traces; //!< One trace control object per display trace allocated to X, Y[n] or Z
+ int m_traceSize; //!< Size of traces in number of samples
+ int m_timeOfsProMill; //!< Start trace shift in 1/1000 trace size
+ bool m_traceStart; //!< Trace is at start point
+ int m_traceFill; //!< Count of samples accumulated into trace
+ int m_zTraceIndex; //!< Index of the trace used for Z input (luminance or false colors)
+ int m_traceCompleteCount; //!< Count of completed traces
+ SampleVector::const_iterator m_triggerPoint; //!< Trigger start location in the samples vector
+
+ void processPrevTrace(SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, std::vector::iterator& trace);
+};
+
+
+
+#endif /* SDRBASE_DSP_SCOPEVISNG_H_ */
diff --git a/sdrbase/dsp/spectrumscopengcombovis.cpp b/sdrbase/dsp/spectrumscopengcombovis.cpp
new file mode 100644
index 000000000..ff5f5402c
--- /dev/null
+++ b/sdrbase/dsp/spectrumscopengcombovis.cpp
@@ -0,0 +1,41 @@
+#include "dsp/spectrumscopengcombovis.h"
+#include "dsp/dspcommands.h"
+#include "util/messagequeue.h"
+
+SpectrumScopeNGComboVis::SpectrumScopeNGComboVis(SpectrumVis* spectrumVis, ScopeVisNG* scopeVis) :
+ m_spectrumVis(spectrumVis),
+ m_scopeVis(scopeVis)
+{
+ setObjectName("SpectrumScopeNGComboVis");
+}
+
+SpectrumScopeNGComboVis::~SpectrumScopeNGComboVis()
+{
+}
+
+void SpectrumScopeNGComboVis::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly)
+{
+ m_scopeVis->feed(begin, end, false);
+ SampleVector::const_iterator triggerPoint = m_scopeVis->getTriggerPoint();
+ m_spectrumVis->feedTriggered(triggerPoint, begin, end, positiveOnly);
+}
+
+void SpectrumScopeNGComboVis::start()
+{
+ m_spectrumVis->start();
+ m_scopeVis->start();
+}
+
+void SpectrumScopeNGComboVis::stop()
+{
+ m_spectrumVis->stop();
+ m_scopeVis->stop();
+}
+
+bool SpectrumScopeNGComboVis::handleMessage(const Message& message)
+{
+ bool spectDone = m_spectrumVis->handleMessage(message);
+ bool scopeDone = m_scopeVis->handleMessage(message);
+
+ return (spectDone || scopeDone);
+}
diff --git a/sdrbase/dsp/spectrumscopengcombovis.h b/sdrbase/dsp/spectrumscopengcombovis.h
new file mode 100644
index 000000000..3369cf02b
--- /dev/null
+++ b/sdrbase/dsp/spectrumscopengcombovis.h
@@ -0,0 +1,27 @@
+#ifndef INCLUDE_SPECTRUMSCOPENGCOMBOVIS_H
+#define INCLUDE_SPECTRUMSCOPENGCOMBOVIS_H
+
+#include
+#include "dsp/spectrumvis.h"
+#include "dsp/scopevisng.h"
+#include "util/export.h"
+
+class Message;
+
+class SDRANGEL_API SpectrumScopeNGComboVis : public BasebandSampleSink {
+public:
+
+ SpectrumScopeNGComboVis(SpectrumVis* spectrumVis, ScopeVisNG* scopeVis);
+ virtual ~SpectrumScopeNGComboVis();
+
+ virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
+ virtual void start();
+ virtual void stop();
+ virtual bool handleMessage(const Message& message);
+
+private:
+ SpectrumVis* m_spectrumVis;
+ ScopeVisNG* m_scopeVis;
+};
+
+#endif // INCLUDE_SPECTRUMSCOPENGCOMBOVIS_H
diff --git a/sdrbase/gui/glscopeng.cpp b/sdrbase/gui/glscopeng.cpp
new file mode 100644
index 000000000..7f2f4c6ae
--- /dev/null
+++ b/sdrbase/gui/glscopeng.cpp
@@ -0,0 +1,996 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 F4EXB //
+// written by Edouard Griffiths //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "glscopeng.h"
+
+GLScopeNG::GLScopeNG(QWidget* parent) :
+ QGLWidget(parent),
+ m_displayMode(DisplayX),
+ m_dataChanged(false),
+ m_configChanged(false),
+ m_traces(0),
+ m_displayGridIntensity(10),
+ m_displayTraceIntensity(50),
+ m_timeBase(1),
+ m_traceSize(0),
+ m_sampleRate(0),
+ m_triggerPre(0),
+ m_timeOfsProMill(0),
+ m_highlightedTraceIndex(0)
+{
+ setAttribute(Qt::WA_OpaquePaintEvent);
+ connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
+ m_timer.start(50);
+
+ m_y1Scale.setFont(font());
+ m_y1Scale.setOrientation(Qt::Vertical);
+ m_y2Scale.setFont(font());
+ m_y2Scale.setOrientation(Qt::Vertical);
+ m_x1Scale.setFont(font());
+ m_x1Scale.setOrientation(Qt::Horizontal);
+ m_x2Scale.setFont(font());
+ m_x2Scale.setOrientation(Qt::Horizontal);
+
+ m_powerOverlayFont.setBold(true);
+ m_powerOverlayFont.setPointSize(font().pointSize()+1);
+}
+
+GLScopeNG::~GLScopeNG()
+{
+ cleanup();
+}
+
+void GLScopeNG::newTraces()
+{
+ if (m_traces)
+ {
+ if(!m_mutex.tryLock(2))
+ return;
+
+ m_dataChanged = true;
+
+ m_mutex.unlock();
+ }
+}
+
+void GLScopeNG::initializeGL()
+{
+ QOpenGLContext *glCurrentContext = QOpenGLContext::currentContext();
+
+ if (glCurrentContext) {
+ if (QOpenGLContext::currentContext()->isValid()) {
+ qDebug() << "GLScopeNG::initializeGL: context:"
+ << " major: " << (QOpenGLContext::currentContext()->format()).majorVersion()
+ << " minor: " << (QOpenGLContext::currentContext()->format()).minorVersion()
+ << " ES: " << (QOpenGLContext::currentContext()->isOpenGLES() ? "yes" : "no");
+ }
+ else {
+ qDebug() << "GLScopeNG::initializeGL: current context is invalid";
+ }
+ } else {
+ qCritical() << "GLScopeNG::initializeGL: no current context";
+ return;
+ }
+
+ QSurface *surface = glCurrentContext->surface();
+
+ if (surface == 0)
+ {
+ qCritical() << "GLScopeNG::initializeGL: no surface attached";
+ return;
+ }
+ else
+ {
+ if (surface->surfaceType() != QSurface::OpenGLSurface)
+ {
+ qCritical() << "GLScopeNG::initializeGL: surface is not an OpenGLSurface: " << surface->surfaceType()
+ << " cannot use an OpenGL context";
+ return;
+ }
+ else
+ {
+ qDebug() << "GLScopeNG::initializeGL: OpenGL surface:"
+ << " class: " << (surface->surfaceClass() == QSurface::Window ? "Window" : "Offscreen");
+ }
+ }
+
+ connect(glCurrentContext, &QOpenGLContext::aboutToBeDestroyed, this, &GLScopeNG::cleanup); // TODO: when migrating to QOpenGLWidget
+
+ QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions();
+ glFunctions->initializeOpenGLFunctions();
+
+ //glDisable(GL_DEPTH_TEST);
+ m_glShaderSimple.initializeGL();
+ m_glShaderLeft1Scale.initializeGL();
+ m_glShaderBottom1Scale.initializeGL();
+ m_glShaderLeft2Scale.initializeGL();
+ m_glShaderBottom2Scale.initializeGL();
+ m_glShaderPowerOverlay.initializeGL();
+}
+
+void GLScopeNG::resizeGL(int width, int height)
+{
+ QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions();
+ glFunctions->glViewport(0, 0, width, height);
+ m_configChanged = true;
+}
+
+void GLScopeNG::paintGL()
+{
+ if(!m_mutex.tryLock(2))
+ return;
+
+ if(m_configChanged)
+ applyConfig();
+
+ QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions();
+ glFunctions->glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glFunctions->glClear(GL_COLOR_BUFFER_BIT);
+
+ if (m_displayMode == DisplayX) // display only trace #0
+ {
+ // draw rect around
+ {
+ GLfloat q3[] {
+ 1, 1,
+ 0, 1,
+ 0, 0,
+ 1, 0
+ };
+
+ QVector4D color(1.0f, 1.0f, 1.0f, 0.5f);
+ m_glShaderSimple.drawContour(m_glScopeMatrix1, color, q3, 4);
+ }
+
+ // paint grid
+ const ScaleEngine::TickList* tickList;
+ const ScaleEngine::Tick* tick;
+
+ // Y1 (X trace or trace #0)
+ {
+ tickList = &m_y1Scale.getTickList();
+
+ GLfloat q3[4*tickList->count()];
+ int effectiveTicks = 0;
+
+ for (int i= 0; i < tickList->count(); i++)
+ {
+ tick = &(*tickList)[i];
+
+ if (tick->major)
+ {
+ if (tick->textSize > 0)
+ {
+ float y = 1 - (tick->pos / m_y1Scale.getSize());
+ q3[4*effectiveTicks] = 0;
+ q3[4*effectiveTicks+1] = y;
+ q3[4*effectiveTicks+2] = 1;
+ q3[4*effectiveTicks+3] = y;
+ effectiveTicks++;
+ }
+ }
+ }
+
+ float blue = 1.0f;
+ QVector4D color(1.0f, 1.0f, blue, (float) m_displayGridIntensity / 100.0f);
+ m_glShaderSimple.drawSegments(m_glScopeMatrix1, color, q3, 2*effectiveTicks);
+ }
+
+ // X1 (time)
+ {
+ tickList = &m_x1Scale.getTickList();
+
+ GLfloat q3[4*tickList->count()];
+ int effectiveTicks = 0;
+ for(int i= 0; i < tickList->count(); i++) {
+ tick = &(*tickList)[i];
+ if(tick->major) {
+ if(tick->textSize > 0) {
+ float x = tick->pos / m_x1Scale.getSize();
+ q3[4*effectiveTicks] = x;
+ q3[4*effectiveTicks+1] = 0;
+ q3[4*effectiveTicks+2] = x;
+ q3[4*effectiveTicks+3] = 1;
+ effectiveTicks++;
+ }
+ }
+ }
+
+ QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
+ m_glShaderSimple.drawSegments(m_glScopeMatrix1, color, q3, 2*effectiveTicks);
+ }
+
+ // paint left #1 scale
+ {
+ GLfloat vtx1[] = {
+ 0, 1,
+ 1, 1,
+ 1, 0,
+ 0, 0
+ };
+ GLfloat tex1[] = {
+ 0, 1,
+ 1, 1,
+ 1, 0,
+ 0, 0
+ };
+ m_glShaderLeft1Scale.drawSurface(m_glLeft1ScaleMatrix, tex1, vtx1, 4);
+ }
+
+ // paint bottom #1 scale
+ {
+ GLfloat vtx1[] = {
+ 0, 1,
+ 1, 1,
+ 1, 0,
+ 0, 0
+ };
+ GLfloat tex1[] = {
+ 0, 1,
+ 1, 1,
+ 1, 0,
+ 0, 0
+ };
+ m_glShaderBottom1Scale.drawSurface(m_glBot1ScaleMatrix, tex1, vtx1, 4);
+ }
+
+ // TODO: paint trigger level #1
+
+ // paint trace #1
+ if (m_traceSize > 0)
+ {
+ const ScopeVisNG::DisplayTrace& trace = (*m_traces)[0];
+ int start = (m_timeOfsProMill/1000.0) * m_traceSize;
+ int end = std::min(start + m_traceSize/m_timeBase, m_traceSize);
+ if(end - start < 2)
+ start--;
+
+ float rectX = m_glScopeRect1.x();
+ float rectY = m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0f;
+ float rectW = m_glScopeRect1.width() * (float)m_timeBase / (float)(m_traceSize - 1);
+ float rectH = -(m_glScopeRect1.height() / 2.0f) * trace.m_amp;
+
+ QVector4D color(1.0f, 1.0f, 0.25f, m_displayTraceIntensity / 100.0f);
+ QMatrix4x4 mat;
+ mat.setToIdentity();
+ mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY);
+ mat.scale(2.0f * rectW, -2.0f * rectH);
+ m_glShaderSimple.drawPolyline(mat, color, (GLfloat *) &trace.m_trace[2*start], end - start);
+ }
+ }
+
+ m_dataChanged = false;
+ m_mutex.unlock();
+}
+
+void GLScopeNG::setSampleRate(int sampleRate)
+{
+ m_sampleRate = sampleRate;
+ m_configChanged = true;
+ update();
+}
+
+void GLScopeNG::setTimeBase(int timeBase)
+{
+ m_timeBase = timeBase;
+ m_configChanged = true;
+ update();
+}
+
+void GLScopeNG::setTriggerPre(Real triggerPre)
+{
+ m_triggerPre = triggerPre;
+ m_configChanged = true;
+ update();
+}
+
+void GLScopeNG::setTimeOfsProMill(int timeOfsProMill)
+{
+ m_timeOfsProMill = timeOfsProMill;
+ m_configChanged = true;
+ update();
+}
+
+void GLScopeNG::setHighlightedTraceIndex(uint32_t traceIndex)
+{
+ m_highlightedTraceIndex = traceIndex;
+ m_configChanged = true;
+ update();
+}
+
+void GLScopeNG::setDisplayMode(DisplayMode displayMode)
+{
+ m_displayMode = displayMode;
+ m_configChanged = true;
+ update();
+}
+
+void GLScopeNG::setTraceSize(int traceSize)
+{
+ m_traceSize = traceSize;
+ m_configChanged = true;
+ update();
+}
+
+void GLScopeNG::applyConfig()
+{
+ m_configChanged = false;
+
+ QFontMetrics fm(font());
+ int M = fm.width("-");
+ float t_start = ((m_timeOfsProMill / 1000.0) - m_triggerPre) * ((float) m_traceSize / m_sampleRate);
+ float t_len = ((float) m_traceSize / m_sampleRate) / (float) m_timeBase;
+
+ m_x1Scale.setRange(Unit::Time, t_start, t_start + t_len); // time scale
+ m_x2Scale.setRange(Unit::Time, t_start, t_start + t_len); // time scale
+
+ if (m_traces)
+ {
+ if (m_traces->size() > 0)
+ {
+ setYScale(m_y1Scale, 0); // This is always the X trace (trace #0)
+ }
+
+ if ((m_traces->size() > 1) && (m_highlightedTraceIndex < m_traces->size()))
+ {
+ setYScale(m_y2Scale, m_highlightedTraceIndex > 0 ? m_highlightedTraceIndex : 1); // if Highlighted trace is #0 (X trace) set it to first Y trace (trace #1)
+ }
+ }
+
+ if ((m_displayMode == DisplayX) || (m_displayMode == DisplayY)) // unique display
+ {
+ int scopeHeight = height() - m_topMargin - m_botMargin;
+ int scopeWidth = width() - m_leftMargin - m_rightMargin;
+
+ m_glScopeRect1 = QRectF(
+ (float) m_leftMargin / (float) width(),
+ (float) m_topMargin / (float) height(),
+ (float) scopeWidth / (float) width(),
+ (float) scopeHeight / (float) height()
+ );
+ m_glScopeMatrix1.setToIdentity();
+ m_glScopeMatrix1.translate (
+ -1.0f + ((float) 2*m_leftMargin / (float) width()),
+ 1.0f - ((float) 2*m_topMargin / (float) height())
+ );
+ m_glScopeMatrix1.scale (
+ (float) 2*scopeWidth / (float) width(),
+ (float) -2*scopeHeight / (float) height()
+ );
+
+ m_glBot1ScaleMatrix.setToIdentity();
+ m_glBot1ScaleMatrix.translate (
+ -1.0f + ((float) 2*m_leftMargin / (float) width()),
+ 1.0f - ((float) 2*(scopeHeight + m_topMargin + 1) / (float) height())
+ );
+ m_glBot1ScaleMatrix.scale (
+ (float) 2*scopeWidth / (float) width(),
+ (float) -2*(m_botMargin - 1) / (float) height()
+ );
+
+ m_glLeft1ScaleMatrix.setToIdentity();
+ m_glLeft1ScaleMatrix.translate (
+ -1.0f,
+ 1.0f - ((float) 2*m_topMargin / (float) height())
+ );
+ m_glLeft1ScaleMatrix.scale (
+ (float) 2*(m_leftMargin-1) / (float) width(),
+ (float) -2*scopeHeight / (float) height()
+ );
+
+ { // X1 scale
+ m_x1Scale.setSize(scopeWidth);
+
+ m_bot1ScalePixmap = QPixmap(
+ scopeWidth,
+ m_botMargin - 1
+ );
+
+ const ScaleEngine::TickList* tickList;
+ const ScaleEngine::Tick* tick;
+
+ m_bot1ScalePixmap.fill(Qt::black);
+ QPainter painter(&m_bot1ScalePixmap);
+ painter.setPen(QColor(0xf0, 0xf0, 0xff));
+ painter.setFont(font());
+ tickList = &m_x1Scale.getTickList();
+
+ for(int i = 0; i < tickList->count(); i++) {
+ tick = &(*tickList)[i];
+ if(tick->major) {
+ if(tick->textSize > 0) {
+ painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text);
+ }
+ }
+ }
+
+ m_glShaderBottom1Scale.initTexture(m_bot1ScalePixmap.toImage());
+
+ } // X1 scale
+
+ if (m_displayMode == DisplayX) // use Y1 scale
+ {
+ m_y1Scale.setSize(scopeHeight);
+
+ m_left1ScalePixmap = QPixmap(
+ m_leftMargin - 1,
+ scopeHeight
+ );
+
+ const ScaleEngine::TickList* tickList;
+ const ScaleEngine::Tick* tick;
+
+ m_left1ScalePixmap.fill(Qt::black);
+ QPainter painter(&m_left1ScalePixmap);
+ painter.setPen(QColor(0xf0, 0xf0, 0xff));
+ painter.setFont(font());
+ tickList = &m_y1Scale.getTickList();
+
+ for(int i = 0; i < tickList->count(); i++) {
+ tick = &(*tickList)[i];
+ if(tick->major) {
+ if(tick->textSize > 0) {
+ painter.drawText(QPointF(m_leftMargin - M - tick->textSize, m_topMargin + scopeHeight - tick->textPos - fm.ascent()/2), tick->text);
+ }
+ }
+ }
+
+ m_glShaderLeft1Scale.initTexture(m_left1ScalePixmap.toImage());
+ }
+ else if (m_displayMode == DisplayY) // use Y2 scale
+ {
+ m_y2Scale.setSize(scopeHeight);
+
+ m_left2ScalePixmap = QPixmap(
+ m_leftMargin - 1,
+ scopeHeight
+ );
+
+ const ScaleEngine::TickList* tickList;
+ const ScaleEngine::Tick* tick;
+
+ m_left2ScalePixmap.fill(Qt::black);
+ QPainter painter(&m_left2ScalePixmap);
+ painter.setPen(QColor(0xf0, 0xf0, 0xff));
+ painter.setFont(font());
+ tickList = &m_y2Scale.getTickList();
+
+ for(int i = 0; i < tickList->count(); i++) {
+ tick = &(*tickList)[i];
+ if(tick->major) {
+ if(tick->textSize > 0) {
+ painter.drawText(QPointF(m_leftMargin - M - tick->textSize, m_topMargin + scopeHeight - tick->textPos - fm.ascent()/2), tick->text);
+ }
+ }
+ }
+
+ m_glShaderLeft2Scale.initTexture(m_left2ScalePixmap.toImage());
+ } // Y scales
+ } // single display
+ else // dual display (X+Y or polar)
+ {
+ // left (first) display
+ if ((m_displayMode == DisplayXYH) || (m_displayMode == DisplayPol)) // horizontal split of first display
+ {
+ int scopeHeight = height() - m_topMargin - m_botMargin;
+ int scopeWidth = (width() - m_rightMargin)/2 - m_leftMargin;
+
+ m_glScopeRect1 = QRectF(
+ (float) m_leftMargin / (float) width(),
+ (float) m_topMargin / (float) height(),
+ (float) scopeWidth / (float) width(),
+ (float) scopeHeight / (float) height()
+ );
+ m_glScopeMatrix1.setToIdentity();
+ m_glScopeMatrix1.translate (
+ -1.0f + ((float) 2*m_leftMargin / (float) width()),
+ 1.0f - ((float) 2*m_topMargin / (float) height())
+ );
+ m_glScopeMatrix1.scale (
+ (float) 2*scopeWidth / (float) width(),
+ (float) -2*scopeHeight / (float) height()
+ );
+
+ m_glBot1ScaleMatrix.setToIdentity();
+ m_glBot1ScaleMatrix.translate (
+ -1.0f + ((float) 2*m_leftMargin / (float) width()),
+ 1.0f - ((float) 2*(scopeHeight + m_topMargin + 1) / (float) height())
+ );
+ m_glBot1ScaleMatrix.scale (
+ (float) 2*scopeWidth / (float) width(),
+ (float) -2*(m_botMargin - 1) / (float) height()
+ );
+
+ m_glLeft1ScaleMatrix.setToIdentity();
+ m_glLeft1ScaleMatrix.translate (
+ -1.0f,
+ 1.0f - ((float) 2*m_topMargin / (float) height())
+ );
+ m_glLeft1ScaleMatrix.scale (
+ (float) 2*(m_leftMargin-1) / (float) width(),
+ (float) -2*scopeHeight / (float) height()
+ );
+
+ { // Y1 scale
+ m_y1Scale.setSize(scopeHeight);
+
+ m_left1ScalePixmap = QPixmap(
+ m_leftMargin - 1,
+ scopeHeight
+ );
+
+ const ScaleEngine::TickList* tickList;
+ const ScaleEngine::Tick* tick;
+
+ m_left1ScalePixmap.fill(Qt::black);
+ QPainter painter(&m_left1ScalePixmap);
+ painter.setPen(QColor(0xf0, 0xf0, 0xff));
+ painter.setFont(font());
+ tickList = &m_y1Scale.getTickList();
+
+ for(int i = 0; i < tickList->count(); i++) {
+ tick = &(*tickList)[i];
+ if(tick->major) {
+ if(tick->textSize > 0) {
+ painter.drawText(QPointF(m_leftMargin - M - tick->textSize, m_topMargin + scopeHeight - tick->textPos - fm.ascent()/2), tick->text);
+ }
+ }
+ }
+
+ m_glShaderLeft1Scale.initTexture(m_left1ScalePixmap.toImage());
+
+ } // Y1 scale
+ { // X1 scale
+ m_x1Scale.setSize(scopeWidth);
+
+ m_bot1ScalePixmap = QPixmap(
+ scopeWidth,
+ m_botMargin - 1
+ );
+
+ const ScaleEngine::TickList* tickList;
+ const ScaleEngine::Tick* tick;
+
+ m_bot1ScalePixmap.fill(Qt::black);
+ QPainter painter(&m_bot1ScalePixmap);
+ painter.setPen(QColor(0xf0, 0xf0, 0xff));
+ painter.setFont(font());
+ tickList = &m_x1Scale.getTickList();
+
+ for(int i = 0; i < tickList->count(); i++) {
+ tick = &(*tickList)[i];
+ if(tick->major) {
+ if(tick->textSize > 0) {
+ painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text);
+ }
+ }
+ }
+
+ m_glShaderBottom1Scale.initTexture(m_bot1ScalePixmap.toImage());
+
+ } // X1 scale
+ }
+ else // vertical split of first display
+ {
+ int scopeHeight = (height() - m_topMargin) / 2 - m_botMargin;
+ int scopeWidth = width() - m_leftMargin - m_rightMargin;
+
+ m_glScopeRect1 = QRectF(
+ (float) m_leftMargin / (float) width(),
+ (float) m_topMargin / (float) height(),
+ (float) scopeWidth / (float) width(),
+ (float) scopeHeight / (float) height()
+ );
+ m_glScopeMatrix1.setToIdentity();
+ m_glScopeMatrix1.translate (
+ -1.0f + ((float) 2*m_leftMargin / (float) width()),
+ 1.0f - ((float) 2*m_topMargin / (float) height())
+ );
+ m_glScopeMatrix1.scale (
+ (float) 2*scopeWidth / (float) width(),
+ (float) -2*scopeHeight / (float) height()
+ );
+
+ m_glBot1ScaleMatrix.setToIdentity();
+ m_glBot1ScaleMatrix.translate (
+ -1.0f + ((float) 2*m_leftMargin / (float) width()),
+ 1.0f - ((float) 2*(scopeHeight + m_topMargin + 1) / (float) height())
+ );
+ m_glBot1ScaleMatrix.scale (
+ (float) 2*scopeWidth / (float) width(),
+ (float) -2*(m_botMargin - 1) / (float) height()
+ );
+
+ m_glLeft1ScaleMatrix.setToIdentity();
+ m_glLeft1ScaleMatrix.translate (
+ -1.0f,
+ 1.0f - ((float) 2*m_topMargin / (float) height())
+ );
+ m_glLeft1ScaleMatrix.scale (
+ (float) 2*(m_leftMargin-1) / (float) width(),
+ (float) -2*scopeHeight / (float) height()
+ );
+
+ { // Y1 scale
+ m_y1Scale.setSize(scopeHeight);
+
+ m_left1ScalePixmap = QPixmap(
+ m_leftMargin - 1,
+ scopeHeight
+ );
+
+ const ScaleEngine::TickList* tickList;
+ const ScaleEngine::Tick* tick;
+
+ m_left1ScalePixmap.fill(Qt::black);
+ QPainter painter(&m_left1ScalePixmap);
+ painter.setPen(QColor(0xf0, 0xf0, 0xff));
+ painter.setFont(font());
+ tickList = &m_y1Scale.getTickList();
+
+ for(int i = 0; i < tickList->count(); i++) {
+ tick = &(*tickList)[i];
+ if(tick->major) {
+ if(tick->textSize > 0) {
+ painter.drawText(QPointF(m_leftMargin - M - tick->textSize, m_topMargin + scopeHeight - tick->textPos - fm.ascent()/2), tick->text);
+ }
+ }
+ }
+
+ m_glShaderLeft1Scale.initTexture(m_left1ScalePixmap.toImage());
+
+ } // Y1 scale
+ { // X1 scale
+ m_x1Scale.setSize(scopeWidth);
+
+ m_bot1ScalePixmap = QPixmap(
+ scopeWidth,
+ m_botMargin - 1
+ );
+
+ const ScaleEngine::TickList* tickList;
+ const ScaleEngine::Tick* tick;
+
+ m_bot1ScalePixmap.fill(Qt::black);
+ QPainter painter(&m_bot1ScalePixmap);
+ painter.setPen(QColor(0xf0, 0xf0, 0xff));
+ painter.setFont(font());
+ tickList = &m_x1Scale.getTickList();
+
+ for(int i = 0; i < tickList->count(); i++) {
+ tick = &(*tickList)[i];
+ if(tick->major) {
+ if(tick->textSize > 0) {
+ painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text);
+ }
+ }
+ }
+
+ m_glShaderBottom1Scale.initTexture(m_bot1ScalePixmap.toImage());
+
+ } // X1 scale
+ } // hotizontal or vertical split of first display
+ // right (second) display
+ if (m_displayMode == DisplayPol) // in Polar mode second display is square and split is horizontal
+ {
+ int scopeHeight = height() - m_topMargin - m_botMargin;
+ int scopeWidth = (width() - m_rightMargin)/2 - m_leftMargin;
+
+ int scopeDim = std::min(scopeWidth, scopeHeight);
+
+ m_glScopeRect2 = QRectF(
+ (float)(m_leftMargin + scopeWidth + m_leftMargin) / (float)width(),
+ (float)m_topMargin / (float)height(),
+ (float) scopeDim / (float)width(),
+ (float)(height() - m_topMargin - m_botMargin) / (float)height()
+ );
+ m_glScopeMatrix2.setToIdentity();
+ m_glScopeMatrix2.translate (
+ -1.0f + ((float) 2*(m_leftMargin + scopeWidth + m_leftMargin) / (float) width()),
+ 1.0f - ((float) 2*m_topMargin / (float) height())
+ );
+ m_glScopeMatrix2.scale (
+ (float) 2*scopeDim / (float) width(),
+ (float) -2*(height() - m_topMargin - m_botMargin) / (float) height()
+ );
+
+ m_glLeft2ScaleMatrix.setToIdentity();
+ m_glLeft2ScaleMatrix.translate (
+ -1.0f + (float) 2*(m_leftMargin + scopeWidth) / (float) width(),
+ 1.0f - ((float) 2*m_topMargin / (float) height())
+ );
+ m_glLeft2ScaleMatrix.scale (
+ (float) 2*(m_leftMargin-1) / (float) width(),
+ (float) -2*scopeHeight / (float) height()
+ );
+
+ m_glBot2ScaleMatrix.setToIdentity();
+ m_glBot2ScaleMatrix.translate (
+ -1.0f + ((float) 2*(m_leftMargin + m_leftMargin + scopeWidth) / (float) width()),
+ 1.0f - ((float) 2*(scopeHeight + m_topMargin + 1) / (float) height())
+ );
+ m_glBot2ScaleMatrix.scale (
+ (float) 2*scopeDim / (float) width(),
+ (float) -2*(m_botMargin - 1) / (float) height()
+ );
+ }
+ else // both displays are similar and share space equally
+ {
+ if (m_displayMode == DisplayXYH) // horizontal split of second display
+ {
+ int scopeHeight = height() - m_topMargin - m_botMargin;
+ int scopeWidth = (width() - m_rightMargin)/2 - m_leftMargin;
+
+ m_glScopeRect2 = QRectF(
+ (float)(m_leftMargin + m_leftMargin + ((width() - m_leftMargin - m_leftMargin - m_rightMargin) / 2)) / (float)width(),
+ (float)m_topMargin / (float)height(),
+ (float)((width() - m_leftMargin - m_leftMargin - m_rightMargin) / 2) / (float)width(),
+ (float)(height() - m_topMargin - m_botMargin) / (float)height()
+ );
+ m_glScopeMatrix2.setToIdentity();
+ m_glScopeMatrix2.translate (
+ -1.0f + ((float) 2*(m_leftMargin + m_leftMargin + ((width() - m_leftMargin - m_leftMargin - m_rightMargin) / 2)) / (float) width()),
+ 1.0f - ((float) 2*m_topMargin / (float) height())
+ );
+ m_glScopeMatrix2.scale (
+ (float) 2*((width() - m_leftMargin - m_leftMargin - m_rightMargin) / 2) / (float) width(),
+ (float) -2*(height() - m_topMargin - m_botMargin) / (float) height()
+ );
+
+ m_glLeft2ScaleMatrix.setToIdentity();
+ m_glLeft2ScaleMatrix.translate (
+ -1.0f + (float) 2*(m_leftMargin + scopeWidth) / (float) width(),
+ 1.0f - ((float) 2*m_topMargin / (float) height())
+ );
+ m_glLeft2ScaleMatrix.scale (
+ (float) 2*(m_leftMargin-1) / (float) width(),
+ (float) -2*scopeHeight / (float) height()
+ );
+
+ m_glBot2ScaleMatrix.setToIdentity();
+ m_glBot2ScaleMatrix.translate (
+ -1.0f + ((float) 2*(m_leftMargin + m_leftMargin + scopeWidth) / (float) width()),
+ 1.0f - ((float) 2*(scopeHeight + m_topMargin + 1) / (float) height())
+ );
+ m_glBot2ScaleMatrix.scale (
+ (float) 2*scopeWidth / (float) width(),
+ (float) -2*(m_botMargin - 1) / (float) height()
+ );
+
+ { // Y2 scale
+ m_y2Scale.setSize(scopeHeight);
+
+ m_left2ScalePixmap = QPixmap(
+ m_leftMargin - 1,
+ scopeHeight
+ );
+
+ const ScaleEngine::TickList* tickList;
+ const ScaleEngine::Tick* tick;
+
+ m_left2ScalePixmap.fill(Qt::black);
+ QPainter painter(&m_left2ScalePixmap);
+ painter.setPen(QColor(0xf0, 0xf0, 0xff));
+ painter.setFont(font());
+ tickList = &m_y2Scale.getTickList();
+
+ for(int i = 0; i < tickList->count(); i++) {
+ tick = &(*tickList)[i];
+ if(tick->major) {
+ if(tick->textSize > 0) {
+ painter.drawText(QPointF(m_leftMargin - M - tick->textSize, m_topMargin + scopeHeight - tick->textPos - fm.ascent()/2), tick->text);
+ }
+ }
+ }
+
+ m_glShaderLeft2Scale.initTexture(m_left2ScalePixmap.toImage());
+
+ } // Y2 scale
+ { // X2 scale
+ m_x2Scale.setSize(scopeWidth);
+ m_bot2ScalePixmap = QPixmap(
+ scopeWidth,
+ m_botMargin - 1
+ );
+
+ const ScaleEngine::TickList* tickList;
+ const ScaleEngine::Tick* tick;
+
+ m_bot2ScalePixmap.fill(Qt::black);
+ QPainter painter(&m_bot2ScalePixmap);
+ painter.setPen(QColor(0xf0, 0xf0, 0xff));
+ painter.setFont(font());
+ tickList = &m_x2Scale.getTickList();
+
+ for(int i = 0; i < tickList->count(); i++) {
+ tick = &(*tickList)[i];
+ if(tick->major) {
+ if(tick->textSize > 0) {
+ painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text);
+ }
+ }
+ }
+
+ m_glShaderBottom2Scale.initTexture(m_bot2ScalePixmap.toImage());
+
+ } // X2 scale
+ }
+ else // vertical split of second display
+ {
+ int scopeHeight = (height() - m_topMargin) / 2 - m_botMargin;
+ int scopeWidth = width() - m_leftMargin - m_rightMargin;
+
+ m_glScopeRect2 = QRectF(
+ (float) m_leftMargin / (float)width(),
+ (float) (m_botMargin + m_topMargin + scopeHeight) / (float)height(),
+ (float) scopeWidth / (float)width(),
+ (float) scopeHeight / (float)height()
+ );
+ m_glScopeMatrix2.setToIdentity();
+ m_glScopeMatrix2.translate (
+ -1.0f + ((float) 2*m_leftMargin / (float) width()),
+ 1.0f - ((float) 2*(m_botMargin + m_topMargin + scopeHeight) / (float) height())
+ );
+ m_glScopeMatrix2.scale (
+ (float) 2*scopeWidth / (float) width(),
+ (float) -2*scopeHeight / (float) height()
+ );
+
+ m_glLeft2ScaleMatrix.setToIdentity();
+ m_glLeft2ScaleMatrix.translate (
+ -1.0f,
+ 1.0f - ((float) 2*(m_topMargin + scopeHeight + m_botMargin) / (float) height())
+ );
+ m_glLeft2ScaleMatrix.scale (
+ (float) 2*(m_leftMargin-1) / (float) width(),
+ (float) -2*scopeHeight / (float) height()
+ );
+
+ m_glBot2ScaleMatrix.setToIdentity();
+ m_glBot2ScaleMatrix.translate (
+ -1.0f + ((float) 2*m_leftMargin / (float) width()),
+ 1.0f - ((float) 2*(scopeHeight + m_topMargin + scopeHeight + m_botMargin + 1) / (float) height())
+ );
+ m_glBot2ScaleMatrix.scale (
+ (float) 2*scopeWidth / (float) width(),
+ (float) -2*(m_botMargin - 1) / (float) height()
+ );
+
+ { // Y2 scale
+ m_y2Scale.setSize(scopeHeight);
+
+ m_left2ScalePixmap = QPixmap(
+ m_leftMargin - 1,
+ scopeHeight
+ );
+
+ const ScaleEngine::TickList* tickList;
+ const ScaleEngine::Tick* tick;
+
+ m_left2ScalePixmap.fill(Qt::black);
+ QPainter painter(&m_left2ScalePixmap);
+ painter.setPen(QColor(0xf0, 0xf0, 0xff));
+ painter.setFont(font());
+ tickList = &m_y2Scale.getTickList();
+
+ for(int i = 0; i < tickList->count(); i++) {
+ tick = &(*tickList)[i];
+ if(tick->major) {
+ if(tick->textSize > 0) {
+ painter.drawText(QPointF(m_leftMargin - M - tick->textSize, m_topMargin + scopeHeight - tick->textPos - fm.ascent()/2), tick->text);
+ }
+ }
+ }
+
+ m_glShaderLeft2Scale.initTexture(m_left2ScalePixmap.toImage());
+
+ } // Y2 scale
+ { // X2 scale
+ m_x2Scale.setSize(scopeWidth);
+ m_bot2ScalePixmap = QPixmap(
+ scopeWidth,
+ m_botMargin - 1
+ );
+
+ const ScaleEngine::TickList* tickList;
+ const ScaleEngine::Tick* tick;
+
+ m_bot2ScalePixmap.fill(Qt::black);
+ QPainter painter(&m_bot2ScalePixmap);
+ painter.setPen(QColor(0xf0, 0xf0, 0xff));
+ painter.setFont(font());
+ tickList = &m_x2Scale.getTickList();
+
+ for(int i = 0; i < tickList->count(); i++) {
+ tick = &(*tickList)[i];
+ if(tick->major) {
+ if(tick->textSize > 0) {
+ painter.drawText(QPointF(tick->textPos, fm.height() - 1), tick->text);
+ }
+ }
+ }
+
+ m_glShaderBottom2Scale.initTexture(m_bot2ScalePixmap.toImage());
+ } // X2 scale
+ } // vertical or horizontal split of second display
+ } // second display square (polar mode) or half space
+ } // single or dual display
+}
+
+void GLScopeNG::setYScale(ScaleEngine& scale, uint32_t highlightedTraceIndex)
+{
+ ScopeVisNG::DisplayTrace trace = (*m_traces)[highlightedTraceIndex];
+ float amp_range = 2.0 / trace.m_amp;
+ float amp_ofs = trace.m_ofs;
+ float pow_floor = -100.0 + trace.m_ofs * 100.0;
+ float pow_range = 100.0 / trace.m_amp;
+
+ switch (trace.m_projectionType)
+ {
+ case ScopeVisNG::ProjectionMagDB: // dB scale
+ scale.setRange(Unit::Decibel, pow_floor, pow_floor + pow_range);
+ break;
+ case ScopeVisNG::ProjectionPhase: // Phase or frequency
+ case ScopeVisNG::ProjectionDPhase:
+ scale.setRange(Unit::None, -1.0/trace.m_amp + amp_ofs, 1.0/trace.m_amp + amp_ofs);
+ break;
+ case ScopeVisNG::ProjectionReal: // Linear generic
+ case ScopeVisNG::ProjectionImag:
+ case ScopeVisNG::ProjectionMagLin:
+ default:
+ if (amp_range < 2.0) {
+ scale.setRange(Unit::None, - amp_range * 500.0 + amp_ofs * 1000.0, amp_range * 500.0 + amp_ofs * 1000.0);
+ } else {
+ scale.setRange(Unit::None, - amp_range * 0.5 + amp_ofs, amp_range * 0.5 + amp_ofs);
+ }
+ break;
+ }
+}
+
+void GLScopeNG::tick()
+{
+ if(m_dataChanged)
+ update();
+}
+
+void GLScopeNG::connectTimer(const QTimer& timer)
+{
+ qDebug() << "GLScopeNG::connectTimer";
+ disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
+ connect(&timer, SIGNAL(timeout()), this, SLOT(tick()));
+ m_timer.stop();
+}
+
+void GLScopeNG::cleanup()
+{
+ //makeCurrent();
+ m_glShaderSimple.cleanup();
+ m_glShaderBottom1Scale.cleanup();
+ m_glShaderBottom2Scale.cleanup();
+ m_glShaderLeft1Scale.cleanup();
+ m_glShaderPowerOverlay.cleanup();
+ //doneCurrent();
+}
+
diff --git a/sdrbase/gui/glscopeng.h b/sdrbase/gui/glscopeng.h
new file mode 100644
index 000000000..67b7d401b
--- /dev/null
+++ b/sdrbase/gui/glscopeng.h
@@ -0,0 +1,131 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 F4EXB //
+// written by Edouard Griffiths //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef SDRBASE_GUI_GLSCOPENG_H_
+#define SDRBASE_GUI_GLSCOPENG_H_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "dsp/dsptypes.h"
+#include "dsp/scopevisng.h"
+#include "gui/scaleengine.h"
+#include "gui/glshadersimple.h"
+#include "gui/glshadertextured.h"
+#include "util/export.h"
+#include "util/bitfieldindex.h"
+
+class QPainter;
+
+class SDRANGEL_API GLScopeNG: public QGLWidget {
+ Q_OBJECT
+
+public:
+ enum DisplayMode {
+ DisplayXYH,
+ DisplayXYV,
+ DisplayX,
+ DisplayY,
+ DisplayPol
+ };
+
+ GLScopeNG(QWidget* parent = 0);
+ virtual ~GLScopeNG();
+
+ void connectTimer(const QTimer& timer);
+
+ void setTraces(const ScopeVisNG::DisplayTraces *traces) { m_traces = traces; m_configChanged = true; }
+ void newTraces();
+
+ void setTriggerPre(Real triggerPre);
+ void setTimeOfsProMill(int timeOfsProMill);
+ void setSampleRate(int sampleRate);
+ void setTimeBase(int timeBase);
+ void setHighlightedTraceIndex(uint32_t traceIndex);
+ void setDisplayMode(DisplayMode displayMode);
+ void setTraceSize(int trceSize);
+
+private:
+ DisplayMode m_displayMode;
+ QTimer m_timer;
+ QMutex m_mutex;
+ bool m_dataChanged;
+ bool m_configChanged;
+ const ScopeVisNG::DisplayTraces *m_traces;
+ int m_sampleRate;
+ int m_timeOfsProMill;
+ Real m_triggerPre;
+ int m_traceSize;
+ int m_timeBase;
+ uint32_t m_highlightedTraceIndex;
+
+ // graphics stuff
+ QRectF m_glScopeRect1;
+ QRectF m_glScopeRect2;
+ QMatrix4x4 m_glScopeMatrix1;
+ QMatrix4x4 m_glScopeMatrix2;
+ QMatrix4x4 m_glLeft1ScaleMatrix;
+ QMatrix4x4 m_glRight1ScaleMatrix;
+ QMatrix4x4 m_glLeft2ScaleMatrix;
+ QMatrix4x4 m_glBot1ScaleMatrix;
+ QMatrix4x4 m_glBot2ScaleMatrix;
+
+ QPixmap m_left1ScalePixmap;
+ QPixmap m_left2ScalePixmap;
+ QPixmap m_bot1ScalePixmap;
+ QPixmap m_bot2ScalePixmap;
+ QPixmap m_powerOverlayPixmap1;
+
+ int m_displayGridIntensity;
+ int m_displayTraceIntensity;
+
+ ScaleEngine m_x1Scale; //!< Display #1 X scale. Time scale
+ ScaleEngine m_x2Scale; //!< Display #2 X scale. Time scale
+ ScaleEngine m_y1Scale; //!< Display #1 Y scale. Always connected to trace #0 (X trace)
+ ScaleEngine m_y2Scale; //!< Display #2 Y scale. Connected to highlighted Y trace (#1..n)
+
+ QFont m_powerOverlayFont;
+
+ GLShaderSimple m_glShaderSimple;
+ GLShaderTextured m_glShaderLeft1Scale;
+ GLShaderTextured m_glShaderBottom1Scale;
+ GLShaderTextured m_glShaderLeft2Scale;
+ GLShaderTextured m_glShaderBottom2Scale;
+ GLShaderTextured m_glShaderPowerOverlay;
+
+ static const int m_topMargin = 5;
+ static const int m_botMargin = 20;
+ static const int m_leftMargin = 35;
+ static const int m_rightMargin = 5;
+
+ void initializeGL();
+ void resizeGL(int width, int height);
+ void paintGL();
+
+ void applyConfig();
+ void setYScale(ScaleEngine& scale, uint32_t highlightedTraceIndex);
+
+protected slots:
+ void cleanup();
+ void tick();
+
+};
+
+#endif /* SDRBASE_GUI_GLSCOPENG_H_ */
diff --git a/sdrbase/gui/glscopenggui.cpp b/sdrbase/gui/glscopenggui.cpp
new file mode 100644
index 000000000..2ebe6d041
--- /dev/null
+++ b/sdrbase/gui/glscopenggui.cpp
@@ -0,0 +1,88 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 F4EXB //
+// written by Edouard Griffiths //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include "glscopenggui.h"
+#include "ui_glscopenggui.h"
+#include "util/simpleserializer.h"
+
+GLScopeNGGUI::GLScopeNGGUI(QWidget* parent) :
+ QWidget(parent),
+ ui(new Ui::GLScopeNGGUI),
+ m_messageQueue(0),
+ m_glScope(0),
+ m_scopeVis(0),
+ m_sampleRate(0)
+{
+ ui->setupUi(this);
+}
+
+GLScopeNGGUI::~GLScopeNGGUI()
+{
+ delete ui;
+}
+
+void GLScopeNGGUI::setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GLScopeNG* glScope)
+{
+ m_messageQueue = messageQueue;
+ m_scopeVis = scopeVis;
+ m_glScope = glScope;
+ applySettings();
+}
+
+void GLScopeNGGUI::setSampleRate(int sampleRate)
+{
+ m_sampleRate = sampleRate;
+}
+
+void GLScopeNGGUI::resetToDefaults()
+{
+}
+
+
+QByteArray GLScopeNGGUI::serialize() const
+{
+ // TODO
+ SimpleSerializer s(1);
+ return s.final();
+}
+
+bool GLScopeNGGUI::deserialize(const QByteArray& data)
+{
+ // TODO
+ SimpleDeserializer d(data);
+
+ if(!d.isValid()) {
+ resetToDefaults();
+ return false;
+ }
+
+ if(d.getVersion() == 1) {
+ return true;
+ } else {
+ resetToDefaults();
+ return false;
+ }
+}
+
+void GLScopeNGGUI::applySettings()
+{
+}
+
+bool GLScopeNGGUI::handleMessage(Message* message)
+{
+ return false;
+}
diff --git a/sdrbase/gui/glscopenggui.h b/sdrbase/gui/glscopenggui.h
new file mode 100644
index 000000000..b5bbceeed
--- /dev/null
+++ b/sdrbase/gui/glscopenggui.h
@@ -0,0 +1,64 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2017 F4EXB //
+// written by Edouard Griffiths //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef SDRBASE_GUI_GLSCOPENGGUI_H_
+#define SDRBASE_GUI_GLSCOPENGGUI_H_
+
+#include
+#include "dsp/dsptypes.h"
+#include "util/export.h"
+#include "util/message.h"
+
+namespace Ui {
+ class GLScopeNGGUI;
+}
+
+class MessageQueue;
+class GLScopeNG;
+class ScopeVisNG;
+
+class SDRANGEL_API GLScopeNGGUI : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit GLScopeNGGUI(QWidget* parent = 0);
+ ~GLScopeNGGUI();
+
+ void setBuddies(MessageQueue* messageQueue, ScopeVisNG* scopeVis, GLScopeNG* glScope);
+
+ void setSampleRate(int sampleRate);
+ void resetToDefaults();
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+
+ bool handleMessage(Message* message);
+
+private:
+ Ui::GLScopeNGGUI* ui;
+
+ MessageQueue* m_messageQueue;
+ ScopeVisNG* m_scopeVis;
+ GLScopeNG* m_glScope;
+
+ int m_sampleRate;
+
+ void applySettings();
+
+};
+
+
+#endif /* SDRBASE_GUI_GLSCOPENGGUI_H_ */
diff --git a/sdrbase/gui/glscopenggui.ui b/sdrbase/gui/glscopenggui.ui
new file mode 100644
index 000000000..9cc4c9d66
--- /dev/null
+++ b/sdrbase/gui/glscopenggui.ui
@@ -0,0 +1,1481 @@
+
+
+ GLScopeNGGUI
+
+
+
+ 0
+ 0
+ 634
+ 120
+
+
+
+
+ 634
+ 0
+
+
+
+
+ 8
+
+
+
+ Oscilloscope
+
+
+
+ 1
+
+
+ 2
+
+
+ 1
+
+
+ 2
+
+
+ 1
+
+ -
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 24
+ 24
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 190
+ 190
+ 190
+
+
+
+
+
+
+
+ Show only X trace (trace #0)
+
+
+ X
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 190
+ 190
+ 190
+
+
+
+
+
+
+
+ Show only Y traces
+
+
+ Y
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Display X and Y traces side by side
+
+
+ ...
+
+
+
+ :/horizontal_w.png:/horizontal_w.png
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Display X and Y trances on top of each other
+
+
+ ...
+
+
+
+ :/vertical_w.png:/vertical_w.png
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Display XY traces and polar trace
+
+
+ ...
+
+
+
+ :/constellation.png:/constellation.png
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 24
+ 24
+
+
+
+ Trace intensity
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Grid intensity
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ t:
+
+
+
+ -
+
+
+
+ 32
+ 0
+
+
+
+ 0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Time range
+
+
+ 1
+
+
+ 100
+
+
+ 1
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ d:
+
+
+
+ -
+
+
+
+ 32
+ 0
+
+
+
+ 0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Time offset
+
+
+ 100
+
+
+ 1
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ L:
+
+
+
+ -
+
+
+
+ 32
+ 0
+
+
+
+ 0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Trace length
+
+
+ 1
+
+
+ 100
+
+
+ 1
+
+
+ 20
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+
+ 52
+ 0
+
+
+
+ Currently displayed trace sample rate (kS/s)
+
+
+ 00000.00
+kS/s
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ 2
+
+
-
+
+
+
+ 20
+ 0
+
+
+
+ Tra
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Trace index (0 is X trace)
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 18
+ 18
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 190
+ 190
+ 190
+
+
+
+
+
+
+
+
+ 10
+
+
+
+ Add a new Y trace
+
+
+ +
+
+
+
+ -
+
+
+
+ 18
+ 18
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 190
+ 190
+ 190
+
+
+
+
+
+
+
+
+ 10
+
+
+
+ Remove current Y trace
+
+
+ -
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+
+ 56
+ 16777215
+
+
+
+ Trace mode
+
+
-
+
+ Real
+
+
+ -
+
+ Imag
+
+
+ -
+
+ Mag
+
+
+ -
+
+ MagdB
+
+
+ -
+
+ Phi
+
+
+ -
+
+ dPhi
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ A:
+
+
+
+ -
+
+
+
+ 32
+ 0
+
+
+
+ Amplification value
+
+
+ 000
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Amplification
+
+
+ 10
+
+
+ 1
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ d:
+
+
+
+ -
+
+
+
+ 32
+ 0
+
+
+
+ Offset value
+
+
+ 0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ 2
+
+
-
+
+
+
+ 16777215
+ 14
+
+
+
+ Coarse offset
+
+
+ -100
+
+
+ 1
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 16777215
+ 14
+
+
+
+ Fine offset
+
+
+ 200
+
+
+ 1
+
+
+ Qt::Horizontal
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ Select this trace as Z input
+
+
+ Z
+
+
+
+ -
+
+
+
+ 52
+ 16777215
+
+
+
+ Trace Z modulation mode (none, luminance, false colors)
+
+
-
+
+ No
+
+
+ -
+
+ Lum
+
+
+ -
+
+ Col
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ M:
+
+
+
+ -
+
+
+ Trace memory index (greater is older)
+
+
+ 00
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Select trace memory (0 is live)
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ 2
+
+
-
+
+
+
+ 20
+ 0
+
+
+
+ Trig
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Sellect triggger in trigger chain
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 18
+ 18
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 190
+ 190
+ 190
+
+
+
+
+
+
+
+
+ 10
+
+
+
+ Add a new trigger to the trigger chain
+
+
+ +
+
+
+
+ -
+
+
+
+ 18
+ 18
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 190
+ 190
+ 190
+
+
+
+
+
+
+
+
+ 10
+
+
+
+ Remove current trigger from the chain
+
+
+ -
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+
+ 56
+ 16777215
+
+
+
+ Trigger mode
+
+
-
+
+ Real
+
+
+ -
+
+ Imag
+
+
+ -
+
+ Mag
+
+
+ -
+
+ MagdB
+
+
+ -
+
+ Phi
+
+
+ -
+
+ dPhi
+
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Number of trigger condition before trigger is actuated
+
+
+
+ -
+
+
+ 00
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Trigger on rising edge
+
+
+ ...
+
+
+
+ :/slopep_icon.png:/slopep_icon.png
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Trigger on falling edge
+
+
+ ...
+
+
+
+ :/slopen_icon.png:/slopen_icon.png
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Trigger on both edges
+
+
+ ...
+
+
+
+ :/slopeb_icon.png:/slopeb_icon.png
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ L:
+
+
+
+ -
+
+
+
+ 32
+ 0
+
+
+
+ 0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ 2
+
+
-
+
+
+
+ 16777215
+ 14
+
+
+
+ Trigger level coarse
+
+
+ -100
+
+
+ 1
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 16777215
+ 14
+
+
+
+ Trigger level fine
+
+
+ 200
+
+
+ 1
+
+
+ Qt::Horizontal
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ D:
+
+
+
+ -
+
+
+
+ 32
+ 0
+
+
+
+ 0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Trigger delay
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ P:
+
+
+
+ -
+
+
+
+ 32
+ 0
+
+
+
+ 0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Pre-trigger delay
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 190
+ 190
+ 190
+
+
+
+
+
+
+
+ One shot trigger
+
+
+ 1
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 255
+ 255
+ 255
+
+
+
+
+
+
+
+
+ 190
+ 190
+ 190
+
+
+
+
+
+
+
+ Freerun (de-activate triggers)
+
+
+ F
+
+
+
+
+
+
+
+
+
+ ButtonSwitch
+ QToolButton
+
+
+
+
+
+
+
+
diff --git a/sdrbase/sdrbase.pro b/sdrbase/sdrbase.pro
index f039fdf0f..76c2dfbc8 100644
--- a/sdrbase/sdrbase.pro
+++ b/sdrbase/sdrbase.pro
@@ -72,6 +72,7 @@ SOURCES += mainwindow.cpp\
dsp/basebandsamplesource.cpp\
dsp/nullsink.cpp\
dsp/spectrumscopecombovis.cpp\
+ dsp/spectrumscopengcombovis.cpp\
dsp/scopevis.cpp\
dsp/spectrumvis.cpp\
dsp/threadedbasebandsamplesink.cpp\
@@ -85,6 +86,8 @@ SOURCES += mainwindow.cpp\
gui/cwkeyergui.cpp\
gui/glscope.cpp\
gui/glscopegui.cpp\
+ gui/glscopeng.cpp\
+ gui/glscopenggui.cpp\
gui/glshadersimple.cpp\
gui/glshadertextured.cpp\
gui/glspectrum.cpp\
@@ -172,6 +175,8 @@ HEADERS += mainwindow.h\
dsp/basebandsamplesink.h\
dsp/basebandsamplesource.h\
dsp/nullsink.h\
+ dsp/spectrumscopecombovis.h\
+ dsp/spectrumscopengcombovis.h\
dsp/scopevis.h\
dsp/spectrumvis.h\
dsp/threadedbasebandsamplesink.h\
@@ -186,6 +191,8 @@ HEADERS += mainwindow.h\
gui/cwkeyergui.h\
gui/glscope.h\
gui/glscopegui.h\
+ gui/glscopeng.h\
+ gui/glscopenggui.h\
gui/glshadersimple.h\
gui/glshadertextured.h\
gui/glspectrum.h\
@@ -227,6 +234,7 @@ FORMS += mainwindow.ui\
gui/cwkeyergui.ui\
gui/audiodialog.ui\
gui/glscopegui.ui\
+ gui/glscopenggui.ui\
gui/aboutdialog.ui\
gui/pluginsdialog.ui\
gui/samplingdevicecontrol.ui\
diff --git a/sdrbase/util/doublebuffer.h b/sdrbase/util/doublebuffer.h
new file mode 100644
index 000000000..a15df77de
--- /dev/null
+++ b/sdrbase/util/doublebuffer.h
@@ -0,0 +1,77 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef SDRBASE_UTIL_DOUBLEBUFFER_H_
+#define SDRBASE_UTIL_DOUBLEBUFFER_H_
+
+#include
+#include
+
+template
+class DoubleBufferSimple
+{
+public:
+ DoubleBufferSimple()
+ {
+ m_size = 0;
+ m_current = m_data.end();
+ }
+
+ ~DoubleBufferSimple() {}
+
+ void resize(int size)
+ {
+ m_size = size;
+ m_data.resize(2*size);
+ m_current = m_data.begin();
+ }
+
+ void write(const typename std::vector::const_iterator& begin, const typename std::vector::const_iterator& cend)
+ {
+ typename std::vector::const_iterator end = cend;
+
+ if ((end - begin) > m_size)
+ {
+ end = begin + m_size;
+ }
+
+ int insize = end - begin;
+
+ std::copy(begin, end, m_current);
+
+ if (((m_current - m_data.begin()) + insize) > m_size)
+ {
+ int sizeLeft = m_size - (m_current - m_data.begin());
+ std::copy(begin, begin + sizeLeft, m_current + m_size);
+ std::copy(begin + sizeLeft, end, m_data.begin());
+ m_current = m_data.begin() + (insize - sizeLeft);
+ }
+ else
+ {
+ std::copy(begin, end, m_current + m_size);
+ m_current += insize;
+ }
+ }
+
+ typename std::vector::iterator getCurrent() const { return m_current + m_size; }
+
+private:
+ int m_size;
+ std::vector m_data;
+ typename std::vector::iterator m_current;
+};
+
+#endif /* SDRBASE_UTIL_DOUBLEBUFFER_H_ */