1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-22 08:04:49 -05:00

New scope: interim state (1)

This commit is contained in:
f4exb 2017-01-29 19:51:45 +01:00
parent 6d4c000107
commit f0f7838765
22 changed files with 5147 additions and 0 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "chanalyzerng.h"
#include <dsp/downchannelizer.h>
#include <QTime>
#include <QDebug>
#include <stdio.h>
#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<<m_spanLog2;
unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1)
m_settingsMutex.lock();
for(SampleVector::const_iterator it = begin; it < end; ++it)
{
//Complex c(it->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;
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_CHANALYZERNG_H
#define INCLUDE_CHANALYZERNG_H
#include <dsp/basebandsamplesink.h>
#include <QMutex>
#include <vector>
#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

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "chanalyzernggui.h"
#include <device/devicesourceapi.h>
#include <dsp/downchannelizer.h>
#include <QDockWidget>
#include <QMainWindow>
#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<<spanLog2);
m_rate = m_channelAnalyzer->getSampleRate() / (1<<spanLog2);
if (ui->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);
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#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<Real> 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

View File

@ -0,0 +1,540 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ChannelAnalyzerNGGUI</class>
<widget class="RollupWidget" name="ChannelAnalyzerNGGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>640</width>
<height>814</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>640</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Channel Analyzer NG</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>10</y>
<width>301</width>
<height>131</height>
</rect>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="DeltaFreqPowLayout">
<item>
<layout class="QHBoxLayout" name="DeltaFrequencyLayout">
<item>
<widget class="QToolButton" name="deltaMinus">
<property name="toolTip">
<string>Frequency shift direction</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset>
<selectedoff>:/plus.png</selectedoff>
<selectedon>:/minus.png</selectedon>
</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="ValueDial" name="deltaFrequency" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>SizeVerCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Demod shift frequency from center in Hz</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="deltaUnits">
<property name="palette">
<palette>
<active>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>26</red>
<green>26</green>
<blue>26</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>26</red>
<green>26</green>
<blue>26</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>118</red>
<green>118</green>
<blue>117</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="text">
<string> Hz</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="ChannelPowerLayout">
<item>
<widget class="QLabel" name="channelPower">
<property name="toolTip">
<string>Channel power</string>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>0.0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelPowerUnits">
<property name="text">
<string> dB</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="SpanLayout">
<item>
<widget class="QLabel" name="spanLabel">
<property name="text">
<string>Rate</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="spanLog2">
<property name="toolTip">
<string>Channel sample rate</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>6</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>3</number>
</property>
<property name="sliderPosition">
<number>3</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="invertedAppearance">
<bool>true</bool>
</property>
<property name="invertedControls">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="spanText">
<property name="text">
<string>6.0k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="ssb">
<property name="toolTip">
<string>SSB/DSB togggle</string>
</property>
<property name="text">
<string>SSB</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="BWLayout">
<item>
<widget class="QLabel" name="BWLabel">
<property name="text">
<string>BW</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="BW">
<property name="toolTip">
<string>Lowpass filter cutoff frequency</string>
</property>
<property name="minimum">
<number>-60</number>
</property>
<property name="maximum">
<number>60</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>30</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="BWText">
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>3.0k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="LowCutLayout">
<item>
<widget class="QLabel" name="lowCutLabel">
<property name="text">
<string>Low cut.</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="lowCut">
<property name="toolTip">
<string>Highpass filter cutoff frequency (SSB)</string>
</property>
<property name="minimum">
<number>-60</number>
</property>
<property name="maximum">
<number>60</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>3</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lowCutText">
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>0.3k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="spectrumContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>180</y>
<width>636</width>
<height>284</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>636</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Channel Spectrum</string>
</property>
<layout class="QVBoxLayout" name="verticalLayoutSpectrum">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="GLSpectrum" name="glSpectrum" native="true">
<property name="minimumSize">
<size>
<width>200</width>
<height>250</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
<pointsize>8</pointsize>
</font>
</property>
</widget>
</item>
<item>
<widget class="GLSpectrumGUI" name="spectrumGUI" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="scopeContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>470</y>
<width>636</width>
<height>284</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>636</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Channel Scope</string>
</property>
<layout class="QVBoxLayout" name="verticalLayoutScope">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="GLScopeNG" name="glScope" native="true">
<property name="minimumSize">
<size>
<width>200</width>
<height>250</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
<pointsize>8</pointsize>
</font>
</property>
</widget>
</item>
<item>
<widget class="GLScopeNGGUI" name="scopeGUI" native="true"/>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupWidget</class>
<extends>QWidget</extends>
<header>gui/rollupwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ValueDial</class>
<extends>QWidget</extends>
<header>gui/valuedial.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLSpectrum</class>
<extends>QWidget</extends>
<header>gui/glspectrum.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLSpectrumGUI</class>
<extends>QWidget</extends>
<header>gui/glspectrumgui.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLScopeNG</class>
<extends>QWidget</extends>
<header>gui/glscopeng.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLScopeNGGUI</class>
<extends>QWidget</extends>
<header>gui/glscopenggui.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrbase/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QtPlugin>
#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);
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_CHANALYZERNGPLUGIN_H
#define INCLUDE_CHANALYZERNGPLUGIN_H
#include <QObject>
#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

299
sdrbase/dsp/scopevisng.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#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<TriggerCondition>::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<Trace>::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<Trace>::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<Trace>::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();
}

285
sdrbase/dsp/scopevisng.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_SCOPEVISNG_H_
#define SDRBASE_DSP_SCOPEVISNG_H_
#include <stdint.h>
#include <vector>
#include <boost/circular_buffer.hpp>
#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<DisplayTrace> 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<Sample> 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<TraceBuffer> m_tracebackBuffers; //!< One complex (Sample type) trace buffer per input source or feed
DoubleBufferSimple<Sample> m_traceback; //!< FIFO to handle delayed processes
int m_preTriggerDelay; //!< Pre-trigger delay in number of samples
std::vector<TriggerCondition> m_triggerConditions; //!< Chain of triggers
int m_currentTriggerIndex; //!< Index of current index in the chain
TriggerState m_triggerState; //!< Current trigger state
std::vector<Trace> 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<Trace>::iterator& trace);
};
#endif /* SDRBASE_DSP_SCOPEVISNG_H_ */

View File

@ -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);
}

View File

@ -0,0 +1,27 @@
#ifndef INCLUDE_SPECTRUMSCOPENGCOMBOVIS_H
#define INCLUDE_SPECTRUMSCOPENGCOMBOVIS_H
#include <dsp/basebandsamplesink.h>
#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

996
sdrbase/gui/glscopeng.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QPainter>
#include <QMouseEvent>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QSurface>
#include <QDebug>
#include <algorithm>
#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();
}

131
sdrbase/gui/glscopeng.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_GUI_GLSCOPENG_H_
#define SDRBASE_GUI_GLSCOPENG_H_
#include <QGLWidget>
#include <QPen>
#include <QTimer>
#include <QMutex>
#include <QFont>
#include <QMatrix4x4>
#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_ */

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#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;
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_GUI_GLSCOPENGGUI_H_
#define SDRBASE_GUI_GLSCOPENGGUI_H_
#include <QWidget>
#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_ */

1481
sdrbase/gui/glscopenggui.ui Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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\

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_UTIL_DOUBLEBUFFER_H_
#define SDRBASE_UTIL_DOUBLEBUFFER_H_
#include <vector>
#include <algorithm>
template<typename T>
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<T>::const_iterator& begin, const typename std::vector<T>::const_iterator& cend)
{
typename std::vector<T>::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<T>::iterator getCurrent() const { return m_current + m_size; }
private:
int m_size;
std::vector<T> m_data;
typename std::vector<T>::iterator m_current;
};
#endif /* SDRBASE_UTIL_DOUBLEBUFFER_H_ */