diff --git a/plugins/channelrx/chanalyzer/CMakeLists.txt b/plugins/channelrx/chanalyzer/CMakeLists.txt index 57fb1f188..59a133619 100644 --- a/plugins/channelrx/chanalyzer/CMakeLists.txt +++ b/plugins/channelrx/chanalyzer/CMakeLists.txt @@ -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( diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index 9d8653b6e..42f28f9cc 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -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(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); diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.h b/plugins/channelrx/chanalyzer/chanalyzergui.h index b9dc95e89..a0d8afc85 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.h +++ b/plugins/channelrx/chanalyzer/chanalyzergui.h @@ -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(); }; diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui index 13fe10ab3..e09c44b2c 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.ui +++ b/plugins/channelrx/chanalyzer/chanalyzergui.ui @@ -418,7 +418,7 @@ - Toggle RRC filter + Toggle RRC filter - Right click for options diff --git a/plugins/channelrx/chanalyzer/chanalyzersettings.cpp b/plugins/channelrx/chanalyzer/chanalyzersettings.cpp index fa2691b5c..b8c5d5f88 100644 --- a/plugins/channelrx/chanalyzer/chanalyzersettings.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzersettings.cpp @@ -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; } diff --git a/plugins/channelrx/chanalyzer/chanalyzersettings.h b/plugins/channelrx/chanalyzer/chanalyzersettings.h index 8512bf77e..80b8ecb48 100644 --- a/plugins/channelrx/chanalyzer/chanalyzersettings.h +++ b/plugins/channelrx/chanalyzer/chanalyzersettings.h @@ -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; diff --git a/plugins/channelrx/chanalyzer/chanalyzersink.cpp b/plugins/channelrx/chanalyzer/chanalyzersink.cpp index fe25b1c65..4ef49fddf 100644 --- a/plugins/channelrx/chanalyzer/chanalyzersink.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzersink.cpp @@ -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[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 & in, std::complex **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); } diff --git a/plugins/channelrx/chanalyzer/chanalyzersink.h b/plugins/channelrx/chanalyzer/chanalyzersink.h index 8e8a72f19..49c2cfc68 100644 --- a/plugins/channelrx/chanalyzer/chanalyzersink.h +++ b/plugins/channelrx/chanalyzer/chanalyzersink.h @@ -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 & in, std::complex **out); + private: + bool m_useFFT; + FIRFilterRRC* m_filterFIR; + FFTFilterRRC* m_filterFFT; + std::complex* 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; diff --git a/plugins/channelrx/chanalyzer/readme.md b/plugins/channelrx/chanalyzer/readme.md index 88f610d2b..34ed24ec0 100644 --- a/plugins/channelrx/chanalyzer/readme.md +++ b/plugins/channelrx/chanalyzer/readme.md @@ -122,7 +122,14 @@ Average total power in dB relative to a +/- 1.0 amplitude signal received in the

10. Toggle root raised cosine filter

-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

11. Tune RRC filter rolloff factor

diff --git a/plugins/channelrx/chanalyzer/rrcfilterdialog.cpp b/plugins/channelrx/chanalyzer/rrcfilterdialog.cpp new file mode 100644 index 000000000..4d2318bc3 --- /dev/null +++ b/plugins/channelrx/chanalyzer/rrcfilterdialog.cpp @@ -0,0 +1,149 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2026 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "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::of(&QSpinBox::valueChanged), this, &RRCFilterDialog::on_symbolSpan_valueChanged); + connect(ui->normalization, QOverload::of(&QComboBox::currentIndexChanged), this, &RRCFilterDialog::on_normalization_currentIndexChanged); + connect(ui->fftSize, QOverload::of(&QComboBox::currentIndexChanged), this, &RRCFilterDialog::on_fftLog2Size_currentIndexChanged); +} diff --git a/plugins/channelrx/chanalyzer/rrcfilterdialog.h b/plugins/channelrx/chanalyzer/rrcfilterdialog.h new file mode 100644 index 000000000..43edab163 --- /dev/null +++ b/plugins/channelrx/chanalyzer/rrcfilterdialog.h @@ -0,0 +1,69 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2026 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// +#ifndef PLUGINS_CHANNELRX_CHANALYZER_RRCFILTERDIALOG_H_ +#define PLUGINS_CHANNELRX_CHANALYZER_RRCFILTERDIALOG_H_ + +#include +#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_ */ diff --git a/plugins/channelrx/chanalyzer/rrcfilterdialog.ui b/plugins/channelrx/chanalyzer/rrcfilterdialog.ui new file mode 100644 index 000000000..aaca1195a --- /dev/null +++ b/plugins/channelrx/chanalyzer/rrcfilterdialog.ui @@ -0,0 +1,258 @@ + + + RRCFilterDialog + + + + 0 + 0 + 407 + 197 + + + + RRC Filter Settings + + + + + + + 370 + 70 + + + + Use a FIR RRC filter + + + QGroupBox { + border: 1px solid gray; + } + + + FIR + + + true + + + false + + + + + 10 + 30 + 91 + 26 + + + + Symbol Span + + + + + + 100 + 30 + 70 + 29 + + + + Symbol span + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 6 + + + 12 + + + 8 + + + + + + 180 + 30 + 101 + 26 + + + + Normalization + + + + + + 280 + 30 + 101 + 32 + + + + RRC normalization type + + + + Energy + + + + + Amplitude + + + + + Gain + + + + + + + + + + 370 + 70 + + + + Use a FFT RRC filter + + + QGroupBox { + border: 1px solid gray; + } + + + FFT + + + true + + + false + + + + + 10 + 30 + 81 + 26 + + + + FFT Size + + + + + + 80 + 30 + 94 + 32 + + + + Size of FFT filter + + + + 128 + + + + + 256 + + + + + 512 + + + + + 1024 + + + + + 2048 + + + + + 4096 + + + + + 8192 + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + accepted() + RRCFilterDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + RRCFilterDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/plugins/channelrx/demodapt/aptdemod.cpp b/plugins/channelrx/demodapt/aptdemod.cpp index 1f9692ca6..0cb4b24cb 100644 --- a/plugins/channelrx/demodapt/aptdemod.cpp +++ b/plugins/channelrx/demodapt/aptdemod.cpp @@ -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")) { diff --git a/plugins/channelrx/demodapt/aptdemodbaseband.cpp b/plugins/channelrx/demodapt/aptdemodbaseband.cpp index 9a1d4f68d..2de354b23 100644 --- a/plugins/channelrx/demodapt/aptdemodbaseband.cpp +++ b/plugins/channelrx/demodapt/aptdemodbaseband.cpp @@ -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()); diff --git a/plugins/channelrx/demodatv/atvdemod.cpp b/plugins/channelrx/demodatv/atvdemod.cpp index b0a0b4084..27d20ce5a 100644 --- a/plugins/channelrx/demodatv/atvdemod.cpp +++ b/plugins/channelrx/demodatv/atvdemod.cpp @@ -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 { diff --git a/plugins/channelrx/demodatv/atvdemodbaseband.cpp b/plugins/channelrx/demodatv/atvdemodbaseband.cpp index 00bad7a2b..e1e1668ab 100644 --- a/plugins/channelrx/demodatv/atvdemodbaseband.cpp +++ b/plugins/channelrx/demodatv/atvdemodbaseband.cpp @@ -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); diff --git a/plugins/channelrx/demoddab/dabdemod.cpp b/plugins/channelrx/demoddab/dabdemod.cpp index 7c62cb930..0e8bec89a 100644 --- a/plugins/channelrx/demoddab/dabdemod.cpp +++ b/plugins/channelrx/demoddab/dabdemod.cpp @@ -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")) { diff --git a/plugins/channelrx/demodfreedv/freedvdemodsink.cpp b/plugins/channelrx/demodfreedv/freedvdemodsink.cpp index 3f6ce20a1..30e73761f 100644 --- a/plugins/channelrx/demodfreedv/freedvdemodsink.cpp +++ b/plugins/channelrx/demodfreedv/freedvdemodsink.cpp @@ -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 diff --git a/plugins/channelrx/demodft8/ft8demod.cpp b/plugins/channelrx/demodft8/ft8demod.cpp index 5ef40f11f..202f666bc 100644 --- a/plugins/channelrx/demodft8/ft8demod.cpp +++ b/plugins/channelrx/demodft8/ft8demod.cpp @@ -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")) { diff --git a/plugins/channelrx/demodils/ilsdemodbaseband.cpp b/plugins/channelrx/demodils/ilsdemodbaseband.cpp index eef131ff6..04b1f4967 100644 --- a/plugins/channelrx/demodils/ilsdemodbaseband.cpp +++ b/plugins/channelrx/demodils/ilsdemodbaseband.cpp @@ -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); diff --git a/plugins/channelrx/demodinmarsat/inmarsatdemodsink.cpp b/plugins/channelrx/demodinmarsat/inmarsatdemodsink.cpp index 7eac2f6ca..df5081b50 100644 --- a/plugins/channelrx/demodinmarsat/inmarsatdemodsink.cpp +++ b/plugins/channelrx/demodinmarsat/inmarsatdemodsink.cpp @@ -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::Gain); m_rrcQ.create(m_settings.m_rrcRolloff, 5, SAMPLES_PER_SYMBOL, RootRaisedCosine::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); } diff --git a/plugins/channelrx/demodinmarsat/inmarsatdemodsink.h b/plugins/channelrx/demodinmarsat/inmarsatdemodsink.h index 6c38b0998..4389fa0ea 100644 --- a/plugins/channelrx/demodinmarsat/inmarsatdemodsink.h +++ b/plugins/channelrx/demodinmarsat/inmarsatdemodsink.h @@ -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 m_rrcI; //!< Square root raised cosine filter for I samples RootRaisedCosine m_rrcQ; //!< Square root raised cosine filter for Q samples diff --git a/plugins/channelrx/freqtracker/freqtracker.cpp b/plugins/channelrx/freqtracker/freqtracker.cpp index 37ba8f936..45c7be45c 100644 --- a/plugins/channelrx/freqtracker/freqtracker.cpp +++ b/plugins/channelrx/freqtracker/freqtracker.cpp @@ -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); } diff --git a/plugins/channelrx/freqtracker/freqtrackerbaseband.cpp b/plugins/channelrx/freqtracker/freqtrackerbaseband.cpp index ec4d9a8a0..adc090874 100644 --- a/plugins/channelrx/freqtracker/freqtrackerbaseband.cpp +++ b/plugins/channelrx/freqtracker/freqtrackerbaseband.cpp @@ -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) { diff --git a/plugins/channelrx/freqtracker/freqtrackersink.cpp b/plugins/channelrx/freqtracker/freqtrackersink.cpp index 2940da900..c1604628f 100644 --- a/plugins/channelrx/freqtracker/freqtrackersink.cpp +++ b/plugins/channelrx/freqtracker/freqtrackersink.cpp @@ -20,7 +20,7 @@ #include #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<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 } diff --git a/plugins/channelrx/freqtracker/freqtrackersink.h b/plugins/channelrx/freqtracker/freqtrackersink.h index d9b2f6edb..58a2277e4 100644 --- a/plugins/channelrx/freqtracker/freqtrackersink.h +++ b/plugins/channelrx/freqtracker/freqtrackersink.h @@ -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(); diff --git a/plugins/channelrx/localsink/localsink.cpp b/plugins/channelrx/localsink/localsink.cpp index ed0e924a5..8acdb8e69 100644 --- a/plugins/channelrx/localsink/localsink.cpp +++ b/plugins/channelrx/localsink/localsink.cpp @@ -384,7 +384,7 @@ void LocalSink::applySettings(const LocalSinkSettings& settings, const QListsetChannelization(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); diff --git a/plugins/channeltx/moddatv/datvmodsource.cpp b/plugins/channeltx/moddatv/datvmodsource.cpp index 63ec75257..1253727e4 100644 --- a/plugins/channeltx/moddatv/datvmodsource.cpp +++ b/plugins/channeltx/moddatv/datvmodsource.cpp @@ -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); } diff --git a/plugins/channeltx/modfreedv/freedvmodbaseband.cpp b/plugins/channeltx/modfreedv/freedvmodbaseband.cpp index a158fba5a..8b1d41841 100644 --- a/plugins/channeltx/modfreedv/freedvmodbaseband.cpp +++ b/plugins/channeltx/modfreedv/freedvmodbaseband.cpp @@ -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); diff --git a/plugins/channeltx/modnfm/nfmmodbaseband.cpp b/plugins/channeltx/modnfm/nfmmodbaseband.cpp index 872e59104..55fd89e57 100644 --- a/plugins/channeltx/modnfm/nfmmodbaseband.cpp +++ b/plugins/channeltx/modnfm/nfmmodbaseband.cpp @@ -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); diff --git a/plugins/feature/afc/afc.cpp b/plugins/feature/afc/afc.cpp index 5c9ddb46b..7ffa5d157 100644 --- a/plugins/feature/afc/afc.cpp +++ b/plugins/feature/afc/afc.cpp @@ -271,7 +271,7 @@ void AFC::applySettings(const AFCSettings& settings, const QList& setti settingsKeys.contains("reverseAPIAddress") || settingsKeys.contains("reverseAPIPort") || settingsKeys.contains("reverseAPIFeatureSetIndex") || - settingsKeys.contains("m_reverseAPIFeatureIndex"); + settingsKeys.contains("reverseAPIFeatureIndex"); webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force); } diff --git a/plugins/feature/ais/ais.cpp b/plugins/feature/ais/ais.cpp index 23df56062..1b5fa3695 100644 --- a/plugins/feature/ais/ais.cpp +++ b/plugins/feature/ais/ais.cpp @@ -142,7 +142,7 @@ void AIS::applySettings(const AISSettings& settings, const QList& setti settingsKeys.contains("reverseAPIAddress") || settingsKeys.contains("reverseAPIPort") || settingsKeys.contains("reverseAPIFeatureSetIndex") || - settingsKeys.contains("m_reverseAPIFeatureIndex"); + settingsKeys.contains("reverseAPIFeatureIndex"); webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force); } diff --git a/plugins/feature/antennatools/antennatools.cpp b/plugins/feature/antennatools/antennatools.cpp index 6ff591953..397b93bdd 100644 --- a/plugins/feature/antennatools/antennatools.cpp +++ b/plugins/feature/antennatools/antennatools.cpp @@ -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); } diff --git a/plugins/feature/aprs/aprs.cpp b/plugins/feature/aprs/aprs.cpp index 3b889c983..52961fd99 100644 --- a/plugins/feature/aprs/aprs.cpp +++ b/plugins/feature/aprs/aprs.cpp @@ -220,7 +220,7 @@ void APRS::applySettings(const APRSSettings& settings, const QList& set settingsKeys.contains("reverseAPIAddress") || settingsKeys.contains("reverseAPIPort") || settingsKeys.contains("reverseAPIFeatureSetIndex") || - settingsKeys.contains("m_reverseAPIFeatureIndex"); + settingsKeys.contains("reverseAPIFeatureIndex"); webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force); } diff --git a/plugins/feature/demodanalyzer/demodanalyzer.cpp b/plugins/feature/demodanalyzer/demodanalyzer.cpp index 5e9bf95b3..c08934848 100644 --- a/plugins/feature/demodanalyzer/demodanalyzer.cpp +++ b/plugins/feature/demodanalyzer/demodanalyzer.cpp @@ -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); } diff --git a/plugins/feature/denoiser/denoiser.cpp b/plugins/feature/denoiser/denoiser.cpp index 4df8d7cc3..5a96619ca 100644 --- a/plugins/feature/denoiser/denoiser.cpp +++ b/plugins/feature/denoiser/denoiser.cpp @@ -299,7 +299,7 @@ void Denoiser::applySettings(const DenoiserSettings& settings, const QList& setti settingsKeys.contains("reverseAPIAddress") || settingsKeys.contains("reverseAPIPort") || settingsKeys.contains("reverseAPIFeatureSetIndex") || - settingsKeys.contains("m_reverseAPIFeatureIndex"); + settingsKeys.contains("reverseAPIFeatureIndex"); webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force); } diff --git a/plugins/feature/morsedecoder/morsedecoder.cpp b/plugins/feature/morsedecoder/morsedecoder.cpp index a744ede3b..1d6298b25 100644 --- a/plugins/feature/morsedecoder/morsedecoder.cpp +++ b/plugins/feature/morsedecoder/morsedecoder.cpp @@ -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); } diff --git a/plugins/feature/pertester/pertester.cpp b/plugins/feature/pertester/pertester.cpp index 6a5316b07..c504fffa7 100644 --- a/plugins/feature/pertester/pertester.cpp +++ b/plugins/feature/pertester/pertester.cpp @@ -202,7 +202,7 @@ void PERTester::applySettings(const PERTesterSettings& settings, const QList& s settingsKeys.contains("reverseAPIAddress") || settingsKeys.contains("reverseAPIPort") || settingsKeys.contains("reverseAPIFeatureSetIndex") || - settingsKeys.contains("m_reverseAPIFeatureIndex"); + settingsKeys.contains("reverseAPIFeatureIndex"); webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force); } diff --git a/plugins/feature/simpleptt/simpleptt.cpp b/plugins/feature/simpleptt/simpleptt.cpp index da472263f..ab85d51e1 100644 --- a/plugins/feature/simpleptt/simpleptt.cpp +++ b/plugins/feature/simpleptt/simpleptt.cpp @@ -220,7 +220,7 @@ void SimplePTT::applySettings(const SimplePTTSettings& settings, const QList& settingsKeys.contains("reverseAPIAddress") || settingsKeys.contains("reverseAPIPort") || settingsKeys.contains("reverseAPIFeatureSetIndex") || - settingsKeys.contains("m_reverseAPIFeatureIndex"); + settingsKeys.contains("reverseAPIFeatureIndex"); webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force); } diff --git a/plugins/feature/startracker/startracker.cpp b/plugins/feature/startracker/startracker.cpp index 1f33d784a..6740c269b 100644 --- a/plugins/feature/startracker/startracker.cpp +++ b/plugins/feature/startracker/startracker.cpp @@ -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); } diff --git a/plugins/feature/startracker/startrackerworker.cpp b/plugins/feature/startracker/startrackerworker.cpp index b37bfdbed..f567d65c2 100644 --- a/plugins/feature/startracker/startrackerworker.cpp +++ b/plugins/feature/startracker/startrackerworker.cpp @@ -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"); diff --git a/plugins/feature/vorlocalizer/vorlocalizer.cpp b/plugins/feature/vorlocalizer/vorlocalizer.cpp index ab9512ae1..6e5d7d010 100644 --- a/plugins/feature/vorlocalizer/vorlocalizer.cpp +++ b/plugins/feature/vorlocalizer/vorlocalizer.cpp @@ -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); } diff --git a/plugins/samplemimo/audiocatsiso/audiocatsiso.cpp b/plugins/samplemimo/audiocatsiso/audiocatsiso.cpp index a2c974694..c50e0eea4 100644 --- a/plugins/samplemimo/audiocatsiso/audiocatsiso.cpp +++ b/plugins/samplemimo/audiocatsiso/audiocatsiso.cpp @@ -969,7 +969,7 @@ void AudioCATSISO::webapiReverseSendSettings(const QList& 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")) { diff --git a/plugins/samplemimo/plutosdrmimo/plutosdrmimo.cpp b/plugins/samplemimo/plutosdrmimo/plutosdrmimo.cpp index de69e98a7..25b6eb1d5 100644 --- a/plugins/samplemimo/plutosdrmimo/plutosdrmimo.cpp +++ b/plugins/samplemimo/plutosdrmimo/plutosdrmimo.cpp @@ -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) { diff --git a/plugins/samplesource/fileinput/fileinputsettings.cpp b/plugins/samplesource/fileinput/fileinputsettings.cpp index 11cd03578..e36aa55ff 100644 --- a/plugins/samplesource/fileinput/fileinputsettings.cpp +++ b/plugins/samplesource/fileinput/fileinputsettings.cpp @@ -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) { diff --git a/plugins/samplesource/sdrplayv3/sdrplayv3settings.cpp b/plugins/samplesource/sdrplayv3/sdrplayv3settings.cpp index 3e8a8e4e2..ce62a11e2 100644 --- a/plugins/samplesource/sdrplayv3/sdrplayv3settings.cpp +++ b/plugins/samplesource/sdrplayv3/sdrplayv3settings.cpp @@ -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")) { diff --git a/plugins/samplesource/sigmffileinput/sigmffileinputsettings.cpp b/plugins/samplesource/sigmffileinput/sigmffileinputsettings.cpp index c7b703551..80d529df1 100644 --- a/plugins/samplesource/sigmffileinput/sigmffileinputsettings.cpp +++ b/plugins/samplesource/sigmffileinput/sigmffileinputsettings.cpp @@ -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) { diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 0aaafa68f..244e63bee 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -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 diff --git a/sdrbase/dsp/fftfilt.cpp b/sdrbase/dsp/fftfilt.cpp index 5293ee0f5..aa8123c76 100644 --- a/sdrbase/dsp/fftfilt.cpp +++ b/sdrbase/dsp/fftfilt.cpp @@ -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(); } - diff --git a/sdrbase/dsp/fftfilt.h b/sdrbase/dsp/fftfilt.h index 5cd062398..09a0901b5 100644 --- a/sdrbase/dsp/fftfilt.h +++ b/sdrbase/dsp/fftfilt.h @@ -52,7 +52,6 @@ public: void create_filter(const std::vector>& 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); diff --git a/sdrbase/dsp/fftfilterrrc.cpp b/sdrbase/dsp/fftfilterrrc.cpp new file mode 100644 index 000000000..228d3ddff --- /dev/null +++ b/sdrbase/dsp/fftfilterrrc.cpp @@ -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 // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dsp/fftfilterrrc.h" + +#include +#include +#include + +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(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(binIndex) / static_cast(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; +} diff --git a/sdrbase/dsp/fftfilterrrc.h b/sdrbase/dsp/fftfilterrrc.h new file mode 100644 index 000000000..d186b2926 --- /dev/null +++ b/sdrbase/dsp/fftfilterrrc.h @@ -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 // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DSP_FFTFILTERRRC_H +#define INCLUDE_DSP_FFTFILTERRRC_H + +#include +#include + +#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; + + /** + * @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* 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 diff --git a/sdrbase/dsp/firfilterrrc.cpp b/sdrbase/dsp/firfilterrrc.cpp new file mode 100644 index 000000000..784f387a5 --- /dev/null +++ b/sdrbase/dsp/firfilterrrc.cpp @@ -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 // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dsp/firfilterrrc.h" + +#include +#include + +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(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(i - center) / static_cast(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(m_samples.size()); + const int halfTaps = static_cast(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(numTaps) - 1) ? 0 : m_ptr + 1; + + return acc; +} diff --git a/sdrbase/dsp/firfilterrrc.h b/sdrbase/dsp/firfilterrrc.h new file mode 100644 index 000000000..5f18a0440 --- /dev/null +++ b/sdrbase/dsp/firfilterrrc.h @@ -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 // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DSP_FIRFILTERRRC_H +#define INCLUDE_DSP_FIRFILTERRRC_H + +#include +#include + +#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; + + /** + * @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(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& 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 m_taps; //!< Filter tap coefficients (symmetric, stored as half + center) + std::vector 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 diff --git a/sdrbench/CMakeLists.txt b/sdrbench/CMakeLists.txt index 9eee7766c..35d3ae141 100644 --- a/sdrbench/CMakeLists.txt +++ b/sdrbench/CMakeLists.txt @@ -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 diff --git a/sdrbench/mainbench.cpp b/sdrbench/mainbench.cpp index 8544ec083..e2cf5fbb1 100644 --- a/sdrbench/mainbench.cpp +++ b/sdrbench/mainbench.cpp @@ -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(); } diff --git a/sdrbench/mainbench.h b/sdrbench/mainbench.h index c2417d9d8..5f91c88f8 100644 --- a/sdrbench/mainbench.h +++ b/sdrbench/mainbench.h @@ -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); diff --git a/sdrbench/parserbench.cpp b/sdrbench/parserbench.cpp index f38d1ebe2..1942d40d9 100644 --- a/sdrbench/parserbench.cpp +++ b/sdrbench/parserbench.cpp @@ -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; } diff --git a/sdrbench/parserbench.h b/sdrbench/parserbench.h index 05476ce61..dc0e092f2 100644 --- a/sdrbench/parserbench.h +++ b/sdrbench/parserbench.h @@ -40,7 +40,9 @@ public: TestGolay2312, TestFT8, TestCallsign, - TestFT8Protocols + TestFT8Protocols, + TestFFTRRCFilter, + TestFIRRRCFilter } TestType; ParserBench(); diff --git a/sdrbench/test_fftrrc.cpp b/sdrbench/test_fftrrc.cpp new file mode 100644 index 000000000..efaa606b3 --- /dev/null +++ b/sdrbench/test_fftrrc.cpp @@ -0,0 +1,77 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2026 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "mainbench.h" +#include "dsp/fftfilterrrc.h" +#include + +void MainBench::testFFTRRCFilter() +{ + qDebug() << "MainBench::testFFTRRCFilter"; + const int RRC_FFT_SIZE = 512; + std::complex* 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); +} diff --git a/sdrbench/test_firrrc.cpp b/sdrbench/test_firrrc.cpp new file mode 100644 index 000000000..5c28fe351 --- /dev/null +++ b/sdrbench/test_firrrc.cpp @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2026 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "mainbench.h" +#include "dsp/firfilterrrc.h" +#include + +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& 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); +} diff --git a/sdrbench/test_rrc.py b/sdrbench/test_rrc.py new file mode 100755 index 000000000..3d8fa9d42 --- /dev/null +++ b/sdrbench/test_rrc.py @@ -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