1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-15 12:51:49 -05:00

Channel Analyzer: refactoring of classes and downsampling chain reorganization

This commit is contained in:
f4exb 2019-11-24 10:12:58 +01:00
parent 4005b70175
commit b49e68e77b
20 changed files with 966 additions and 729 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -5,6 +5,8 @@ set(chanalyzer_SOURCES
chanalyzergui.cpp
chanalyzerplugin.cpp
chanalyzersettings.cpp
chanalyzersink.cpp
chanalyzerbaseband.cpp
chanalyzerwebapiadapter.cpp
chanalyzergui.ui
)
@ -14,6 +16,8 @@ set(chanalyzer_HEADERS
chanalyzergui.h
chanalyzerplugin.h
chanalyzersettings.h
chanalyzersink.h
chanalyzerbaseband.h
chanalyzerwebapiadapter.h
)

View File

@ -17,201 +17,77 @@
#include <QTime>
#include <QDebug>
#include <QThread>
#include <stdio.h>
#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<<m_settings.m_spanLog2;
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++)
{
// 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<float> 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 if (DSPSignalNotification::match(cmd))
{
DSPSignalNotification& cfg = (DSPSignalNotification&) cmd;
m_basebandSampleRate = cfg.getSampleRate();
DSPSignalNotification *notif = new DSPSignalNotification(cfg);
m_basebandSink->getInputMessageQueue()->push(notif);
if (getMessageQueueToGUI())
{
DSPSignalNotification *notifToGUI = new DSPSignalNotification(cfg);
getMessageQueueToGUI()->push(notifToGUI);
}
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)
{
m_nco.setFreq(-inputFrequencyOffset, inputSampleRate);
}
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)
{
setFilters(inputSampleRate, m_settings.m_bandwidth, m_settings.m_lowCutoff);
m_pll.setSampleRate(inputSampleRate / (1<<m_settings.m_spanLog2));
m_fll.setSampleRate(inputSampleRate / (1<<m_settings.m_spanLog2));
}
m_settingsMutex.unlock();
}
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);
}
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<<settings.m_spanLog2));
m_fll.setSampleRate(sampleRate / (1<<settings.m_spanLog2));
m_settingsMutex.unlock();
}
if ((settings.m_bandwidth != m_settings.m_bandwidth) ||
(settings.m_lowCutoff != m_settings.m_lowCutoff)|| force)
{
m_settingsMutex.lock();
setFilters(settings.m_downSample ? settings.m_downSampleRate : m_inputSampleRate, settings.m_bandwidth, settings.m_lowCutoff);
m_settingsMutex.unlock();
}
if ((settings.m_rrcRolloff != m_settings.m_rrcRolloff) || force)
{
float sampleRate = settings.m_downSample ? (float) settings.m_downSampleRate : (float) m_inputSampleRate;
m_settingsMutex.lock();
RRCFilter->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<<m_settings.m_spanLog2);
m_pll.setSampleRate(sampleRate);
m_fll.setSampleRate(sampleRate);
}
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);
}
}
ChannelAnalyzerBaseband::MsgConfigureChannelAnalyzerBaseband *msg
= ChannelAnalyzerBaseband::MsgConfigureChannelAnalyzerBaseband::create(settings, force);
m_basebandSink->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;
}
}

View File

@ -15,31 +15,21 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_CHANALYZERNG_H
#define INCLUDE_CHANALYZERNG_H
#ifndef INCLUDE_CHANALYZER_H
#define INCLUDE_CHANALYZER_H
#include <QMutex>
#include <vector>
#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<<m_settings.m_spanLog2; }
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(); }
int getChannelSampleRate() const { return m_basebandSink->getChannelSampleRate(); }
int getDecimation() const { return 1<<m_settings.m_log2Decim; }
double getMagSq() const { return m_basebandSink->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<double, double, 480> 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<float> a = m_corr->run(s/(SDR_RX_SCALEF/768.0f), 0);
std::complex<float> 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

View File

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

View File

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

View File

@ -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<<m_settings.m_log2Decim);
}
void ChannelAnalyzerGUI::on_signalSelect_currentIndexChanged(int index)
@ -294,7 +288,7 @@ void ChannelAnalyzerGUI::on_signalSelect_currentIndexChanged(int index)
m_settings.m_inputType = (ChannelAnalyzerSettings::InputType) index;
if (m_settings.m_inputType == ChannelAnalyzerSettings::InputAutoCorr) {
m_scopeVis->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<<m_settings.m_spanLog2);
if (m_rate == 0) {
m_rate = 48000;
}
qDebug("ChannelAnalyzerGUI::setNewFinalRate: %d m_spanLog2: %d", m_rate, m_settings.m_spanLog2);
unsigned int channelSampleRate = m_basebandSampleRate / (1<<m_settings.m_log2Decim);
ui->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);
}
}

View File

@ -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<double, double, 40> 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);

View File

@ -179,6 +179,144 @@
</item>
<item>
<layout class="QHBoxLayout" name="ChannelSamplingLayout">
<item>
<widget class="QComboBox" name="log2Decim">
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Channel decimation</string>
</property>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>16</string>
</property>
</item>
<item>
<property name="text">
<string>32</string>
</property>
</item>
<item>
<property name="text">
<string>64</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="useRationalDownsampler">
<property name="toolTip">
<string>Use rational downsampler</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_down.png</normaloff>:/arrow_down.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="ValueDial" name="rationalDownSamplerRate" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="toolTip">
<string>Rational downsampler output rate</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelSampleRateUnits">
<property name="text">
<string>S/s</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="sinkSampleRateText">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Analyzer (sink) sample rate</string>
</property>
<property name="text">
<string>00000.0 kS/s</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="signalSelect">
<property name="toolTip">
<string>Select input signal</string>
</property>
<item>
<property name="text">
<string>Sig</string>
</property>
</item>
<item>
<property name="text">
<string>Lock</string>
</property>
</item>
<item>
<property name="text">
<string>ACorr</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QToolButton" name="pll">
<property name="toolTip">
@ -240,144 +378,6 @@
</item>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="useRationalDownsampler">
<property name="toolTip">
<string>Use rational downsampler</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_down.png</normaloff>:/arrow_down.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="ValueDial" name="channelSampleRate" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="toolTip">
<string>Rational downsampler output rate</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelSampleRateUnits">
<property name="text">
<string>S/s</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="spanLog2">
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Channel decimation</string>
</property>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>16</string>
</property>
</item>
<item>
<property name="text">
<string>32</string>
</property>
</item>
<item>
<property name="text">
<string>64</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="spanText">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Channel final sample rate</string>
</property>
<property name="text">
<string>00000.0 kS/s</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="signalSelect">
<property name="toolTip">
<string>Select input signal</string>
</property>
<item>
<property name="text">
<string>Sig</string>
</property>
</item>
<item>
<property name="text">
<string>Lock</string>
</property>
</item>
<item>
<property name="text">
<string>ACorr</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>

View File

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

View File

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

View File

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

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "chanalyzersink.h"
#include <QTime>
#include <QDebug>
#include <stdio.h>
#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<float> 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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#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<double, double, 480> 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<float> 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

View File

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

View File

@ -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.
<h3>2: Locked loop</h3>
<h3>2: Decimation by a power of two</h3>
This combo can select half-band decimation from baseband sample rate by a power of two.
<h3>3: Toggle the rational downsampler</h3>
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.
<h3>4: Rational downsampler output rate</h3>
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.
<h3>5: Analyzer (sink) sample rate</h3>
This is the resulting sample rate after decimation and possible rational downsampler that is used by the spectrum and scope visualizations
<h3>6: signal selection</h3>
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.
&#9758; 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
<h3>7: Locked loop</h3>
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.
<h3>3: Locked loop mode</h3>
<h3>8: Locked loop mode</h3>
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.
<h3>4: Toggle the rational downsampler</h3>
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.
<h3>5: Rational downsampler output rate</h3>
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.
<h3>6: Downsampler by a power of two</h3>
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.
<h3>7: Processing sample rate</h3>
This is the resulting sample rate that will be used by the spectrum and scope visualizations
<h3>8: signal selection</h3>
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.
&#9758; 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
<h3>9. Channel power</h3>
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.
<h3>13. Lowpass filter cut-off frequency</h3>
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.
<h3>14. SSB filtering</h3>
<h3>13. SSB filtering</h3>
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.
<h3>15. Select highpass filter cut-off frequency</h3>
<h3>14. Select highpass filter cut-off frequency</h3>
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.
<h3>16. Highpass filter cut-off frequency</h3>
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.
<h2>D. Scope global controls line</h2>

View File

@ -40,6 +40,7 @@ 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 getBasebandSampleRate() const { return m_basebandSampleRate; }
int getChannelSampleRate() const { return m_channelSampleRate; }
int getChannelFrequencyOffset() const { return m_channelFrequencyOffset; }

View File

@ -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<<m_liveLog2Decim));
setSampleRate(m_liveSampleRate);
}
}
void ScopeVis::setLiveRateLog2Decim(int log2Decim)
{
m_liveLog2Decim = log2Decim;
setLiveRate(m_liveSampleRate);
}
void ScopeVis::setSampleRate(int sampleRate)
{
qDebug("ScopeVis::setSampleRate: %d S/s", sampleRate);

View File

@ -156,7 +156,6 @@ public:
virtual ~ScopeVis();
void setLiveRate(int sampleRate);
void setLiveRateLog2Decim(int log2Decim);
void configure(uint32_t traceSize, uint32_t timeBase, uint32_t timeOfsProMill, uint32_t triggerPre, bool freeRun);
void addTrace(const TraceData& traceData);
void changeTrace(const TraceData& traceData, uint32_t traceIndex);
@ -1148,7 +1147,6 @@ private:
int m_triggerLocation; //!< Trigger location from end point
int m_sampleRate; //!< Actual sample rate being used
int m_liveSampleRate; //!< Sample rate in live mode
int m_liveLog2Decim; //!< Sample rate decimation log2 in live mode
TraceBackDiscreteMemory m_traceDiscreteMemory; //!< Complex trace memory for triggered states TODO: vectorize when more than on input is allowed
bool m_freeRun; //!< True if free running (trigger globally disabled)
int m_maxTraceDelay; //!< Maximum trace delay