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