mirror of
https://github.com/f4exb/sdrangel.git
synced 2026-03-30 19:55:52 -04:00
Merge pull request #2645 from f4exb/fix-fftrrc-2
Rework FFT RRC filter and add a FIR RRC filter
This commit is contained in:
commit
3ecf974f88
@ -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(
|
||||
|
||||
@ -30,12 +30,14 @@
|
||||
#include "gui/basicchannelsettingsdialog.h"
|
||||
#include "gui/dialpopup.h"
|
||||
#include "gui/dialogpositioner.h"
|
||||
#include "gui/crightclickenabler.h"
|
||||
#include "plugin/pluginapi.h"
|
||||
#include "util/db.h"
|
||||
#include "maincore.h"
|
||||
|
||||
#include "ui_chanalyzergui.h"
|
||||
#include "chanalyzer.h"
|
||||
#include "rrcfilterdialog.h"
|
||||
#include "chanalyzergui.h"
|
||||
|
||||
ChannelAnalyzerGUI* ChannelAnalyzerGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
|
||||
@ -559,6 +561,9 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device
|
||||
m_channelAnalyzer->setScopeVis(m_scopeVis);
|
||||
m_channelAnalyzer->setMessageQueueToGUI(getInputMessageQueue());
|
||||
|
||||
m_rrcRightClickEnabler = new CRightClickEnabler(ui->rrcFilter);
|
||||
connect(m_rrcRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(rrcSetupDialog(const QPoint &)));
|
||||
|
||||
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
|
||||
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
|
||||
ui->deltaFrequency->setValueRange(false, 8, -99999999, 99999999);
|
||||
@ -735,6 +740,47 @@ void ChannelAnalyzerGUI::enterEvent(EnterEventType* event)
|
||||
ChannelGUI::enterEvent(event);
|
||||
}
|
||||
|
||||
void ChannelAnalyzerGUI::rrcSetupDialog(const QPoint& p)
|
||||
{
|
||||
m_rrcFilterDialog = new RRCFilterDialog();
|
||||
m_rrcFilterDialog->move(p);
|
||||
m_rrcFilterDialog->setRRCType(m_settings.m_rrcType);
|
||||
m_rrcFilterDialog->setRRCSymbolSpan(m_settings.m_rrcSymbolSpan);
|
||||
m_rrcFilterDialog->setRRCNormalization(m_settings.m_rrcNormalization);
|
||||
m_rrcFilterDialog->setRRCFFTLog2Size(m_settings.m_rrcFFTLog2Size);
|
||||
QObject::connect(m_rrcFilterDialog, &RRCFilterDialog::valueChanged, this, &ChannelAnalyzerGUI::rrcSetup);
|
||||
m_rrcFilterDialog->exec();
|
||||
m_rrcFilterDialog->deleteLater();
|
||||
m_rrcFilterDialog = nullptr;
|
||||
}
|
||||
|
||||
void ChannelAnalyzerGUI::rrcSetup(int iValueChanged)
|
||||
{
|
||||
auto valueChanged = static_cast<RRCFilterDialog::ValueChanged>(iValueChanged);
|
||||
|
||||
switch(valueChanged)
|
||||
{
|
||||
case RRCFilterDialog::ValueChanged::ChangedRRCType:
|
||||
m_settings.m_rrcType = m_rrcFilterDialog->getRRCType();
|
||||
applySettings(QStringList({"rrcType"}));
|
||||
break;
|
||||
case RRCFilterDialog::ValueChanged::ChangedRRCSymbolSpan:
|
||||
m_settings.m_rrcSymbolSpan = m_rrcFilterDialog->getRRCSymbolSpan();
|
||||
applySettings(QStringList({"rrcSymbolSpan"}));
|
||||
break;
|
||||
case RRCFilterDialog::ValueChanged::ChangedRRCNormalization:
|
||||
m_settings.m_rrcNormalization = m_rrcFilterDialog->getRRCNormalization();
|
||||
applySettings(QStringList({"rrcNormalization"}));
|
||||
break;
|
||||
case RRCFilterDialog::ValueChanged::ChangedRRCFFTLog2Size:
|
||||
m_settings.m_rrcFFTLog2Size = m_rrcFilterDialog->getRRCFFTLog2Size();
|
||||
applySettings(QStringList({"rrcFFTLog2Size"}));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelAnalyzerGUI::makeUIConnections()
|
||||
{
|
||||
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &ChannelAnalyzerGUI::on_deltaFrequency_changed);
|
||||
|
||||
@ -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();
|
||||
};
|
||||
|
||||
@ -418,7 +418,7 @@
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="rrcFilter">
|
||||
<property name="toolTip">
|
||||
<string>Toggle RRC filter</string>
|
||||
<string>Toggle RRC filter - Right click for options</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -28,6 +28,52 @@
|
||||
const unsigned int ChannelAnalyzerSink::m_ssbFftLen = 1024;
|
||||
const unsigned int ChannelAnalyzerSink::m_corrFFTLen = 4*m_ssbFftLen;
|
||||
|
||||
ChannelAnalyzerSink::RRCHelper::RRCHelper(int flen) :
|
||||
m_useFFT(false),
|
||||
m_filterFIR(new FIRFilterRRC()),
|
||||
m_filterFFT(new FFTFilterRRC(m_ssbFftLen)),
|
||||
m_buffer(new std::complex<float>[flen/2]),
|
||||
m_bufferPos(0),
|
||||
flen2(flen/2)
|
||||
{
|
||||
}
|
||||
|
||||
ChannelAnalyzerSink::RRCHelper::~RRCHelper()
|
||||
{
|
||||
delete m_filterFIR;
|
||||
delete[] m_buffer;
|
||||
}
|
||||
|
||||
void ChannelAnalyzerSink::RRCHelper::setUseFFT(bool useFFT)
|
||||
{
|
||||
m_useFFT = useFFT;
|
||||
m_bufferPos = 0;
|
||||
}
|
||||
|
||||
void ChannelAnalyzerSink::RRCHelper::create(float symbolRate, float rolloff, unsigned int samplesPerSymbol, FIRFilterRRC::Normalization normalization)
|
||||
{
|
||||
m_filterFIR->create(symbolRate, rolloff, samplesPerSymbol, normalization);
|
||||
m_filterFFT->create(symbolRate, rolloff);
|
||||
}
|
||||
|
||||
int ChannelAnalyzerSink::RRCHelper::runFilt(const std::complex<float> & in, std::complex<float> **out)
|
||||
{
|
||||
if (m_useFFT) {
|
||||
return m_filterFFT->process(in, out);
|
||||
} else {
|
||||
m_buffer[m_bufferPos++] = m_filterFIR->filter(in);
|
||||
}
|
||||
|
||||
if (m_bufferPos < flen2) {
|
||||
*out = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
m_bufferPos = 0;
|
||||
*out = m_buffer;
|
||||
return flen2;
|
||||
}
|
||||
|
||||
ChannelAnalyzerSink::ChannelAnalyzerSink() :
|
||||
m_channelSampleRate(48000),
|
||||
m_channelFrequencyOffset(0),
|
||||
@ -39,7 +85,8 @@ ChannelAnalyzerSink::ChannelAnalyzerSink() :
|
||||
m_magsq = 0;
|
||||
SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_channelSampleRate, m_settings.m_bandwidth / m_channelSampleRate, m_ssbFftLen);
|
||||
DSBFilter = new fftfilt(m_settings.m_bandwidth / m_channelSampleRate, 2*m_ssbFftLen);
|
||||
RRCFilter = new fftfilt(m_settings.m_bandwidth / m_channelSampleRate, 2*m_ssbFftLen);
|
||||
m_rrcHelper = new RRCHelper(2*m_ssbFftLen);
|
||||
// m_rrcHelper->setUseFFT(true);
|
||||
m_corr = new fftcorr(2*m_corrFFTLen); // 8k for 4k effective samples
|
||||
m_pll.computeCoefficients(m_settings.m_pllBandwidth, m_settings.m_pllDampingFactor, m_settings.m_pllLoopGain);
|
||||
m_costasLoop.computeCoefficients(m_settings.m_pllBandwidth);
|
||||
@ -52,7 +99,7 @@ ChannelAnalyzerSink::~ChannelAnalyzerSink()
|
||||
{
|
||||
delete SSBFilter;
|
||||
delete DSBFilter;
|
||||
delete RRCFilter;
|
||||
delete m_rrcHelper;
|
||||
delete m_corr;
|
||||
}
|
||||
|
||||
@ -126,7 +173,7 @@ void ChannelAnalyzerSink::processOneSample(Complex& c, fftfilt::cmplx *sideband)
|
||||
else
|
||||
{
|
||||
if (m_settings.m_rrc) {
|
||||
n_out = RRCFilter->runFilt(c, &sideband);
|
||||
n_out = m_rrcHelper->runFilt(c, &sideband);
|
||||
} else {
|
||||
n_out = DSBFilter->runDSB(c, &sideband);
|
||||
}
|
||||
@ -240,7 +287,7 @@ void ChannelAnalyzerSink::setFilters(int sampleRate, float bandwidth, float lowC
|
||||
|
||||
SSBFilter->create_filter(lowCutoff / sampleRate, bandwidth / sampleRate);
|
||||
DSBFilter->create_dsb_filter(bandwidth / sampleRate);
|
||||
RRCFilter->create_rrc_filter(bandwidth / sampleRate, m_settings.m_rrcRolloff / 100.0);
|
||||
m_rrcHelper->create(bandwidth / sampleRate, m_settings.m_rrcRolloff / 100.0, m_settings.m_rrcSymbolSpan, (FIRFilterRRC::Normalization) m_settings.m_rrcNormalization);
|
||||
}
|
||||
|
||||
void ChannelAnalyzerSink::applySettings(const ChannelAnalyzerSettings& settings, const QStringList& settingsKeys, bool force)
|
||||
@ -314,6 +361,29 @@ void ChannelAnalyzerSink::applySettings(const ChannelAnalyzerSettings& settings,
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.m_rrcType != m_settings.m_rrcType || force)
|
||||
{
|
||||
m_rrcHelper->setUseFFT(settings.m_rrcType == ChannelAnalyzerSettings::RRCFFT);
|
||||
}
|
||||
|
||||
if (settings.m_rrcSymbolSpan != m_settings.m_rrcSymbolSpan || force)
|
||||
{
|
||||
m_rrcHelper->create(m_settings.m_bandwidth / (float) m_sinkSampleRate, m_settings.m_rrcRolloff / 100.0, settings.m_rrcSymbolSpan, (FIRFilterRRC::Normalization) m_settings.m_rrcNormalization);
|
||||
}
|
||||
|
||||
if (settings.m_rrcNormalization != m_settings.m_rrcNormalization || force)
|
||||
{
|
||||
m_rrcHelper->create(m_settings.m_bandwidth / (float) m_sinkSampleRate, m_settings.m_rrcRolloff / 100.0, m_settings.m_rrcSymbolSpan, (FIRFilterRRC::Normalization) settings.m_rrcNormalization);
|
||||
}
|
||||
|
||||
if (settings.m_rrcFFTLog2Size != m_settings.m_rrcFFTLog2Size || force)
|
||||
{
|
||||
delete m_rrcHelper;
|
||||
m_rrcHelper = new RRCHelper(1 << settings.m_rrcFFTLog2Size);
|
||||
m_rrcHelper->create(settings.m_bandwidth / (float) m_sinkSampleRate, settings.m_rrcRolloff / 100.0, settings.m_rrcSymbolSpan, (FIRFilterRRC::Normalization) settings.m_rrcNormalization);
|
||||
m_rrcHelper->setUseFFT(settings.m_rrcType == ChannelAnalyzerSettings::RRCFFT);
|
||||
}
|
||||
|
||||
if (force) {
|
||||
m_settings = settings;
|
||||
} else {
|
||||
@ -383,5 +453,5 @@ void ChannelAnalyzerSink::applySampleRate()
|
||||
m_pll.setSampleRate(sampleRate);
|
||||
m_fll.setSampleRate(sampleRate);
|
||||
m_costasLoop.setSampleRate(sampleRate);
|
||||
RRCFilter->create_rrc_filter(m_settings.m_bandwidth / (float) sampleRate, m_settings.m_rrcRolloff / 100.0);
|
||||
m_rrcHelper->create(m_settings.m_bandwidth / (float) sampleRate, m_settings.m_rrcRolloff / 100.0, m_settings.m_rrcSymbolSpan, (FIRFilterRRC::Normalization) m_settings.m_rrcNormalization);
|
||||
}
|
||||
|
||||
@ -25,6 +25,8 @@
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/fftcorr.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/firfilterrrc.h"
|
||||
#include "dsp/fftfilterrrc.h"
|
||||
#include "dsp/phaselockcomplex.h"
|
||||
#include "dsp/freqlockcomplex.h"
|
||||
#include "dsp/costasloop.h"
|
||||
@ -57,6 +59,22 @@ public:
|
||||
static const unsigned int m_ssbFftLen;
|
||||
|
||||
private:
|
||||
class RRCHelper {
|
||||
public:
|
||||
RRCHelper(int flen);
|
||||
~RRCHelper();
|
||||
void setUseFFT(bool useFFT);
|
||||
void create(float symbolRate, float rolloff, unsigned int samplesPerSymbol, FIRFilterRRC::Normalization normalization);
|
||||
int runFilt(const std::complex<float> & in, std::complex<float> **out);
|
||||
private:
|
||||
bool m_useFFT;
|
||||
FIRFilterRRC* m_filterFIR;
|
||||
FFTFilterRRC* m_filterFFT;
|
||||
std::complex<float>* m_buffer;
|
||||
int m_bufferPos;
|
||||
int flen2;
|
||||
};
|
||||
|
||||
int m_channelSampleRate;
|
||||
int m_channelFrequencyOffset;
|
||||
int m_sinkSampleRate;
|
||||
@ -76,7 +94,7 @@ private:
|
||||
|
||||
fftfilt* SSBFilter;
|
||||
fftfilt* DSBFilter;
|
||||
fftfilt* RRCFilter;
|
||||
RRCHelper* m_rrcHelper;
|
||||
fftcorr* m_corr;
|
||||
|
||||
SampleVector m_sampleBuffer;
|
||||
|
||||
@ -122,7 +122,14 @@ Average total power in dB relative to a +/- 1.0 amplitude signal received in the
|
||||
|
||||
<h3>10. Toggle root raised cosine filter</h3>
|
||||
|
||||
Use this toggle button to activate or de-activate the root raised cosine (RRC) filter. When active the bandpass boxcar filter is replaced by a RRC filter. This takes effect only in normal (DSB) mode (see control 14).
|
||||
Use this toggle button to activate or de-activate the root raised cosine (RRC) filter. When active the bandpass boxcar filter is replaced by a RRC filter. This takes effect only in normal (DSB) mode (see control 14). Note that the bandwidth of the filter should be equal to the symbol rate regardless of the actual bandwidth.
|
||||
|
||||
Right clicking on the button gives access to the RRC filter options:
|
||||
|
||||
- Choice between FFT and FIR filter
|
||||
- Size of the FFT filter
|
||||
- Symbol span for the FIR filter
|
||||
- Normalization type for the FIR filter
|
||||
|
||||
<h3>11. Tune RRC filter rolloff factor</h3>
|
||||
|
||||
|
||||
149
plugins/channelrx/chanalyzer/rrcfilterdialog.cpp
Normal file
149
plugins/channelrx/chanalyzer/rrcfilterdialog.cpp
Normal file
@ -0,0 +1,149 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2026 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "rrcfilterdialog.h"
|
||||
#include "ui_rrcfilterdialog.h"
|
||||
|
||||
RRCFilterDialog::RRCFilterDialog(QWidget* parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::RRCFilterDialog),
|
||||
m_rrcType(ChannelAnalyzerSettings::RRCFIR),
|
||||
m_rrcSymbolSpan(8),
|
||||
m_rrcNormalization(ChannelAnalyzerSettings::RRCNormGain),
|
||||
m_rrcFFTLog2Size(9)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
makeUIConnections();
|
||||
}
|
||||
|
||||
RRCFilterDialog::~RRCFilterDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void RRCFilterDialog::setRRCType(ChannelAnalyzerSettings::RRCType rrcType)
|
||||
{
|
||||
ui->firGroup->blockSignals(true);
|
||||
ui->fftGroup->blockSignals(true);
|
||||
|
||||
ui->firGroup->setChecked(false);
|
||||
ui->fftGroup->setChecked(false);
|
||||
|
||||
switch(rrcType)
|
||||
{
|
||||
case ChannelAnalyzerSettings::RRCFIR:
|
||||
ui->firGroup->setChecked(true);
|
||||
break;
|
||||
case ChannelAnalyzerSettings::RRCFFT:
|
||||
ui->fftGroup->setChecked(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ui->firGroup->blockSignals(false);
|
||||
ui->fftGroup->blockSignals(false);
|
||||
|
||||
m_rrcType = rrcType;
|
||||
}
|
||||
|
||||
void RRCFilterDialog::setRRCSymbolSpan(int symbolSpan)
|
||||
{
|
||||
m_rrcSymbolSpan = symbolSpan;
|
||||
ui->symbolSpan->setValue(symbolSpan);
|
||||
}
|
||||
|
||||
void RRCFilterDialog::setRRCNormalization(ChannelAnalyzerSettings::RRCNormalization normalization)
|
||||
{
|
||||
m_rrcNormalization = normalization;
|
||||
ui->normalization->setCurrentIndex((int) normalization);
|
||||
}
|
||||
|
||||
void RRCFilterDialog::setRRCFFTLog2Size(int log2Size)
|
||||
{
|
||||
m_rrcFFTLog2Size = log2Size;
|
||||
ui->fftSize->setCurrentIndex(log2Size - 7);
|
||||
}
|
||||
|
||||
void RRCFilterDialog::on_symbolSpan_valueChanged(int value)
|
||||
{
|
||||
m_rrcSymbolSpan = value;
|
||||
emit valueChanged(ChangedRRCSymbolSpan);
|
||||
}
|
||||
|
||||
void RRCFilterDialog::on_normalization_currentIndexChanged(int index)
|
||||
{
|
||||
m_rrcNormalization = (ChannelAnalyzerSettings::RRCNormalization) index;
|
||||
emit valueChanged(ChangedRRCNormalization);
|
||||
}
|
||||
|
||||
void RRCFilterDialog::on_fftLog2Size_currentIndexChanged(int index)
|
||||
{
|
||||
m_rrcFFTLog2Size = index + 7;
|
||||
emit valueChanged(ChangedRRCFFTLog2Size);
|
||||
}
|
||||
|
||||
void RRCFilterDialog::on_firGroup_clicked(bool checked)
|
||||
{
|
||||
qDebug("RRCFilterDialog::on_firGroup_clicked: checked: %d", checked);
|
||||
|
||||
if (checked)
|
||||
{
|
||||
ui->fftGroup->blockSignals(true);
|
||||
ui->fftGroup->setChecked(false);
|
||||
ui->fftGroup->blockSignals(false);
|
||||
|
||||
m_rrcType = ChannelAnalyzerSettings::RRCFIR;
|
||||
emit valueChanged(ChangedRRCType);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->firGroup->blockSignals(true);
|
||||
ui->firGroup->setChecked(true);
|
||||
ui->firGroup->blockSignals(false);
|
||||
}
|
||||
}
|
||||
|
||||
void RRCFilterDialog::on_fftGroup_clicked(bool checked)
|
||||
{
|
||||
qDebug("RRCFilterDialog::on_fftGroup_clicked: checked: %d", checked);
|
||||
|
||||
if (checked)
|
||||
{
|
||||
ui->firGroup->blockSignals(true);
|
||||
ui->firGroup->setChecked(false);
|
||||
ui->firGroup->blockSignals(false);
|
||||
|
||||
m_rrcType = ChannelAnalyzerSettings::RRCFFT;
|
||||
emit valueChanged(ChangedRRCType);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->fftGroup->blockSignals(true);
|
||||
ui->fftGroup->setChecked(true);
|
||||
ui->fftGroup->blockSignals(false);
|
||||
}
|
||||
}
|
||||
|
||||
void RRCFilterDialog::makeUIConnections()
|
||||
{
|
||||
connect(ui->firGroup, &QGroupBox::clicked, this, &RRCFilterDialog::on_firGroup_clicked);
|
||||
connect(ui->fftGroup, &QGroupBox::clicked, this, &RRCFilterDialog::on_fftGroup_clicked);
|
||||
connect(ui->symbolSpan, QOverload<int>::of(&QSpinBox::valueChanged), this, &RRCFilterDialog::on_symbolSpan_valueChanged);
|
||||
connect(ui->normalization, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RRCFilterDialog::on_normalization_currentIndexChanged);
|
||||
connect(ui->fftSize, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RRCFilterDialog::on_fftLog2Size_currentIndexChanged);
|
||||
}
|
||||
69
plugins/channelrx/chanalyzer/rrcfilterdialog.h
Normal file
69
plugins/channelrx/chanalyzer/rrcfilterdialog.h
Normal file
@ -0,0 +1,69 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2026 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
#ifndef PLUGINS_CHANNELRX_CHANALYZER_RRCFILTERDIALOG_H_
|
||||
#define PLUGINS_CHANNELRX_CHANALYZER_RRCFILTERDIALOG_H_
|
||||
|
||||
#include <QDialog>
|
||||
#include "chanalyzersettings.h"
|
||||
|
||||
|
||||
namespace Ui {
|
||||
class RRCFilterDialog;
|
||||
}
|
||||
|
||||
class RRCFilterDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ValueChanged {
|
||||
ChangedRRCType,
|
||||
ChangedRRCSymbolSpan,
|
||||
ChangedRRCNormalization,
|
||||
ChangedRRCFFTLog2Size,
|
||||
};
|
||||
|
||||
explicit RRCFilterDialog(QWidget* parent = nullptr);
|
||||
~RRCFilterDialog() override;
|
||||
|
||||
void setRRCType(ChannelAnalyzerSettings::RRCType rrcType);
|
||||
void setRRCSymbolSpan(int symbolSpan);
|
||||
void setRRCNormalization(ChannelAnalyzerSettings::RRCNormalization normalization);
|
||||
void setRRCFFTLog2Size(int log2Size);
|
||||
|
||||
ChannelAnalyzerSettings::RRCType getRRCType() const { return m_rrcType; }
|
||||
int getRRCSymbolSpan() const { return m_rrcSymbolSpan; }
|
||||
ChannelAnalyzerSettings::RRCNormalization getRRCNormalization() const { return m_rrcNormalization; }
|
||||
int getRRCFFTLog2Size() const { return m_rrcFFTLog2Size; }
|
||||
|
||||
signals:
|
||||
void valueChanged(int valueChanged);
|
||||
|
||||
private:
|
||||
Ui::RRCFilterDialog* ui;
|
||||
ChannelAnalyzerSettings::RRCType m_rrcType;
|
||||
int m_rrcSymbolSpan;
|
||||
ChannelAnalyzerSettings::RRCNormalization m_rrcNormalization;
|
||||
int m_rrcFFTLog2Size;
|
||||
|
||||
void on_firGroup_clicked(bool checked);
|
||||
void on_fftGroup_clicked(bool checked);
|
||||
void on_symbolSpan_valueChanged(int value);
|
||||
void on_normalization_currentIndexChanged(int index);
|
||||
void on_fftLog2Size_currentIndexChanged(int index);
|
||||
void makeUIConnections();
|
||||
};
|
||||
|
||||
#endif /* PLUGINS_CHANNELRX_CHANALYZER_RRCFILTERDIALOG_H_ */
|
||||
258
plugins/channelrx/chanalyzer/rrcfilterdialog.ui
Normal file
258
plugins/channelrx/chanalyzer/rrcfilterdialog.ui
Normal file
@ -0,0 +1,258 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>RRCFilterDialog</class>
|
||||
<widget class="QDialog" name="RRCFilterDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>407</width>
|
||||
<height>197</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>RRC Filter Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="firGroup">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>370</width>
|
||||
<height>70</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Use a FIR RRC filter</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QGroupBox {
|
||||
border: 1px solid gray;
|
||||
} </string>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>FIR</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QLabel" name="symbolSpanLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>30</y>
|
||||
<width>91</width>
|
||||
<height>26</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Symbol Span</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QSpinBox" name="symbolSpan">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>100</x>
|
||||
<y>30</y>
|
||||
<width>70</width>
|
||||
<height>29</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Symbol span</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>8</number>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="normalizationLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>180</x>
|
||||
<y>30</y>
|
||||
<width>101</width>
|
||||
<height>26</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Normalization</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="normalization">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>280</x>
|
||||
<y>30</y>
|
||||
<width>101</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>RRC normalization type</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Energy</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Amplitude</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Gain</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="fftGroup">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>370</width>
|
||||
<height>70</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Use a FFT RRC filter</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QGroupBox {
|
||||
border: 1px solid gray;
|
||||
} </string>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>FFT</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QLabel" name="fftSizeLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>30</y>
|
||||
<width>81</width>
|
||||
<height>26</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>FFT Size</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="fftSize">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>80</x>
|
||||
<y>30</y>
|
||||
<width>94</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Size of FFT filter</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>128</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>256</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>512</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1024</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2048</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>4096</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>8192</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>RRCFilterDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>RRCFilterDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@ -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")) {
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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")) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -332,8 +332,6 @@ InmarsatDemodSink::InmarsatDemodSink(InmarsatDemod *stdCDemod) :
|
||||
{
|
||||
m_magsq = 0.0;
|
||||
|
||||
m_rrcFilter = new fftfilt(m_settings.m_rfBandwidth / (float) m_channelSampleRate, RRC_FILTER_SIZE);
|
||||
|
||||
m_rrcI.create(m_settings.m_rrcRolloff, 5, SAMPLES_PER_SYMBOL, RootRaisedCosine<Real>::Gain);
|
||||
m_rrcQ.create(m_settings.m_rrcRolloff, 5, SAMPLES_PER_SYMBOL, RootRaisedCosine<Real>::Gain);
|
||||
|
||||
@ -358,8 +356,6 @@ InmarsatDemodSink::InmarsatDemodSink(InmarsatDemod *stdCDemod) :
|
||||
|
||||
InmarsatDemodSink::~InmarsatDemodSink()
|
||||
{
|
||||
delete m_rrcFilter;
|
||||
m_rrcFilter = nullptr;
|
||||
delete m_equalizer;
|
||||
m_equalizer = nullptr;
|
||||
}
|
||||
@ -473,10 +469,8 @@ void InmarsatDemodSink::processOneSample(Complex &ci)
|
||||
// Send signals to scope that are updated for each invocation of this method
|
||||
sampleToScopeA(cScaled, magsq, agcZ, m_agc.getGain(), m_agc.getAverage(), cCFO);
|
||||
|
||||
// RRC Matched filter
|
||||
fftfilt::cmplx *rrcFilterOut = nullptr;
|
||||
int n_out = m_rrcFilter->runFilt(cCFO, &rrcFilterOut);
|
||||
|
||||
// Bufferized RRC Matched filter
|
||||
int n_out;
|
||||
m_rrcBuffer[m_rrcBufferIndex++] = cCFO;
|
||||
|
||||
if (m_rrcBufferIndex == RRC_FILTER_SIZE/2)
|
||||
@ -491,7 +485,6 @@ void InmarsatDemodSink::processOneSample(Complex &ci)
|
||||
|
||||
for (int i = 0; i < n_out; i++)
|
||||
{
|
||||
//Complex rrc = rrcFilterOut[i];
|
||||
Complex rrc (m_rrcI.filter(m_rrcBuffer[i].real()), m_rrcQ.filter(m_rrcBuffer[i].imag()));
|
||||
|
||||
// Symbol synchronizer
|
||||
@ -704,10 +697,6 @@ void InmarsatDemodSink::applySettings(const InmarsatDemodSettings& settings, con
|
||||
m_costasLoop.setMinFreq(-freqMax);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("rfBandwidth") || settingsKeys.contains("rrcRolloff") || force) {
|
||||
m_rrcFilter->create_rrc_filter(settings.m_rfBandwidth / (float) m_channelSampleRate, settings.m_rrcRolloff);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("pllBandwidth") || force) {
|
||||
m_costasLoop.computeCoefficients(settings.m_pllBW);
|
||||
}
|
||||
|
||||
@ -25,7 +25,6 @@
|
||||
#include "dsp/channelsamplesink.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/fftengine.h"
|
||||
#include "dsp/fftwindow.h"
|
||||
#include "dsp/costasloop.h"
|
||||
@ -213,7 +212,6 @@ private:
|
||||
bool m_locked;
|
||||
|
||||
static const int RRC_FILTER_SIZE = 256;
|
||||
fftfilt *m_rrcFilter;
|
||||
Complex m_rrcBuffer[RRC_FILTER_SIZE];
|
||||
RootRaisedCosine<Real> m_rrcI; //!< Square root raised cosine filter for I samples
|
||||
RootRaisedCosine<Real> m_rrcQ; //!< Square root raised cosine filter for Q samples
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
#include <QDebug>
|
||||
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/firfilterrrc.h"
|
||||
#include "dsp/spectrumvis.h"
|
||||
#include "util/db.h"
|
||||
#include "util/messagequeue.h"
|
||||
@ -58,7 +58,8 @@ FreqTrackerSink::FreqTrackerSink() :
|
||||
m_sampleBuffer.resize(m_sampleBufferSize);
|
||||
m_sum = Complex{0.0, 0.0};
|
||||
|
||||
m_rrcFilter = new fftfilt(m_settings.m_rfBandwidth / m_sinkSampleRate, 2*1024);
|
||||
m_rrcFilter = new FIRFilterRRC();
|
||||
m_rrcFilter->create(m_settings.m_rfBandwidth / m_sinkSampleRate, m_settings.m_rrcRolloff / 100.0f, 8, FIRFilterRRC::Normalization::Energy);
|
||||
m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain
|
||||
applyChannelSettings(m_channelSampleRate, m_inputFrequencyOffset, true);
|
||||
}
|
||||
@ -103,10 +104,9 @@ void FreqTrackerSink::feed(const SampleVector::const_iterator& begin, const Samp
|
||||
}
|
||||
}
|
||||
|
||||
void FreqTrackerSink::processOneSample(Complex &ci)
|
||||
void FreqTrackerSink::processOneSample(const Complex &ci)
|
||||
{
|
||||
fftfilt::cmplx *sideband;
|
||||
int n_out;
|
||||
Complex sideband;
|
||||
int decim = 1<<m_settings.m_spanLog2;
|
||||
m_sum += ci;
|
||||
|
||||
@ -120,72 +120,64 @@ void FreqTrackerSink::processOneSample(Complex &ci)
|
||||
m_undersampleCount = 0;
|
||||
}
|
||||
|
||||
if (m_settings.m_rrc)
|
||||
{
|
||||
n_out = m_rrcFilter->runFilt(ci, &sideband);
|
||||
}
|
||||
else
|
||||
{
|
||||
n_out = 1;
|
||||
sideband = &ci;
|
||||
if (m_settings.m_rrc) {
|
||||
sideband = m_rrcFilter->filter(ci);
|
||||
} else {
|
||||
sideband = ci;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_out; i++)
|
||||
Real re = sideband.real() / SDR_RX_SCALEF;
|
||||
Real im = sideband.imag() / SDR_RX_SCALEF;
|
||||
Real magsq = re*re + im*im;
|
||||
m_movingAverage(magsq);
|
||||
m_magsq = m_movingAverage.asDouble();
|
||||
m_magsqSum += magsq;
|
||||
|
||||
if (magsq > m_magsqPeak)
|
||||
{
|
||||
Real re = sideband[i].real() / SDR_RX_SCALEF;
|
||||
Real im = sideband[i].imag() / SDR_RX_SCALEF;
|
||||
Real magsq = re*re + im*im;
|
||||
m_movingAverage(magsq);
|
||||
m_magsq = m_movingAverage.asDouble();
|
||||
m_magsqSum += magsq;
|
||||
m_magsqPeak = magsq;
|
||||
}
|
||||
|
||||
if (magsq > m_magsqPeak)
|
||||
m_magsqCount++;
|
||||
|
||||
if (m_magsq < m_squelchLevel)
|
||||
{
|
||||
if (m_squelchGate > 0)
|
||||
{
|
||||
m_magsqPeak = magsq;
|
||||
}
|
||||
|
||||
m_magsqCount++;
|
||||
|
||||
if (m_magsq < m_squelchLevel)
|
||||
{
|
||||
if (m_squelchGate > 0)
|
||||
{
|
||||
if (m_squelchCount > 0) {
|
||||
m_squelchCount--;
|
||||
}
|
||||
|
||||
m_squelchOpen = m_squelchCount >= m_squelchGate;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_squelchOpen = false;
|
||||
if (m_squelchCount > 0) {
|
||||
m_squelchCount--;
|
||||
}
|
||||
|
||||
m_squelchOpen = m_squelchCount >= m_squelchGate;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_squelchGate > 0)
|
||||
{
|
||||
if (m_squelchCount < 2*m_squelchGate) {
|
||||
m_squelchCount++;
|
||||
}
|
||||
|
||||
m_squelchOpen = m_squelchCount >= m_squelchGate;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_squelchOpen = true;
|
||||
}
|
||||
m_squelchOpen = false;
|
||||
}
|
||||
|
||||
if (m_squelchOpen)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_squelchGate > 0)
|
||||
{
|
||||
if (m_settings.m_trackerType == FreqTrackerSettings::TrackerFLL) {
|
||||
m_fll.feed(re, im);
|
||||
} else if (m_settings.m_trackerType == FreqTrackerSettings::TrackerPLL) {
|
||||
m_pll.feed(re, im);
|
||||
if (m_squelchCount < 2*m_squelchGate) {
|
||||
m_squelchCount++;
|
||||
}
|
||||
}
|
||||
|
||||
m_squelchOpen = m_squelchCount >= m_squelchGate;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_squelchOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_squelchOpen)
|
||||
{
|
||||
if (m_settings.m_trackerType == FreqTrackerSettings::TrackerFLL) {
|
||||
m_fll.feed(re, im);
|
||||
} else if (m_settings.m_trackerType == FreqTrackerSettings::TrackerPLL) {
|
||||
m_pll.feed(re, im);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_spectrumSink && (m_sampleBufferCount == m_sampleBufferSize))
|
||||
@ -249,15 +241,19 @@ void FreqTrackerSink::applyChannelSettings(int sinkSampleRate, int channelSample
|
||||
|
||||
void FreqTrackerSink::applySettings(const QStringList& settingsKeys, const FreqTrackerSettings& settings, bool force)
|
||||
{
|
||||
if (settingsKeys.empty()) {
|
||||
return; // nothing to apply
|
||||
}
|
||||
|
||||
if (!settings.m_tracking) {
|
||||
qDebug() << "FreqTrackerSink::applySettings:" << settings.getDebugString(settingsKeys, force);
|
||||
}
|
||||
|
||||
if ((settingsKeys.contains("m_squelch") && (m_settings.m_squelch != settings.m_squelch)) || force) {
|
||||
if ((settingsKeys.contains("squelch") && (m_settings.m_squelch != settings.m_squelch)) || force) {
|
||||
m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch);
|
||||
}
|
||||
|
||||
if ((settingsKeys.contains("m_tracking") && (m_settings.m_tracking != settings.m_tracking)) || force)
|
||||
if ((settingsKeys.contains("tracking") && (m_settings.m_tracking != settings.m_tracking)) || force)
|
||||
{
|
||||
m_avgDeltaFreq = 0.0;
|
||||
m_lastCorrAbs = 0;
|
||||
@ -269,7 +265,7 @@ void FreqTrackerSink::applySettings(const QStringList& settingsKeys, const FreqT
|
||||
}
|
||||
}
|
||||
|
||||
if ((settingsKeys.contains("m_trackerType") && (m_settings.m_trackerType != settings.m_trackerType)) || force)
|
||||
if ((settingsKeys.contains("trackerType") && (m_settings.m_trackerType != settings.m_trackerType)) || force)
|
||||
{
|
||||
m_lastCorrAbs = 0;
|
||||
m_avgDeltaFreq = 0.0;
|
||||
@ -287,7 +283,7 @@ void FreqTrackerSink::applySettings(const QStringList& settingsKeys, const FreqT
|
||||
}
|
||||
}
|
||||
|
||||
if ((settingsKeys.contains("m_pllPskOrder") && (m_settings.m_pllPskOrder != settings.m_pllPskOrder)) || force)
|
||||
if ((settingsKeys.contains("pllPskOrder") && (m_settings.m_pllPskOrder != settings.m_pllPskOrder)) || force)
|
||||
{
|
||||
if (settings.m_pllPskOrder < 32) {
|
||||
m_pll.setPskOrder(settings.m_pllPskOrder);
|
||||
@ -296,9 +292,9 @@ void FreqTrackerSink::applySettings(const QStringList& settingsKeys, const FreqT
|
||||
|
||||
bool useInterpolator = false;
|
||||
|
||||
if ((settingsKeys.contains("m_rrcRolloff") && (m_settings.m_rrcRolloff != settings.m_rrcRolloff))
|
||||
|| (settingsKeys.contains("m_rfBandwidth") && (m_settings.m_rfBandwidth != settings.m_rfBandwidth))
|
||||
|| (settingsKeys.contains("m_squelchGate") && (m_settings.m_squelchGate != settings.m_squelchGate)) || force) {
|
||||
if ((settingsKeys.contains("rrcRolloff") && (m_settings.m_rrcRolloff != settings.m_rrcRolloff))
|
||||
|| (settingsKeys.contains("rfBandwidth") && (m_settings.m_rfBandwidth != settings.m_rfBandwidth))
|
||||
|| (settingsKeys.contains("squelchGate") && (m_settings.m_squelchGate != settings.m_squelchGate)) || force) {
|
||||
useInterpolator = true;
|
||||
}
|
||||
|
||||
@ -329,7 +325,7 @@ void FreqTrackerSink::setInterpolator()
|
||||
m_interpolator.create(16, m_channelSampleRate, m_settings.m_rfBandwidth / 2.2f);
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) m_sinkSampleRate;
|
||||
m_rrcFilter->create_rrc_filter(m_settings.m_rfBandwidth / m_sinkSampleRate, m_settings.m_rrcRolloff / 100.0);
|
||||
m_rrcFilter->create(m_settings.m_rfBandwidth / m_sinkSampleRate, m_settings.m_rrcRolloff / 100.0f, 8, FIRFilterRRC::Normalization::Energy);
|
||||
m_squelchGate = (m_sinkSampleRate / 100) * m_settings.m_squelchGate; // gate is given in 10s of ms at channel sample rate
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -384,7 +384,7 @@ void LocalSink::applySettings(const LocalSinkSettings& settings, const QList<QSt
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -185,14 +185,14 @@ bool AMModBaseband::handleMessage(const Message& cmd)
|
||||
|
||||
void AMModBaseband::applySettings(const QStringList& settingsKeys, const AMModSettings& settings, bool force)
|
||||
{
|
||||
if ((settingsKeys.contains("m_inputFrequencyOffset") && (m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset)) || force)
|
||||
if ((settingsKeys.contains("inputFrequencyOffset") && (m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset)) || force)
|
||||
{
|
||||
m_channelizer->setChannelization(m_source.getAudioSampleRate(), settings.m_inputFrequencyOffset);
|
||||
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
m_source.applyAudioSampleRate(m_source.getAudioSampleRate()); // reapply in case of channel sample rate change
|
||||
}
|
||||
|
||||
if ((settingsKeys.contains("m_audioDeviceName") && (settings.m_audioDeviceName != m_settings.m_audioDeviceName)) || force)
|
||||
if ((settingsKeys.contains("audioDeviceName") && (settings.m_audioDeviceName != m_settings.m_audioDeviceName)) || force)
|
||||
{
|
||||
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
|
||||
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
|
||||
@ -207,7 +207,7 @@ void AMModBaseband::applySettings(const QStringList& settingsKeys, const AMModSe
|
||||
}
|
||||
}
|
||||
|
||||
if ((settingsKeys.contains("m_modAFInput") && (settings.m_modAFInput != m_settings.m_modAFInput)) || force)
|
||||
if ((settingsKeys.contains("modAFInput") && (settings.m_modAFInput != m_settings.m_modAFInput)) || force)
|
||||
{
|
||||
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
|
||||
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
|
||||
@ -219,7 +219,7 @@ void AMModBaseband::applySettings(const QStringList& settingsKeys, const AMModSe
|
||||
}
|
||||
}
|
||||
|
||||
if ((settingsKeys.contains("m_feedbackAudioDeviceName") && (settings.m_feedbackAudioDeviceName != m_settings.m_feedbackAudioDeviceName)) || force)
|
||||
if ((settingsKeys.contains("feedbackAudioDeviceName") && (settings.m_feedbackAudioDeviceName != m_settings.m_feedbackAudioDeviceName)) || force)
|
||||
{
|
||||
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
|
||||
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_feedbackAudioDeviceName);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -271,7 +271,7 @@ void AFC::applySettings(const AFCSettings& settings, const QList<QString>& setti
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -142,7 +142,7 @@ void AIS::applySettings(const AISSettings& settings, const QList<QString>& setti
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -220,7 +220,7 @@ void APRS::applySettings(const APRSSettings& settings, const QList<QString>& set
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -299,7 +299,7 @@ void Denoiser::applySettings(const DenoiserSettings& settings, const QList<QStri
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -301,7 +301,7 @@ void GS232Controller::applySettings(const GS232ControllerSettings& settings, con
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
@ -742,4 +742,3 @@ void GS232Controller::scanSerialPorts()
|
||||
m_serialPorts = serialPorts;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -180,7 +180,7 @@ void JogdialController::applySettings(const JogdialControllerSettings& settings,
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -151,7 +151,7 @@ void Map::applySettings(const MapSettings& settings, const QList<QString>& setti
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -202,7 +202,7 @@ void PERTester::applySettings(const PERTesterSettings& settings, const QList<QSt
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -145,7 +145,7 @@ void Radiosonde::applySettings(const RadiosondeSettings& settings, const QList<Q
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -179,7 +179,7 @@ void RigCtlServer::applySettings(const RigCtlServerSettings& settings, const QLi
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -222,7 +222,7 @@ void SatelliteTracker::applySettings(const SatelliteTrackerSettings& settings, c
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -172,7 +172,7 @@ void SIDMain::applySettings(const SIDSettings& settings, const QList<QString>& s
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -220,7 +220,7 @@ void SimplePTT::applySettings(const SimplePTTSettings& settings, const QList<QSt
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -122,7 +122,7 @@ void SkyMap::applySettings(const SkyMapSettings& settings, const QList<QString>&
|
||||
settingsKeys.contains("reverseAPIAddress") ||
|
||||
settingsKeys.contains("reverseAPIPort") ||
|
||||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
|
||||
settingsKeys.contains("m_reverseAPIFeatureIndex");
|
||||
settingsKeys.contains("reverseAPIFeatureIndex");
|
||||
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -969,7 +969,7 @@ void AudioCATSISO::webapiReverseSendSettings(const QList<QString>& deviceSetting
|
||||
if (deviceSettingsKeys.contains("catPTTMethodIndex")) {
|
||||
swgAudioCATSISOSettings->setCatPttMethodIndex(settings.m_catPTTMethodIndex);
|
||||
}
|
||||
if (deviceSettingsKeys.contains("m_catDTRHigh")) {
|
||||
if (deviceSettingsKeys.contains("catDTRHigh")) {
|
||||
swgAudioCATSISOSettings->setCatDtrHigh(settings.m_catDTRHigh ? 1 : 0);
|
||||
}
|
||||
if (deviceSettingsKeys.contains("catRTSHigh")) {
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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")) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -52,7 +52,6 @@ public:
|
||||
void create_filter(const std::vector<std::pair<float, float>>& limits, bool pass = true); //!< Windowless version
|
||||
void create_dsb_filter(float f2, FFTWindow::Function wf = FFTWindow::Blackman);
|
||||
void create_asym_filter(float fopp, float fin, FFTWindow::Function wf = FFTWindow::Blackman); //!< two different filters for in band and opposite band
|
||||
void create_rrc_filter(float fb, float a); //!< root raised cosine. fb is half the band pass
|
||||
|
||||
int noFilt(const cmplx& in, cmplx **out);
|
||||
int runFilt(const cmplx& in, cmplx **out);
|
||||
|
||||
187
sdrbase/dsp/fftfilterrrc.cpp
Normal file
187
sdrbase/dsp/fftfilterrrc.cpp
Normal file
@ -0,0 +1,187 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2026 SDRangel contributors //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dsp/fftfilterrrc.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
FFTFilterRRC::FFTFilterRRC(int len) :
|
||||
m_fftLen(len),
|
||||
m_fftLen2(len / 2),
|
||||
m_fft(nullptr),
|
||||
m_filter(nullptr),
|
||||
m_fftBuffer(nullptr),
|
||||
m_overlapBuffer(nullptr),
|
||||
m_outputBuffer(nullptr),
|
||||
m_inputIndex(0),
|
||||
m_symbolRate(0.0f),
|
||||
m_rolloff(0.0f)
|
||||
{
|
||||
initFilter();
|
||||
}
|
||||
|
||||
FFTFilterRRC::~FFTFilterRRC()
|
||||
{
|
||||
delete m_fft;
|
||||
delete[] m_filter;
|
||||
delete[] m_fftBuffer;
|
||||
delete[] m_overlapBuffer;
|
||||
delete[] m_outputBuffer;
|
||||
}
|
||||
|
||||
void FFTFilterRRC::initFilter()
|
||||
{
|
||||
m_fft = new g_fft<float>(m_fftLen);
|
||||
|
||||
m_filter = new Complex[m_fftLen];
|
||||
m_fftBuffer = new Complex[m_fftLen];
|
||||
m_overlapBuffer = new Complex[m_fftLen2];
|
||||
m_outputBuffer = new Complex[m_fftLen2];
|
||||
|
||||
// Initialize all buffers to zero
|
||||
std::fill(m_filter, m_filter + m_fftLen, Complex(0.0f, 0.0f));
|
||||
std::fill(m_fftBuffer, m_fftBuffer + m_fftLen, Complex(0.0f, 0.0f));
|
||||
std::fill(m_overlapBuffer, m_overlapBuffer + m_fftLen2, Complex(0.0f, 0.0f));
|
||||
std::fill(m_outputBuffer, m_outputBuffer + m_fftLen2, Complex(0.0f, 0.0f));
|
||||
|
||||
m_inputIndex = 0;
|
||||
}
|
||||
|
||||
void FFTFilterRRC::create(float symbolRate, float rolloff)
|
||||
{
|
||||
m_symbolRate = symbolRate;
|
||||
m_rolloff = rolloff;
|
||||
|
||||
// Clamp rolloff to valid range [0, 1]
|
||||
if (m_rolloff < 0.0f) {
|
||||
m_rolloff = 0.0f;
|
||||
} else if (m_rolloff > 1.0f) {
|
||||
m_rolloff = 1.0f;
|
||||
}
|
||||
|
||||
// Create filter directly in frequency domain
|
||||
std::fill(m_filter, m_filter + m_fftLen, Complex(0.0f, 0.0f));
|
||||
|
||||
for (int i = 0; i < m_fftLen; i++) {
|
||||
m_filter[i] = computeRRCResponse(m_symbolRate, m_rolloff, i, m_fftLen);
|
||||
}
|
||||
|
||||
// Normalize for unity gain in passband
|
||||
float maxMag = 0.0f;
|
||||
for (int i = 0; i < m_fftLen; i++) {
|
||||
float mag = std::abs(m_filter[i]);
|
||||
if (mag > maxMag) {
|
||||
maxMag = mag;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxMag > 0.0f) {
|
||||
for (int i = 0; i < m_fftLen; i++) {
|
||||
m_filter[i] /= maxMag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FFTFilterRRC::Complex FFTFilterRRC::computeRRCResponse(
|
||||
float symbolRate,
|
||||
float rolloff,
|
||||
int binIndex,
|
||||
int fftLen) const
|
||||
{
|
||||
// Normalize frequency to [-0.5, 0.5]
|
||||
// FFT bins: 0 to fftLen/2-1 are positive frequencies (0 to 0.5)
|
||||
// fftLen/2 to fftLen-1 are negative frequencies (-0.5 to 0)
|
||||
float freq = static_cast<float>(binIndex) / static_cast<float>(fftLen);
|
||||
|
||||
// Map to [-0.5, 0.5] range
|
||||
if (freq > 0.5f) {
|
||||
freq -= 1.0f;
|
||||
}
|
||||
|
||||
// Absolute frequency
|
||||
float absFreq = std::abs(freq);
|
||||
|
||||
// Compute frequency boundaries
|
||||
// For RRC: passband is Rs/2, where Rs is symbol rate
|
||||
// Transition band: Rs/2 * (1-beta) to Rs/2 * (1+beta)
|
||||
float f1 = symbolRate * (1.0f - rolloff) / 2.0f; // Start of transition band
|
||||
float f2 = symbolRate * (1.0f + rolloff) / 2.0f; // End of transition band
|
||||
|
||||
Complex response;
|
||||
|
||||
if (absFreq <= f1) {
|
||||
// Passband: constant response
|
||||
response = Complex(1.0f, 0.0f);
|
||||
} else if (absFreq > f1 && absFreq <= f2) {
|
||||
// Transition band: raised cosine roll-off
|
||||
// H(f) = 0.5 * [1 + cos(pi/beta * (|f|/Rs - (1-beta)/2))]
|
||||
float normalizedFreq = absFreq / symbolRate; // Normalize by symbol rate
|
||||
float arg = (M_PI / rolloff) * (normalizedFreq - (1.0f - rolloff) / 2.0f);
|
||||
float amplitude = 0.5f * (1.0f + std::cos(arg));
|
||||
response = Complex(amplitude, 0.0f);
|
||||
} else {
|
||||
// Stopband: zero response
|
||||
response = Complex(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
int FFTFilterRRC::process(const Complex& input, Complex** output)
|
||||
{
|
||||
// Store input sample in first half of FFT buffer
|
||||
m_fftBuffer[m_inputIndex] = input;
|
||||
m_inputIndex++;
|
||||
|
||||
// Process when first half is full
|
||||
if (m_inputIndex < m_fftLen2) {
|
||||
return 0; // Not enough samples yet
|
||||
}
|
||||
|
||||
// Second half of FFT buffer is already zero-padded from initialization
|
||||
// or previous processing (overlap-add method)
|
||||
|
||||
// Perform forward FFT
|
||||
m_fft->ComplexFFT(m_fftBuffer);
|
||||
|
||||
// Apply filter in frequency domain (element-wise multiplication)
|
||||
for (int i = 0; i < m_fftLen; i++) {
|
||||
m_fftBuffer[i] *= m_filter[i];
|
||||
}
|
||||
|
||||
// Perform inverse FFT
|
||||
m_fft->InverseComplexFFT(m_fftBuffer);
|
||||
|
||||
// Overlap-add: first half gets added to overlap buffer, second half becomes new overlap
|
||||
for (int i = 0; i < m_fftLen2; i++) {
|
||||
m_outputBuffer[i] = m_fftBuffer[i] + m_overlapBuffer[i];
|
||||
m_overlapBuffer[i] = m_fftBuffer[i + m_fftLen2];
|
||||
}
|
||||
|
||||
// Reset input index and clear first half of FFT buffer for next block
|
||||
m_inputIndex = 0;
|
||||
std::fill(m_fftBuffer, m_fftBuffer + m_fftLen2, Complex(0.0f, 0.0f));
|
||||
|
||||
// Return pointer to output buffer
|
||||
*output = m_outputBuffer;
|
||||
return m_fftLen2;
|
||||
}
|
||||
150
sdrbase/dsp/fftfilterrrc.h
Normal file
150
sdrbase/dsp/fftfilterrrc.h
Normal file
@ -0,0 +1,150 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2026 SDRangel contributors //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_DSP_FFTFILTERRRC_H
|
||||
#define INCLUDE_DSP_FFTFILTERRRC_H
|
||||
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
|
||||
#include "gfft.h"
|
||||
#include "export.h"
|
||||
|
||||
/**
|
||||
* @brief FFT-based root raised cosine filter for complex signals
|
||||
*
|
||||
* Implements a frequency-domain root raised cosine (RRC) filter using overlap-add
|
||||
* FFT convolution. The RRC filter is commonly used for pulse shaping in digital
|
||||
* communications to minimize intersymbol interference (ISI) while satisfying
|
||||
* the Nyquist criterion.
|
||||
*
|
||||
* The filter operates in the frequency domain for computational efficiency,
|
||||
* applying the RRC frequency response directly to the FFT bins of the input signal.
|
||||
*/
|
||||
class SDRBASE_API FFTFilterRRC
|
||||
{
|
||||
public:
|
||||
using Complex = std::complex<float>;
|
||||
|
||||
/**
|
||||
* @brief Construct FFT-based root raised cosine filter
|
||||
*
|
||||
* @param len FFT length (should be power of 2 for efficiency)
|
||||
*/
|
||||
explicit FFTFilterRRC(int len);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~FFTFilterRRC();
|
||||
|
||||
// Disable copy semantics (manages raw pointers)
|
||||
FFTFilterRRC(const FFTFilterRRC&) = delete;
|
||||
FFTFilterRRC& operator=(const FFTFilterRRC&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Create root raised cosine filter in frequency domain
|
||||
*
|
||||
* @param symbolRate Symbol rate in Hz (normalized to sample rate)
|
||||
* @param rolloff Roll-off factor (beta) - typically 0.2 to 0.5
|
||||
* 0 = rectangular, 1 = full cosine roll-off
|
||||
*
|
||||
* The filter bandwidth extends from -symbolRate*(1+rolloff)/2 to
|
||||
* +symbolRate*(1+rolloff)/2 in normalized frequency (0 to 0.5 = Nyquist).
|
||||
*
|
||||
* Creates mathematically correct RRC frequency response for use in
|
||||
* digital communications applications.
|
||||
*/
|
||||
void create(float symbolRate, float rolloff);
|
||||
/**
|
||||
* @brief Process one complex input sample through the filter
|
||||
*
|
||||
* Uses overlap-add FFT convolution. Accumulates input samples until
|
||||
* a half-buffer is filled, then performs FFT, frequency-domain multiplication
|
||||
* with the RRC filter, IFFT, and overlap-add to produce output samples.
|
||||
*
|
||||
* @param input Complex input sample
|
||||
* @param output Pointer to output buffer (will point to internal buffer)
|
||||
* @return Number of output samples available (0 or fftLen/2)
|
||||
*/
|
||||
int process(const Complex& input, Complex** output);
|
||||
|
||||
/**
|
||||
* @brief Get FFT length
|
||||
* @return FFT length in samples
|
||||
*/
|
||||
int getFFTLength() const { return m_fftLen; }
|
||||
|
||||
/**
|
||||
* @brief Get current symbol rate
|
||||
* @return Symbol rate (normalized)
|
||||
*/
|
||||
float getSymbolRate() const { return m_symbolRate; }
|
||||
|
||||
/**
|
||||
* @brief Get current rolloff factor
|
||||
* @return Rolloff factor (beta)
|
||||
*/
|
||||
float getRolloff() const { return m_rolloff; }
|
||||
|
||||
/**
|
||||
* @brief Get pointer to frequency-domain filter coefficients
|
||||
*
|
||||
* The filter coefficients are in the frequency domain, corresponding to the FFT bins.
|
||||
*
|
||||
* @return Pointer to array of complex filter coefficients (length = FFT length)
|
||||
*/
|
||||
const Complex* getFilter() const { return m_filter; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Initialize internal buffers and FFT engine
|
||||
*/
|
||||
void initFilter();
|
||||
|
||||
/**
|
||||
* @brief Compute RRC frequency response for a given frequency bin
|
||||
*
|
||||
* Implements the RRC transfer function in frequency domain:
|
||||
* - For |f| <= (1-beta)/(2T): H(f) = T
|
||||
* - For (1-beta)/(2T) < |f| <= (1+beta)/(2T): H(f) = T/2 * [1 + cos(pi*T/beta * (|f| - (1-beta)/(2T)))]
|
||||
* - For |f| > (1+beta)/(2T): H(f) = 0
|
||||
*
|
||||
* @param symbolRate Symbol rate (normalized to sample rate)
|
||||
* @param rolloff Roll-off factor (beta)
|
||||
* @param binIndex FFT bin index
|
||||
* @param fftLen FFT length
|
||||
* @return Complex frequency response at this bin
|
||||
*/
|
||||
Complex computeRRCResponse(float symbolRate, float rolloff, int binIndex, int fftLen) const;
|
||||
|
||||
int m_fftLen; //!< FFT length
|
||||
int m_fftLen2; //!< Half FFT length
|
||||
g_fft<float>* m_fft; //!< FFT engine
|
||||
Complex* m_filter; //!< Frequency-domain filter coefficients
|
||||
Complex* m_fftBuffer; //!< Time-domain input buffer for FFT
|
||||
Complex* m_overlapBuffer; //!< Overlap buffer for overlap-add
|
||||
Complex* m_outputBuffer; //!< Output buffer
|
||||
int m_inputIndex; //!< Current input buffer index
|
||||
float m_symbolRate; //!< Current symbol rate (normalized)
|
||||
float m_rolloff; //!< Current rolloff factor
|
||||
};
|
||||
|
||||
#endif // INCLUDE_DSP_FFTFILTERRRC_H
|
||||
192
sdrbase/dsp/firfilterrrc.cpp
Normal file
192
sdrbase/dsp/firfilterrrc.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2026 SDRangel contributors //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dsp/firfilterrrc.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
FIRFilterRRC::FIRFilterRRC() :
|
||||
m_ptr(0),
|
||||
m_symbolRate(0.0f),
|
||||
m_rolloff(0.0f),
|
||||
m_samplesPerSymbol(0)
|
||||
{
|
||||
}
|
||||
|
||||
void FIRFilterRRC::create(float symbolRate, float rolloff, int symbolSpan, Normalization normalization)
|
||||
{
|
||||
m_symbolRate = symbolRate;
|
||||
m_rolloff = rolloff;
|
||||
|
||||
// Clamp rolloff to valid range [0, 1]
|
||||
if (m_rolloff < 0.0f) {
|
||||
m_rolloff = 0.0f;
|
||||
} else if (m_rolloff > 1.0f) {
|
||||
m_rolloff = 1.0f;
|
||||
}
|
||||
|
||||
// Calculate samples per symbol
|
||||
m_samplesPerSymbol = static_cast<int>(std::round(1.0f / symbolRate));
|
||||
|
||||
// Calculate number of taps (always odd for symmetry)
|
||||
int numTaps = symbolSpan * m_samplesPerSymbol + 1;
|
||||
if ((numTaps & 1) == 0) {
|
||||
numTaps++; // Ensure odd number of taps
|
||||
}
|
||||
|
||||
// Allocate tap storage (only half + center due to symmetry)
|
||||
int halfTaps = numTaps / 2 + 1;
|
||||
m_taps.resize(halfTaps);
|
||||
|
||||
// Calculate filter taps
|
||||
int center = numTaps / 2;
|
||||
for (int i = 0; i < halfTaps; i++)
|
||||
{
|
||||
// Time offset from center in symbol periods
|
||||
float t = static_cast<float>(i - center) / static_cast<float>(m_samplesPerSymbol);
|
||||
m_taps[i] = computeRRCTap(t, m_rolloff);
|
||||
}
|
||||
|
||||
// Apply normalization
|
||||
if (normalization == Normalization::Energy)
|
||||
{
|
||||
// Normalize energy: sqrt(sum of squares) = 1
|
||||
float sum = 0.0f;
|
||||
for (int i = 0; i < halfTaps - 1; i++) {
|
||||
sum += m_taps[i] * m_taps[i] * 2.0f; // Two symmetric taps
|
||||
}
|
||||
sum += m_taps[halfTaps - 1] * m_taps[halfTaps - 1]; // Center tap
|
||||
float scale = std::sqrt(sum);
|
||||
|
||||
if (scale > 0.0f) {
|
||||
for (int i = 0; i < halfTaps; i++) {
|
||||
m_taps[i] /= scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (normalization == Normalization::Amplitude)
|
||||
{
|
||||
// Normalize amplitude: max output for bipolar sequence ≈ 1
|
||||
float maxGain = 0.0f;
|
||||
for (int offset = 0; offset < m_samplesPerSymbol; offset++)
|
||||
{
|
||||
float gain = 0.0f;
|
||||
for (int i = offset; i < halfTaps - 1; i += m_samplesPerSymbol) {
|
||||
gain += std::abs(m_taps[i]) * 2.0f; // Both sides
|
||||
}
|
||||
// Add center tap if aligned
|
||||
if ((center - offset) % m_samplesPerSymbol == 0) {
|
||||
gain += std::abs(m_taps[halfTaps - 1]);
|
||||
}
|
||||
if (gain > maxGain) {
|
||||
maxGain = gain;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxGain > 0.0f) {
|
||||
for (int i = 0; i < halfTaps; i++) {
|
||||
m_taps[i] /= maxGain;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (normalization == Normalization::Gain)
|
||||
{
|
||||
// Normalize for unity gain: sum of taps = 1
|
||||
float sum = 0.0f;
|
||||
for (int i = 0; i < halfTaps - 1; i++) {
|
||||
sum += m_taps[i] * 2.0f; // Two symmetric taps
|
||||
}
|
||||
sum += m_taps[halfTaps - 1]; // Center tap
|
||||
|
||||
if (sum > 0.0f) {
|
||||
for (int i = 0; i < halfTaps; i++) {
|
||||
m_taps[i] /= sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize sample buffer
|
||||
m_samples.resize(numTaps);
|
||||
std::fill(m_samples.begin(), m_samples.end(), Complex(0.0f, 0.0f));
|
||||
m_ptr = 0;
|
||||
}
|
||||
|
||||
float FIRFilterRRC::computeRRCTap(float t, float rolloff) const
|
||||
{
|
||||
constexpr float Ts = 1.0f; // Symbol period (normalized)
|
||||
const float beta = rolloff;
|
||||
|
||||
// Handle special case: t = 0
|
||||
if (t == 0.0f) {
|
||||
// h(0) = (1/T) * (1 + beta*(4/π - 1))
|
||||
return (1.0f / Ts) * (1.0f + beta * (4.0f / M_PI - 1.0f));
|
||||
}
|
||||
|
||||
// Check for zeros of denominator: 1 - (4*β*t/T)² = 0
|
||||
// This occurs at t = ±T/(4*β)
|
||||
const float denomCheck = 4.0f * beta * t / Ts;
|
||||
if (std::abs(std::abs(denomCheck) - 1.0f) < 1e-6f && beta > 0.0f)
|
||||
{
|
||||
// h(±T/4β) = (β/(T*√2)) * [(1+2/π)*sin(π/4β) + (1-2/π)*cos(π/4β)]
|
||||
const float arg = M_PI / (4.0f * beta);
|
||||
return (beta / (Ts * std::sqrt(2.0f))) *
|
||||
((1.0f + 2.0f / M_PI) * std::sin(arg) +
|
||||
(1.0f - 2.0f / M_PI) * std::cos(arg));
|
||||
}
|
||||
|
||||
// General case
|
||||
const float numerator = std::sin(M_PI * t / Ts * (1.0f - beta)) +
|
||||
4.0f * beta * t / Ts * std::cos(M_PI * t / Ts * (1.0f + beta));
|
||||
const float denominator = M_PI * t / Ts * (1.0f - denomCheck * denomCheck);
|
||||
|
||||
return numerator / (denominator * Ts);
|
||||
}
|
||||
|
||||
FIRFilterRRC::Complex FIRFilterRRC::filter(const Complex& input)
|
||||
{
|
||||
const int numTaps = static_cast<int>(m_samples.size());
|
||||
const int halfTaps = static_cast<int>(m_taps.size()) - 1;
|
||||
|
||||
// Store input sample in circular buffer
|
||||
m_samples[m_ptr] = input;
|
||||
|
||||
// Perform convolution using symmetry
|
||||
Complex acc(0.0f, 0.0f);
|
||||
int a = m_ptr;
|
||||
int b = (a == numTaps - 1) ? 0 : a + 1;
|
||||
|
||||
// Process symmetric pairs
|
||||
for (int i = 0; i < halfTaps; i++)
|
||||
{
|
||||
acc += (m_samples[a] + m_samples[b]) * m_taps[i];
|
||||
|
||||
a = (a == 0) ? numTaps - 1 : a - 1;
|
||||
b = (b == numTaps - 1) ? 0 : b + 1;
|
||||
}
|
||||
|
||||
// Add center tap
|
||||
acc += m_samples[a] * m_taps[halfTaps];
|
||||
|
||||
// Advance circular buffer pointer
|
||||
m_ptr = (m_ptr == static_cast<size_t>(numTaps) - 1) ? 0 : m_ptr + 1;
|
||||
|
||||
return acc;
|
||||
}
|
||||
153
sdrbase/dsp/firfilterrrc.h
Normal file
153
sdrbase/dsp/firfilterrrc.h
Normal file
@ -0,0 +1,153 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2026 SDRangel contributors //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_DSP_FIRFILTERRRC_H
|
||||
#define INCLUDE_DSP_FIRFILTERRRC_H
|
||||
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
/**
|
||||
* @brief FIR root raised cosine filter for complex signals
|
||||
*
|
||||
* Implements a time-domain FIR root raised cosine (RRC) filter using direct
|
||||
* convolution. The RRC filter is commonly used for pulse shaping in digital
|
||||
* communications to minimize intersymbol interference (ISI) while satisfying
|
||||
* the Nyquist criterion.
|
||||
*
|
||||
* This filter offers sample-by-sample processing with low latency, making it
|
||||
* suitable for real-time applications. For longer filters or higher throughput,
|
||||
* consider using FFTFilterRRC which uses frequency-domain processing.
|
||||
*/
|
||||
class SDRBASE_API FIRFilterRRC
|
||||
{
|
||||
public:
|
||||
using Complex = std::complex<float>;
|
||||
|
||||
/**
|
||||
* @brief Normalization modes for filter taps
|
||||
*/
|
||||
enum class Normalization {
|
||||
Energy, //!< Impulse response energy normalized (TX+RX = raised cosine peak = 1) - for matched filter pairs
|
||||
Amplitude, //!< Bipolar symbol sequence output amplitude normalized to ±1 - for pulse amplitude preservation
|
||||
Gain //!< Unity gain (0 dB) - tap coefficients sum to 1 - for continuous wave input
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Construct FIR root raised cosine filter
|
||||
*/
|
||||
FIRFilterRRC();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~FIRFilterRRC() = default;
|
||||
|
||||
/**
|
||||
* @brief Create root raised cosine filter taps
|
||||
*
|
||||
* @param symbolRate Symbol rate (normalized to sample rate, 0 to 0.5)
|
||||
* @param rolloff Roll-off factor (beta) - typically 0.2 to 0.5
|
||||
* 0 = rectangular, 1 = full cosine roll-off
|
||||
* @param symbolSpan Number of symbols over which filter is spread
|
||||
* Typical values: 6-12 (more = better filtering, more delay)
|
||||
* @param normalization How to normalize the filter taps
|
||||
*
|
||||
* Total number of taps = symbolSpan * samplesPerSymbol + 1 (always odd).
|
||||
* For symbolRate=0.05 (20 samples/symbol), symbolSpan=8 gives 161 taps.
|
||||
*/
|
||||
void create(float symbolRate, float rolloff, int symbolSpan = 8,
|
||||
Normalization normalization = Normalization::Energy);
|
||||
|
||||
/**
|
||||
* @brief Process one complex input sample through the filter
|
||||
*
|
||||
* Direct convolution with stored tap coefficients. Processes samples
|
||||
* one at a time with minimal latency.
|
||||
*
|
||||
* @param input Complex input sample
|
||||
* @return Filtered complex output sample
|
||||
*/
|
||||
Complex filter(const Complex& input);
|
||||
|
||||
/**
|
||||
* @brief Get current symbol rate
|
||||
* @return Symbol rate (normalized)
|
||||
*/
|
||||
float getSymbolRate() const { return m_symbolRate; }
|
||||
|
||||
/**
|
||||
* @brief Get current rolloff factor
|
||||
* @return Rolloff factor (beta)
|
||||
*/
|
||||
float getRolloff() const { return m_rolloff; }
|
||||
|
||||
/**
|
||||
* @brief Get number of taps in filter
|
||||
* @return Number of filter taps
|
||||
*/
|
||||
int getNumTaps() const { return static_cast<int>(m_taps.size()); }
|
||||
|
||||
/**
|
||||
* @brief Get samples per symbol
|
||||
* @return Samples per symbol (1/symbolRate)
|
||||
*/
|
||||
int getSamplesPerSymbol() const { return m_samplesPerSymbol; }
|
||||
|
||||
/**
|
||||
* @brief Get filter delay in samples
|
||||
* @return Group delay (approximately numTaps/2)
|
||||
*/
|
||||
int getDelay() const { return getNumTaps() / 2; }
|
||||
|
||||
/**
|
||||
* @brief Get reference to filter tap coefficients
|
||||
*
|
||||
* The taps are stored as half + center due to symmetry. The full impulse
|
||||
* response can be reconstructed by mirroring the taps around the center.
|
||||
*
|
||||
* @return Reference to vector of filter tap coefficients (half + center)
|
||||
*/
|
||||
const std::vector<float>& getTaps() const { return m_taps; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Compute time-domain RRC tap value
|
||||
*
|
||||
* Implements the mathematical RRC impulse response:
|
||||
* h(t) = [sin(π*t/T*(1-β)) + 4*β*t/T*cos(π*t/T*(1+β))] / [π*t/T*(1-(4*β*t/T)²)]
|
||||
*
|
||||
* @param t Time index (in symbol periods)
|
||||
* @param rolloff Roll-off factor (beta)
|
||||
* @return Tap value at time t
|
||||
*/
|
||||
float computeRRCTap(float t, float rolloff) const;
|
||||
|
||||
std::vector<float> m_taps; //!< Filter tap coefficients (symmetric, stored as half + center)
|
||||
std::vector<Complex> m_samples; //!< Circular buffer for input samples
|
||||
size_t m_ptr; //!< Current position in circular buffer
|
||||
float m_symbolRate; //!< Symbol rate (normalized)
|
||||
float m_rolloff; //!< Rolloff factor (beta)
|
||||
int m_samplesPerSymbol; //!< Samples per symbol (1/symbolRate)
|
||||
};
|
||||
|
||||
#endif // INCLUDE_DSP_FIRFILTERRRC_H
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -40,7 +40,9 @@ public:
|
||||
TestGolay2312,
|
||||
TestFT8,
|
||||
TestCallsign,
|
||||
TestFT8Protocols
|
||||
TestFT8Protocols,
|
||||
TestFFTRRCFilter,
|
||||
TestFIRRRCFilter
|
||||
} TestType;
|
||||
|
||||
ParserBench();
|
||||
|
||||
77
sdrbench/test_fftrrc.cpp
Normal file
77
sdrbench/test_fftrrc.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2026 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "mainbench.h"
|
||||
#include "dsp/fftfilterrrc.h"
|
||||
#include <QDebug>
|
||||
|
||||
void MainBench::testFFTRRCFilter()
|
||||
{
|
||||
qDebug() << "MainBench::testFFTRRCFilter";
|
||||
const int RRC_FFT_SIZE = 512;
|
||||
std::complex<float>* rrcFilterOut = nullptr;
|
||||
FFTFilterRRC filter(RRC_FFT_SIZE);
|
||||
filter.create(0.05f, 0.35f); // 2400 baud, 0.35 rolloff
|
||||
|
||||
qDebug() << "MainBench::testFFTRRCFilter: filter created";
|
||||
FILE *fd_filter = fopen("test_rrc_filter.txt", "w");
|
||||
|
||||
for (int i = 0; i < RRC_FFT_SIZE; i++) {
|
||||
fprintf(fd_filter, "%f\n", std::abs(filter.getFilter()[i]));
|
||||
}
|
||||
qDebug() << "MainBench::testFFTRRCFilter: filter coefficients written to test_rrc_filter.txt";
|
||||
fclose(fd_filter);
|
||||
|
||||
qDebug() << "MainBench::testFFTRRCFilter: running filter";
|
||||
FILE *fd = fopen("test_rrc.txt", "w");
|
||||
int outLen = 0;
|
||||
|
||||
for (int i = 0; i < 5000; i++)
|
||||
{
|
||||
int ss = 48000 / 2400; // Samples per symbol at 2400 baud
|
||||
int d = i / ss; // Symbol index at 2400 baud
|
||||
Real s = (d % 2 == 0) ? 1.0f : -1.0f; // BPSK symbol sequence
|
||||
Real x = (i % ss == 0 ? s : 0.0f); // Pulsed signal at 2400 Hz
|
||||
// Real phi = i * (1200.0 / 48000.0) * (2*3.141);
|
||||
// Real x = sin(phi) > 0.0 ? 1.0f : -1.0f; // Simulate a BPSK signal at 1200 baud
|
||||
filter.process(Complex(x, 0.0f), &rrcFilterOut);
|
||||
outLen++;
|
||||
|
||||
if (outLen % (RRC_FFT_SIZE / 2) == 0)
|
||||
{
|
||||
// printf("\ni=%d n_out: %d\n", i, n_out);
|
||||
|
||||
for (int j = 0; j < outLen; j++)
|
||||
{
|
||||
Complex rrc = rrcFilterOut[j];
|
||||
fprintf(fd, "%f\n", rrc.real());
|
||||
}
|
||||
|
||||
outLen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// output any remaining samples
|
||||
for (int j = 0; j < outLen; j++)
|
||||
{
|
||||
Complex rrc = rrcFilterOut[j];
|
||||
fprintf(fd, "%f\n", rrc.real());
|
||||
}
|
||||
|
||||
qDebug() << "MainBench::testFFTRRCFilter: output samples written to test_fftrrc.txt";
|
||||
fclose(fd);
|
||||
}
|
||||
50
sdrbench/test_firrrc.cpp
Normal file
50
sdrbench/test_firrrc.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2026 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "mainbench.h"
|
||||
#include "dsp/firfilterrrc.h"
|
||||
#include <QDebug>
|
||||
|
||||
void MainBench::testFIRRRCFilter()
|
||||
{
|
||||
qDebug() << "MainBench::testFIRRRCFilter";
|
||||
FIRFilterRRC filter;
|
||||
filter.create(0.05f, 0.35f, 8, FIRFilterRRC::Normalization::Gain); // 2400 baud, 0.35 rolloff
|
||||
|
||||
qDebug() << "MainBench::testFIRRRCFilter: filter created";
|
||||
FILE *fd_filter = fopen("test_rrc_filter.txt", "w");
|
||||
const std::vector<float>& taps = filter.getTaps();
|
||||
for (auto tap : taps) {
|
||||
fprintf(fd_filter, "%f\n", std::abs(tap));
|
||||
}
|
||||
qDebug() << "MainBench::testFIRRRCFilter: filter coefficients written to test_rrc_filter.txt";
|
||||
fclose(fd_filter);
|
||||
|
||||
qDebug() << "MainBench::testFIRRRCFilter: running filter";
|
||||
FILE *fd = fopen("test_rrc.txt", "w");
|
||||
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
Real phi = i * (1200.0 / 48000.0) * (2*3.141);
|
||||
Real x = sin(phi) > 0.0 ? 1.0f : -1.0f; // Simulate a BPSK signal at 1200 baud
|
||||
Complex rrc = filter.filter(Complex(x, 0.0f));
|
||||
fprintf(fd, "%f\n", rrc.real());
|
||||
}
|
||||
|
||||
qDebug() << "MainBench::testFIRRRCFilter: output samples written to test_firrrc.txt";
|
||||
fclose(fd);
|
||||
}
|
||||
41
sdrbench/test_rrc.py
Executable file
41
sdrbench/test_rrc.py
Executable file
@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
with open('../build/test_rrc_filter.txt', 'r') as f:
|
||||
filter_data = f.read()
|
||||
|
||||
filter_out = [float(x) for x in filter_data.splitlines()]
|
||||
xf = np.array(filter_out)
|
||||
|
||||
with open('../build/test_rrc.txt', 'r') as f:
|
||||
data = f.read()
|
||||
|
||||
out = [float(x) for x in data.splitlines()]
|
||||
# marks = [127, 254, 383, 511, 639, 767, 895, 1023]
|
||||
# marks = [127, 254, 383, 511]
|
||||
x = np.array(out)
|
||||
|
||||
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 6))
|
||||
|
||||
ax2.plot(xf)
|
||||
ax2.set_title('RRC Filter Coefficients')
|
||||
ax2.set_xlabel('Index')
|
||||
ax2.set_ylabel('Amplitude')
|
||||
ax2.grid(True)
|
||||
|
||||
ax1.set_title('RRC Filter Output (Real Part)')
|
||||
ax1.plot(x)
|
||||
# ax1.scatter(marks, x[marks],
|
||||
# color='red', marker='.', s=100, zorder=5, edgecolors='black')
|
||||
# plt.annotate('126', xy=(126, x[126]), xytext=(6, 0),
|
||||
# arrowprops=dict(facecolor='black', shrink=0.05),
|
||||
# fontsize=12, ha='center')
|
||||
ax1.set_xlabel('Index')
|
||||
ax1.set_ylabel('Real part')
|
||||
ax1.grid(True)
|
||||
|
||||
|
||||
plt.subplots_adjust(hspace=0.4)
|
||||
plt.savefig('plot.png', dpi=150, bbox_inches='tight')
|
||||
plt.close() # Prevents memory leaks
|
||||
Loading…
x
Reference in New Issue
Block a user