diff --git a/doc/img/ChAnalyzerNG_plugin_settings.png b/doc/img/ChAnalyzerNG_plugin_settings.png index 7d6feeaae..f80fabcb6 100644 Binary files a/doc/img/ChAnalyzerNG_plugin_settings.png and b/doc/img/ChAnalyzerNG_plugin_settings.png differ diff --git a/doc/img/ChAnalyzerNG_plugin_settings.xcf b/doc/img/ChAnalyzerNG_plugin_settings.xcf index 56d69b377..160df81b0 100644 Binary files a/doc/img/ChAnalyzerNG_plugin_settings.xcf and b/doc/img/ChAnalyzerNG_plugin_settings.xcf differ 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<