From b49e68e77b05285f67ed750dd7f6079322b077e6 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 24 Nov 2019 10:12:58 +0100 Subject: [PATCH] Channel Analyzer: refactoring of classes and downsampling chain reorganization --- doc/img/ChAnalyzerNG_plugin_settings.png | Bin 22617 -> 19706 bytes doc/img/ChAnalyzerNG_plugin_settings.xcf | Bin 136213 -> 92035 bytes plugins/channelrx/chanalyzer/CMakeLists.txt | 8 +- plugins/channelrx/chanalyzer/chanalyzer.cpp | 341 +++--------------- plugins/channelrx/chanalyzer/chanalyzer.h | 172 ++------- .../chanalyzer/chanalyzerbaseband.cpp | 156 ++++++++ .../channelrx/chanalyzer/chanalyzerbaseband.h | 90 +++++ .../channelrx/chanalyzer/chanalyzergui.cpp | 114 +++--- plugins/channelrx/chanalyzer/chanalyzergui.h | 10 +- plugins/channelrx/chanalyzer/chanalyzergui.ui | 276 +++++++------- .../channelrx/chanalyzer/chanalyzerplugin.cpp | 2 +- .../chanalyzer/chanalyzersettings.cpp | 24 +- .../channelrx/chanalyzer/chanalyzersettings.h | 8 +- .../channelrx/chanalyzer/chanalyzersink.cpp | 269 ++++++++++++++ plugins/channelrx/chanalyzer/chanalyzersink.h | 123 +++++++ .../chanalyzer/chanalyzerwebapiadapter.cpp | 16 +- plugins/channelrx/chanalyzer/readme.md | 72 ++-- sdrbase/dsp/downsamplechannelizer.h | 3 +- sdrgui/dsp/scopevis.cpp | 9 +- sdrgui/dsp/scopevis.h | 2 - 20 files changed, 966 insertions(+), 729 deletions(-) create mode 100644 plugins/channelrx/chanalyzer/chanalyzerbaseband.cpp create mode 100644 plugins/channelrx/chanalyzer/chanalyzerbaseband.h create mode 100644 plugins/channelrx/chanalyzer/chanalyzersink.cpp create mode 100644 plugins/channelrx/chanalyzer/chanalyzersink.h diff --git a/doc/img/ChAnalyzerNG_plugin_settings.png b/doc/img/ChAnalyzerNG_plugin_settings.png index 7d6feeaae062627fb689b7e56381ac34db78a929..f80fabcb60126ad810450885778e43483c76b518 100644 GIT binary patch literal 19706 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 diff --git a/plugins/channelrx/chanalyzer/CMakeLists.txt b/plugins/channelrx/chanalyzer/CMakeLists.txt index a93f72035..942f94311 100644 --- a/plugins/channelrx/chanalyzer/CMakeLists.txt +++ b/plugins/channelrx/chanalyzer/CMakeLists.txt @@ -4,7 +4,9 @@ set(chanalyzer_SOURCES chanalyzer.cpp chanalyzergui.cpp chanalyzerplugin.cpp - chanalyzersettings.cpp + chanalyzersettings.cpp + chanalyzersink.cpp + chanalyzerbaseband.cpp chanalyzerwebapiadapter.cpp chanalyzergui.ui ) @@ -13,7 +15,9 @@ set(chanalyzer_HEADERS chanalyzer.h chanalyzergui.h chanalyzerplugin.h - chanalyzersettings.h + chanalyzersettings.h + chanalyzersink.h + chanalyzerbaseband.h chanalyzerwebapiadapter.h ) diff --git a/plugins/channelrx/chanalyzer/chanalyzer.cpp b/plugins/channelrx/chanalyzer/chanalyzer.cpp index cac11cf05..ef682d2eb 100644 --- a/plugins/channelrx/chanalyzer/chanalyzer.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzer.cpp @@ -17,201 +17,77 @@ #include #include +#include + #include #include "device/deviceapi.h" #include "audio/audiooutput.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/downchannelizer.h" +#include "dsp/dspcommands.h" #include "chanalyzer.h" MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelAnalyzer, Message) -MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgReportChannelSampleRateChanged, Message) const QString ChannelAnalyzer::m_channelIdURI = "sdrangel.channel.chanalyzer"; const QString ChannelAnalyzer::m_channelId = "ChannelAnalyzer"; -const unsigned int ChannelAnalyzer::m_corrFFTLen = 4*ssbFftLen; ChannelAnalyzer::ChannelAnalyzer(DeviceAPI *deviceAPI) : ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), m_deviceAPI(deviceAPI), - m_sampleSink(0), - m_settingsMutex(QMutex::Recursive) + m_basebandSampleRate(0) { + qDebug("ChannelAnalyzer::ChannelAnalyzer"); setObjectName(m_channelId); - m_undersampleCount = 0; - m_sum = 0; - m_usb = true; - m_magsq = 0; - m_useInterpolator = false; - m_interpolatorDistance = 1.0f; - m_interpolatorDistanceRemain = 0.0f; - m_inputSampleRate = 48000; - m_inputFrequencyOffset = 0; - SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_inputSampleRate, m_settings.m_bandwidth / m_inputSampleRate, ssbFftLen); - DSBFilter = new fftfilt(m_settings.m_bandwidth / m_inputSampleRate, 2*ssbFftLen); - RRCFilter = new fftfilt(m_settings.m_bandwidth / m_inputSampleRate, 2*ssbFftLen); - m_corr = new fftcorr(2*m_corrFFTLen); // 8k for 4k effective samples - m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain + m_thread = new QThread(this); + m_basebandSink = new ChannelAnalyzerBaseband(); + m_basebandSink->moveToThread(m_thread); - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); applySettings(m_settings, true); - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - m_deviceAPI->addChannelSink(m_threadedChannelizer); + m_deviceAPI->addChannelSink(this); m_deviceAPI->addChannelSinkAPI(this); } ChannelAnalyzer::~ChannelAnalyzer() { m_deviceAPI->removeChannelSinkAPI(this); - m_deviceAPI->removeChannelSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; - delete SSBFilter; - delete DSBFilter; - delete RRCFilter; - delete m_corr; + m_deviceAPI->removeChannelSink(this); + delete m_basebandSink; + delete m_thread; } void ChannelAnalyzer::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) { (void) positiveOnly; - fftfilt::cmplx *sideband = 0; - Complex ci; - - m_settingsMutex.lock(); - - for(SampleVector::const_iterator it = begin; it < end; ++it) - { - Complex c(it->real(), it->imag()); - c *= m_nco.nextIQ(); - - if (m_useInterpolator) - { - if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) - { - processOneSample(ci, sideband); - m_interpolatorDistanceRemain += m_interpolatorDistance; - } - } - else - { - processOneSample(c, sideband); - } - } - - if(m_sampleSink != 0) - { - m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), m_settings.m_ssb); // m_ssb = positive only - } - - m_sampleBuffer.clear(); - - m_settingsMutex.unlock(); + m_basebandSink->feed(begin, end); } -void ChannelAnalyzer::processOneSample(Complex& c, fftfilt::cmplx *sideband) -{ - int n_out; - int decim = 1<runSSB(c, &sideband, m_usb); - } - else - { - if (m_settings.m_rrc) { - n_out = RRCFilter->runFilt(c, &sideband); - } 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 - 1))) // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) - { - m_sum /= decim; - Real re = m_sum.real() / SDR_RX_SCALEF; - Real im = m_sum.imag() / SDR_RX_SCALEF; - m_magsq = re*re + im*im; - m_channelPowerAvg(m_magsq); - std::complex mix; - - if (m_settings.m_pll) - { - if (m_settings.m_fll) - { - m_fll.feed(re, im); - // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) - mix = m_sum * std::conj(m_fll.getComplex()); - } - else - { - m_pll.feed(re, im); - // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) - mix = m_sum * std::conj(m_pll.getComplex()); - } - } - - feedOneSample(m_settings.m_pll ? mix : m_sum, m_settings.m_fll ? m_fll.getComplex() : m_pll.getComplex()); - m_sum = 0; - } - } -} void ChannelAnalyzer::start() { - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + qDebug() << "ChannelAnalyzer::start"; + + if (m_basebandSampleRate != 0) { + m_basebandSink->setBasebandSampleRate(m_basebandSampleRate); + } + + m_basebandSink->reset(); + m_thread->start(); } void ChannelAnalyzer::stop() { + qDebug() << "ChannelAnalyzer::stop"; + m_thread->exit(); + m_thread->wait(); } bool ChannelAnalyzer::handleMessage(const Message& cmd) { - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - qDebug() << "ChannelAnalyzer::handleMessage: DownChannelizer::MsgChannelizerNotification:" - << " sampleRate: " << notif.getSampleRate() - << " frequencyOffset: " << notif.getFrequencyOffset(); - - applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); - - if (getMessageQueueToGUI()) - { - MsgReportChannelSampleRateChanged *msg = MsgReportChannelSampleRateChanged::create(); - getMessageQueueToGUI()->push(msg); - } - - return true; - } - else if (MsgConfigureChannelizer::match(cmd)) - { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - qDebug() << "ChannelAnalyzer::handleMessage: MsgConfigureChannelizer:" - << " sampleRate: " << cfg.getSampleRate() - << " centerFrequency: " << cfg.getCenterFrequency(); - - m_channelizer->configure(m_channelizer->getInputMessageQueue(), - cfg.getSampleRate(), - cfg.getCenterFrequency()); - - return true; - } - else if (MsgConfigureChannelAnalyzer::match(cmd)) + if (MsgConfigureChannelAnalyzer::match(cmd)) { qDebug("ChannelAnalyzer::handleMessage: MsgConfigureChannelAnalyzer"); MsgConfigureChannelAnalyzer& cfg = (MsgConfigureChannelAnalyzer&) cmd; @@ -220,175 +96,46 @@ bool ChannelAnalyzer::handleMessage(const Message& cmd) return true; } - else - { - // Processed through GUI -// if (m_sampleSink != 0) -// { -// return m_sampleSink->handleMessage(cmd); -// } -// else -// { -// return false; -// } - return false; - } -} - -void ChannelAnalyzer::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) -{ - qDebug() << "ChannelAnalyzer::applyChannelSettings:" - << " inputSampleRate: " << inputSampleRate - << " inputFrequencyOffset: " << inputFrequencyOffset; - - if ((m_inputFrequencyOffset != inputFrequencyOffset) || - (m_inputSampleRate != inputSampleRate) || force) + else if (DSPSignalNotification::match(cmd)) { - m_nco.setFreq(-inputFrequencyOffset, inputSampleRate); - } + DSPSignalNotification& cfg = (DSPSignalNotification&) cmd; + m_basebandSampleRate = cfg.getSampleRate(); + DSPSignalNotification *notif = new DSPSignalNotification(cfg); + m_basebandSink->getInputMessageQueue()->push(notif); - if ((m_inputSampleRate != inputSampleRate) || force) - { - m_settingsMutex.lock(); - - m_interpolator.create(16, inputSampleRate, inputSampleRate / 2.2f); - m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) inputSampleRate / (Real) m_settings.m_downSampleRate; - - if (!m_settings.m_downSample) + if (getMessageQueueToGUI()) { - setFilters(inputSampleRate, m_settings.m_bandwidth, m_settings.m_lowCutoff); - m_pll.setSampleRate(inputSampleRate / (1<push(notifToGUI); } - m_settingsMutex.unlock(); + return true; } - - m_inputSampleRate = inputSampleRate; - m_inputFrequencyOffset = inputFrequencyOffset; -} - -void ChannelAnalyzer::setFilters(int sampleRate, float bandwidth, float lowCutoff) -{ - qDebug("ChannelAnalyzer::setFilters: sampleRate: %d bandwidth: %f lowCutoff: %f", - sampleRate, bandwidth, lowCutoff); - - if (bandwidth < 0) - { - bandwidth = -bandwidth; - lowCutoff = -lowCutoff; - m_usb = false; - } - else - { - m_usb = true; - } - - if (bandwidth < 100.0f) - { - bandwidth = 100.0f; - lowCutoff = 0; - } - - SSBFilter->create_filter(lowCutoff / sampleRate, bandwidth / sampleRate); - DSBFilter->create_dsb_filter(bandwidth / sampleRate); - RRCFilter->create_rrc_filter(bandwidth / sampleRate, m_settings.m_rrcRolloff / 100.0); + else + { + return false; + } } void ChannelAnalyzer::applySettings(const ChannelAnalyzerSettings& settings, bool force) { qDebug() << "ChannelAnalyzer::applySettings:" - << " m_downSample: " << settings.m_downSample - << " m_downSampleRate: " << settings.m_downSampleRate + << " m_rationalDownSample: " << settings.m_rationalDownSample + << " m_rationalDownSamplerRate: " << settings.m_rationalDownSamplerRate << " m_rcc: " << settings.m_rrc << " m_rrcRolloff: " << settings.m_rrcRolloff / 100.0 << " m_bandwidth: " << settings.m_bandwidth << " m_lowCutoff: " << settings.m_lowCutoff - << " m_spanLog2: " << settings.m_spanLog2 + << " m_log2Decim: " << settings.m_log2Decim << " m_ssb: " << settings.m_ssb << " m_pll: " << settings.m_pll << " m_fll: " << settings.m_fll << " m_pllPskOrder: " << settings.m_pllPskOrder << " m_inputType: " << (int) settings.m_inputType; - if ((settings.m_downSampleRate != m_settings.m_downSampleRate) || force) - { - m_settingsMutex.lock(); - m_interpolator.create(16, m_inputSampleRate, m_inputSampleRate / 2.2); - m_interpolatorDistanceRemain = 0.0f; - m_interpolatorDistance = (Real) m_inputSampleRate / (Real) settings.m_downSampleRate; - m_settingsMutex.unlock(); - } - - if ((settings.m_downSample != m_settings.m_downSample) || force) - { - int sampleRate = settings.m_downSample ? settings.m_downSampleRate : m_inputSampleRate; - - m_settingsMutex.lock(); - m_useInterpolator = settings.m_downSample; - setFilters(sampleRate, settings.m_bandwidth, settings.m_lowCutoff); - m_pll.setSampleRate(sampleRate / (1<create_rrc_filter(settings.m_bandwidth / sampleRate, settings.m_rrcRolloff / 100.0); - m_settingsMutex.unlock(); - } - - if ((settings.m_spanLog2 != m_settings.m_spanLog2) || force) - { - int sampleRate = (settings.m_downSample ? settings.m_downSampleRate : m_inputSampleRate) / (1<getInputMessageQueue()->push(msg); m_settings = settings; } - -Real ChannelAnalyzer::getPllFrequency() const -{ - if (m_settings.m_fll) { - return m_fll.getFreq(); - } else if (m_settings.m_pll) { - return m_pll.getFreq(); - } else { - return 0.0; - } -} diff --git a/plugins/channelrx/chanalyzer/chanalyzer.h b/plugins/channelrx/chanalyzer/chanalyzer.h index 5e12a95ec..403caaba7 100644 --- a/plugins/channelrx/chanalyzer/chanalyzer.h +++ b/plugins/channelrx/chanalyzer/chanalyzer.h @@ -15,31 +15,21 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDE_CHANALYZERNG_H -#define INCLUDE_CHANALYZERNG_H +#ifndef INCLUDE_CHANALYZER_H +#define INCLUDE_CHANALYZER_H #include #include #include "dsp/basebandsamplesink.h" #include "channel/channelapi.h" -#include "dsp/interpolator.h" -#include "dsp/ncof.h" -#include "dsp/fftcorr.h" -#include "dsp/fftfilt.h" -#include "dsp/phaselockcomplex.h" -#include "dsp/freqlockcomplex.h" -#include "audio/audiofifo.h" #include "util/message.h" #include "util/movingaverage.h" -#include "chanalyzersettings.h" +#include "chanalyzerbaseband.h" -#define ssbFftLen 1024 - -class DeviceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; +class QThread; +class DownSampleChannelizer; class ChannelAnalyzer : public BasebandSampleSink, public ChannelAPI { public: @@ -50,8 +40,7 @@ public: const ChannelAnalyzerSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureChannelAnalyzer* create(const ChannelAnalyzerSettings& settings, bool force) - { + static MsgConfigureChannelAnalyzer* create(const ChannelAnalyzerSettings& settings, bool force) { return new MsgConfigureChannelAnalyzer(settings, force); } @@ -66,71 +55,19 @@ public: { } }; - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) - { - return new MsgConfigureChannelizer(sampleRate, centerFrequency); - } - - private: - int m_sampleRate; - int m_centerFrequency; - - MsgConfigureChannelizer(int sampleRate, int centerFrequency) : - Message(), - m_sampleRate(sampleRate), - m_centerFrequency(centerFrequency) - { } - }; - - class MsgReportChannelSampleRateChanged : public Message { - MESSAGE_CLASS_DECLARATION - - public: - - static MsgReportChannelSampleRateChanged* create() - { - return new MsgReportChannelSampleRateChanged(); - } - - private: - - MsgReportChannelSampleRateChanged() : - Message() - { } - }; - ChannelAnalyzer(DeviceAPI *deviceAPI); virtual ~ChannelAnalyzer(); virtual void destroy() { delete this; } - void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } + void setSampleSink(BasebandSampleSink* sampleSink) { m_basebandSink->setSampleSink(sampleSink); } -// void configure(MessageQueue* messageQueue, -// int channelSampleRate, -// Real Bandwidth, -// Real LowCutoff, -// int spanLog2, -// bool ssb, -// bool pll, -// bool fll, -// unsigned int pllPskOrder); - - DownChannelizer *getChannelizer() { return m_channelizer; } - int getInputSampleRate() const { return m_inputSampleRate; } - int getChannelSampleRate() const { return m_settings.m_downSample ? m_settings.m_downSampleRate : m_inputSampleRate; } - int getDecimation() const { return 1<getChannelSampleRate(); } + int getDecimation() const { return 1<getMagSq(); } + double getMagSqAvg() const { return m_basebandSink->getMagSqAvg(); } + bool isPllLocked() const { return m_basebandSink->isPllLocked(); } + Real getPllFrequency() const { return m_basebandSink->getPllFrequency(); } + Real getPllDeltaPhase() const { return m_basebandSink->getPllDeltaPhase(); } + Real getPllPhase() const { return m_basebandSink->getPllPhase(); } virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); virtual void start(); @@ -139,7 +76,7 @@ public: virtual void getIdentifier(QString& id) { id = objectName(); } virtual void getTitle(QString& title) { title = objectName(); } - virtual qint64 getCenterFrequency() const { return m_settings.m_frequency; } + virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } virtual QByteArray serialize() const { return QByteArray(); } virtual bool deserialize(const QByteArray& data) { (void) data; return false; } @@ -151,87 +88,20 @@ public: { (void) streamIndex; (void) sinkElseSource; - return m_settings.m_frequency; + return m_settings.m_inputFrequencyOffset; } static const QString m_channelIdURI; static const QString m_channelId; - static const unsigned int m_corrFFTLen; private: DeviceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; + QThread *m_thread; + ChannelAnalyzerBaseband *m_basebandSink; ChannelAnalyzerSettings m_settings; + int m_basebandSampleRate; //!< stored from device message used when starting baseband sink - int m_inputSampleRate; - int m_inputFrequencyOffset; - int m_undersampleCount; - fftfilt::cmplx m_sum; - bool m_usb; - double m_magsq; - bool m_useInterpolator; - - NCOF m_nco; - PhaseLockComplex m_pll; - FreqLockComplex m_fll; - Interpolator m_interpolator; - Real m_interpolatorDistance; - Real m_interpolatorDistanceRemain; - - fftfilt* SSBFilter; - fftfilt* DSBFilter; - fftfilt* RRCFilter; - fftcorr* m_corr; - - BasebandSampleSink* m_sampleSink; - SampleVector m_sampleBuffer; - MovingAverageUtil m_channelPowerAvg; - QMutex m_settingsMutex; - -// void apply(bool force = false); - void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const ChannelAnalyzerSettings& settings, bool force = false); - void setFilters(int sampleRate, float bandwidth, float lowCutoff); - void processOneSample(Complex& c, fftfilt::cmplx *sideband); - - inline void feedOneSample(const fftfilt::cmplx& s, const fftfilt::cmplx& pll) - { - switch (m_settings.m_inputType) - { - case ChannelAnalyzerSettings::InputPLL: - { - if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(pll.imag()*SDR_RX_SCALEF, pll.real()*SDR_RX_SCALEF)); - } else { - m_sampleBuffer.push_back(Sample(pll.real()*SDR_RX_SCALEF, pll.imag()*SDR_RX_SCALEF)); - } - } - break; - case ChannelAnalyzerSettings::InputAutoCorr: - { - //std::complex a = m_corr->run(s/(SDR_RX_SCALEF/768.0f), 0); - std::complex a = m_corr->run(s/SDR_RX_SCALEF, 0); - - if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(a.imag(), a.real())); - } else { - m_sampleBuffer.push_back(Sample(a.real(), a.imag())); - } - } - break; - case ChannelAnalyzerSettings::InputSignal: - default: - { - if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(s.imag(), s.real())); - } else { - m_sampleBuffer.push_back(Sample(s.real(), s.imag())); - } - } - break; - } - } }; -#endif // INCLUDE_CHANALYZERNG_H +#endif // INCLUDE_CHANALYZER_H diff --git a/plugins/channelrx/chanalyzer/chanalyzerbaseband.cpp b/plugins/channelrx/chanalyzer/chanalyzerbaseband.cpp new file mode 100644 index 000000000..1a04bec16 --- /dev/null +++ b/plugins/channelrx/chanalyzer/chanalyzerbaseband.cpp @@ -0,0 +1,156 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "dsp/downsamplechannelizer.h" + +#include "chanalyzerbaseband.h" + +MESSAGE_CLASS_DEFINITION(ChannelAnalyzerBaseband::MsgConfigureChannelAnalyzerBaseband, Message) + +ChannelAnalyzerBaseband::ChannelAnalyzerBaseband() : + m_mutex(QMutex::Recursive) +{ + qDebug("ChannelAnalyzerBaseband::ChannelAnalyzerBaseband"); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); + m_channelizer = new DownSampleChannelizer(&m_sink); + + QObject::connect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &ChannelAnalyzerBaseband::handleData, + Qt::QueuedConnection + ); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); +} + +ChannelAnalyzerBaseband::~ChannelAnalyzerBaseband() +{ + delete m_channelizer; +} + +void ChannelAnalyzerBaseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_sampleFifo.reset(); +} + +void ChannelAnalyzerBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + m_sampleFifo.write(begin, end); +} + +void ChannelAnalyzerBaseband::handleData() +{ + QMutexLocker mutexLocker(&m_mutex); + + while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0)) + { + SampleVector::iterator part1begin; + SampleVector::iterator part1end; + SampleVector::iterator part2begin; + SampleVector::iterator part2end; + + std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end); + + // first part of FIFO data + if (part1begin != part1end) { + m_channelizer->feed(part1begin, part1end); + } + + // second part of FIFO data (used when block wraps around) + if(part2begin != part2end) { + m_channelizer->feed(part2begin, part2end); + } + + m_sampleFifo.readCommit((unsigned int) count); + } +} + +void ChannelAnalyzerBaseband::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool ChannelAnalyzerBaseband::handleMessage(const Message& cmd) +{ + if (MsgConfigureChannelAnalyzerBaseband::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureChannelAnalyzerBaseband& cfg = (MsgConfigureChannelAnalyzerBaseband&) cmd; + qDebug() << "ChannelAnalyzerBaseband::handleMessage: MsgConfigureChannelAnalyzerBaseband"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + qDebug() << "ChannelAnalyzerBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); + m_channelizer->setBasebandSampleRate(notif.getSampleRate()); + unsigned int desiredSampleRate = notif.getSampleRate() / (1<setChannelization(desiredSampleRate, m_settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(desiredSampleRate, m_channelizer->getChannelFrequencyOffset()); + + return true; + } + else + { + return false; + } +} + +void ChannelAnalyzerBaseband::applySettings(const ChannelAnalyzerSettings& settings, bool force) +{ + if ((settings.m_log2Decim != m_settings.m_log2Decim) + || (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)|| force) + { + unsigned int desiredSampleRate = m_channelizer->getBasebandSampleRate() / (1<setChannelization(desiredSampleRate, settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + } + + m_sink.applySettings(settings, force); + m_settings = settings; +} + +int ChannelAnalyzerBaseband::getChannelSampleRate() const +{ + return m_channelizer->getChannelSampleRate(); +} + + +void ChannelAnalyzerBaseband::setBasebandSampleRate(int sampleRate) +{ + m_channelizer->setBasebandSampleRate(sampleRate); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); +} \ No newline at end of file diff --git a/plugins/channelrx/chanalyzer/chanalyzerbaseband.h b/plugins/channelrx/chanalyzer/chanalyzerbaseband.h new file mode 100644 index 000000000..022289008 --- /dev/null +++ b/plugins/channelrx/chanalyzer/chanalyzerbaseband.h @@ -0,0 +1,90 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_CHANNELANALYZERBASEBAND_H +#define INCLUDE_CHANNELANALYZERBASEBAND_H + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "chanalyzersink.h" + +class DownSampleChannelizer; + +class ChannelAnalyzerBaseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigureChannelAnalyzerBaseband : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const ChannelAnalyzerSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureChannelAnalyzerBaseband* create(const ChannelAnalyzerSettings& settings, bool force) + { + return new MsgConfigureChannelAnalyzerBaseband(settings, force); + } + + private: + ChannelAnalyzerSettings m_settings; + bool m_force; + + MsgConfigureChannelAnalyzerBaseband(const ChannelAnalyzerSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + ChannelAnalyzerBaseband(); + ~ChannelAnalyzerBaseband(); + void reset(); + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + int getChannelSampleRate() const; + double getMagSq() { return m_sink.getMagSq(); } + double getMagSqAvg() const { return (double) m_sink.getMagSqAvg(); } + void setBasebandSampleRate(int sampleRate); + void setSampleSink(BasebandSampleSink* sampleSink) { m_sink.setSampleSink(sampleSink); } + bool isPllLocked() const { return m_sink.isPllLocked(); } + Real getPllFrequency() const { return m_sink.getPllFrequency(); } + Real getPllDeltaPhase() const { return m_sink.getPllDeltaPhase(); } + Real getPllPhase() const { return m_sink.getPllPhase(); } + +private: + SampleSinkFifo m_sampleFifo; + DownSampleChannelizer *m_channelizer; + ChannelAnalyzerSink m_sink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + ChannelAnalyzerSettings m_settings; + QMutex m_mutex; + + bool handleMessage(const Message& cmd); + void applySettings(const ChannelAnalyzerSettings& settings, bool force = false); + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + +#endif // INCLUDE_CHANNELANALYZERBASEBAND_H \ No newline at end of file diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index 588cb9d03..f1715ad1b 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -24,6 +24,7 @@ #include "dsp/spectrumscopecombovis.h" #include "dsp/spectrumvis.h" #include "dsp/dspengine.h" +#include "dsp/dspcommands.h" #include "gui/glspectrum.h" #include "gui/glscope.h" #include "gui/basicchannelsettingsdialog.h" @@ -65,7 +66,7 @@ qint64 ChannelAnalyzerGUI::getCenterFrequency() const void ChannelAnalyzerGUI::setCenterFrequency(qint64 centerFrequency) { m_channelMarker.setCenterFrequency(centerFrequency); - m_settings.m_frequency = m_channelMarker.getCenterFrequency(); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); applySettings(); } @@ -77,7 +78,7 @@ void ChannelAnalyzerGUI::resetToDefaults() void ChannelAnalyzerGUI::displaySettings() { m_channelMarker.blockSignals(true); - m_channelMarker.setCenterFrequency(m_settings.m_frequency); + m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); m_channelMarker.setBandwidth(m_settings.m_bandwidth * 2); m_channelMarker.setTitle(m_settings.m_title); m_channelMarker.setLowCutoff(m_settings.m_lowCutoff); @@ -101,13 +102,10 @@ void ChannelAnalyzerGUI::displaySettings() setTitleColor(m_settings.m_rgbColor); setWindowTitle(m_channelMarker.getTitle()); - ui->channelSampleRate->setValueRange(7, 0.501*m_channelAnalyzer->getInputSampleRate(), m_channelAnalyzer->getInputSampleRate()); - ui->channelSampleRate->setValue(m_settings.m_downSampleRate); - blockApplySettings(true); - ui->useRationalDownsampler->setChecked(m_settings.m_downSample); - setNewFinalRate(); + ui->useRationalDownsampler->setChecked(m_settings.m_rationalDownSample); + setSinkSampleRate(); if (m_settings.m_ssb) { ui->BWLabel->setText("LP"); } else { @@ -116,8 +114,8 @@ void ChannelAnalyzerGUI::displaySettings() ui->ssb->setChecked(m_settings.m_ssb); ui->BW->setValue(m_settings.m_bandwidth/100); ui->lowCut->setValue(m_settings.m_lowCutoff/100); - ui->deltaFrequency->setValue(m_settings.m_frequency); - ui->spanLog2->setCurrentIndex(m_settings.m_spanLog2); + ui->deltaFrequency->setValue(m_settings.m_inputFrequencyOffset); + ui->log2Decim->setCurrentIndex(m_settings.m_log2Decim); displayPLLSettings(); ui->signalSelect->setCurrentIndex((int) m_settings.m_inputType); ui->rrcFilter->setChecked(m_settings.m_rrc); @@ -145,18 +143,19 @@ void ChannelAnalyzerGUI::displayPLLSettings() void ChannelAnalyzerGUI::setSpectrumDisplay() { - qDebug("ChannelAnalyzerGUI::setSpectrumDisplay: m_rate: %d", m_rate); + int sinkSampleRate = getSinkSampleRate(); + qDebug("ChannelAnalyzerGUI::setSpectrumDisplay: m_sinkSampleRate: %d", sinkSampleRate); if (m_settings.m_ssb) { - ui->glSpectrum->setCenterFrequency(m_rate/4); - ui->glSpectrum->setSampleRate(m_rate/2); + ui->glSpectrum->setCenterFrequency(sinkSampleRate/4); + ui->glSpectrum->setSampleRate(sinkSampleRate/2); ui->glSpectrum->setSsbSpectrum(true); ui->glSpectrum->setLsbDisplay(ui->BW->value() < 0); } else { ui->glSpectrum->setCenterFrequency(0); - ui->glSpectrum->setSampleRate(m_rate); + ui->glSpectrum->setSampleRate(sinkSampleRate); ui->glSpectrum->setSsbSpectrum(false); ui->glSpectrum->setLsbDisplay(false); } @@ -186,15 +185,11 @@ bool ChannelAnalyzerGUI::deserialize(const QByteArray& data) bool ChannelAnalyzerGUI::handleMessage(const Message& message) { - if (ChannelAnalyzer::MsgReportChannelSampleRateChanged::match(message)) + if (DSPSignalNotification::match(message)) { - qDebug() << "ChannelAnalyzerGUI::handleMessage: MsgReportChannelSampleRateChanged:" << m_channelAnalyzer->getInputSampleRate(); - ui->channelSampleRate->setValueRange(7, 0.501*m_channelAnalyzer->getInputSampleRate(), m_channelAnalyzer->getInputSampleRate()); - ui->channelSampleRate->setValue(m_settings.m_downSampleRate); - m_settings.m_downSampleRate = ui->channelSampleRate->getValueNew(); - setNewFinalRate(); - - return true; + DSPSignalNotification& cmd = (DSPSignalNotification&) message; + m_basebandSampleRate = cmd.getSampleRate(); + setSinkSampleRate(); } return false; @@ -218,6 +213,7 @@ void ChannelAnalyzerGUI::handleInputMessages() void ChannelAnalyzerGUI::channelMarkerChangedByCursor() { ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); applySettings(); } @@ -246,10 +242,10 @@ void ChannelAnalyzerGUI::tick() } } -void ChannelAnalyzerGUI::on_channelSampleRate_changed(quint64 value) +void ChannelAnalyzerGUI::on_rationalDownSamplerRate_changed(quint64 value) { - m_settings.m_downSampleRate = value; - setNewFinalRate(); + m_settings.m_rationalDownSamplerRate = value; + setSinkSampleRate(); applySettings(); } @@ -275,18 +271,16 @@ void ChannelAnalyzerGUI::on_pllPskOrder_currentIndexChanged(int index) void ChannelAnalyzerGUI::on_useRationalDownsampler_toggled(bool checked) { - m_settings.m_downSample = checked; - setNewFinalRate(); + m_settings.m_rationalDownSample = checked; + setSinkSampleRate(); applySettings(); } -int ChannelAnalyzerGUI::getRequestedChannelSampleRate() +int ChannelAnalyzerGUI::getSinkSampleRate() { - if (ui->useRationalDownsampler->isChecked()) { - return ui->channelSampleRate->getValueNew(); - } else { - return m_channelAnalyzer->getChannelizer()->getInputSampleRate(); - } + return m_settings.m_rationalDownSample ? + m_settings.m_rationalDownSamplerRate + : m_basebandSampleRate / (1<setTraceChunkSize(ChannelAnalyzer::m_corrFFTLen); + m_scopeVis->setTraceChunkSize(ChannelAnalyzerSink::m_corrFFTLen); } else { m_scopeVis->setTraceChunkSize(ScopeVis::m_traceChunkDefaultSize); } @@ -306,7 +300,7 @@ void ChannelAnalyzerGUI::on_signalSelect_currentIndexChanged(int index) void ChannelAnalyzerGUI::on_deltaFrequency_changed(qint64 value) { m_channelMarker.setCenterFrequency(value); - m_settings.m_frequency = m_channelMarker.getCenterFrequency(); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); applySettings(); } @@ -342,14 +336,14 @@ void ChannelAnalyzerGUI::on_lowCut_valueChanged(int value) applySettings(); } -void ChannelAnalyzerGUI::on_spanLog2_currentIndexChanged(int index) +void ChannelAnalyzerGUI::on_log2Decim_currentIndexChanged(int index) { if ((index < 0) || (index > 6)) { return; } - m_settings.m_spanLog2 = index; - setNewFinalRate(); + m_settings.m_log2Decim = index; + setSinkSampleRate(); applySettings(); } @@ -379,7 +373,7 @@ void ChannelAnalyzerGUI::onMenuDialogCalled(const QPoint& p) dialog.move(p); dialog.exec(); - m_settings.m_frequency = m_channelMarker.getCenterFrequency(); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); m_settings.m_title = m_channelMarker.getTitle(); @@ -399,7 +393,7 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device m_deviceUISet(deviceUISet), m_channelMarker(this), m_doApplySettings(true), - m_rate(48000) + m_basebandSampleRate(48000) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); @@ -417,11 +411,10 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); - ui->channelSampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); - ui->channelSampleRate->setValueRange(7, 0.501*m_rate, m_rate); + ui->rationalDownSamplerRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); - ui->glSpectrum->setCenterFrequency(m_rate/2); - ui->glSpectrum->setSampleRate(m_rate); + ui->glSpectrum->setCenterFrequency(m_basebandSampleRate/2); + ui->glSpectrum->setSampleRate(m_basebandSampleRate); ui->glSpectrum->setDisplayWaterfall(true); ui->glSpectrum->setDisplayMaxHold(true); ui->glSpectrum->setSsbSpectrum(false); @@ -433,7 +426,7 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device m_channelMarker.blockSignals(true); m_channelMarker.setColor(Qt::gray); - m_channelMarker.setBandwidth(m_rate); + m_channelMarker.setBandwidth(m_basebandSampleRate); m_channelMarker.setSidebands(ChannelMarker::usb); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("Channel Analyzer"); @@ -470,28 +463,33 @@ ChannelAnalyzerGUI::~ChannelAnalyzerGUI() delete ui; } -void ChannelAnalyzerGUI::setNewFinalRate() +void ChannelAnalyzerGUI::setSinkSampleRate() { - m_rate = getRequestedChannelSampleRate() / (1<rationalDownSamplerRate->setValueRange(7, 0.5*channelSampleRate, channelSampleRate); + ui->rationalDownSamplerRate->setValue(m_settings.m_rationalDownSamplerRate); + + unsigned int sinkSampleRate = getSinkSampleRate(); + + qDebug("ChannelAnalyzerGUI::setSinkSampleRate: channelSampleRate: %u sinkSampleRate: %u", + channelSampleRate, sinkSampleRate); setFiltersUIBoundaries(); - QString s = QString::number(m_rate/1000.0, 'f', 1); - ui->spanText->setText(tr("%1 kS/s").arg(s)); + QString s = QString::number(sinkSampleRate/1000.0, 'f', 1); + ui->sinkSampleRateText->setText(tr("%1 kS/s").arg(s)); - m_scopeVis->setLiveRate(getRequestedChannelSampleRate()); + m_scopeVis->setLiveRate(sinkSampleRate == 0 ? 48000 : sinkSampleRate); } void ChannelAnalyzerGUI::setFiltersUIBoundaries() { + int sinkSampleRate = getSinkSampleRate(); bool dsb = !ui->ssb->isChecked(); int bw = ui->BW->value(); int lw = ui->lowCut->value(); - int bwMax = m_rate / 200; + int bwMax = sinkSampleRate / 200; bw = bw < -bwMax ? -bwMax : bw > bwMax ? bwMax : bw; @@ -555,17 +553,9 @@ void ChannelAnalyzerGUI::applySettings(bool force) { if (m_doApplySettings) { - int sampleRate = getRequestedChannelSampleRate(); - - ChannelAnalyzer::MsgConfigureChannelizer *msgChannelizer = - ChannelAnalyzer::MsgConfigureChannelizer::create(sampleRate, m_channelMarker.getCenterFrequency()); - m_channelAnalyzer->getInputMessageQueue()->push(msgChannelizer); - ChannelAnalyzer::MsgConfigureChannelAnalyzer* message = ChannelAnalyzer::MsgConfigureChannelAnalyzer::create( m_settings, force); m_channelAnalyzer->getInputMessageQueue()->push(message); - - m_scopeVis->setLiveRateLog2Decim(m_settings.m_spanLog2); } } diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.h b/plugins/channelrx/chanalyzer/chanalyzergui.h index a76590241..b930a4b14 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.h +++ b/plugins/channelrx/chanalyzer/chanalyzergui.h @@ -68,7 +68,7 @@ private: ChannelMarker m_channelMarker; ChannelAnalyzerSettings m_settings; bool m_doApplySettings; - int m_rate; //!< sample rate after final in-channel decimation (spanlog2) + int m_basebandSampleRate; //!< sample rate after final in-channel decimation (spanlog2) MovingAverageUtil m_channelPowerAvg; ChannelAnalyzer* m_channelAnalyzer; @@ -80,8 +80,8 @@ private: explicit ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); virtual ~ChannelAnalyzerGUI(); - int getRequestedChannelSampleRate(); - void setNewFinalRate(); //!< set sample rate after final in-channel decimation + int getSinkSampleRate(); //!< get actual sink sample rate from GUI settings + void setSinkSampleRate(); //!< set sample rate after full decimation chain void setFiltersUIBoundaries(); void blockApplySettings(bool block); @@ -95,7 +95,7 @@ private: private slots: void on_deltaFrequency_changed(qint64 value); - void on_channelSampleRate_changed(quint64 value); + void on_rationalDownSamplerRate_changed(quint64 value); void on_pll_toggled(bool checked); void on_pllPskOrder_currentIndexChanged(int index); void on_useRationalDownsampler_toggled(bool checked); @@ -104,7 +104,7 @@ private slots: void on_rrcRolloff_valueChanged(int value); void on_BW_valueChanged(int value); void on_lowCut_valueChanged(int value); - void on_spanLog2_currentIndexChanged(int index); + void on_log2Decim_currentIndexChanged(int index); void on_ssb_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui index 30b482199..19aaf5e83 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.ui +++ b/plugins/channelrx/chanalyzer/chanalyzergui.ui @@ -179,6 +179,144 @@ + + + + + 50 + 16777215 + + + + Channel decimation + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + 32 + + + + + 64 + + + + + + + + Use rational downsampler + + + + + + + :/arrow_down.png:/arrow_down.png + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Rational downsampler output rate + + + + + + + S/s + + + + + + + + 80 + 0 + + + + Analyzer (sink) sample rate + + + 00000.0 kS/s + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Select input signal + + + + Sig + + + + + Lock + + + + + ACorr + + + + @@ -240,144 +378,6 @@ - - - - Use rational downsampler - - - - - - - :/arrow_down.png:/arrow_down.png - - - - - - - - 0 - 0 - - - - - 32 - 16 - - - - - Liberation Mono - 12 - - - - PointingHandCursor - - - Rational downsampler output rate - - - - - - - S/s - - - - - - - - 50 - 16777215 - - - - Channel decimation - - - - 1 - - - - - 2 - - - - - 4 - - - - - 8 - - - - - 16 - - - - - 32 - - - - - 64 - - - - - - - - - 80 - 0 - - - - Channel final sample rate - - - 00000.0 kS/s - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Select input signal - - - - Sig - - - - - Lock - - - - - ACorr - - - - diff --git a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp index 8abef564d..7bf9ffddc 100644 --- a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp @@ -25,7 +25,7 @@ const PluginDescriptor ChannelAnalyzerPlugin::m_pluginDescriptor = { QString("Channel Analyzer"), - QString("4.12.1"), + QString("4.12.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/chanalyzer/chanalyzersettings.cpp b/plugins/channelrx/chanalyzer/chanalyzersettings.cpp index 03b949555..f8a313509 100644 --- a/plugins/channelrx/chanalyzer/chanalyzersettings.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzersettings.cpp @@ -33,12 +33,12 @@ ChannelAnalyzerSettings::ChannelAnalyzerSettings() : void ChannelAnalyzerSettings::resetToDefaults() { - m_frequency = 0; - m_downSample = false; - m_downSampleRate = 0; + m_inputFrequencyOffset = 0; + m_rationalDownSample = false; + m_rationalDownSamplerRate = 0; m_bandwidth = 5000; m_lowCutoff = 300; - m_spanLog2 = 0; + m_log2Decim = 0; m_ssb = false; m_pll = false; m_fll = false; @@ -54,16 +54,16 @@ QByteArray ChannelAnalyzerSettings::serialize() const { SimpleSerializer s(1); - s.writeS32(1, m_frequency); + s.writeS32(1, m_inputFrequencyOffset); s.writeS32(2, m_bandwidth); s.writeBlob(3, m_spectrumGUI->serialize()); s.writeU32(4, m_rgbColor); s.writeS32(5, m_lowCutoff); - s.writeS32(6, m_spanLog2); + s.writeS32(6, m_log2Decim); s.writeBool(7, m_ssb); s.writeBlob(8, m_scopeGUI->serialize()); - s.writeBool(9, m_downSample); - s.writeU32(10, m_downSampleRate); + s.writeBool(9, m_rationalDownSample); + s.writeU32(10, m_rationalDownSamplerRate); s.writeBool(11, m_pll); s.writeBool(12, m_fll); s.writeU32(13, m_pllPskOrder); @@ -90,7 +90,7 @@ bool ChannelAnalyzerSettings::deserialize(const QByteArray& data) QByteArray bytetmp; int tmp; - d.readS32(1, &m_frequency, 0); + d.readS32(1, &m_inputFrequencyOffset, 0); d.readS32(2, &m_bandwidth, 5000); if (m_spectrumGUI) { @@ -100,7 +100,7 @@ bool ChannelAnalyzerSettings::deserialize(const QByteArray& data) d.readU32(4, &m_rgbColor); d.readS32(5, &m_lowCutoff, 3); - d.readS32(6, &m_spanLog2, 0); + d.readS32(6, &m_log2Decim, 0); d.readBool(7, &m_ssb, false); if (m_scopeGUI) { @@ -108,8 +108,8 @@ bool ChannelAnalyzerSettings::deserialize(const QByteArray& data) m_scopeGUI->deserialize(bytetmp); } - d.readBool(9, &m_downSample, false); - d.readU32(10, &m_downSampleRate, 2000U); + d.readBool(9, &m_rationalDownSample, false); + d.readU32(10, &m_rationalDownSamplerRate, 2000U); d.readBool(11, &m_pll, false); d.readBool(12, &m_fll, false); d.readU32(13, &m_pllPskOrder, 1); diff --git a/plugins/channelrx/chanalyzer/chanalyzersettings.h b/plugins/channelrx/chanalyzer/chanalyzersettings.h index d1d678545..b230c8752 100644 --- a/plugins/channelrx/chanalyzer/chanalyzersettings.h +++ b/plugins/channelrx/chanalyzer/chanalyzersettings.h @@ -31,12 +31,12 @@ struct ChannelAnalyzerSettings InputAutoCorr }; - int m_frequency; - bool m_downSample; - quint32 m_downSampleRate; + int m_inputFrequencyOffset; + bool m_rationalDownSample; + quint32 m_rationalDownSamplerRate; int m_bandwidth; int m_lowCutoff; - int m_spanLog2; + int m_log2Decim; bool m_ssb; bool m_pll; bool m_fll; diff --git a/plugins/channelrx/chanalyzer/chanalyzersink.cpp b/plugins/channelrx/chanalyzer/chanalyzersink.cpp new file mode 100644 index 000000000..a3abea600 --- /dev/null +++ b/plugins/channelrx/chanalyzer/chanalyzersink.cpp @@ -0,0 +1,269 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "chanalyzersink.h" + +#include +#include +#include + +#include "dsp/basebandsamplesink.h" + +const unsigned int ChannelAnalyzerSink::m_ssbFftLen = 1024; +const unsigned int ChannelAnalyzerSink::m_corrFFTLen = 4*m_ssbFftLen; + +ChannelAnalyzerSink::ChannelAnalyzerSink() : + m_channelSampleRate(48000), + m_channelFrequencyOffset(0), + m_sampleSink(nullptr) +{ + m_usb = true; + m_magsq = 0; + m_interpolatorDistance = 1.0f; + m_interpolatorDistanceRemain = 0.0f; + SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_channelSampleRate, m_settings.m_bandwidth / m_channelSampleRate, m_ssbFftLen); + DSBFilter = new fftfilt(m_settings.m_bandwidth / m_channelSampleRate, 2*m_ssbFftLen); + RRCFilter = new fftfilt(m_settings.m_bandwidth / m_channelSampleRate, 2*m_ssbFftLen); + m_corr = new fftcorr(2*m_corrFFTLen); // 8k for 4k effective samples + m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain + + applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); + applySettings(m_settings, true); +} + +ChannelAnalyzerSink::~ChannelAnalyzerSink() +{ + delete SSBFilter; + delete DSBFilter; + delete RRCFilter; + delete m_corr; +} + +void ChannelAnalyzerSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + fftfilt::cmplx *sideband = 0; + Complex ci; + + for (SampleVector::const_iterator it = begin; it < end; ++it) + { + Complex c(it->real(), it->imag()); + c *= m_nco.nextIQ(); + + if (m_settings.m_rationalDownSample) + { + if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci, sideband); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + else + { + processOneSample(c, sideband); + } + } + + if (m_sampleSink) { + m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), m_settings.m_ssb); // m_ssb = positive only + } + + m_sampleBuffer.clear(); +} + +void ChannelAnalyzerSink::processOneSample(Complex& c, fftfilt::cmplx *sideband) +{ + int n_out; + + if (m_settings.m_ssb) + { + n_out = SSBFilter->runSSB(c, &sideband, m_usb); + } + else + { + if (m_settings.m_rrc) { + n_out = RRCFilter->runFilt(c, &sideband); + } else { + n_out = DSBFilter->runDSB(c, &sideband); + } + } + + for (int i = 0; i < n_out; i++) + { + fftfilt::cmplx si = sideband[i]; + Real re = si.real() / SDR_RX_SCALEF; + Real im = si.imag() / SDR_RX_SCALEF; + m_magsq = re*re + im*im; + m_channelPowerAvg(m_magsq); + std::complex mix; + + if (m_settings.m_pll) + { + if (m_settings.m_fll) + { + m_fll.feed(re, im); + // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) + mix = si * std::conj(m_fll.getComplex()); + } + else + { + m_pll.feed(re, im); + // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) + mix = si * std::conj(m_pll.getComplex()); + } + } + + feedOneSample(m_settings.m_pll ? mix : si, m_settings.m_fll ? m_fll.getComplex() : m_pll.getComplex()); + } +} + +void ChannelAnalyzerSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) +{ + qDebug() << "ChannelAnalyzerSink::applyChannelSettings:" + << " channelSampleRate: " << channelSampleRate + << " channelFrequencyOffset: " << channelFrequencyOffset; + + if ((m_channelFrequencyOffset != channelFrequencyOffset) || + (m_channelSampleRate != channelSampleRate) || force) + { + m_nco.setFreq(-channelFrequencyOffset, channelSampleRate); + } + + if ((m_channelSampleRate != channelSampleRate) || force) + { + m_interpolator.create(16, channelSampleRate, channelSampleRate / 2.2f); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) channelSampleRate / (Real) m_settings.m_rationalDownSamplerRate; + + int sinkSampleRate = m_settings.m_rationalDownSample ? m_settings.m_rationalDownSamplerRate : channelSampleRate; + setFilters(sinkSampleRate, m_settings.m_bandwidth, m_settings.m_lowCutoff); + m_pll.setSampleRate(sinkSampleRate); + m_fll.setSampleRate(sinkSampleRate); + } + + m_channelSampleRate = channelSampleRate; + m_channelFrequencyOffset = channelFrequencyOffset; +} + +void ChannelAnalyzerSink::setFilters(int sampleRate, float bandwidth, float lowCutoff) +{ + qDebug("ChannelAnalyzerSink::setFilters: sampleRate: %d bandwidth: %f lowCutoff: %f", + sampleRate, bandwidth, lowCutoff); + + if (bandwidth < 0) + { + bandwidth = -bandwidth; + lowCutoff = -lowCutoff; + m_usb = false; + } + else + { + m_usb = true; + } + + if (bandwidth < 100.0f) + { + bandwidth = 100.0f; + lowCutoff = 0; + } + + SSBFilter->create_filter(lowCutoff / sampleRate, bandwidth / sampleRate); + DSBFilter->create_dsb_filter(bandwidth / sampleRate); + RRCFilter->create_rrc_filter(bandwidth / sampleRate, m_settings.m_rrcRolloff / 100.0); +} + +void ChannelAnalyzerSink::applySettings(const ChannelAnalyzerSettings& settings, bool force) +{ + qDebug() << "ChannelAnalyzerSink::applySettings:" + << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset + << " m_rationalDownSample: " << settings.m_rationalDownSample + << " m_rationalDownSamplerRate: " << settings.m_rationalDownSamplerRate + << " m_rcc: " << settings.m_rrc + << " m_rrcRolloff: " << settings.m_rrcRolloff / 100.0 + << " m_bandwidth: " << settings.m_bandwidth + << " m_lowCutoff: " << settings.m_lowCutoff + << " m_log2Decim: " << settings.m_log2Decim + << " m_ssb: " << settings.m_ssb + << " m_pll: " << settings.m_pll + << " m_fll: " << settings.m_fll + << " m_pllPskOrder: " << settings.m_pllPskOrder + << " m_inputType: " << (int) settings.m_inputType; + + if ((settings.m_rationalDownSamplerRate != m_settings.m_rationalDownSamplerRate) || force) + { + m_interpolator.create(16, m_channelSampleRate, m_channelSampleRate / 2.2); + m_interpolatorDistanceRemain = 0.0f; + m_interpolatorDistance = (Real) m_channelSampleRate / (Real) settings.m_rationalDownSamplerRate; + } + + if ((settings.m_rationalDownSample != m_settings.m_rationalDownSample) || force) + { + int sinkSampleRate = settings.m_rationalDownSample ? settings.m_rationalDownSamplerRate : m_channelSampleRate; + + setFilters(sinkSampleRate, settings.m_bandwidth, settings.m_lowCutoff); + m_pll.setSampleRate(sinkSampleRate); + m_fll.setSampleRate(sinkSampleRate); + } + + if ((settings.m_bandwidth != m_settings.m_bandwidth) || + (settings.m_lowCutoff != m_settings.m_lowCutoff)|| force) + { + int sinkSampleRate = settings.m_rationalDownSample ? settings.m_rationalDownSamplerRate : m_channelSampleRate; + setFilters(sinkSampleRate, settings.m_bandwidth, settings.m_lowCutoff); + } + + if ((settings.m_rrcRolloff != m_settings.m_rrcRolloff) || force) + { + float sinkSampleRate = settings.m_rationalDownSample ? (float) settings.m_rationalDownSamplerRate : (float) m_channelSampleRate; + RRCFilter->create_rrc_filter(settings.m_bandwidth / sinkSampleRate, settings.m_rrcRolloff / 100.0); + } + + if (settings.m_pll != m_settings.m_pll || force) + { + if (settings.m_pll) + { + m_pll.reset(); + m_fll.reset(); + } + } + + if (settings.m_fll != m_settings.m_fll || force) + { + if (settings.m_fll) { + m_fll.reset(); + } + } + + if (settings.m_pllPskOrder != m_settings.m_pllPskOrder || force) + { + if (settings.m_pllPskOrder < 32) { + m_pll.setPskOrder(settings.m_pllPskOrder); + } + } + + m_settings = settings; +} + +Real ChannelAnalyzerSink::getPllFrequency() const +{ + if (m_settings.m_fll) { + return m_fll.getFreq(); + } else if (m_settings.m_pll) { + return m_pll.getFreq(); + } else { + return 0.0; + } +} diff --git a/plugins/channelrx/chanalyzer/chanalyzersink.h b/plugins/channelrx/chanalyzer/chanalyzersink.h new file mode 100644 index 000000000..a6d374c1e --- /dev/null +++ b/plugins/channelrx/chanalyzer/chanalyzersink.h @@ -0,0 +1,123 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_CHANALYZERSINK_H +#define INCLUDE_CHANALYZERSINK_H + +#include "dsp/channelsamplesink.h" +#include "dsp/interpolator.h" +#include "dsp/ncof.h" +#include "dsp/fftcorr.h" +#include "dsp/fftfilt.h" +#include "dsp/phaselockcomplex.h" +#include "dsp/freqlockcomplex.h" +#include "audio/audiofifo.h" + +#include "util/movingaverage.h" + +#include "chanalyzersettings.h" + +class BasebandSampleSink; + +class ChannelAnalyzerSink : public ChannelSampleSink { +public: + ChannelAnalyzerSink(); + ~ChannelAnalyzerSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false); + void applySettings(const ChannelAnalyzerSettings& settings, bool force = false); + + double getMagSq() const { return m_magsq; } + double getMagSqAvg() const { return (double) m_channelPowerAvg; } + bool isPllLocked() const { return m_settings.m_pll && m_pll.locked(); } + Real getPllFrequency() const; + Real getPllDeltaPhase() const { return m_pll.getDeltaPhi(); } + Real getPllPhase() const { return m_pll.getPhiHat(); } + void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } + + static const unsigned int m_corrFFTLen; + static const unsigned int m_ssbFftLen; + +private: + int m_channelSampleRate; + int m_channelFrequencyOffset; + ChannelAnalyzerSettings m_settings; + + bool m_usb; + double m_magsq; + + NCOF m_nco; + PhaseLockComplex m_pll; + FreqLockComplex m_fll; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + + fftfilt* SSBFilter; + fftfilt* DSBFilter; + fftfilt* RRCFilter; + fftcorr* m_corr; + + SampleVector m_sampleBuffer; + MovingAverageUtil m_channelPowerAvg; + + BasebandSampleSink* m_sampleSink; + + void setFilters(int sampleRate, float bandwidth, float lowCutoff); + void processOneSample(Complex& c, fftfilt::cmplx *sideband); + + inline void feedOneSample(const fftfilt::cmplx& s, const fftfilt::cmplx& pll) + { + switch (m_settings.m_inputType) + { + case ChannelAnalyzerSettings::InputPLL: + { + if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(pll.imag()*SDR_RX_SCALEF, pll.real()*SDR_RX_SCALEF)); + } else { + m_sampleBuffer.push_back(Sample(pll.real()*SDR_RX_SCALEF, pll.imag()*SDR_RX_SCALEF)); + } + } + break; + case ChannelAnalyzerSettings::InputAutoCorr: + { + std::complex a = m_corr->run(s/SDR_RX_SCALEF, 0); + + if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(a.imag(), a.real())); + } else { + m_sampleBuffer.push_back(Sample(a.real(), a.imag())); + } + } + break; + case ChannelAnalyzerSettings::InputSignal: + default: + { + if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(s.imag(), s.real())); + } else { + m_sampleBuffer.push_back(Sample(s.real(), s.imag())); + } + } + break; + } + } +}; + +#endif // INCLUDE_CHANALYZERSINK_H diff --git a/plugins/channelrx/chanalyzer/chanalyzerwebapiadapter.cpp b/plugins/channelrx/chanalyzer/chanalyzerwebapiadapter.cpp index 300da89eb..b41c537ef 100644 --- a/plugins/channelrx/chanalyzer/chanalyzerwebapiadapter.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzerwebapiadapter.cpp @@ -46,12 +46,12 @@ void ChannelAnalyzerWebAPIAdapter::webapiFormatChannelSettings( const GLScopeSettings& scopeSettings, const GLSpectrumSettings& spectrumSettings) { - response.getChannelAnalyzerSettings()->setFrequency(settings.m_frequency); - response.getChannelAnalyzerSettings()->setDownSample(settings.m_downSample ? 1 : 0); - response.getChannelAnalyzerSettings()->setDownSampleRate(settings.m_downSampleRate); + response.getChannelAnalyzerSettings()->setFrequency(settings.m_inputFrequencyOffset); + response.getChannelAnalyzerSettings()->setDownSample(settings.m_rationalDownSample ? 1 : 0); + response.getChannelAnalyzerSettings()->setDownSampleRate(settings.m_rationalDownSamplerRate); response.getChannelAnalyzerSettings()->setBandwidth(settings.m_bandwidth); response.getChannelAnalyzerSettings()->setLowCutoff(settings.m_lowCutoff); - response.getChannelAnalyzerSettings()->setSpanLog2(settings.m_spanLog2); + response.getChannelAnalyzerSettings()->setSpanLog2(settings.m_log2Decim); response.getChannelAnalyzerSettings()->setSsb(settings.m_ssb ? 1 : 0); response.getChannelAnalyzerSettings()->setPll(settings.m_pll ? 1 : 0); response.getChannelAnalyzerSettings()->setFll(settings.m_fll ? 1 : 0); @@ -169,16 +169,16 @@ void ChannelAnalyzerWebAPIAdapter::webapiUpdateChannelSettings( settings.m_bandwidth = response.getChannelAnalyzerSettings()->getBandwidth(); } if (channelSettingsKeys.contains("downSample")) { - settings.m_downSample = response.getChannelAnalyzerSettings()->getDownSample() != 0; + settings.m_rationalDownSample = response.getChannelAnalyzerSettings()->getDownSample() != 0; } if (channelSettingsKeys.contains("downSampleRate")) { - settings.m_downSampleRate = response.getChannelAnalyzerSettings()->getDownSampleRate(); + settings.m_rationalDownSamplerRate = response.getChannelAnalyzerSettings()->getDownSampleRate(); } if (channelSettingsKeys.contains("fll")) { settings.m_fll = response.getChannelAnalyzerSettings()->getFll() != 0; } if (channelSettingsKeys.contains("frequency")) { - settings.m_frequency = response.getChannelAnalyzerSettings()->getFrequency(); + settings.m_inputFrequencyOffset = response.getChannelAnalyzerSettings()->getFrequency(); } if (channelSettingsKeys.contains("inputType")) { settings.m_inputType = (ChannelAnalyzerSettings::InputType) response.getChannelAnalyzerSettings()->getInputType(); @@ -202,7 +202,7 @@ void ChannelAnalyzerWebAPIAdapter::webapiUpdateChannelSettings( settings.m_rrcRolloff = response.getChannelAnalyzerSettings()->getRrcRolloff(); } if (channelSettingsKeys.contains("spanLog2")) { - settings.m_spanLog2 = response.getChannelAnalyzerSettings()->getSpanLog2(); + settings.m_log2Decim = response.getChannelAnalyzerSettings()->getSpanLog2(); } if (channelSettingsKeys.contains("ssb")) { settings.m_ssb = response.getChannelAnalyzerSettings()->getSsb() != 0; diff --git a/plugins/channelrx/chanalyzer/readme.md b/plugins/channelrx/chanalyzer/readme.md index e248a254b..020f17c1f 100644 --- a/plugins/channelrx/chanalyzer/readme.md +++ b/plugins/channelrx/chanalyzer/readme.md @@ -46,13 +46,39 @@ Note 2: the spectrum view (Channel spectrum) is not presented here. Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. -

2: Locked loop

+

2: Decimation by a power of two

+ +This combo can select half-band decimation from baseband sample rate by a power of two. + +

3: Toggle the rational downsampler

+ +The channel sample rate is given by the baseband sample rate possibly decimated by a power of two with the control above. This sample rate can be optionally further downsampled to any value between 1.0 and 0.5 using a rational downsampler. Thus the final sample rate available to the analyzer (sink sample rate) can take any value between consecutive half-band decimator values. In conjunction with the decimator this permits a precise control of the timings independently of the baseband sample rate. Some devices are flexible on their sample rate some like the Airspy are not. + +

4: Rational downsampler output rate

+ +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 2000 S/s and the maximum value is the source plugin output sample rate. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +

5: Analyzer (sink) sample rate

+ +This is the resulting sample rate after decimation and possible rational downsampler that is used by the spectrum and scope visualizations + +

6: signal selection

+ +Use this combo to select which (complex) signal to use as the display source: + + - Sig: the main signal possibly mixed with PLL/FLL output (see 2 and 3) + - Lock: the output signal (NCO) from PLL or FLL + - ACorr: Auto-correlation of the main signal. It is a fixed 4096 point auto-correlation using FFT technique thus spanning the length of 4096 samples. The trace may show more samples in which case you will see the successive auto-correlation results. + +☞ Auto-correlation hint: because there is always a peak of magnitude at t=0 triggering on the magnitude will make sure the trace starts at t=0 + +

7: Locked loop

Locks a PLL or FLL (depends on control 3) on the signal and mixes its NCO with the input signal. This is mostly useful for carrier recovery on PSK modulations (PLL is used). This effectively de-rotates the signal and symbol points (constellation) can be seen in XY mode with real part as X and imagiary part as Y. When the PLL is locked the icon lights up in green. The frequency shift from carrier appears in the tooltip. Locking indicator is pretty sharp with about +/- 100 Hz range. The FLL has no indicator. -

3: Locked loop mode

+

8: Locked loop mode

Use this combo to control the locked loop type: @@ -63,32 +89,6 @@ Use this combo to control the locked loop type: - 16: PLL for 16-PSK modulation (16-phase). Locks to a 16-PSK transmission - F: FLL. Actually a frequency follower. This effectively implements an AFC for FM modulations. -

4: Toggle the rational downsampler

- -The input channel sample rate is given by the source device sample rate possibly downsampled by a power of two in the source device plugin. This input sample rate can be optionally downsampled to any value using a rational downsampler. This allows a precise control of the timings independently of the source plugin sample rate. Some devices are flexible on their sample rate some like the Airspy are not. - -

5: Rational downsampler output rate

- -Use the wheels to adjust the sample rate that will be used in the rest of the signal processing in the channel. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 2000 S/s and the maximum value is the source plugin output sample rate. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. - -

6: Downsampler by a power of two

- -This combo can select a further downsampling by a power of two. This downsampling applies on the signal coming either directly from the source plugin when the rational downsampler is disabled or from the output of the rational downsampler if it is engaged. - -

7: Processing sample rate

- -This is the resulting sample rate that will be used by the spectrum and scope visualizations - -

8: signal selection

- -Use this combo to select which (complex) signal to use as the display source: - - - Sig: the main signal possibly mixed with PLL/FLL output (see 2 and 3) - - Lock: the output signal (NCO) from PLL or FLL - - ACorr: Auto-correlation of the main signal. It is a fixed 4096 point auto-correlation using FFT technique thus spanning the length of 4096 samples. The trace may show more samples in which case you will see the successive auto-correlation results. - -☞ Auto-correlation hint: because there is always a peak of magnitude at t=0 triggering on the magnitude will make sure the trace starts at t=0 -

9. Channel power

Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band. @@ -107,27 +107,23 @@ In SSB mode this filter is a complex filter that can lowpass on either side of t In normal (DSB) mode this filter is a real filter that lowpass on both sides of the zero (center) frequency symmetrically. Therefore it acts as a bandpass filter centered on the zero frequency and therefore it is labeled as "BP". The value displayed in (9) is the full bandwidth of the filter. -

13. Lowpass filter cut-off frequency

+The bandwidth value display depends on SSB/DSB selection: + - in SSB mode this is the complex cut-off frequency and is negative for LSB. + - in normal (DSB) mode this is the full bandwidth of the real lowpass filter centered around zero frequency. -In SSB mode this is the complex cut-off frequency and is negative for LSB. - -In normal (DSB) mode this is the full bandwidth of the real lowpass filter centered around zero frequency. - -

14. SSB filtering

+

13. SSB filtering

When this toggle is engaged the signal is filtered either above (USB) or below (LSB) the channel center frequency. The sideband is selected according to the sign of the lowpass filter cut-off frequency (8): if positive the USB is selected else the LSB. In LSB mode the spectrum is reversed. When SSB is off the lowpass filter is actually a bandpass filter around the channel center frequency. -

15. Select highpass filter cut-off frequency

+

14. Select highpass filter cut-off frequency

In SSB mode this controls the cut-off frequency of the complex highpass filter which is the filter closest to the zero frequency. This cut-off frequency is always at least 0.1 kHz in absolute value below the lowpass filter cut-off frequency (8). In normal (DSB) mode this filter is not active. -

16. Highpass filter cut-off frequency

- -This is the cut-off frequency of the highpass filter in kHz. It is zero or negative in LSB mode. +The value displayed is the cut-off frequency of the highpass filter in kHz. It is zero or negative in LSB mode.

D. Scope global controls line

diff --git a/sdrbase/dsp/downsamplechannelizer.h b/sdrbase/dsp/downsamplechannelizer.h index 09c14bf5a..9d7e36c7d 100644 --- a/sdrbase/dsp/downsamplechannelizer.h +++ b/sdrbase/dsp/downsamplechannelizer.h @@ -40,7 +40,8 @@ public: void setDecimation(unsigned int log2Decim, unsigned int filterChainHash); //!< Define channelizer with decimation factor and filter chain definition void setChannelization(int requestedSampleRate, qint64 requestedCenterFrequency); //!< Define channelizer with requested sample rate and center frequency (shift in the baseband) void setBasebandSampleRate(int basebandSampleRate, bool decim = false); //!< decim: true => use direct decimation false => use channel configuration - int getChannelSampleRate() const { return m_channelSampleRate; } + int getBasebandSampleRate() const { return m_basebandSampleRate; } + int getChannelSampleRate() const { return m_channelSampleRate; } int getChannelFrequencyOffset() const { return m_channelFrequencyOffset; } protected: diff --git a/sdrgui/dsp/scopevis.cpp b/sdrgui/dsp/scopevis.cpp index 0181bbd06..f2ed21902 100644 --- a/sdrgui/dsp/scopevis.cpp +++ b/sdrgui/dsp/scopevis.cpp @@ -58,7 +58,6 @@ ScopeVis::ScopeVis(GLScope* glScope) : m_triggerLocation(0), m_sampleRate(0), m_liveSampleRate(0), - m_liveLog2Decim(0), m_traceDiscreteMemory(m_nbTraceMemories), m_freeRun(true), m_maxTraceDelay(0), @@ -86,16 +85,10 @@ void ScopeVis::setLiveRate(int sampleRate) m_liveSampleRate = sampleRate; if (m_currentTraceMemoryIndex == 0) { // update only in live mode - setSampleRate(m_liveSampleRate/(1<