2019-11-24 04:12:58 -05:00
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// 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>
|
|
|
|
|
2021-06-05 13:26:26 -04:00
|
|
|
#include "dsp/scopevis.h"
|
2019-11-24 04:12:58 -05:00
|
|
|
|
|
|
|
const unsigned int ChannelAnalyzerSink::m_ssbFftLen = 1024;
|
|
|
|
const unsigned int ChannelAnalyzerSink::m_corrFFTLen = 4*m_ssbFftLen;
|
|
|
|
|
|
|
|
ChannelAnalyzerSink::ChannelAnalyzerSink() :
|
|
|
|
m_channelSampleRate(48000),
|
|
|
|
m_channelFrequencyOffset(0),
|
2020-07-19 20:07:40 -04:00
|
|
|
m_sinkSampleRate(48000),
|
2021-03-05 08:37:49 -05:00
|
|
|
m_costasLoop(0.002, 2),
|
2021-06-05 13:26:26 -04:00
|
|
|
m_scopeVis(nullptr)
|
2019-11-24 04:12:58 -05:00
|
|
|
{
|
|
|
|
m_usb = true;
|
|
|
|
m_magsq = 0;
|
|
|
|
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
|
2021-03-05 08:37:49 -05:00
|
|
|
m_pll.computeCoefficients(m_settings.m_pllBandwidth, m_settings.m_pllDampingFactor, m_settings.m_pllLoopGain);
|
|
|
|
m_costasLoop.computeCoefficients(m_settings.m_pllBandwidth);
|
2019-11-24 04:12:58 -05:00
|
|
|
|
2020-07-19 20:07:40 -04:00
|
|
|
applyChannelSettings(m_channelSampleRate, m_sinkSampleRate, m_channelFrequencyOffset, true);
|
2019-11-24 04:12:58 -05:00
|
|
|
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;
|
|
|
|
|
|
|
|
for (SampleVector::const_iterator it = begin; it < end; ++it)
|
|
|
|
{
|
2020-07-20 20:08:58 -04:00
|
|
|
Complex ci;
|
2019-11-24 04:12:58 -05:00
|
|
|
Complex c(it->real(), it->imag());
|
|
|
|
c *= m_nco.nextIQ();
|
|
|
|
|
2020-07-20 20:08:58 -04:00
|
|
|
if (m_decimator.getDecim() == 1)
|
2020-07-13 16:00:15 -04:00
|
|
|
{
|
2023-04-01 21:39:39 -04:00
|
|
|
if (m_settings.m_rationalDownSample)
|
|
|
|
{
|
|
|
|
Complex cj;
|
|
|
|
|
|
|
|
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &cj))
|
|
|
|
{
|
|
|
|
processOneSample(cj, sideband);
|
|
|
|
m_interpolatorDistanceRemain += m_interpolatorDistance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
processOneSample(c, sideband);
|
|
|
|
}
|
2020-07-13 16:00:15 -04:00
|
|
|
}
|
|
|
|
else
|
2019-11-24 04:12:58 -05:00
|
|
|
{
|
2020-07-20 20:08:58 -04:00
|
|
|
if (m_decimator.decimate(c, ci))
|
2019-11-24 04:12:58 -05:00
|
|
|
{
|
2020-07-20 20:08:58 -04:00
|
|
|
if (m_settings.m_rationalDownSample)
|
2020-07-19 05:48:02 -04:00
|
|
|
{
|
2020-07-20 20:08:58 -04:00
|
|
|
Complex cj;
|
|
|
|
|
|
|
|
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, ci, &cj))
|
|
|
|
{
|
|
|
|
processOneSample(cj, sideband);
|
|
|
|
m_interpolatorDistanceRemain += m_interpolatorDistance;
|
|
|
|
}
|
2020-07-19 05:48:02 -04:00
|
|
|
}
|
2020-07-20 20:08:58 -04:00
|
|
|
else
|
|
|
|
{
|
2020-07-19 05:48:02 -04:00
|
|
|
processOneSample(ci, sideband);
|
|
|
|
}
|
2019-11-24 04:12:58 -05:00
|
|
|
}
|
2020-07-20 20:08:58 -04:00
|
|
|
}
|
2019-11-24 04:12:58 -05:00
|
|
|
}
|
|
|
|
|
2021-06-05 13:26:26 -04:00
|
|
|
if (m_scopeVis)
|
|
|
|
{
|
|
|
|
std::vector<SampleVector::const_iterator> vbegin;
|
|
|
|
vbegin.push_back(m_sampleBuffer.begin());
|
|
|
|
m_scopeVis->feed(vbegin, m_sampleBuffer.end() - m_sampleBuffer.begin());
|
|
|
|
}
|
2019-11-24 04:12:58 -05:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2021-03-05 08:37:49 -05:00
|
|
|
// Use -fPLL to mix (exchange PLL real and image in the complex multiplication)
|
|
|
|
if (m_settings.m_costasLoop)
|
|
|
|
{
|
|
|
|
m_costasLoop.feed(re, im);
|
|
|
|
mix = si * std::conj(m_costasLoop.getComplex());
|
|
|
|
feedOneSample(mix, m_costasLoop.getComplex());
|
|
|
|
}
|
|
|
|
else if (m_settings.m_fll)
|
2019-11-24 04:12:58 -05:00
|
|
|
{
|
|
|
|
m_fll.feed(re, im);
|
|
|
|
mix = si * std::conj(m_fll.getComplex());
|
2021-03-05 08:37:49 -05:00
|
|
|
feedOneSample(mix, m_fll.getComplex());
|
2019-11-24 04:12:58 -05:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_pll.feed(re, im);
|
|
|
|
mix = si * std::conj(m_pll.getComplex());
|
2021-03-05 08:37:49 -05:00
|
|
|
feedOneSample(mix, m_pll.getComplex());
|
2019-11-24 04:12:58 -05:00
|
|
|
}
|
|
|
|
}
|
2021-03-05 08:37:49 -05:00
|
|
|
else
|
|
|
|
feedOneSample(si, si);
|
2019-11-24 04:12:58 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-13 16:00:15 -04:00
|
|
|
void ChannelAnalyzerSink::applyChannelSettings(int channelSampleRate, int sinkSampleRate, int channelFrequencyOffset, bool force)
|
2019-11-24 04:12:58 -05:00
|
|
|
{
|
|
|
|
qDebug() << "ChannelAnalyzerSink::applyChannelSettings:"
|
|
|
|
<< " channelSampleRate: " << channelSampleRate
|
2020-07-13 16:00:15 -04:00
|
|
|
<< " sinkSampleRate: " << sinkSampleRate
|
2019-11-24 04:12:58 -05:00
|
|
|
<< " channelFrequencyOffset: " << channelFrequencyOffset;
|
2020-07-20 20:08:58 -04:00
|
|
|
bool doApplySampleRate = false;
|
2019-11-24 04:12:58 -05:00
|
|
|
|
|
|
|
if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
|
|
|
|
(m_channelSampleRate != channelSampleRate) || force)
|
|
|
|
{
|
|
|
|
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
|
|
|
|
}
|
|
|
|
|
2020-07-13 16:00:15 -04:00
|
|
|
if ((m_channelSampleRate != channelSampleRate)
|
|
|
|
|| (m_sinkSampleRate != sinkSampleRate) || force)
|
2019-11-24 04:12:58 -05:00
|
|
|
{
|
2020-07-20 20:08:58 -04:00
|
|
|
m_interpolator.create(16, sinkSampleRate, sinkSampleRate / 4.0f);
|
2019-11-24 04:12:58 -05:00
|
|
|
m_interpolatorDistanceRemain = 0;
|
2020-07-20 20:08:58 -04:00
|
|
|
m_interpolatorDistance = (Real) sinkSampleRate / (Real) m_settings.m_rationalDownSamplerRate;
|
2020-07-19 05:48:02 -04:00
|
|
|
|
|
|
|
int decim = channelSampleRate / sinkSampleRate;
|
|
|
|
m_decimator.setLog2Decim(0);
|
|
|
|
|
2021-01-19 06:36:56 -05:00
|
|
|
for (int i = 0; i < 7; i++) // find log2 between 0 and 6
|
2020-07-19 05:48:02 -04:00
|
|
|
{
|
2020-11-14 19:11:16 -05:00
|
|
|
if ((decim & 1) == 1)
|
2020-07-19 05:48:02 -04:00
|
|
|
{
|
|
|
|
qDebug() << "ChannelAnalyzerSink::applyChannelSettings: log2decim: " << i;
|
|
|
|
m_decimator.setLog2Decim(i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
decim >>= 1;
|
|
|
|
}
|
2020-07-20 20:08:58 -04:00
|
|
|
|
|
|
|
doApplySampleRate = true;
|
2019-11-24 04:12:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
m_channelSampleRate = channelSampleRate;
|
|
|
|
m_channelFrequencyOffset = channelFrequencyOffset;
|
2020-07-13 16:00:15 -04:00
|
|
|
m_sinkSampleRate = sinkSampleRate;
|
2020-07-20 20:08:58 -04:00
|
|
|
|
|
|
|
if (doApplySampleRate) {
|
|
|
|
applySampleRate();
|
|
|
|
}
|
2019-11-24 04:12:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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_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
|
2020-07-20 20:08:58 -04:00
|
|
|
<< " m_rationalDownSample: " << settings.m_rationalDownSample
|
|
|
|
<< " m_rationalDownSamplerRate: " << settings.m_rationalDownSamplerRate
|
2019-11-24 04:12:58 -05:00
|
|
|
<< " m_ssb: " << settings.m_ssb
|
|
|
|
<< " m_pll: " << settings.m_pll
|
|
|
|
<< " m_fll: " << settings.m_fll
|
2021-03-05 08:37:49 -05:00
|
|
|
<< " m_costasLoop: " << settings.m_costasLoop
|
2019-11-24 04:12:58 -05:00
|
|
|
<< " m_pllPskOrder: " << settings.m_pllPskOrder
|
2021-03-05 08:37:49 -05:00
|
|
|
<< " m_pllBandwidth: " << settings.m_pllBandwidth
|
|
|
|
<< " m_pllDampingFactor: " << settings.m_pllDampingFactor
|
|
|
|
<< " m_pllLoopGain: " << settings.m_pllLoopGain
|
2019-11-24 04:12:58 -05:00
|
|
|
<< " m_inputType: " << (int) settings.m_inputType;
|
2020-07-20 20:08:58 -04:00
|
|
|
bool doApplySampleRate = false;
|
2019-11-24 04:12:58 -05:00
|
|
|
|
|
|
|
if ((settings.m_bandwidth != m_settings.m_bandwidth) ||
|
2020-07-20 20:08:58 -04:00
|
|
|
(settings.m_lowCutoff != m_settings.m_lowCutoff) ||
|
|
|
|
(settings.m_rrcRolloff != m_settings.m_rrcRolloff) || force)
|
2019-11-24 04:12:58 -05:00
|
|
|
{
|
2020-07-20 20:08:58 -04:00
|
|
|
doApplySampleRate = true;
|
2019-11-24 04:12:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (settings.m_pll != m_settings.m_pll || force)
|
|
|
|
{
|
|
|
|
if (settings.m_pll)
|
|
|
|
{
|
|
|
|
m_pll.reset();
|
|
|
|
m_fll.reset();
|
2021-03-05 08:37:49 -05:00
|
|
|
m_costasLoop.reset();
|
2019-11-24 04:12:58 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (settings.m_fll != m_settings.m_fll || force)
|
|
|
|
{
|
|
|
|
if (settings.m_fll) {
|
|
|
|
m_fll.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 08:37:49 -05:00
|
|
|
if (settings.m_costasLoop != m_settings.m_costasLoop || force)
|
|
|
|
{
|
|
|
|
if (settings.m_costasLoop) {
|
|
|
|
m_costasLoop.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-24 04:12:58 -05:00
|
|
|
if (settings.m_pllPskOrder != m_settings.m_pllPskOrder || force)
|
|
|
|
{
|
|
|
|
if (settings.m_pllPskOrder < 32) {
|
|
|
|
m_pll.setPskOrder(settings.m_pllPskOrder);
|
|
|
|
}
|
2021-03-05 08:37:49 -05:00
|
|
|
if (settings.m_pllPskOrder < 16) {
|
|
|
|
m_costasLoop.setPskOrder(settings.m_pllPskOrder);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((settings.m_pllBandwidth != m_settings.m_pllBandwidth)
|
|
|
|
|| (settings.m_pllDampingFactor != m_settings.m_pllDampingFactor)
|
|
|
|
|| (settings.m_pllLoopGain != m_settings.m_pllLoopGain)
|
|
|
|
|| force)
|
|
|
|
{
|
|
|
|
m_pll.computeCoefficients(settings.m_pllBandwidth, settings.m_pllDampingFactor, settings.m_pllLoopGain);
|
|
|
|
m_costasLoop.computeCoefficients(settings.m_pllBandwidth);
|
2019-11-24 04:12:58 -05:00
|
|
|
}
|
|
|
|
|
2020-07-20 20:08:58 -04:00
|
|
|
if ((settings.m_rationalDownSample != m_settings.m_rationalDownSample) ||
|
|
|
|
(settings.m_rationalDownSamplerRate != m_settings.m_rationalDownSamplerRate) || force)
|
|
|
|
{
|
|
|
|
m_interpolator.create(16, m_sinkSampleRate, m_sinkSampleRate / 4.0f);
|
|
|
|
m_interpolatorDistanceRemain = 0;
|
|
|
|
m_interpolatorDistance = (Real) m_sinkSampleRate / (Real) settings.m_rationalDownSamplerRate;
|
|
|
|
doApplySampleRate = true;
|
|
|
|
}
|
|
|
|
|
2019-11-24 04:12:58 -05:00
|
|
|
m_settings = settings;
|
2020-07-20 20:08:58 -04:00
|
|
|
|
2023-04-01 21:39:39 -04:00
|
|
|
qDebug() << "ChannelAnalyzerSink::applySettings:"
|
|
|
|
<< " m_rationalDownSample: " << settings.m_rationalDownSample;
|
|
|
|
|
2020-07-20 20:08:58 -04:00
|
|
|
if (doApplySampleRate) {
|
|
|
|
applySampleRate();
|
|
|
|
}
|
2019-11-24 04:12:58 -05:00
|
|
|
}
|
|
|
|
|
2021-03-05 08:37:49 -05:00
|
|
|
bool ChannelAnalyzerSink::isPllLocked() const
|
|
|
|
{
|
|
|
|
if (m_settings.m_pll)
|
|
|
|
return m_pll.locked();
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-11-24 04:12:58 -05:00
|
|
|
Real ChannelAnalyzerSink::getPllFrequency() const
|
|
|
|
{
|
2021-03-05 08:37:49 -05:00
|
|
|
if (m_settings.m_costasLoop)
|
|
|
|
return m_costasLoop.getFreq();
|
|
|
|
else if (m_settings.m_fll)
|
2019-11-24 04:12:58 -05:00
|
|
|
return m_fll.getFreq();
|
2021-03-05 08:37:49 -05:00
|
|
|
else if (m_settings.m_pll)
|
2019-11-24 04:12:58 -05:00
|
|
|
return m_pll.getFreq();
|
2021-03-05 08:37:49 -05:00
|
|
|
else
|
2019-11-24 04:12:58 -05:00
|
|
|
return 0.0;
|
2021-03-05 08:37:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
Real ChannelAnalyzerSink::getPllPhase() const
|
|
|
|
{
|
|
|
|
if (m_settings.m_costasLoop)
|
|
|
|
return m_costasLoop.getPhiHat();
|
|
|
|
else if (m_settings.m_pll)
|
|
|
|
return m_pll.getPhiHat();
|
|
|
|
else
|
|
|
|
return 0.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
Real ChannelAnalyzerSink::getPllDeltaPhase() const
|
|
|
|
{
|
|
|
|
if (m_settings.m_pll)
|
|
|
|
return m_pll.getDeltaPhi();
|
|
|
|
else
|
|
|
|
return 0.0f;
|
2019-11-24 04:12:58 -05:00
|
|
|
}
|
2020-07-20 20:08:58 -04:00
|
|
|
|
|
|
|
int ChannelAnalyzerSink::getActualSampleRate()
|
|
|
|
{
|
|
|
|
if (m_settings.m_rationalDownSample) {
|
|
|
|
return m_settings.m_rationalDownSamplerRate;
|
|
|
|
} else {
|
|
|
|
return m_sinkSampleRate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChannelAnalyzerSink::applySampleRate()
|
|
|
|
{
|
|
|
|
int sampleRate = getActualSampleRate();
|
|
|
|
qDebug("ChannelAnalyzerSink::applySampleRate: sampleRate: %d m_interpolatorDistance: %f", sampleRate, m_interpolatorDistance);
|
|
|
|
setFilters(sampleRate, m_settings.m_bandwidth, m_settings.m_lowCutoff);
|
|
|
|
m_pll.setSampleRate(sampleRate);
|
|
|
|
m_fll.setSampleRate(sampleRate);
|
2021-03-05 08:37:49 -05:00
|
|
|
m_costasLoop.setSampleRate(sampleRate);
|
2020-07-20 20:08:58 -04:00
|
|
|
RRCFilter->create_rrc_filter(m_settings.m_bandwidth / (float) sampleRate, m_settings.m_rrcRolloff / 100.0);
|
2020-11-14 19:11:16 -05:00
|
|
|
}
|