1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-03-30 19:55:52 -04:00

Merge pull request #2645 from f4exb/fix-fftrrc-2

Rework FFT RRC filter and add a FIR RRC filter
This commit is contained in:
Edouard Griffiths 2026-02-13 15:13:49 +01:00 committed by GitHub
commit 3ecf974f88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
71 changed files with 1696 additions and 185 deletions

View File

@ -8,7 +8,9 @@ set(chanalyzer_SOURCES
chanalyzersink.cpp
chanalyzerbaseband.cpp
chanalyzerwebapiadapter.cpp
rrcfilterdialog.cpp
chanalyzergui.ui
rrcfilterdialog.ui
)
set(chanalyzer_HEADERS
@ -19,6 +21,7 @@ set(chanalyzer_HEADERS
chanalyzersink.h
chanalyzerbaseband.h
chanalyzerwebapiadapter.h
rrcfilterdialog.h
)
include_directories(

View File

@ -30,12 +30,14 @@
#include "gui/basicchannelsettingsdialog.h"
#include "gui/dialpopup.h"
#include "gui/dialogpositioner.h"
#include "gui/crightclickenabler.h"
#include "plugin/pluginapi.h"
#include "util/db.h"
#include "maincore.h"
#include "ui_chanalyzergui.h"
#include "chanalyzer.h"
#include "rrcfilterdialog.h"
#include "chanalyzergui.h"
ChannelAnalyzerGUI* ChannelAnalyzerGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
@ -559,6 +561,9 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device
m_channelAnalyzer->setScopeVis(m_scopeVis);
m_channelAnalyzer->setMessageQueueToGUI(getInputMessageQueue());
m_rrcRightClickEnabler = new CRightClickEnabler(ui->rrcFilter);
connect(m_rrcRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(rrcSetupDialog(const QPoint &)));
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 8, -99999999, 99999999);
@ -735,6 +740,47 @@ void ChannelAnalyzerGUI::enterEvent(EnterEventType* event)
ChannelGUI::enterEvent(event);
}
void ChannelAnalyzerGUI::rrcSetupDialog(const QPoint& p)
{
m_rrcFilterDialog = new RRCFilterDialog();
m_rrcFilterDialog->move(p);
m_rrcFilterDialog->setRRCType(m_settings.m_rrcType);
m_rrcFilterDialog->setRRCSymbolSpan(m_settings.m_rrcSymbolSpan);
m_rrcFilterDialog->setRRCNormalization(m_settings.m_rrcNormalization);
m_rrcFilterDialog->setRRCFFTLog2Size(m_settings.m_rrcFFTLog2Size);
QObject::connect(m_rrcFilterDialog, &RRCFilterDialog::valueChanged, this, &ChannelAnalyzerGUI::rrcSetup);
m_rrcFilterDialog->exec();
m_rrcFilterDialog->deleteLater();
m_rrcFilterDialog = nullptr;
}
void ChannelAnalyzerGUI::rrcSetup(int iValueChanged)
{
auto valueChanged = static_cast<RRCFilterDialog::ValueChanged>(iValueChanged);
switch(valueChanged)
{
case RRCFilterDialog::ValueChanged::ChangedRRCType:
m_settings.m_rrcType = m_rrcFilterDialog->getRRCType();
applySettings(QStringList({"rrcType"}));
break;
case RRCFilterDialog::ValueChanged::ChangedRRCSymbolSpan:
m_settings.m_rrcSymbolSpan = m_rrcFilterDialog->getRRCSymbolSpan();
applySettings(QStringList({"rrcSymbolSpan"}));
break;
case RRCFilterDialog::ValueChanged::ChangedRRCNormalization:
m_settings.m_rrcNormalization = m_rrcFilterDialog->getRRCNormalization();
applySettings(QStringList({"rrcNormalization"}));
break;
case RRCFilterDialog::ValueChanged::ChangedRRCFFTLog2Size:
m_settings.m_rrcFFTLog2Size = m_rrcFilterDialog->getRRCFFTLog2Size();
applySettings(QStringList({"rrcFFTLog2Size"}));
break;
default:
break;
}
}
void ChannelAnalyzerGUI::makeUIConnections()
{
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &ChannelAnalyzerGUI::on_deltaFrequency_changed);

View File

@ -35,6 +35,8 @@ class BasebandSampleSink;
class ChannelAnalyzer;
class SpectrumVis;
class ScopeVis;
class RRCFilterDialog;
class CRightClickEnabler;
namespace Ui {
class ChannelAnalyzerGUI;
@ -83,6 +85,8 @@ private:
SpectrumVis* m_spectrumVis;
ScopeVis* m_scopeVis;
MessageQueue m_inputMessageQueue;
RRCFilterDialog* m_rrcFilterDialog;
CRightClickEnabler* m_rrcRightClickEnabler;
explicit ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = nullptr);
virtual ~ChannelAnalyzerGUI();
@ -123,6 +127,8 @@ private slots:
void on_ssb_toggled(bool checked);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void rrcSetupDialog(const QPoint& p);
void rrcSetup(int valueChanged);
void handleInputMessages();
void tick();
};

View File

@ -418,7 +418,7 @@
<item>
<widget class="ButtonSwitch" name="rrcFilter">
<property name="toolTip">
<string>Toggle RRC filter</string>
<string>Toggle RRC filter - Right click for options</string>
</property>
<property name="text">
<string/>

View File

@ -46,6 +46,10 @@ void ChannelAnalyzerSettings::resetToDefaults()
m_costasLoop = false;
m_rrc = false;
m_rrcRolloff = 35; // 0.35
m_rrcType = RRCFIR;
m_rrcSymbolSpan = 8;
m_rrcNormalization = RRCNormGain;
m_rrcFFTLog2Size = 9; // 512
m_pllPskOrder = 1;
m_pllBandwidth = 0.002f;
m_pllDampingFactor = 0.5f;
@ -110,6 +114,10 @@ QByteArray ChannelAnalyzerSettings::serialize() const
s.writeS32(29, m_workspaceIndex);
s.writeBlob(30, m_geometryBytes);
s.writeBool(31, m_hidden);
s.writeS32(32, m_rrcType);
s.writeS32(33, m_rrcSymbolSpan);
s.writeS32(34, m_rrcNormalization);
s.writeS32(35, m_rrcFFTLog2Size);
return s.final();
}
@ -187,6 +195,12 @@ bool ChannelAnalyzerSettings::deserialize(const QByteArray& data)
d.readS32(29, &m_workspaceIndex, 0);
d.readBlob(30, &m_geometryBytes);
d.readBool(31, &m_hidden, false);
d.readS32(32, &tmp, 0);
m_rrcType = (RRCType) tmp;
d.readS32(33, &m_rrcSymbolSpan, 8);
d.readS32(34, &tmp, 2);
m_rrcNormalization = (RRCNormalization) tmp;
d.readS32(35, &m_rrcFFTLog2Size, 9);
return true;
}
@ -235,6 +249,18 @@ void ChannelAnalyzerSettings::applySettings(const QStringList& settingsKeys, con
if (settingsKeys.contains("rrcRolloff")) {
m_rrcRolloff = settings.m_rrcRolloff;
}
if (settingsKeys.contains("rrcType")) {
m_rrcType = settings.m_rrcType;
}
if (settingsKeys.contains("rrcSymbolSpan")) {
m_rrcSymbolSpan = settings.m_rrcSymbolSpan;
}
if (settingsKeys.contains("rrcNormalization")) {
m_rrcNormalization = settings.m_rrcNormalization;
}
if (settingsKeys.contains("rrcFFTLog2Size")) {
m_rrcFFTLog2Size = settings.m_rrcFFTLog2Size;
}
if (settingsKeys.contains("pllPskOrder")) {
m_pllPskOrder = settings.m_pllPskOrder;
}
@ -322,6 +348,18 @@ QString ChannelAnalyzerSettings::getDebugString(const QStringList& settingsKeys,
if (settingsKeys.contains("rrcRolloff") || force) {
ostr << " m_rrcRolloff: " << m_rrcRolloff;
}
if (settingsKeys.contains("rrcType") || force) {
ostr << " m_rrcType: " << m_rrcType;
}
if (settingsKeys.contains("rrcSymbolSpan") || force) {
ostr << " m_rrcSymbolSpan: " << m_rrcSymbolSpan;
}
if (settingsKeys.contains("rrcNormalization") || force) {
ostr << " m_rrcNormalization: " << m_rrcNormalization;
}
if (settingsKeys.contains("rrcFFTLog2Size") || force) {
ostr << " m_rrcFFTLog2Size: " << m_rrcFFTLog2Size;
}
if (settingsKeys.contains("pllPskOrder") || force) {
ostr << " m_pllPskOrder: " << m_pllPskOrder;
}

View File

@ -34,6 +34,19 @@ struct ChannelAnalyzerSettings
InputAutoCorr
};
enum RRCType
{
RRCFIR,
RRCFFT
};
enum RRCNormalization
{
RRCNormEnergy,
RRCNormAmpltude,
RRCNormGain
};
int m_inputFrequencyOffset;
bool m_rationalDownSample;
quint32 m_rationalDownSamplerRate;
@ -46,6 +59,10 @@ struct ChannelAnalyzerSettings
bool m_costasLoop;
bool m_rrc;
quint32 m_rrcRolloff; //!< in 100ths
RRCType m_rrcType;
int m_rrcSymbolSpan;
RRCNormalization m_rrcNormalization;
int m_rrcFFTLog2Size;
unsigned int m_pllPskOrder;
float m_pllBandwidth;
float m_pllDampingFactor;

View File

@ -28,6 +28,52 @@
const unsigned int ChannelAnalyzerSink::m_ssbFftLen = 1024;
const unsigned int ChannelAnalyzerSink::m_corrFFTLen = 4*m_ssbFftLen;
ChannelAnalyzerSink::RRCHelper::RRCHelper(int flen) :
m_useFFT(false),
m_filterFIR(new FIRFilterRRC()),
m_filterFFT(new FFTFilterRRC(m_ssbFftLen)),
m_buffer(new std::complex<float>[flen/2]),
m_bufferPos(0),
flen2(flen/2)
{
}
ChannelAnalyzerSink::RRCHelper::~RRCHelper()
{
delete m_filterFIR;
delete[] m_buffer;
}
void ChannelAnalyzerSink::RRCHelper::setUseFFT(bool useFFT)
{
m_useFFT = useFFT;
m_bufferPos = 0;
}
void ChannelAnalyzerSink::RRCHelper::create(float symbolRate, float rolloff, unsigned int samplesPerSymbol, FIRFilterRRC::Normalization normalization)
{
m_filterFIR->create(symbolRate, rolloff, samplesPerSymbol, normalization);
m_filterFFT->create(symbolRate, rolloff);
}
int ChannelAnalyzerSink::RRCHelper::runFilt(const std::complex<float> & in, std::complex<float> **out)
{
if (m_useFFT) {
return m_filterFFT->process(in, out);
} else {
m_buffer[m_bufferPos++] = m_filterFIR->filter(in);
}
if (m_bufferPos < flen2) {
*out = nullptr;
return 0;
}
m_bufferPos = 0;
*out = m_buffer;
return flen2;
}
ChannelAnalyzerSink::ChannelAnalyzerSink() :
m_channelSampleRate(48000),
m_channelFrequencyOffset(0),
@ -39,7 +85,8 @@ ChannelAnalyzerSink::ChannelAnalyzerSink() :
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_rrcHelper = new RRCHelper(2*m_ssbFftLen);
// m_rrcHelper->setUseFFT(true);
m_corr = new fftcorr(2*m_corrFFTLen); // 8k for 4k effective samples
m_pll.computeCoefficients(m_settings.m_pllBandwidth, m_settings.m_pllDampingFactor, m_settings.m_pllLoopGain);
m_costasLoop.computeCoefficients(m_settings.m_pllBandwidth);
@ -52,7 +99,7 @@ ChannelAnalyzerSink::~ChannelAnalyzerSink()
{
delete SSBFilter;
delete DSBFilter;
delete RRCFilter;
delete m_rrcHelper;
delete m_corr;
}
@ -126,7 +173,7 @@ void ChannelAnalyzerSink::processOneSample(Complex& c, fftfilt::cmplx *sideband)
else
{
if (m_settings.m_rrc) {
n_out = RRCFilter->runFilt(c, &sideband);
n_out = m_rrcHelper->runFilt(c, &sideband);
} else {
n_out = DSBFilter->runDSB(c, &sideband);
}
@ -240,7 +287,7 @@ void ChannelAnalyzerSink::setFilters(int sampleRate, float bandwidth, float lowC
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);
m_rrcHelper->create(bandwidth / sampleRate, m_settings.m_rrcRolloff / 100.0, m_settings.m_rrcSymbolSpan, (FIRFilterRRC::Normalization) m_settings.m_rrcNormalization);
}
void ChannelAnalyzerSink::applySettings(const ChannelAnalyzerSettings& settings, const QStringList& settingsKeys, bool force)
@ -314,6 +361,29 @@ void ChannelAnalyzerSink::applySettings(const ChannelAnalyzerSettings& settings,
}
}
if (settings.m_rrcType != m_settings.m_rrcType || force)
{
m_rrcHelper->setUseFFT(settings.m_rrcType == ChannelAnalyzerSettings::RRCFFT);
}
if (settings.m_rrcSymbolSpan != m_settings.m_rrcSymbolSpan || force)
{
m_rrcHelper->create(m_settings.m_bandwidth / (float) m_sinkSampleRate, m_settings.m_rrcRolloff / 100.0, settings.m_rrcSymbolSpan, (FIRFilterRRC::Normalization) m_settings.m_rrcNormalization);
}
if (settings.m_rrcNormalization != m_settings.m_rrcNormalization || force)
{
m_rrcHelper->create(m_settings.m_bandwidth / (float) m_sinkSampleRate, m_settings.m_rrcRolloff / 100.0, m_settings.m_rrcSymbolSpan, (FIRFilterRRC::Normalization) settings.m_rrcNormalization);
}
if (settings.m_rrcFFTLog2Size != m_settings.m_rrcFFTLog2Size || force)
{
delete m_rrcHelper;
m_rrcHelper = new RRCHelper(1 << settings.m_rrcFFTLog2Size);
m_rrcHelper->create(settings.m_bandwidth / (float) m_sinkSampleRate, settings.m_rrcRolloff / 100.0, settings.m_rrcSymbolSpan, (FIRFilterRRC::Normalization) settings.m_rrcNormalization);
m_rrcHelper->setUseFFT(settings.m_rrcType == ChannelAnalyzerSettings::RRCFFT);
}
if (force) {
m_settings = settings;
} else {
@ -383,5 +453,5 @@ void ChannelAnalyzerSink::applySampleRate()
m_pll.setSampleRate(sampleRate);
m_fll.setSampleRate(sampleRate);
m_costasLoop.setSampleRate(sampleRate);
RRCFilter->create_rrc_filter(m_settings.m_bandwidth / (float) sampleRate, m_settings.m_rrcRolloff / 100.0);
m_rrcHelper->create(m_settings.m_bandwidth / (float) sampleRate, m_settings.m_rrcRolloff / 100.0, m_settings.m_rrcSymbolSpan, (FIRFilterRRC::Normalization) m_settings.m_rrcNormalization);
}

View File

@ -25,6 +25,8 @@
#include "dsp/nco.h"
#include "dsp/fftcorr.h"
#include "dsp/fftfilt.h"
#include "dsp/firfilterrrc.h"
#include "dsp/fftfilterrrc.h"
#include "dsp/phaselockcomplex.h"
#include "dsp/freqlockcomplex.h"
#include "dsp/costasloop.h"
@ -57,6 +59,22 @@ public:
static const unsigned int m_ssbFftLen;
private:
class RRCHelper {
public:
RRCHelper(int flen);
~RRCHelper();
void setUseFFT(bool useFFT);
void create(float symbolRate, float rolloff, unsigned int samplesPerSymbol, FIRFilterRRC::Normalization normalization);
int runFilt(const std::complex<float> & in, std::complex<float> **out);
private:
bool m_useFFT;
FIRFilterRRC* m_filterFIR;
FFTFilterRRC* m_filterFFT;
std::complex<float>* m_buffer;
int m_bufferPos;
int flen2;
};
int m_channelSampleRate;
int m_channelFrequencyOffset;
int m_sinkSampleRate;
@ -76,7 +94,7 @@ private:
fftfilt* SSBFilter;
fftfilt* DSBFilter;
fftfilt* RRCFilter;
RRCHelper* m_rrcHelper;
fftcorr* m_corr;
SampleVector m_sampleBuffer;

View File

@ -122,7 +122,14 @@ Average total power in dB relative to a +/- 1.0 amplitude signal received in the
<h3>10. Toggle root raised cosine filter</h3>
Use this toggle button to activate or de-activate the root raised cosine (RRC) filter. When active the bandpass boxcar filter is replaced by a RRC filter. This takes effect only in normal (DSB) mode (see control 14).
Use this toggle button to activate or de-activate the root raised cosine (RRC) filter. When active the bandpass boxcar filter is replaced by a RRC filter. This takes effect only in normal (DSB) mode (see control 14). Note that the bandwidth of the filter should be equal to the symbol rate regardless of the actual bandwidth.
Right clicking on the button gives access to the RRC filter options:
- Choice between FFT and FIR filter
- Size of the FFT filter
- Symbol span for the FIR filter
- Normalization type for the FIR filter
<h3>11. Tune RRC filter rolloff factor</h3>

View File

@ -0,0 +1,149 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2026 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// 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 "rrcfilterdialog.h"
#include "ui_rrcfilterdialog.h"
RRCFilterDialog::RRCFilterDialog(QWidget* parent) :
QDialog(parent),
ui(new Ui::RRCFilterDialog),
m_rrcType(ChannelAnalyzerSettings::RRCFIR),
m_rrcSymbolSpan(8),
m_rrcNormalization(ChannelAnalyzerSettings::RRCNormGain),
m_rrcFFTLog2Size(9)
{
ui->setupUi(this);
makeUIConnections();
}
RRCFilterDialog::~RRCFilterDialog()
{
delete ui;
}
void RRCFilterDialog::setRRCType(ChannelAnalyzerSettings::RRCType rrcType)
{
ui->firGroup->blockSignals(true);
ui->fftGroup->blockSignals(true);
ui->firGroup->setChecked(false);
ui->fftGroup->setChecked(false);
switch(rrcType)
{
case ChannelAnalyzerSettings::RRCFIR:
ui->firGroup->setChecked(true);
break;
case ChannelAnalyzerSettings::RRCFFT:
ui->fftGroup->setChecked(true);
break;
default:
break;
}
ui->firGroup->blockSignals(false);
ui->fftGroup->blockSignals(false);
m_rrcType = rrcType;
}
void RRCFilterDialog::setRRCSymbolSpan(int symbolSpan)
{
m_rrcSymbolSpan = symbolSpan;
ui->symbolSpan->setValue(symbolSpan);
}
void RRCFilterDialog::setRRCNormalization(ChannelAnalyzerSettings::RRCNormalization normalization)
{
m_rrcNormalization = normalization;
ui->normalization->setCurrentIndex((int) normalization);
}
void RRCFilterDialog::setRRCFFTLog2Size(int log2Size)
{
m_rrcFFTLog2Size = log2Size;
ui->fftSize->setCurrentIndex(log2Size - 7);
}
void RRCFilterDialog::on_symbolSpan_valueChanged(int value)
{
m_rrcSymbolSpan = value;
emit valueChanged(ChangedRRCSymbolSpan);
}
void RRCFilterDialog::on_normalization_currentIndexChanged(int index)
{
m_rrcNormalization = (ChannelAnalyzerSettings::RRCNormalization) index;
emit valueChanged(ChangedRRCNormalization);
}
void RRCFilterDialog::on_fftLog2Size_currentIndexChanged(int index)
{
m_rrcFFTLog2Size = index + 7;
emit valueChanged(ChangedRRCFFTLog2Size);
}
void RRCFilterDialog::on_firGroup_clicked(bool checked)
{
qDebug("RRCFilterDialog::on_firGroup_clicked: checked: %d", checked);
if (checked)
{
ui->fftGroup->blockSignals(true);
ui->fftGroup->setChecked(false);
ui->fftGroup->blockSignals(false);
m_rrcType = ChannelAnalyzerSettings::RRCFIR;
emit valueChanged(ChangedRRCType);
}
else
{
ui->firGroup->blockSignals(true);
ui->firGroup->setChecked(true);
ui->firGroup->blockSignals(false);
}
}
void RRCFilterDialog::on_fftGroup_clicked(bool checked)
{
qDebug("RRCFilterDialog::on_fftGroup_clicked: checked: %d", checked);
if (checked)
{
ui->firGroup->blockSignals(true);
ui->firGroup->setChecked(false);
ui->firGroup->blockSignals(false);
m_rrcType = ChannelAnalyzerSettings::RRCFFT;
emit valueChanged(ChangedRRCType);
}
else
{
ui->fftGroup->blockSignals(true);
ui->fftGroup->setChecked(true);
ui->fftGroup->blockSignals(false);
}
}
void RRCFilterDialog::makeUIConnections()
{
connect(ui->firGroup, &QGroupBox::clicked, this, &RRCFilterDialog::on_firGroup_clicked);
connect(ui->fftGroup, &QGroupBox::clicked, this, &RRCFilterDialog::on_fftGroup_clicked);
connect(ui->symbolSpan, QOverload<int>::of(&QSpinBox::valueChanged), this, &RRCFilterDialog::on_symbolSpan_valueChanged);
connect(ui->normalization, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RRCFilterDialog::on_normalization_currentIndexChanged);
connect(ui->fftSize, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RRCFilterDialog::on_fftLog2Size_currentIndexChanged);
}

View File

@ -0,0 +1,69 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2026 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// 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 PLUGINS_CHANNELRX_CHANALYZER_RRCFILTERDIALOG_H_
#define PLUGINS_CHANNELRX_CHANALYZER_RRCFILTERDIALOG_H_
#include <QDialog>
#include "chanalyzersettings.h"
namespace Ui {
class RRCFilterDialog;
}
class RRCFilterDialog : public QDialog {
Q_OBJECT
public:
enum ValueChanged {
ChangedRRCType,
ChangedRRCSymbolSpan,
ChangedRRCNormalization,
ChangedRRCFFTLog2Size,
};
explicit RRCFilterDialog(QWidget* parent = nullptr);
~RRCFilterDialog() override;
void setRRCType(ChannelAnalyzerSettings::RRCType rrcType);
void setRRCSymbolSpan(int symbolSpan);
void setRRCNormalization(ChannelAnalyzerSettings::RRCNormalization normalization);
void setRRCFFTLog2Size(int log2Size);
ChannelAnalyzerSettings::RRCType getRRCType() const { return m_rrcType; }
int getRRCSymbolSpan() const { return m_rrcSymbolSpan; }
ChannelAnalyzerSettings::RRCNormalization getRRCNormalization() const { return m_rrcNormalization; }
int getRRCFFTLog2Size() const { return m_rrcFFTLog2Size; }
signals:
void valueChanged(int valueChanged);
private:
Ui::RRCFilterDialog* ui;
ChannelAnalyzerSettings::RRCType m_rrcType;
int m_rrcSymbolSpan;
ChannelAnalyzerSettings::RRCNormalization m_rrcNormalization;
int m_rrcFFTLog2Size;
void on_firGroup_clicked(bool checked);
void on_fftGroup_clicked(bool checked);
void on_symbolSpan_valueChanged(int value);
void on_normalization_currentIndexChanged(int index);
void on_fftLog2Size_currentIndexChanged(int index);
void makeUIConnections();
};
#endif /* PLUGINS_CHANNELRX_CHANALYZER_RRCFILTERDIALOG_H_ */

View File

@ -0,0 +1,258 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RRCFilterDialog</class>
<widget class="QDialog" name="RRCFilterDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>407</width>
<height>197</height>
</rect>
</property>
<property name="windowTitle">
<string>RRC Filter Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="firGroup">
<property name="minimumSize">
<size>
<width>370</width>
<height>70</height>
</size>
</property>
<property name="toolTip">
<string>Use a FIR RRC filter</string>
</property>
<property name="styleSheet">
<string notr="true">QGroupBox {
border: 1px solid gray;
} </string>
</property>
<property name="title">
<string>FIR</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<widget class="QLabel" name="symbolSpanLabel">
<property name="geometry">
<rect>
<x>10</x>
<y>30</y>
<width>91</width>
<height>26</height>
</rect>
</property>
<property name="text">
<string>Symbol Span</string>
</property>
</widget>
<widget class="QSpinBox" name="symbolSpan">
<property name="geometry">
<rect>
<x>100</x>
<y>30</y>
<width>70</width>
<height>29</height>
</rect>
</property>
<property name="toolTip">
<string>Symbol span</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="minimum">
<number>6</number>
</property>
<property name="maximum">
<number>12</number>
</property>
<property name="value">
<number>8</number>
</property>
</widget>
<widget class="QLabel" name="normalizationLabel">
<property name="geometry">
<rect>
<x>180</x>
<y>30</y>
<width>101</width>
<height>26</height>
</rect>
</property>
<property name="text">
<string>Normalization</string>
</property>
</widget>
<widget class="QComboBox" name="normalization">
<property name="geometry">
<rect>
<x>280</x>
<y>30</y>
<width>101</width>
<height>32</height>
</rect>
</property>
<property name="toolTip">
<string>RRC normalization type</string>
</property>
<item>
<property name="text">
<string>Energy</string>
</property>
</item>
<item>
<property name="text">
<string>Amplitude</string>
</property>
</item>
<item>
<property name="text">
<string>Gain</string>
</property>
</item>
</widget>
</widget>
</item>
<item>
<widget class="QGroupBox" name="fftGroup">
<property name="minimumSize">
<size>
<width>370</width>
<height>70</height>
</size>
</property>
<property name="toolTip">
<string>Use a FFT RRC filter</string>
</property>
<property name="styleSheet">
<string notr="true">QGroupBox {
border: 1px solid gray;
} </string>
</property>
<property name="title">
<string>FFT</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<widget class="QLabel" name="fftSizeLabel">
<property name="geometry">
<rect>
<x>10</x>
<y>30</y>
<width>81</width>
<height>26</height>
</rect>
</property>
<property name="text">
<string>FFT Size</string>
</property>
</widget>
<widget class="QComboBox" name="fftSize">
<property name="geometry">
<rect>
<x>80</x>
<y>30</y>
<width>94</width>
<height>32</height>
</rect>
</property>
<property name="toolTip">
<string>Size of FFT filter</string>
</property>
<item>
<property name="text">
<string>128</string>
</property>
</item>
<item>
<property name="text">
<string>256</string>
</property>
</item>
<item>
<property name="text">
<string>512</string>
</property>
</item>
<item>
<property name="text">
<string>1024</string>
</property>
</item>
<item>
<property name="text">
<string>2048</string>
</property>
</item>
<item>
<property name="text">
<string>4096</string>
</property>
</item>
<item>
<property name="text">
<string>8192</string>
</property>
</item>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RRCFilterDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>RRCFilterDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -415,7 +415,7 @@ void APTDemod::webapiUpdateChannelSettings(
if (channelSettingsKeys.contains("transparencyThreshold")) {
settings.m_transparencyThreshold = response.getAptDemodSettings()->getTransparencyThreshold();
}
if (channelSettingsKeys.contains("m_opacityThreshold")) {
if (channelSettingsKeys.contains("opacityThreshold")) {
settings.m_opacityThreshold = response.getAptDemodSettings()->getOpacityThreshold();
}
if (channelSettingsKeys.contains("palettes")) {

View File

@ -156,7 +156,7 @@ bool APTDemodBaseband::handleMessage(const Message& cmd)
void APTDemodBaseband::applySettings(const QStringList& settingsKeys, const APTDemodSettings& settings, bool force)
{
if ((settingsKeys.contains("m_inputFrequencyOffset")) || force)
if ((settingsKeys.contains("inputFrequencyOffset")) || force)
{
m_channelizer->setChannelization(APTDEMOD_AUDIO_SAMPLE_RATE, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());

View File

@ -174,7 +174,7 @@ void ATVDemod::applySettings(const QStringList& settingsKeys, const ATVDemodSett
{
qDebug() << "ATVDemod::applySettings:" << settings.getDebugString(settingsKeys, force);
if (settingsKeys.contains("m_streamIndex") && m_settings.m_streamIndex != settings.m_streamIndex)
if (settingsKeys.contains("streamIndex") && m_settings.m_streamIndex != settings.m_streamIndex)
{
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
{

View File

@ -151,7 +151,7 @@ void ATVDemodBaseband::applySettings(const QStringList& settingsKeys, const ATVD
{
qDebug() << "ATVDemodBaseband::applySettings" << settings.getDebugString(settingsKeys, force);
if ((settingsKeys.contains("m_inputFrequencyOffset") && (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)) || force)
if ((settingsKeys.contains("inputFrequencyOffset") && (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)) || force)
{
unsigned int desiredSampleRate = m_channelizer->getBasebandSampleRate();
m_channelizer->setChannelization(desiredSampleRate, settings.m_inputFrequencyOffset);

View File

@ -477,7 +477,7 @@ void DABDemod::webapiUpdateChannelSettings(
if (channelSettingsKeys.contains("program")) {
settings.m_program = *response.getDabDemodSettings()->getProgram();
}
if (channelSettingsKeys.contains("m_volume")) {
if (channelSettingsKeys.contains("volume")) {
settings.m_volume = response.getDabDemodSettings()->getVolume();
}
if (channelSettingsKeys.contains("audioMute")) {

View File

@ -507,7 +507,7 @@ void FreeDVDemodSink::applySettings(const QStringList& settingsKeys, const FreeD
{
qDebug() << "FreeDVDemodSink::applySettings:" << settings.getDebugString(settingsKeys, force);
if ((settingsKeys.contains("m_volume") && (settings.m_volume != m_settings.m_volume)) || force)
if ((settingsKeys.contains("volume") && (settings.m_volume != m_settings.m_volume)) || force)
{
m_volume = settings.m_volume;
m_volume /= 4.0; // for 3276.8

View File

@ -440,7 +440,7 @@ void FT8Demod::webapiUpdateChannelSettings(
if (channelSettingsKeys.contains("recordWav")) {
settings.m_recordWav = response.getFt8DemodSettings()->getRecordWav() != 0;
}
if (channelSettingsKeys.contains("m_logMessages")) {
if (channelSettingsKeys.contains("logMessages")) {
settings.m_logMessages = response.getFt8DemodSettings()->getLogMessages() != 0;
}
if (channelSettingsKeys.contains("nbDecoderThreads")) {

View File

@ -166,7 +166,7 @@ bool ILSDemodBaseband::handleMessage(const Message& cmd)
void ILSDemodBaseband::applySettings(const QStringList& settingsKeys, const ILSDemodSettings& settings, bool force)
{
if ((settingsKeys.contains("m_inputFrequencyOffset") && (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)) || force)
if ((settingsKeys.contains("inputFrequencyOffset") && (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)) || force)
{
m_channelizer->setChannelization(ILSDemodSettings::ILSDEMOD_CHANNEL_SAMPLE_RATE, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
@ -177,7 +177,7 @@ void ILSDemodBaseband::applySettings(const QStringList& settingsKeys, const ILSD
}
}
if ((settingsKeys.contains("m_audioDeviceName") && (settings.m_audioDeviceName != m_settings.m_audioDeviceName)) || force)
if ((settingsKeys.contains("audioDeviceName") && (settings.m_audioDeviceName != m_settings.m_audioDeviceName)) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName);

View File

@ -332,8 +332,6 @@ InmarsatDemodSink::InmarsatDemodSink(InmarsatDemod *stdCDemod) :
{
m_magsq = 0.0;
m_rrcFilter = new fftfilt(m_settings.m_rfBandwidth / (float) m_channelSampleRate, RRC_FILTER_SIZE);
m_rrcI.create(m_settings.m_rrcRolloff, 5, SAMPLES_PER_SYMBOL, RootRaisedCosine<Real>::Gain);
m_rrcQ.create(m_settings.m_rrcRolloff, 5, SAMPLES_PER_SYMBOL, RootRaisedCosine<Real>::Gain);
@ -358,8 +356,6 @@ InmarsatDemodSink::InmarsatDemodSink(InmarsatDemod *stdCDemod) :
InmarsatDemodSink::~InmarsatDemodSink()
{
delete m_rrcFilter;
m_rrcFilter = nullptr;
delete m_equalizer;
m_equalizer = nullptr;
}
@ -473,10 +469,8 @@ void InmarsatDemodSink::processOneSample(Complex &ci)
// Send signals to scope that are updated for each invocation of this method
sampleToScopeA(cScaled, magsq, agcZ, m_agc.getGain(), m_agc.getAverage(), cCFO);
// RRC Matched filter
fftfilt::cmplx *rrcFilterOut = nullptr;
int n_out = m_rrcFilter->runFilt(cCFO, &rrcFilterOut);
// Bufferized RRC Matched filter
int n_out;
m_rrcBuffer[m_rrcBufferIndex++] = cCFO;
if (m_rrcBufferIndex == RRC_FILTER_SIZE/2)
@ -491,7 +485,6 @@ void InmarsatDemodSink::processOneSample(Complex &ci)
for (int i = 0; i < n_out; i++)
{
//Complex rrc = rrcFilterOut[i];
Complex rrc (m_rrcI.filter(m_rrcBuffer[i].real()), m_rrcQ.filter(m_rrcBuffer[i].imag()));
// Symbol synchronizer
@ -704,10 +697,6 @@ void InmarsatDemodSink::applySettings(const InmarsatDemodSettings& settings, con
m_costasLoop.setMinFreq(-freqMax);
}
if (settingsKeys.contains("rfBandwidth") || settingsKeys.contains("rrcRolloff") || force) {
m_rrcFilter->create_rrc_filter(settings.m_rfBandwidth / (float) m_channelSampleRate, settings.m_rrcRolloff);
}
if (settingsKeys.contains("pllBandwidth") || force) {
m_costasLoop.computeCoefficients(settings.m_pllBW);
}

View File

@ -25,7 +25,6 @@
#include "dsp/channelsamplesink.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "dsp/fftfilt.h"
#include "dsp/fftengine.h"
#include "dsp/fftwindow.h"
#include "dsp/costasloop.h"
@ -213,7 +212,6 @@ private:
bool m_locked;
static const int RRC_FILTER_SIZE = 256;
fftfilt *m_rrcFilter;
Complex m_rrcBuffer[RRC_FILTER_SIZE];
RootRaisedCosine<Real> m_rrcI; //!< Square root raised cosine filter for I samples
RootRaisedCosine<Real> m_rrcQ; //!< Square root raised cosine filter for Q samples

View File

@ -227,6 +227,10 @@ void FreqTracker::setCenterFrequency(qint64 frequency)
void FreqTracker::applySettings(const QStringList& settingsKeys, const FreqTrackerSettings& settings, bool force)
{
if (settingsKeys.empty()) {
return; // nothing to apply
}
if (!settings.m_tracking) {
qDebug() << "FreqTracker::applySettings:" << settings.getDebugString(settingsKeys, force);
}

View File

@ -134,6 +134,10 @@ bool FreqTrackerBaseband::handleMessage(const Message& cmd)
void FreqTrackerBaseband::applySettings(const QStringList& settingsKeys, const FreqTrackerSettings& settings, bool force)
{
if (settingsKeys.empty()) {
return; // nothing to apply
}
if ((settingsKeys.contains("inputFrequencyOffset") && (m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset))
|| (settingsKeys.contains("log2Decim") && (m_settings.m_log2Decim != settings.m_log2Decim)) || force)
{

View File

@ -20,7 +20,7 @@
#include <QDebug>
#include "dsp/dspengine.h"
#include "dsp/fftfilt.h"
#include "dsp/firfilterrrc.h"
#include "dsp/spectrumvis.h"
#include "util/db.h"
#include "util/messagequeue.h"
@ -58,7 +58,8 @@ FreqTrackerSink::FreqTrackerSink() :
m_sampleBuffer.resize(m_sampleBufferSize);
m_sum = Complex{0.0, 0.0};
m_rrcFilter = new fftfilt(m_settings.m_rfBandwidth / m_sinkSampleRate, 2*1024);
m_rrcFilter = new FIRFilterRRC();
m_rrcFilter->create(m_settings.m_rfBandwidth / m_sinkSampleRate, m_settings.m_rrcRolloff / 100.0f, 8, FIRFilterRRC::Normalization::Energy);
m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain
applyChannelSettings(m_channelSampleRate, m_inputFrequencyOffset, true);
}
@ -103,10 +104,9 @@ void FreqTrackerSink::feed(const SampleVector::const_iterator& begin, const Samp
}
}
void FreqTrackerSink::processOneSample(Complex &ci)
void FreqTrackerSink::processOneSample(const Complex &ci)
{
fftfilt::cmplx *sideband;
int n_out;
Complex sideband;
int decim = 1<<m_settings.m_spanLog2;
m_sum += ci;
@ -120,72 +120,64 @@ void FreqTrackerSink::processOneSample(Complex &ci)
m_undersampleCount = 0;
}
if (m_settings.m_rrc)
{
n_out = m_rrcFilter->runFilt(ci, &sideband);
}
else
{
n_out = 1;
sideband = &ci;
if (m_settings.m_rrc) {
sideband = m_rrcFilter->filter(ci);
} else {
sideband = ci;
}
for (int i = 0; i < n_out; i++)
Real re = sideband.real() / SDR_RX_SCALEF;
Real im = sideband.imag() / SDR_RX_SCALEF;
Real magsq = re*re + im*im;
m_movingAverage(magsq);
m_magsq = m_movingAverage.asDouble();
m_magsqSum += magsq;
if (magsq > m_magsqPeak)
{
Real re = sideband[i].real() / SDR_RX_SCALEF;
Real im = sideband[i].imag() / SDR_RX_SCALEF;
Real magsq = re*re + im*im;
m_movingAverage(magsq);
m_magsq = m_movingAverage.asDouble();
m_magsqSum += magsq;
m_magsqPeak = magsq;
}
if (magsq > m_magsqPeak)
m_magsqCount++;
if (m_magsq < m_squelchLevel)
{
if (m_squelchGate > 0)
{
m_magsqPeak = magsq;
}
m_magsqCount++;
if (m_magsq < m_squelchLevel)
{
if (m_squelchGate > 0)
{
if (m_squelchCount > 0) {
m_squelchCount--;
}
m_squelchOpen = m_squelchCount >= m_squelchGate;
}
else
{
m_squelchOpen = false;
if (m_squelchCount > 0) {
m_squelchCount--;
}
m_squelchOpen = m_squelchCount >= m_squelchGate;
}
else
{
if (m_squelchGate > 0)
{
if (m_squelchCount < 2*m_squelchGate) {
m_squelchCount++;
}
m_squelchOpen = m_squelchCount >= m_squelchGate;
}
else
{
m_squelchOpen = true;
}
m_squelchOpen = false;
}
if (m_squelchOpen)
}
else
{
if (m_squelchGate > 0)
{
if (m_settings.m_trackerType == FreqTrackerSettings::TrackerFLL) {
m_fll.feed(re, im);
} else if (m_settings.m_trackerType == FreqTrackerSettings::TrackerPLL) {
m_pll.feed(re, im);
if (m_squelchCount < 2*m_squelchGate) {
m_squelchCount++;
}
}
m_squelchOpen = m_squelchCount >= m_squelchGate;
}
else
{
m_squelchOpen = true;
}
}
if (m_squelchOpen)
{
if (m_settings.m_trackerType == FreqTrackerSettings::TrackerFLL) {
m_fll.feed(re, im);
} else if (m_settings.m_trackerType == FreqTrackerSettings::TrackerPLL) {
m_pll.feed(re, im);
}
}
if (m_spectrumSink && (m_sampleBufferCount == m_sampleBufferSize))
@ -249,15 +241,19 @@ void FreqTrackerSink::applyChannelSettings(int sinkSampleRate, int channelSample
void FreqTrackerSink::applySettings(const QStringList& settingsKeys, const FreqTrackerSettings& settings, bool force)
{
if (settingsKeys.empty()) {
return; // nothing to apply
}
if (!settings.m_tracking) {
qDebug() << "FreqTrackerSink::applySettings:" << settings.getDebugString(settingsKeys, force);
}
if ((settingsKeys.contains("m_squelch") && (m_settings.m_squelch != settings.m_squelch)) || force) {
if ((settingsKeys.contains("squelch") && (m_settings.m_squelch != settings.m_squelch)) || force) {
m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch);
}
if ((settingsKeys.contains("m_tracking") && (m_settings.m_tracking != settings.m_tracking)) || force)
if ((settingsKeys.contains("tracking") && (m_settings.m_tracking != settings.m_tracking)) || force)
{
m_avgDeltaFreq = 0.0;
m_lastCorrAbs = 0;
@ -269,7 +265,7 @@ void FreqTrackerSink::applySettings(const QStringList& settingsKeys, const FreqT
}
}
if ((settingsKeys.contains("m_trackerType") && (m_settings.m_trackerType != settings.m_trackerType)) || force)
if ((settingsKeys.contains("trackerType") && (m_settings.m_trackerType != settings.m_trackerType)) || force)
{
m_lastCorrAbs = 0;
m_avgDeltaFreq = 0.0;
@ -287,7 +283,7 @@ void FreqTrackerSink::applySettings(const QStringList& settingsKeys, const FreqT
}
}
if ((settingsKeys.contains("m_pllPskOrder") && (m_settings.m_pllPskOrder != settings.m_pllPskOrder)) || force)
if ((settingsKeys.contains("pllPskOrder") && (m_settings.m_pllPskOrder != settings.m_pllPskOrder)) || force)
{
if (settings.m_pllPskOrder < 32) {
m_pll.setPskOrder(settings.m_pllPskOrder);
@ -296,9 +292,9 @@ void FreqTrackerSink::applySettings(const QStringList& settingsKeys, const FreqT
bool useInterpolator = false;
if ((settingsKeys.contains("m_rrcRolloff") && (m_settings.m_rrcRolloff != settings.m_rrcRolloff))
|| (settingsKeys.contains("m_rfBandwidth") && (m_settings.m_rfBandwidth != settings.m_rfBandwidth))
|| (settingsKeys.contains("m_squelchGate") && (m_settings.m_squelchGate != settings.m_squelchGate)) || force) {
if ((settingsKeys.contains("rrcRolloff") && (m_settings.m_rrcRolloff != settings.m_rrcRolloff))
|| (settingsKeys.contains("rfBandwidth") && (m_settings.m_rfBandwidth != settings.m_rfBandwidth))
|| (settingsKeys.contains("squelchGate") && (m_settings.m_squelchGate != settings.m_squelchGate)) || force) {
useInterpolator = true;
}
@ -329,7 +325,7 @@ void FreqTrackerSink::setInterpolator()
m_interpolator.create(16, m_channelSampleRate, m_settings.m_rfBandwidth / 2.2f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) m_sinkSampleRate;
m_rrcFilter->create_rrc_filter(m_settings.m_rfBandwidth / m_sinkSampleRate, m_settings.m_rrcRolloff / 100.0);
m_rrcFilter->create(m_settings.m_rfBandwidth / m_sinkSampleRate, m_settings.m_rrcRolloff / 100.0f, 8, FIRFilterRRC::Normalization::Energy);
m_squelchGate = (m_sinkSampleRate / 100) * m_settings.m_squelchGate; // gate is given in 10s of ms at channel sample rate
}

View File

@ -32,7 +32,7 @@
#include "freqtrackersettings.h"
class SpectrumVis;
class fftfilt;
class FIRFilterRRC;
class MessageQueue;
class QTimer;
@ -110,7 +110,7 @@ private:
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
fftfilt* m_rrcFilter;
FIRFilterRRC* m_rrcFilter;
Real m_squelchLevel;
uint32_t m_squelchCount;
@ -136,7 +136,7 @@ private:
void setInterpolator();
void connectTimer();
void disconnectTimer();
void processOneSample(Complex &ci);
void processOneSample(const Complex &ci);
private slots:
void tick();

View File

@ -384,7 +384,7 @@ void LocalSink::applySettings(const LocalSinkSettings& settings, const QList<QSt
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -185,14 +185,14 @@ bool AMModBaseband::handleMessage(const Message& cmd)
void AMModBaseband::applySettings(const QStringList& settingsKeys, const AMModSettings& settings, bool force)
{
if ((settingsKeys.contains("m_inputFrequencyOffset") && (m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset)) || force)
if ((settingsKeys.contains("inputFrequencyOffset") && (m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset)) || force)
{
m_channelizer->setChannelization(m_source.getAudioSampleRate(), settings.m_inputFrequencyOffset);
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
m_source.applyAudioSampleRate(m_source.getAudioSampleRate()); // reapply in case of channel sample rate change
}
if ((settingsKeys.contains("m_audioDeviceName") && (settings.m_audioDeviceName != m_settings.m_audioDeviceName)) || force)
if ((settingsKeys.contains("audioDeviceName") && (settings.m_audioDeviceName != m_settings.m_audioDeviceName)) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
@ -207,7 +207,7 @@ void AMModBaseband::applySettings(const QStringList& settingsKeys, const AMModSe
}
}
if ((settingsKeys.contains("m_modAFInput") && (settings.m_modAFInput != m_settings.m_modAFInput)) || force)
if ((settingsKeys.contains("modAFInput") && (settings.m_modAFInput != m_settings.m_modAFInput)) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
@ -219,7 +219,7 @@ void AMModBaseband::applySettings(const QStringList& settingsKeys, const AMModSe
}
}
if ((settingsKeys.contains("m_feedbackAudioDeviceName") && (settings.m_feedbackAudioDeviceName != m_settings.m_feedbackAudioDeviceName)) || force)
if ((settingsKeys.contains("feedbackAudioDeviceName") && (settings.m_feedbackAudioDeviceName != m_settings.m_feedbackAudioDeviceName)) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_feedbackAudioDeviceName);

View File

@ -692,9 +692,9 @@ void DATVModSource::applySettings(const QStringList& settingsKeys, const DATVMod
{
qDebug() << "DATVModSource::applySettings:" << settings.getDebugString(settingsKeys, force);
if ((settingsKeys.contains("m_rfBandwidth") && settings.m_rfBandwidth != m_settings.m_rfBandwidth)
|| (settingsKeys.contains("m_modulation") && settings.m_modulation != m_settings.m_modulation)
|| (settingsKeys.contains("m_symbolRate") && settings.m_symbolRate != m_settings.m_symbolRate)
if ((settingsKeys.contains("rfBandwidth") && settings.m_rfBandwidth != m_settings.m_rfBandwidth)
|| (settingsKeys.contains("modulation") && settings.m_modulation != m_settings.m_modulation)
|| (settingsKeys.contains("symbolRate") && settings.m_symbolRate != m_settings.m_symbolRate)
|| force)
{
if (settings.m_symbolRate > 0)
@ -718,9 +718,9 @@ void DATVModSource::applySettings(const QStringList& settingsKeys, const DATVMod
qWarning() << "DATVModSource::applySettings: symbolRate must be greater than 0.";
}
if ((settingsKeys.contains("m_source") && settings.m_source != m_settings.m_source)
|| (settingsKeys.contains("m_udpAddress") && settings.m_udpAddress != m_settings.m_udpAddress)
|| (settingsKeys.contains("m_udpPort") && settings.m_udpPort != m_settings.m_udpPort)
if ((settingsKeys.contains("source") && settings.m_source != m_settings.m_source)
|| (settingsKeys.contains("udpAddress") && settings.m_udpAddress != m_settings.m_udpAddress)
|| (settingsKeys.contains("udpPort") && settings.m_udpPort != m_settings.m_udpPort)
|| force)
{
if (m_udpSocket)
@ -740,8 +740,8 @@ void DATVModSource::applySettings(const QStringList& settingsKeys, const DATVMod
}
}
if ((settingsKeys.contains("m_standard") && settings.m_standard != m_settings.m_standard)
|| (settingsKeys.contains("m_modulation") && settings.m_modulation != m_settings.m_modulation)
if ((settingsKeys.contains("standard") && settings.m_standard != m_settings.m_standard)
|| (settingsKeys.contains("modulation") && settings.m_modulation != m_settings.m_modulation)
|| force)
{
m_symbolSel = 0;
@ -750,10 +750,10 @@ void DATVModSource::applySettings(const QStringList& settingsKeys, const DATVMod
m_sampleIdx = 0;
}
if ((settingsKeys.contains("m_standard") && settings.m_standard != m_settings.m_standard)
|| (settingsKeys.contains("m_modulation") && settings.m_modulation != m_settings.m_modulation)
|| (settingsKeys.contains("m_fec") && settings.m_fec != m_settings.m_fec)
|| (settingsKeys.contains("m_rollOff") && settings.m_rollOff != m_settings.m_rollOff)
if ((settingsKeys.contains("standard") && settings.m_standard != m_settings.m_standard)
|| (settingsKeys.contains("modulation") && settings.m_modulation != m_settings.m_modulation)
|| (settingsKeys.contains("fec") && settings.m_fec != m_settings.m_fec)
|| (settingsKeys.contains("rollOff") && settings.m_rollOff != m_settings.m_rollOff)
|| force)
{
if (settings.m_standard == DATVModSettings::DVB_S)
@ -865,15 +865,15 @@ void DATVModSource::applySettings(const QStringList& settingsKeys, const DATVMod
}
}
if ((settingsKeys.contains("m_imageServiceProvider") && settings.m_imageServiceProvider != m_settings.m_imageServiceProvider) || force) {
if ((settingsKeys.contains("imageServiceProvider") && settings.m_imageServiceProvider != m_settings.m_imageServiceProvider) || force) {
m_tsGenerator.set_service_provider(settings.m_imageServiceProvider.toStdString());
}
if ((settingsKeys.contains("m_imageServiceName") && settings.m_imageServiceName != m_settings.m_imageServiceName) || force) {
if ((settingsKeys.contains("imageServiceName") && settings.m_imageServiceName != m_settings.m_imageServiceName) || force) {
m_tsGenerator.set_service_name(settings.m_imageServiceName.toStdString());
}
if ((settingsKeys.contains("m_imageCodec") && settings.m_imageCodec != m_settings.m_imageCodec) || force) {
if ((settingsKeys.contains("imageCodec") && settings.m_imageCodec != m_settings.m_imageCodec) || force) {
m_tsGenerator.set_codec(settings.m_imageCodec);
}

View File

@ -173,7 +173,7 @@ bool FreeDVModBaseband::handleMessage(const Message& cmd)
void FreeDVModBaseband::applySettings(const QStringList& settingsKeys, const FreeDVModSettings& settings, bool force)
{
if ((settingsKeys.contains("m_freeDVMode") && settings.m_freeDVMode != m_settings.m_freeDVMode) || force)
if ((settingsKeys.contains("freeDVMode") && settings.m_freeDVMode != m_settings.m_freeDVMode) || force)
{
int modemSampleRate = FreeDVModSettings::getModSampleRate(settings.m_freeDVMode);
m_source.applyFreeDVMode(settings.m_freeDVMode);
@ -181,13 +181,13 @@ void FreeDVModBaseband::applySettings(const QStringList& settingsKeys, const Fre
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
if ((settingsKeys.contains("m_inputFrequencyOffset") && settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
if ((settingsKeys.contains("inputFrequencyOffset") && settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer->setChannelization(m_source.getModemSampleRate(), settings.m_inputFrequencyOffset);
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
if ((settingsKeys.contains("m_audioDeviceName") && settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
if ((settingsKeys.contains("audioDeviceName") && settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
@ -199,7 +199,7 @@ void FreeDVModBaseband::applySettings(const QStringList& settingsKeys, const Fre
}
}
if ((settingsKeys.contains("m_modAFInput") && settings.m_modAFInput != m_settings.m_modAFInput) || force)
if ((settingsKeys.contains("modAFInput") && settings.m_modAFInput != m_settings.m_modAFInput) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);

View File

@ -184,7 +184,7 @@ bool NFMModBaseband::handleMessage(const Message& cmd)
void NFMModBaseband::applySettings(const QStringList& settingsKeys, const NFMModSettings& settings, bool force)
{
if ((settingsKeys.contains("m_inputFrequencyOffset") && settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
if ((settingsKeys.contains("inputFrequencyOffset") && settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer->setChannelization(m_source.getAudioSampleRate(), settings.m_inputFrequencyOffset);
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
@ -192,7 +192,7 @@ void NFMModBaseband::applySettings(const QStringList& settingsKeys, const NFMMod
}
if ((settingsKeys.contains("m_audioDeviceName") && settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
if ((settingsKeys.contains("audioDeviceName") && settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
@ -207,7 +207,7 @@ void NFMModBaseband::applySettings(const QStringList& settingsKeys, const NFMMod
}
}
if ((settingsKeys.contains("m_modAFInput") && settings.m_modAFInput != m_settings.m_modAFInput) || force)
if ((settingsKeys.contains("modAFInput") && settings.m_modAFInput != m_settings.m_modAFInput) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
@ -219,7 +219,7 @@ void NFMModBaseband::applySettings(const QStringList& settingsKeys, const NFMMod
}
}
if ((settingsKeys.contains("m_feedbackAudioDeviceName") && settings.m_feedbackAudioDeviceName != m_settings.m_feedbackAudioDeviceName) || force)
if ((settingsKeys.contains("feedbackAudioDeviceName") && settings.m_feedbackAudioDeviceName != m_settings.m_feedbackAudioDeviceName) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_feedbackAudioDeviceName);

View File

@ -271,7 +271,7 @@ void AFC::applySettings(const AFCSettings& settings, const QList<QString>& setti
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -142,7 +142,7 @@ void AIS::applySettings(const AISSettings& settings, const QList<QString>& setti
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -107,7 +107,7 @@ void AntennaTools::applySettings(const AntennaToolsSettings& settings, const QLi
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
+ settingsKeys.contains("m_reverseAPIFeatureIndex");
+ settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -220,7 +220,7 @@ void APRS::applySettings(const APRSSettings& settings, const QList<QString>& set
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -297,7 +297,7 @@ void DemodAnalyzer::applySettings(const DemodAnalyzerSettings& settings, const Q
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -299,7 +299,7 @@ void Denoiser::applySettings(const DenoiserSettings& settings, const QList<QStri
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -301,7 +301,7 @@ void GS232Controller::applySettings(const GS232ControllerSettings& settings, con
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}
@ -742,4 +742,3 @@ void GS232Controller::scanSerialPorts()
m_serialPorts = serialPorts;
}
}

View File

@ -180,7 +180,7 @@ void JogdialController::applySettings(const JogdialControllerSettings& settings,
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -151,7 +151,7 @@ void Map::applySettings(const MapSettings& settings, const QList<QString>& setti
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -345,7 +345,7 @@ void MorseDecoder::applySettings(const MorseDecoderSettings& settings, const QLi
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -202,7 +202,7 @@ void PERTester::applySettings(const PERTesterSettings& settings, const QList<QSt
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -145,7 +145,7 @@ void Radiosonde::applySettings(const RadiosondeSettings& settings, const QList<Q
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -179,7 +179,7 @@ void RigCtlServer::applySettings(const RigCtlServerSettings& settings, const QLi
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -222,7 +222,7 @@ void SatelliteTracker::applySettings(const SatelliteTrackerSettings& settings, c
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -172,7 +172,7 @@ void SIDMain::applySettings(const SIDSettings& settings, const QList<QString>& s
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -220,7 +220,7 @@ void SimplePTT::applySettings(const SimplePTTSettings& settings, const QList<QSt
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -122,7 +122,7 @@ void SkyMap::applySettings(const SkyMapSettings& settings, const QList<QString>&
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -259,7 +259,7 @@ void StarTracker::applySettings(const StarTrackerSettings& settings, const QList
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -163,7 +163,7 @@ void StarTrackerWorker::applySettings(const StarTrackerSettings& settings, const
if (settingsKeys.contains("drawSunOnMap")
|| settingsKeys.contains("drawMoonOnMap")
|| settingsKeys.contains("drawStarOnMap")
|| settingsKeys.contains("m_target"))
|| settingsKeys.contains("target"))
{
if (!settings.m_drawSunOnMap && m_settings.m_drawSunOnMap)
removeFromMap("Sun");

View File

@ -353,7 +353,7 @@ void VORLocalizer::applySettings(const VORLocalizerSettings& settings, const QLi
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
settingsKeys.contains("reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}

View File

@ -969,7 +969,7 @@ void AudioCATSISO::webapiReverseSendSettings(const QList<QString>& deviceSetting
if (deviceSettingsKeys.contains("catPTTMethodIndex")) {
swgAudioCATSISOSettings->setCatPttMethodIndex(settings.m_catPTTMethodIndex);
}
if (deviceSettingsKeys.contains("m_catDTRHigh")) {
if (deviceSettingsKeys.contains("catDTRHigh")) {
swgAudioCATSISOSettings->setCatDtrHigh(settings.m_catDTRHigh ? 1 : 0);
}
if (deviceSettingsKeys.contains("catRTSHigh")) {

View File

@ -461,7 +461,7 @@ bool PlutoSDRMIMO::applySettings(const PlutoSDRMIMOSettings& settings, const QLi
settingsKeys.contains("lpfRxFIREnable") ||
settingsKeys.contains("lpfRxFIRlog2Decim") ||
settingsKeys.contains("lpfRxFIRBW") ||
settingsKeys.contains("m_lpfRxFIRGain") || force)
settingsKeys.contains("lpfRxFIRGain") || force)
{
if (plutoBox)
{

View File

@ -184,7 +184,7 @@ QString FileInputSettings::getDebugString(const QStringList& settingsKeys, bool
if (settingsKeys.contains("title") || force) {
ostr << " m_title: " << m_title.toStdString();
}
if (settingsKeys.contains("m_fileName") || force) {
if (settingsKeys.contains("fileName") || force) {
ostr << " m_fileName: " << m_fileName.toStdString();
}
if (settingsKeys.contains("accelerationFactor") || force) {

View File

@ -231,7 +231,7 @@ void SDRPlayV3Settings::applySettings(const QStringList& settingsKeys, const SDR
if (settingsKeys.contains("iqOrder")) {
m_iqOrder = settings.m_iqOrder;
}
if (settingsKeys.contains("m_transverterDeltaFrequency")) {
if (settingsKeys.contains("transverterDeltaFrequency")) {
m_transverterDeltaFrequency = settings.m_transverterDeltaFrequency;
}
if (settingsKeys.contains("replayOffset")) {

View File

@ -135,7 +135,7 @@ QString SigMFFileInputSettings::getDebugString(const QStringList& settingsKeys,
if (settingsKeys.contains("title") || force) {
ostr << " m_title: " << m_title.toStdString();
}
if (settingsKeys.contains("m_fileName") || force) {
if (settingsKeys.contains("fileName") || force) {
ostr << " m_fileName: " << m_fileName.toStdString();
}
if (settingsKeys.contains("accelerationFactor") || force) {

View File

@ -145,6 +145,7 @@ set(sdrbase_SOURCES
dsp/fftengine.cpp
dsp/fftfactory.cpp
dsp/fftfilt.cpp
dsp/fftfilterrrc.cpp
dsp/fftnr.cpp
dsp/fftwindow.cpp
dsp/filterrc.cpp
@ -152,6 +153,7 @@ set(sdrbase_SOURCES
dsp/filerecord.cpp
dsp/filerecordinterface.cpp
dsp/firfilter.cpp
dsp/firfilterrrc.cpp
dsp/fmpreemphasis.cpp
dsp/freqlockcomplex.cpp
dsp/interpolator.cpp
@ -373,6 +375,7 @@ set(sdrbase_HEADERS
dsp/fftengine.h
dsp/fftfactory.h
dsp/fftfilt.h
dsp/fftfilterrrc.h
dsp/fftnr.h
dsp/fftwengine.h
dsp/fftwindow.h
@ -381,6 +384,7 @@ set(sdrbase_HEADERS
dsp/filerecord.h
dsp/filerecordinterface.h
dsp/firfilter.h
dsp/firfilterrrc.h
dsp/fmpreemphasis.h
dsp/freqlockcomplex.h
dsp/gfft.h

View File

@ -420,32 +420,6 @@ void fftfilt::create_asym_filter(float fopp, float fin, FFTWindow::Function wf)
}
}
// This filter is constructed directly from frequency domain response. Run with runFilt.
void fftfilt::create_rrc_filter(float fb, float a)
{
std::fill(filter, filter+flen, 0);
for (int i = 0; i < flen; i++) {
filter[i] = frrc(fb, a, i, flen);
}
// normalize the output filter for unity gain
float scale = 0, mag;
for (int i = 0; i < flen; i++)
{
mag = abs(filter[i]);
if (mag > scale) {
scale = mag;
}
}
if (scale != 0)
{
for (int i = 0; i < flen; i++) {
filter[i] /= scale;
}
}
}
// test bypass
int fftfilt::noFilt(const cmplx & in, cmplx **out)
{
@ -687,4 +661,3 @@ void sfft::fetch(float *result)
*result = itr->bins.real() * itr->bins.real()
+ itr->bins.imag() * itr->bins.imag();
}

View File

@ -52,7 +52,6 @@ public:
void create_filter(const std::vector<std::pair<float, float>>& limits, bool pass = true); //!< Windowless version
void create_dsb_filter(float f2, FFTWindow::Function wf = FFTWindow::Blackman);
void create_asym_filter(float fopp, float fin, FFTWindow::Function wf = FFTWindow::Blackman); //!< two different filters for in band and opposite band
void create_rrc_filter(float fb, float a); //!< root raised cosine. fb is half the band pass
int noFilt(const cmplx& in, cmplx **out);
int runFilt(const cmplx& in, cmplx **out);

View File

@ -0,0 +1,187 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2026 SDRangel contributors //
// //
// 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 "dsp/fftfilterrrc.h"
#include <algorithm>
#include <cmath>
#include <cstring>
FFTFilterRRC::FFTFilterRRC(int len) :
m_fftLen(len),
m_fftLen2(len / 2),
m_fft(nullptr),
m_filter(nullptr),
m_fftBuffer(nullptr),
m_overlapBuffer(nullptr),
m_outputBuffer(nullptr),
m_inputIndex(0),
m_symbolRate(0.0f),
m_rolloff(0.0f)
{
initFilter();
}
FFTFilterRRC::~FFTFilterRRC()
{
delete m_fft;
delete[] m_filter;
delete[] m_fftBuffer;
delete[] m_overlapBuffer;
delete[] m_outputBuffer;
}
void FFTFilterRRC::initFilter()
{
m_fft = new g_fft<float>(m_fftLen);
m_filter = new Complex[m_fftLen];
m_fftBuffer = new Complex[m_fftLen];
m_overlapBuffer = new Complex[m_fftLen2];
m_outputBuffer = new Complex[m_fftLen2];
// Initialize all buffers to zero
std::fill(m_filter, m_filter + m_fftLen, Complex(0.0f, 0.0f));
std::fill(m_fftBuffer, m_fftBuffer + m_fftLen, Complex(0.0f, 0.0f));
std::fill(m_overlapBuffer, m_overlapBuffer + m_fftLen2, Complex(0.0f, 0.0f));
std::fill(m_outputBuffer, m_outputBuffer + m_fftLen2, Complex(0.0f, 0.0f));
m_inputIndex = 0;
}
void FFTFilterRRC::create(float symbolRate, float rolloff)
{
m_symbolRate = symbolRate;
m_rolloff = rolloff;
// Clamp rolloff to valid range [0, 1]
if (m_rolloff < 0.0f) {
m_rolloff = 0.0f;
} else if (m_rolloff > 1.0f) {
m_rolloff = 1.0f;
}
// Create filter directly in frequency domain
std::fill(m_filter, m_filter + m_fftLen, Complex(0.0f, 0.0f));
for (int i = 0; i < m_fftLen; i++) {
m_filter[i] = computeRRCResponse(m_symbolRate, m_rolloff, i, m_fftLen);
}
// Normalize for unity gain in passband
float maxMag = 0.0f;
for (int i = 0; i < m_fftLen; i++) {
float mag = std::abs(m_filter[i]);
if (mag > maxMag) {
maxMag = mag;
}
}
if (maxMag > 0.0f) {
for (int i = 0; i < m_fftLen; i++) {
m_filter[i] /= maxMag;
}
}
}
FFTFilterRRC::Complex FFTFilterRRC::computeRRCResponse(
float symbolRate,
float rolloff,
int binIndex,
int fftLen) const
{
// Normalize frequency to [-0.5, 0.5]
// FFT bins: 0 to fftLen/2-1 are positive frequencies (0 to 0.5)
// fftLen/2 to fftLen-1 are negative frequencies (-0.5 to 0)
float freq = static_cast<float>(binIndex) / static_cast<float>(fftLen);
// Map to [-0.5, 0.5] range
if (freq > 0.5f) {
freq -= 1.0f;
}
// Absolute frequency
float absFreq = std::abs(freq);
// Compute frequency boundaries
// For RRC: passband is Rs/2, where Rs is symbol rate
// Transition band: Rs/2 * (1-beta) to Rs/2 * (1+beta)
float f1 = symbolRate * (1.0f - rolloff) / 2.0f; // Start of transition band
float f2 = symbolRate * (1.0f + rolloff) / 2.0f; // End of transition band
Complex response;
if (absFreq <= f1) {
// Passband: constant response
response = Complex(1.0f, 0.0f);
} else if (absFreq > f1 && absFreq <= f2) {
// Transition band: raised cosine roll-off
// H(f) = 0.5 * [1 + cos(pi/beta * (|f|/Rs - (1-beta)/2))]
float normalizedFreq = absFreq / symbolRate; // Normalize by symbol rate
float arg = (M_PI / rolloff) * (normalizedFreq - (1.0f - rolloff) / 2.0f);
float amplitude = 0.5f * (1.0f + std::cos(arg));
response = Complex(amplitude, 0.0f);
} else {
// Stopband: zero response
response = Complex(0.0f, 0.0f);
}
return response;
}
int FFTFilterRRC::process(const Complex& input, Complex** output)
{
// Store input sample in first half of FFT buffer
m_fftBuffer[m_inputIndex] = input;
m_inputIndex++;
// Process when first half is full
if (m_inputIndex < m_fftLen2) {
return 0; // Not enough samples yet
}
// Second half of FFT buffer is already zero-padded from initialization
// or previous processing (overlap-add method)
// Perform forward FFT
m_fft->ComplexFFT(m_fftBuffer);
// Apply filter in frequency domain (element-wise multiplication)
for (int i = 0; i < m_fftLen; i++) {
m_fftBuffer[i] *= m_filter[i];
}
// Perform inverse FFT
m_fft->InverseComplexFFT(m_fftBuffer);
// Overlap-add: first half gets added to overlap buffer, second half becomes new overlap
for (int i = 0; i < m_fftLen2; i++) {
m_outputBuffer[i] = m_fftBuffer[i] + m_overlapBuffer[i];
m_overlapBuffer[i] = m_fftBuffer[i + m_fftLen2];
}
// Reset input index and clear first half of FFT buffer for next block
m_inputIndex = 0;
std::fill(m_fftBuffer, m_fftBuffer + m_fftLen2, Complex(0.0f, 0.0f));
// Return pointer to output buffer
*output = m_outputBuffer;
return m_fftLen2;
}

150
sdrbase/dsp/fftfilterrrc.h Normal file
View File

@ -0,0 +1,150 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2026 SDRangel contributors //
// //
// 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_DSP_FFTFILTERRRC_H
#define INCLUDE_DSP_FFTFILTERRRC_H
#include <complex>
#include <vector>
#include "gfft.h"
#include "export.h"
/**
* @brief FFT-based root raised cosine filter for complex signals
*
* Implements a frequency-domain root raised cosine (RRC) filter using overlap-add
* FFT convolution. The RRC filter is commonly used for pulse shaping in digital
* communications to minimize intersymbol interference (ISI) while satisfying
* the Nyquist criterion.
*
* The filter operates in the frequency domain for computational efficiency,
* applying the RRC frequency response directly to the FFT bins of the input signal.
*/
class SDRBASE_API FFTFilterRRC
{
public:
using Complex = std::complex<float>;
/**
* @brief Construct FFT-based root raised cosine filter
*
* @param len FFT length (should be power of 2 for efficiency)
*/
explicit FFTFilterRRC(int len);
/**
* @brief Destructor
*/
~FFTFilterRRC();
// Disable copy semantics (manages raw pointers)
FFTFilterRRC(const FFTFilterRRC&) = delete;
FFTFilterRRC& operator=(const FFTFilterRRC&) = delete;
/**
* @brief Create root raised cosine filter in frequency domain
*
* @param symbolRate Symbol rate in Hz (normalized to sample rate)
* @param rolloff Roll-off factor (beta) - typically 0.2 to 0.5
* 0 = rectangular, 1 = full cosine roll-off
*
* The filter bandwidth extends from -symbolRate*(1+rolloff)/2 to
* +symbolRate*(1+rolloff)/2 in normalized frequency (0 to 0.5 = Nyquist).
*
* Creates mathematically correct RRC frequency response for use in
* digital communications applications.
*/
void create(float symbolRate, float rolloff);
/**
* @brief Process one complex input sample through the filter
*
* Uses overlap-add FFT convolution. Accumulates input samples until
* a half-buffer is filled, then performs FFT, frequency-domain multiplication
* with the RRC filter, IFFT, and overlap-add to produce output samples.
*
* @param input Complex input sample
* @param output Pointer to output buffer (will point to internal buffer)
* @return Number of output samples available (0 or fftLen/2)
*/
int process(const Complex& input, Complex** output);
/**
* @brief Get FFT length
* @return FFT length in samples
*/
int getFFTLength() const { return m_fftLen; }
/**
* @brief Get current symbol rate
* @return Symbol rate (normalized)
*/
float getSymbolRate() const { return m_symbolRate; }
/**
* @brief Get current rolloff factor
* @return Rolloff factor (beta)
*/
float getRolloff() const { return m_rolloff; }
/**
* @brief Get pointer to frequency-domain filter coefficients
*
* The filter coefficients are in the frequency domain, corresponding to the FFT bins.
*
* @return Pointer to array of complex filter coefficients (length = FFT length)
*/
const Complex* getFilter() const { return m_filter; }
private:
/**
* @brief Initialize internal buffers and FFT engine
*/
void initFilter();
/**
* @brief Compute RRC frequency response for a given frequency bin
*
* Implements the RRC transfer function in frequency domain:
* - For |f| <= (1-beta)/(2T): H(f) = T
* - For (1-beta)/(2T) < |f| <= (1+beta)/(2T): H(f) = T/2 * [1 + cos(pi*T/beta * (|f| - (1-beta)/(2T)))]
* - For |f| > (1+beta)/(2T): H(f) = 0
*
* @param symbolRate Symbol rate (normalized to sample rate)
* @param rolloff Roll-off factor (beta)
* @param binIndex FFT bin index
* @param fftLen FFT length
* @return Complex frequency response at this bin
*/
Complex computeRRCResponse(float symbolRate, float rolloff, int binIndex, int fftLen) const;
int m_fftLen; //!< FFT length
int m_fftLen2; //!< Half FFT length
g_fft<float>* m_fft; //!< FFT engine
Complex* m_filter; //!< Frequency-domain filter coefficients
Complex* m_fftBuffer; //!< Time-domain input buffer for FFT
Complex* m_overlapBuffer; //!< Overlap buffer for overlap-add
Complex* m_outputBuffer; //!< Output buffer
int m_inputIndex; //!< Current input buffer index
float m_symbolRate; //!< Current symbol rate (normalized)
float m_rolloff; //!< Current rolloff factor
};
#endif // INCLUDE_DSP_FFTFILTERRRC_H

View File

@ -0,0 +1,192 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2026 SDRangel contributors //
// //
// 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 "dsp/firfilterrrc.h"
#include <algorithm>
#include <cmath>
FIRFilterRRC::FIRFilterRRC() :
m_ptr(0),
m_symbolRate(0.0f),
m_rolloff(0.0f),
m_samplesPerSymbol(0)
{
}
void FIRFilterRRC::create(float symbolRate, float rolloff, int symbolSpan, Normalization normalization)
{
m_symbolRate = symbolRate;
m_rolloff = rolloff;
// Clamp rolloff to valid range [0, 1]
if (m_rolloff < 0.0f) {
m_rolloff = 0.0f;
} else if (m_rolloff > 1.0f) {
m_rolloff = 1.0f;
}
// Calculate samples per symbol
m_samplesPerSymbol = static_cast<int>(std::round(1.0f / symbolRate));
// Calculate number of taps (always odd for symmetry)
int numTaps = symbolSpan * m_samplesPerSymbol + 1;
if ((numTaps & 1) == 0) {
numTaps++; // Ensure odd number of taps
}
// Allocate tap storage (only half + center due to symmetry)
int halfTaps = numTaps / 2 + 1;
m_taps.resize(halfTaps);
// Calculate filter taps
int center = numTaps / 2;
for (int i = 0; i < halfTaps; i++)
{
// Time offset from center in symbol periods
float t = static_cast<float>(i - center) / static_cast<float>(m_samplesPerSymbol);
m_taps[i] = computeRRCTap(t, m_rolloff);
}
// Apply normalization
if (normalization == Normalization::Energy)
{
// Normalize energy: sqrt(sum of squares) = 1
float sum = 0.0f;
for (int i = 0; i < halfTaps - 1; i++) {
sum += m_taps[i] * m_taps[i] * 2.0f; // Two symmetric taps
}
sum += m_taps[halfTaps - 1] * m_taps[halfTaps - 1]; // Center tap
float scale = std::sqrt(sum);
if (scale > 0.0f) {
for (int i = 0; i < halfTaps; i++) {
m_taps[i] /= scale;
}
}
}
else if (normalization == Normalization::Amplitude)
{
// Normalize amplitude: max output for bipolar sequence ≈ 1
float maxGain = 0.0f;
for (int offset = 0; offset < m_samplesPerSymbol; offset++)
{
float gain = 0.0f;
for (int i = offset; i < halfTaps - 1; i += m_samplesPerSymbol) {
gain += std::abs(m_taps[i]) * 2.0f; // Both sides
}
// Add center tap if aligned
if ((center - offset) % m_samplesPerSymbol == 0) {
gain += std::abs(m_taps[halfTaps - 1]);
}
if (gain > maxGain) {
maxGain = gain;
}
}
if (maxGain > 0.0f) {
for (int i = 0; i < halfTaps; i++) {
m_taps[i] /= maxGain;
}
}
}
else if (normalization == Normalization::Gain)
{
// Normalize for unity gain: sum of taps = 1
float sum = 0.0f;
for (int i = 0; i < halfTaps - 1; i++) {
sum += m_taps[i] * 2.0f; // Two symmetric taps
}
sum += m_taps[halfTaps - 1]; // Center tap
if (sum > 0.0f) {
for (int i = 0; i < halfTaps; i++) {
m_taps[i] /= sum;
}
}
}
// Initialize sample buffer
m_samples.resize(numTaps);
std::fill(m_samples.begin(), m_samples.end(), Complex(0.0f, 0.0f));
m_ptr = 0;
}
float FIRFilterRRC::computeRRCTap(float t, float rolloff) const
{
constexpr float Ts = 1.0f; // Symbol period (normalized)
const float beta = rolloff;
// Handle special case: t = 0
if (t == 0.0f) {
// h(0) = (1/T) * (1 + beta*(4/π - 1))
return (1.0f / Ts) * (1.0f + beta * (4.0f / M_PI - 1.0f));
}
// Check for zeros of denominator: 1 - (4*β*t/T)² = 0
// This occurs at t = ±T/(4*β)
const float denomCheck = 4.0f * beta * t / Ts;
if (std::abs(std::abs(denomCheck) - 1.0f) < 1e-6f && beta > 0.0f)
{
// h(±T/4β) = (β/(T*√2)) * [(1+2/π)*sin(π/4β) + (1-2/π)*cos(π/4β)]
const float arg = M_PI / (4.0f * beta);
return (beta / (Ts * std::sqrt(2.0f))) *
((1.0f + 2.0f / M_PI) * std::sin(arg) +
(1.0f - 2.0f / M_PI) * std::cos(arg));
}
// General case
const float numerator = std::sin(M_PI * t / Ts * (1.0f - beta)) +
4.0f * beta * t / Ts * std::cos(M_PI * t / Ts * (1.0f + beta));
const float denominator = M_PI * t / Ts * (1.0f - denomCheck * denomCheck);
return numerator / (denominator * Ts);
}
FIRFilterRRC::Complex FIRFilterRRC::filter(const Complex& input)
{
const int numTaps = static_cast<int>(m_samples.size());
const int halfTaps = static_cast<int>(m_taps.size()) - 1;
// Store input sample in circular buffer
m_samples[m_ptr] = input;
// Perform convolution using symmetry
Complex acc(0.0f, 0.0f);
int a = m_ptr;
int b = (a == numTaps - 1) ? 0 : a + 1;
// Process symmetric pairs
for (int i = 0; i < halfTaps; i++)
{
acc += (m_samples[a] + m_samples[b]) * m_taps[i];
a = (a == 0) ? numTaps - 1 : a - 1;
b = (b == numTaps - 1) ? 0 : b + 1;
}
// Add center tap
acc += m_samples[a] * m_taps[halfTaps];
// Advance circular buffer pointer
m_ptr = (m_ptr == static_cast<size_t>(numTaps) - 1) ? 0 : m_ptr + 1;
return acc;
}

153
sdrbase/dsp/firfilterrrc.h Normal file
View File

@ -0,0 +1,153 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2026 SDRangel contributors //
// //
// 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_DSP_FIRFILTERRRC_H
#define INCLUDE_DSP_FIRFILTERRRC_H
#include <complex>
#include <vector>
#include "export.h"
/**
* @brief FIR root raised cosine filter for complex signals
*
* Implements a time-domain FIR root raised cosine (RRC) filter using direct
* convolution. The RRC filter is commonly used for pulse shaping in digital
* communications to minimize intersymbol interference (ISI) while satisfying
* the Nyquist criterion.
*
* This filter offers sample-by-sample processing with low latency, making it
* suitable for real-time applications. For longer filters or higher throughput,
* consider using FFTFilterRRC which uses frequency-domain processing.
*/
class SDRBASE_API FIRFilterRRC
{
public:
using Complex = std::complex<float>;
/**
* @brief Normalization modes for filter taps
*/
enum class Normalization {
Energy, //!< Impulse response energy normalized (TX+RX = raised cosine peak = 1) - for matched filter pairs
Amplitude, //!< Bipolar symbol sequence output amplitude normalized to ±1 - for pulse amplitude preservation
Gain //!< Unity gain (0 dB) - tap coefficients sum to 1 - for continuous wave input
};
/**
* @brief Construct FIR root raised cosine filter
*/
FIRFilterRRC();
/**
* @brief Destructor
*/
~FIRFilterRRC() = default;
/**
* @brief Create root raised cosine filter taps
*
* @param symbolRate Symbol rate (normalized to sample rate, 0 to 0.5)
* @param rolloff Roll-off factor (beta) - typically 0.2 to 0.5
* 0 = rectangular, 1 = full cosine roll-off
* @param symbolSpan Number of symbols over which filter is spread
* Typical values: 6-12 (more = better filtering, more delay)
* @param normalization How to normalize the filter taps
*
* Total number of taps = symbolSpan * samplesPerSymbol + 1 (always odd).
* For symbolRate=0.05 (20 samples/symbol), symbolSpan=8 gives 161 taps.
*/
void create(float symbolRate, float rolloff, int symbolSpan = 8,
Normalization normalization = Normalization::Energy);
/**
* @brief Process one complex input sample through the filter
*
* Direct convolution with stored tap coefficients. Processes samples
* one at a time with minimal latency.
*
* @param input Complex input sample
* @return Filtered complex output sample
*/
Complex filter(const Complex& input);
/**
* @brief Get current symbol rate
* @return Symbol rate (normalized)
*/
float getSymbolRate() const { return m_symbolRate; }
/**
* @brief Get current rolloff factor
* @return Rolloff factor (beta)
*/
float getRolloff() const { return m_rolloff; }
/**
* @brief Get number of taps in filter
* @return Number of filter taps
*/
int getNumTaps() const { return static_cast<int>(m_taps.size()); }
/**
* @brief Get samples per symbol
* @return Samples per symbol (1/symbolRate)
*/
int getSamplesPerSymbol() const { return m_samplesPerSymbol; }
/**
* @brief Get filter delay in samples
* @return Group delay (approximately numTaps/2)
*/
int getDelay() const { return getNumTaps() / 2; }
/**
* @brief Get reference to filter tap coefficients
*
* The taps are stored as half + center due to symmetry. The full impulse
* response can be reconstructed by mirroring the taps around the center.
*
* @return Reference to vector of filter tap coefficients (half + center)
*/
const std::vector<float>& getTaps() const { return m_taps; }
private:
/**
* @brief Compute time-domain RRC tap value
*
* Implements the mathematical RRC impulse response:
* h(t) = [sin(π*t/T*(1-β)) + 4*β*t/T*cos(π*t/T*(1+β))] / [π*t/T*(1-(4*β*t/T)²)]
*
* @param t Time index (in symbol periods)
* @param rolloff Roll-off factor (beta)
* @return Tap value at time t
*/
float computeRRCTap(float t, float rolloff) const;
std::vector<float> m_taps; //!< Filter tap coefficients (symmetric, stored as half + center)
std::vector<Complex> m_samples; //!< Circular buffer for input samples
size_t m_ptr; //!< Current position in circular buffer
float m_symbolRate; //!< Symbol rate (normalized)
float m_rolloff; //!< Rolloff factor (beta)
int m_samplesPerSymbol; //!< Samples per symbol (1/symbolRate)
};
#endif // INCLUDE_DSP_FIRFILTERRRC_H

View File

@ -11,6 +11,8 @@ set(sdrbench_SOURCES
test_ft8.cpp
test_callsign.cpp
test_ft8protocols.cpp
test_fftrrc.cpp
test_firrrc.cpp
)
set(sdrbench_HEADERS

View File

@ -73,6 +73,10 @@ void MainBench::run()
testCallsign(m_parser.getArgsStr());
} else if (m_parser.getTestType() == ParserBench::TestFT8Protocols) {
testFT8Protocols(m_parser.getArgsStr());
} else if (m_parser.getTestType() == ParserBench::TestFFTRRCFilter) {
testFFTRRCFilter();
} else if (m_parser.getTestType() == ParserBench::TestFIRRRCFilter) {
testFIRRRCFilter();
} else {
qDebug() << "MainBench::run: unknown test type: " << m_parser.getTestType();
}

View File

@ -57,6 +57,8 @@ private:
void testDecimateFI();
void testDecimateFF();
void testGolay2312();
void testFFTRRCFilter();
void testFIRRRCFilter();
void testFT8(const QString& wavFile, const QString& argsStr); //!< use with sdrbench/samples/ft8/230105_091630.wav in -f option
void testFT8Protocols(const QString& argsStr);
void testCallsign(const QString& argsStr);

View File

@ -24,7 +24,7 @@
ParserBench::ParserBench() :
m_testOption(QStringList() << "t" << "test",
"Test type: decimateii, decimatefi, decimateff, decimateif, decimateinfii, decimatesupii, ambe, golay2312, ft8, ft8protocols, callsign"
"Test type: decimateii, decimatefi, decimateff, decimateif, decimateinfii, decimatesupii, ambe, golay2312, ft8, ft8protocols, callsign, fftrrcfilter, firrrcfilter.",
"test",
"decimateii"),
m_nbSamplesOption(QStringList() << "n" << "nb-samples",
@ -151,6 +151,10 @@ ParserBench::TestType ParserBench::getTestType() const
return TestCallsign;
} else if (m_testStr == "ft8protocols") {
return TestFT8Protocols;
} else if (m_testStr == "fftrrcfilter") {
return TestFFTRRCFilter;
} else if (m_testStr == "firrrcfilter") {
return TestFIRRRCFilter;
} else {
return TestDecimatorsII;
}

View File

@ -40,7 +40,9 @@ public:
TestGolay2312,
TestFT8,
TestCallsign,
TestFT8Protocols
TestFT8Protocols,
TestFFTRRCFilter,
TestFIRRRCFilter
} TestType;
ParserBench();

77
sdrbench/test_fftrrc.cpp Normal file
View File

@ -0,0 +1,77 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2026 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// 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 "mainbench.h"
#include "dsp/fftfilterrrc.h"
#include <QDebug>
void MainBench::testFFTRRCFilter()
{
qDebug() << "MainBench::testFFTRRCFilter";
const int RRC_FFT_SIZE = 512;
std::complex<float>* rrcFilterOut = nullptr;
FFTFilterRRC filter(RRC_FFT_SIZE);
filter.create(0.05f, 0.35f); // 2400 baud, 0.35 rolloff
qDebug() << "MainBench::testFFTRRCFilter: filter created";
FILE *fd_filter = fopen("test_rrc_filter.txt", "w");
for (int i = 0; i < RRC_FFT_SIZE; i++) {
fprintf(fd_filter, "%f\n", std::abs(filter.getFilter()[i]));
}
qDebug() << "MainBench::testFFTRRCFilter: filter coefficients written to test_rrc_filter.txt";
fclose(fd_filter);
qDebug() << "MainBench::testFFTRRCFilter: running filter";
FILE *fd = fopen("test_rrc.txt", "w");
int outLen = 0;
for (int i = 0; i < 5000; i++)
{
int ss = 48000 / 2400; // Samples per symbol at 2400 baud
int d = i / ss; // Symbol index at 2400 baud
Real s = (d % 2 == 0) ? 1.0f : -1.0f; // BPSK symbol sequence
Real x = (i % ss == 0 ? s : 0.0f); // Pulsed signal at 2400 Hz
// Real phi = i * (1200.0 / 48000.0) * (2*3.141);
// Real x = sin(phi) > 0.0 ? 1.0f : -1.0f; // Simulate a BPSK signal at 1200 baud
filter.process(Complex(x, 0.0f), &rrcFilterOut);
outLen++;
if (outLen % (RRC_FFT_SIZE / 2) == 0)
{
// printf("\ni=%d n_out: %d\n", i, n_out);
for (int j = 0; j < outLen; j++)
{
Complex rrc = rrcFilterOut[j];
fprintf(fd, "%f\n", rrc.real());
}
outLen = 0;
}
}
// output any remaining samples
for (int j = 0; j < outLen; j++)
{
Complex rrc = rrcFilterOut[j];
fprintf(fd, "%f\n", rrc.real());
}
qDebug() << "MainBench::testFFTRRCFilter: output samples written to test_fftrrc.txt";
fclose(fd);
}

50
sdrbench/test_firrrc.cpp Normal file
View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2026 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// 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 "mainbench.h"
#include "dsp/firfilterrrc.h"
#include <QDebug>
void MainBench::testFIRRRCFilter()
{
qDebug() << "MainBench::testFIRRRCFilter";
FIRFilterRRC filter;
filter.create(0.05f, 0.35f, 8, FIRFilterRRC::Normalization::Gain); // 2400 baud, 0.35 rolloff
qDebug() << "MainBench::testFIRRRCFilter: filter created";
FILE *fd_filter = fopen("test_rrc_filter.txt", "w");
const std::vector<float>& taps = filter.getTaps();
for (auto tap : taps) {
fprintf(fd_filter, "%f\n", std::abs(tap));
}
qDebug() << "MainBench::testFIRRRCFilter: filter coefficients written to test_rrc_filter.txt";
fclose(fd_filter);
qDebug() << "MainBench::testFIRRRCFilter: running filter";
FILE *fd = fopen("test_rrc.txt", "w");
for (int i = 0; i < 1000; i++)
{
Real phi = i * (1200.0 / 48000.0) * (2*3.141);
Real x = sin(phi) > 0.0 ? 1.0f : -1.0f; // Simulate a BPSK signal at 1200 baud
Complex rrc = filter.filter(Complex(x, 0.0f));
fprintf(fd, "%f\n", rrc.real());
}
qDebug() << "MainBench::testFIRRRCFilter: output samples written to test_firrrc.txt";
fclose(fd);
}

41
sdrbench/test_rrc.py Executable file
View File

@ -0,0 +1,41 @@
#!/usr/bin/env python3
import matplotlib.pyplot as plt
import numpy as np
with open('../build/test_rrc_filter.txt', 'r') as f:
filter_data = f.read()
filter_out = [float(x) for x in filter_data.splitlines()]
xf = np.array(filter_out)
with open('../build/test_rrc.txt', 'r') as f:
data = f.read()
out = [float(x) for x in data.splitlines()]
# marks = [127, 254, 383, 511, 639, 767, 895, 1023]
# marks = [127, 254, 383, 511]
x = np.array(out)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 6))
ax2.plot(xf)
ax2.set_title('RRC Filter Coefficients')
ax2.set_xlabel('Index')
ax2.set_ylabel('Amplitude')
ax2.grid(True)
ax1.set_title('RRC Filter Output (Real Part)')
ax1.plot(x)
# ax1.scatter(marks, x[marks],
# color='red', marker='.', s=100, zorder=5, edgecolors='black')
# plt.annotate('126', xy=(126, x[126]), xytext=(6, 0),
# arrowprops=dict(facecolor='black', shrink=0.05),
# fontsize=12, ha='center')
ax1.set_xlabel('Index')
ax1.set_ylabel('Real part')
ax1.grid(True)
plt.subplots_adjust(hspace=0.4)
plt.savefig('plot.png', dpi=150, bbox_inches='tight')
plt.close() # Prevents memory leaks