1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-21 23:55:13 -05:00

SSB demod noise reduction. Fixes #1874

This commit is contained in:
f4exb 2023-11-05 10:33:27 +01:00
parent 18458dc567
commit 179cd71c54
24 changed files with 1114 additions and 13 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

View File

@ -125,7 +125,7 @@ Values are expressed in kHz and step is 100 Hz.
- In SSB mode this is the lower cutoff (USB: positive frequencies) or higher cutoff (LSB: negative frequencies) of the in channel single side band bandpass filter.
- In DSB mode it is inactive and set to zero (double side band filter).
<h3>13: Volume and AGC</h3>
<h3>13: Volume AGC Noise Reduction</h3>
![SSB volume and AGC controls](../../../doc/img/SSBDemod_plugin_vol.png)
@ -141,15 +141,69 @@ If you are into digging weak signals out of the noise you probably will not turn
This AGC is based on the calculated magnitude (square root of power of the filtered signal as I² + Q²) and will try to adjust audio volume as if a -20dB power signal was received.
<h4>13.2A: AGC clamping</h4>
<h4>13.3: AGC clamping</h4>
When on this clamps signal at the maximum amplitude. Normally this is not needed for most signals as the AGC amplitude order is quite conservative at 10% of the maximum. You may switch it on if you notice a loud click when a transmission starts.
<h4>13.3: AGC time constant</h4>
<h4>13.4: Noise Reduction</h4>
This is a FFT based noise reduction and is engaged only in SSB mode (USB, LSB).
Usually this will not work great on weak signals and should not be used in this case. This feature is mostly intended at reducing ear fatigue when some background noise is present on a fairly strong signal. You can combine it with the squelch (13.7) to suppress the waterfall noise when there is no signal.
The spectrum display (15) is that of the filtered signal. The AGC when engaged (13.2) is also working with the filtered signal.
Use this button to toggle noise reduction on/off. Right click on this button to open a dialog controlling noise reduction filter characteristics:
![SSB noise reduction controls](../../../doc/img/SSBDemod_plugin_nr.png)
<h4>13.4.1: Noise reduction scheme</h4>
Use this combo box to choose the noise reduction scheme among the follwing:
- **Average**: calculates the average of magnitudes of the FFT (PSD) and sets the magnitude threshold to this average multiplied by a factor that can be set with the control next (13.4.2).
- **Avg Std Dev**: calculates the average and standard deviation (sigma) of magnitudes of the FFT and sets the threshold at average plus half sigma multiplied by a factor that can be set with the control next (13.4.2). This is optimal for voice signals (SSB).
- **Peaks**: selects the bins of the FFT with highest magnitudes. You can select the number of these peaks with the control next (13.4.2). This can be used for CW signals with a small number of peaks or even just one effectively realizing a peak filter.
<h4>13.4.2: Noise reduction parameter</h4>
This parameter depends on the noise reduction scheme
- With average this is the multiplier of the average
- With average and standard deviation this is the standard deviation (sigma) multiplier.
- With FFT peaks this is the number of peaks.
<h4>13.4.3: Smoothing filter constant (alpha)</h4>
With average based schemes this controls the characteristic of the exponential filter used to smoothen the average or the threshold in case of the average plus standard deviation scheme.
Smoothing prevents the return of noise during short pauses in the signal like voice signals. For voice signals (SSB) a time constant of 1 or 2 seconds gives good results (as shown in screenshot).
The exponential filter is governed by this equation:
$y(k) = \alpha y(k-1) + (1-\alpha)x(k)$
where:
- $x(k)$ is the input at time step k
- $y(k)$ is the filtered output at time step k
- $\alpha$ is a constant between 0 and 1 and is the smoothing factor. The larger the value the more smoothing occurs
The smoothing filter constant $\alpha$ is entered as minus the value in dB of $(1-\alpha)$ i.e. $-dB(1-\alpha)$. Thus a larger number corresponds to a longer time constant showed in 13.4.4. Using a logarithmic input (dB) allows a finer control of the filter time constant. The actual value of alpha will appear in the tooltip.
<h4>13.4.4: Smoothing filter time constant</h4>
The time constant of an exponential filter $\tau$ with constant time steps $T$ is expressed as $\tau = -T/ln(\alpha)$. Here the time step is the time of one FFT which is the FFT length divided by the sample rate.
The resulting time constant $\tau$ is displayed here in seconds.
<h4>13.5: AGC time constant</h4>
This is the time window in milliseconds of the moving average used to calculate the signal power average. It can be varied in powers of two from 16 to 2048 ms that is: 16, 32, 64, 128, 256, 512, 1024 and 2048 ms. The most practical values are between 128 and 512 ms.
<h4>13.4: Signal power threshold (squelch)</h4>
<h4>13.6: Signal power threshold (squelch)</h4>
Active only in AGC mode.
@ -161,7 +215,7 @@ To turn off the squelch completely move the knob all the way down (left). Then "
The signal power is calculated as the moving average over the AGC time constant (11.3) of the power of the filtered signal as I² + Q².
<h4>13.5: Signal power threshold (squelch) gate</h4>
<h4>13.7: Signal power threshold (squelch) gate</h4>
Active only in AGC mode with squelch enabled.

View File

@ -262,6 +262,12 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force)
<< " m_agcTimeLog2: " << settings.m_agcTimeLog2
<< " agcPowerThreshold: " << settings.m_agcPowerThreshold
<< " agcThresholdGate: " << settings.m_agcThresholdGate
<< " m_dnr: " << settings.m_dnr
<< " m_dnrScheme: " << settings.m_dnrScheme
<< " m_dnrAboveAvgFactor: " << settings.m_dnrAboveAvgFactor
<< " m_dnrSigmaFactor: " << settings.m_dnrSigmaFactor
<< " m_dnrNbPeaks: " << settings.m_dnrNbPeaks
<< " m_dnrAlpha: " << settings.m_dnrAlpha
<< " m_audioDeviceName: " << settings.m_audioDeviceName
<< " m_streamIndex: " << settings.m_streamIndex
<< " m_useReverseAPI: " << settings.m_useReverseAPI
@ -324,6 +330,24 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force)
if ((m_settings.m_agc != settings.m_agc) || force) {
reverseAPIKeys.append("agc");
}
if ((m_settings.m_dnr != settings.m_dnr) || force) {
reverseAPIKeys.append("dnr");
}
if ((m_settings.m_dnrScheme != settings.m_dnrScheme) || force) {
reverseAPIKeys.append("dnrScheme");
}
if ((m_settings.m_dnrAboveAvgFactor != settings.m_dnrAboveAvgFactor) || force) {
reverseAPIKeys.append("dnrAboveAvgFactor");
}
if ((m_settings.m_dnrSigmaFactor != settings.m_dnrSigmaFactor) || force) {
reverseAPIKeys.append("dnrSigmaFactor");
}
if ((m_settings.m_dnrNbPeaks != settings.m_dnrNbPeaks) || force) {
reverseAPIKeys.append("dnrNbPeaks");
}
if ((m_settings.m_dnrAlpha != settings.m_dnrAlpha) || force) {
reverseAPIKeys.append("dnrAlpha");
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{

View File

@ -193,6 +193,12 @@ void SSBDemodGUI::on_agcClamping_toggled(bool checked)
applySettings();
}
void SSBDemodGUI::on_dnr_toggled(bool checked)
{
m_settings.m_dnr = checked;
applySettings();
}
void SSBDemodGUI::on_agcTimeLog2_valueChanged(int value)
{
QString s = QString::number((1<<value), 'f', 0);
@ -337,7 +343,8 @@ SSBDemodGUI::SSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
m_audioFlipChannels(false),
m_audioMute(false),
m_squelchOpen(false),
m_audioSampleRate(-1)
m_audioSampleRate(-1),
m_fftNRDialog(nullptr)
{
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channelrx/demodssb/readme.md";
@ -356,6 +363,9 @@ SSBDemodGUI::SSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute);
connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect(const QPoint &)));
CRightClickEnabler *dnrRightClickEnabler = new CRightClickEnabler(ui->dnr);
connect(dnrRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(dnrSetupDialog(const QPoint &)));
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
@ -602,6 +612,7 @@ void SSBDemodGUI::displaySettings()
ui->agc->setChecked(m_settings.m_agc);
ui->agcClamping->setChecked(m_settings.m_agcClamping);
ui->dnr->setChecked(m_settings.m_dnr);
ui->audioBinaural->setChecked(m_settings.m_audioBinaural);
ui->audioFlipChannels->setChecked(m_settings.m_audioFlipChannels);
ui->audioMute->setChecked(m_settings.m_audioMute);
@ -698,7 +709,6 @@ void SSBDemodGUI::enterEvent(EnterEventType* event)
void SSBDemodGUI::audioSelect(const QPoint& p)
{
qDebug("SSBDemodGUI::audioSelect");
AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName);
audioSelect.move(p);
audioSelect.exec();
@ -710,6 +720,57 @@ void SSBDemodGUI::audioSelect(const QPoint& p)
}
}
void SSBDemodGUI::dnrSetupDialog(const QPoint& p)
{
m_fftNRDialog = new FFTNRDialog();
m_fftNRDialog->move(p);
QObject::connect(m_fftNRDialog, &FFTNRDialog::valueChanged, this, &SSBDemodGUI::dnrSetup);
m_fftNRDialog->setScheme((FFTNoiseReduction::Scheme) m_settings.m_dnrScheme);
m_fftNRDialog->setAboveAvgFactor(m_settings.m_dnrAboveAvgFactor);
m_fftNRDialog->setSigmaFactor(m_settings.m_dnrSigmaFactor);
m_fftNRDialog->setNbPeaks(m_settings.m_dnrNbPeaks);
m_fftNRDialog->setAlpha(m_settings.m_dnrAlpha, 2048, m_audioSampleRate);
m_fftNRDialog->exec();
QObject::disconnect(m_fftNRDialog, &FFTNRDialog::valueChanged, this, &SSBDemodGUI::dnrSetup);
m_fftNRDialog->deleteLater();
m_fftNRDialog = nullptr;
}
void SSBDemodGUI::dnrSetup(int32_t iValueChanged)
{
if (!m_fftNRDialog) {
return;
}
FFTNRDialog::ValueChanged valueChanged = (FFTNRDialog::ValueChanged) iValueChanged;
switch (valueChanged)
{
case FFTNRDialog::ValueChanged::ChangedScheme:
m_settings.m_dnrScheme = m_fftNRDialog->getScheme();
applySettings();
break;
case FFTNRDialog::ValueChanged::ChangedAboveAvgFactor:
m_settings.m_dnrAboveAvgFactor = m_fftNRDialog->getAboveAvgFactor();
applySettings();
break;
case FFTNRDialog::ValueChanged::ChangedSigmaFactor:
m_settings.m_dnrSigmaFactor = m_fftNRDialog->getSigmaFactor();
applySettings();
break;
case FFTNRDialog::ValueChanged::ChangedNbPeaks:
m_settings.m_dnrNbPeaks = m_fftNRDialog->getNbPeaks();
applySettings();
break;
case FFTNRDialog::ValueChanged::ChangedAlpha:
m_settings.m_dnrAlpha = m_fftNRDialog->getAlpha();
applySettings();
break;
default:
break;
}
}
void SSBDemodGUI::tick()
{
double magsqAvg, magsqPeak;
@ -758,6 +819,7 @@ void SSBDemodGUI::makeUIConnections()
QObject::connect(ui->volume, &QDial::valueChanged, this, &SSBDemodGUI::on_volume_valueChanged);
QObject::connect(ui->agc, &ButtonSwitch::toggled, this, &SSBDemodGUI::on_agc_toggled);
QObject::connect(ui->agcClamping, &ButtonSwitch::toggled, this, &SSBDemodGUI::on_agcClamping_toggled);
QObject::connect(ui->dnr, &ButtonSwitch::toggled, this, &SSBDemodGUI::on_dnr_toggled);
QObject::connect(ui->agcTimeLog2, &QDial::valueChanged, this, &SSBDemodGUI::on_agcTimeLog2_valueChanged);
QObject::connect(ui->agcPowerThreshold, &QDial::valueChanged, this, &SSBDemodGUI::on_agcPowerThreshold_valueChanged);
QObject::connect(ui->agcThresholdGate, &QDial::valueChanged, this, &SSBDemodGUI::on_agcThresholdGate_valueChanged);

View File

@ -6,6 +6,7 @@
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "dsp/movingaverage.h"
#include "gui/fftnrdialog.h"
#include "util/messagequeue.h"
#include "settings/rollupstate.h"
#include "ssbdemodsettings.h"
@ -70,6 +71,7 @@ private:
SSBDemod* m_ssbDemod;
SpectrumVis* m_spectrumVis;
MessageQueue m_inputMessageQueue;
FFTNRDialog* m_fftNRDialog;
QIcon m_iconDSBUSB;
QIcon m_iconDSBLSB;
@ -101,6 +103,7 @@ private slots:
void on_volume_valueChanged(int value);
void on_agc_toggled(bool checked);
void on_agcClamping_toggled(bool checked);
void on_dnr_toggled(bool checked);
void on_agcTimeLog2_valueChanged(int value);
void on_agcPowerThreshold_valueChanged(int value);
void on_agcThresholdGate_valueChanged(int value);
@ -113,6 +116,8 @@ private slots:
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();
void audioSelect(const QPoint& p);
void dnrSetupDialog(const QPoint& p);
void dnrSetup(int valueChanged);
void tick();
};

View File

@ -868,6 +868,19 @@
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="dnr">
<property name="toolTip">
<string>Toggle Digital Noise Reduction</string>
</property>
<property name="text">
<string>NR</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="agcTimeLog2">
<property name="maximumSize">

View File

@ -52,6 +52,12 @@ void SSBDemodSettings::resetToDefaults()
m_agcTimeLog2 = 7;
m_volume = 1.0;
m_inputFrequencyOffset = 0;
m_dnr = false;
m_dnrScheme = 0;
m_dnrAboveAvgFactor = 40.0f;
m_dnrSigmaFactor = 4.0f;
m_dnrNbPeaks = 20;
m_dnrAlpha = 1.0;
m_rgbColor = QColor(0, 255, 0).rgb();
m_title = "SSB Demodulator";
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
@ -102,6 +108,12 @@ QByteArray SSBDemodSettings::serialize() const
s.writeBlob(26, m_geometryBytes);
s.writeBool(27, m_hidden);
s.writeU32(29, m_filterIndex);
s.writeBool(30, m_dnr);
s.writeS32(31, m_dnrScheme);
s.writeFloat(32, m_dnrAboveAvgFactor);
s.writeFloat(33, m_dnrSigmaFactor);
s.writeS32(34, m_dnrNbPeaks);
s.writeFloat(35, m_dnrAlpha);
for (unsigned int i = 0; i < 10; i++)
{
@ -179,6 +191,12 @@ bool SSBDemodSettings::deserialize(const QByteArray& data)
d.readBool(27, &m_hidden, false);
d.readU32(29, &utmp, 0);
m_filterIndex = utmp < 10 ? utmp : 0;
d.readBool(30, &m_dnr, false);
d.readS32(31, &m_dnrScheme, 0);
d.readFloat(32, &m_dnrAboveAvgFactor, 40.0f);
d.readFloat(33, &m_dnrSigmaFactor, 4.0f);
d.readS32(34, &m_dnrNbPeaks, 20);
d.readFloat(35, &m_dnrAlpha, 1.0);
for (unsigned int i = 0; (i < 10); i++)
{

View File

@ -19,6 +19,7 @@
#define PLUGINS_CHANNELRX_DEMODSSB_SSBDEMODSETTINGS_H_
#include <QByteArray>
#include <QString>
#include "dsp/fftwindow.h"
@ -55,6 +56,12 @@ struct SSBDemodSettings
int m_agcTimeLog2;
int m_agcPowerThreshold;
int m_agcThresholdGate;
bool m_dnr;
int m_dnrScheme;
float m_dnrAboveAvgFactor;
float m_dnrSigmaFactor;
int m_dnrNbPeaks;
float m_dnrAlpha;
quint32 m_rgbColor;
QString m_title;
QString m_audioDeviceName;

View File

@ -33,7 +33,7 @@
#include "ssbdemodsink.h"
const int SSBDemodSink::m_ssbFftLen = 1024;
const int SSBDemodSink::m_ssbFftLen = 2048;
const int SSBDemodSink::m_agcTarget = 3276; // 32768/10 -10 dB amplitude => -20 dB power: center of normal signal
SSBDemodSink::SSBDemodSink() :
@ -258,6 +258,11 @@ void SSBDemodSink::processOneSample(Complex &ci)
}
}
void SSBDemodSink::setDNR(bool dnr)
{
SSBFilter->setDNR(dnr);
}
void SSBDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "SSBDemodSink::applyChannelSettings:"
@ -352,6 +357,12 @@ void SSBDemodSink::applySettings(const SSBDemodSettings& settings, bool force)
<< " m_agcTimeLog2: " << settings.m_agcTimeLog2
<< " agcPowerThreshold: " << settings.m_agcPowerThreshold
<< " agcThresholdGate: " << settings.m_agcThresholdGate
<< " m_dnr: " << settings.m_dnr
<< " m_dnrScheme: " << settings.m_dnrScheme
<< " m_dnrAboveAvgFactor: " << settings.m_dnrAboveAvgFactor
<< " m_dnrSigmaFactor: " << settings.m_dnrSigmaFactor
<< " m_dnrNbPeaks: " << settings.m_dnrNbPeaks
<< " m_dnrAlpha: " << settings.m_dnrAlpha
<< " m_audioDeviceName: " << settings.m_audioDeviceName
<< " m_streamIndex: " << settings.m_streamIndex
<< " m_useReverseAPI: " << settings.m_useReverseAPI
@ -444,6 +455,30 @@ void SSBDemodSink::applySettings(const SSBDemodSettings& settings, bool force)
<< " agcClamping: " << agcClamping;
}
if ((m_settings.m_dnr != settings.m_dnr) || force) {
setDNR(settings.m_dnr);
}
if ((m_settings.m_dnrScheme != settings.m_dnrScheme) || force) {
SSBFilter->setDNRScheme((FFTNoiseReduction::Scheme) settings.m_dnrScheme);
}
if ((m_settings.m_dnrAboveAvgFactor != settings.m_dnrAboveAvgFactor) || force) {
SSBFilter->setDNRAboveAvgFactor(settings.m_dnrAboveAvgFactor);
}
if ((m_settings.m_dnrSigmaFactor != settings.m_dnrSigmaFactor) || force) {
SSBFilter->setDNRSigmaFactor(settings.m_dnrSigmaFactor);
}
if ((m_settings.m_dnrNbPeaks != settings.m_dnrNbPeaks) || force) {
SSBFilter->setDNRNbPeaks(settings.m_dnrNbPeaks);
}
if ((m_settings.m_dnrAlpha != settings.m_dnrAlpha) || force) {
SSBFilter->setDNRAlpha(settings.m_dnrAlpha);
}
m_spanLog2 = settings.m_filterBank[settings.m_filterIndex].m_spanLog2;
m_audioBinaual = settings.m_audioBinaural;
m_audioFlipChannels = settings.m_audioFlipChannels;

View File

@ -50,6 +50,7 @@ public:
bool getAudioActive() const { return m_audioActive; }
void setChannel(ChannelAPI *channel) { m_channel = channel; }
void setAudioFifoLabel(const QString& label) { m_audioFifo.setLabel(label); }
void setDNR(bool dnr);
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{

View File

@ -145,6 +145,7 @@ set(sdrbase_SOURCES
dsp/fftengine.cpp
dsp/fftfactory.cpp
dsp/fftfilt.cpp
dsp/fftnr.cpp
dsp/fftwindow.cpp
dsp/filterrc.cpp
dsp/filtermbe.cpp
@ -351,6 +352,7 @@ set(sdrbase_HEADERS
dsp/fftengine.h
dsp/fftfactory.h
dsp/fftfilt.h
dsp/fftnr.h
dsp/fftwengine.h
dsp/fftwindow.h
dsp/filterrc.h

View File

@ -78,28 +78,34 @@ void fftfilt::init_filter()
// f1 == 0 ==> low pass filter
// f2 == 0 ==> high pass filter
//------------------------------------------------------------------------------
fftfilt::fftfilt(int len)
fftfilt::fftfilt(int len) :
m_noiseReduction(len)
{
flen = len;
pass = 0;
window = 0;
m_dnr = false;
init_filter();
}
fftfilt::fftfilt(float f1, float f2, int len)
fftfilt::fftfilt(float f1, float f2, int len) :
m_noiseReduction(len)
{
flen = len;
pass = 0;
window = 0;
m_dnr = false;
init_filter();
create_filter(f1, f2);
}
fftfilt::fftfilt(float f2, int len)
fftfilt::fftfilt(float f2, int len) :
m_noiseReduction(len)
{
flen = len;
pass = 0;
window = 0;
m_dnr = false;
init_filter();
create_dsb_filter(f2);
}
@ -468,20 +474,51 @@ int fftfilt::runSSB(const cmplx & in, cmplx **out, bool usb, bool getDC)
// get or reject DC component
data[0] = getDC ? data[0]*filter[0] : 0;
m_noiseReduction.setScheme(m_dnrScheme);
m_noiseReduction.init();
// Discard frequencies for ssb
if (usb)
{
for (int i = 1; i < flen2; i++) {
for (int i = 1; i < flen2; i++)
{
data[i] *= filter[i];
data[flen2 + i] = 0;
if (m_dnr)
{
m_noiseReduction.push(data[i], i);
m_noiseReduction.push(data[flen2 + i], flen2 + i);
}
}
}
else
{
for (int i = 1; i < flen2; i++) {
for (int i = 1; i < flen2; i++)
{
data[i] = 0;
data[flen2 + i] *= filter[flen2 + i];
if (m_dnr)
{
m_noiseReduction.push(data[i], i);
m_noiseReduction.push(data[flen2 + i], flen2 + i);
}
}
}
if (m_dnr)
{
m_noiseReduction.m_aboveAvgFactor = m_dnrAboveAvgFactor;
m_noiseReduction.m_sigmaFactor = m_dnrSigmaFactor;
m_noiseReduction.m_nbPeaks = m_dnrNbPeaks;
m_noiseReduction.calc();
for (int i = 0; i < flen; i++)
{
if (m_noiseReduction.cut(i)) {
data[i] = 0;
}
}
}

View File

@ -10,6 +10,7 @@
#include "gfft.h"
#include "fftwindow.h"
#include "fftnr.h"
#include "export.h"
//----------------------------------------------------------------------
@ -39,7 +40,15 @@ public:
int runDSB(const cmplx& in, cmplx **out, bool getDC = true);
int runAsym(const cmplx & in, cmplx **out, bool usb); //!< Asymmetrical fitering can be used for vestigial sideband
void setDNR(bool dnr) { m_dnr = dnr; }
void setDNRScheme(FFTNoiseReduction::Scheme scheme) { m_dnrScheme = scheme; }
void setDNRAboveAvgFactor(float aboveAvgFactor) { m_dnrAboveAvgFactor = aboveAvgFactor; }
void setDNRSigmaFactor(float sigmaFactor) { m_dnrSigmaFactor = sigmaFactor; }
void setDNRNbPeaks(int nbPeaks) { m_dnrNbPeaks = nbPeaks; }
void setDNRAlpha(float alpha) { m_noiseReduction.setAlpha(alpha); }
protected:
// helper class for FFT based noise reduction
int flen;
int flen2;
g_fft<float> *fft;
@ -51,6 +60,12 @@ protected:
int inptr;
int pass;
int window;
bool m_dnr;
FFTNoiseReduction::Scheme m_dnrScheme;
float m_dnrAboveAvgFactor; //!< above average factor
float m_dnrSigmaFactor; //!< sigma multiplicator for average + std deviation
int m_dnrNbPeaks; //!< number of peaks (peaks scheme)
FFTNoiseReduction m_noiseReduction;
inline float fsinc(float fc, int i, int len)
{

148
sdrbase/dsp/fftnr.cpp Normal file
View File

@ -0,0 +1,148 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// Helper class for noise reduction //
// //
// 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 <algorithm>
#include <numeric>
#include <QDebug>
#include "fftnr.h"
FFTNoiseReduction::FFTNoiseReduction(int len) :
m_flen(len)
{
m_scheme = SchemeAverage;
m_mags = new float[m_flen];
m_tmp = new float[m_flen];
m_aboveAvgFactor = 1.0;
m_sigmaFactor = 1.0;
m_nbPeaks = m_flen;
}
FFTNoiseReduction::~FFTNoiseReduction()
{
delete[] m_mags;
delete[] m_tmp;
}
void FFTNoiseReduction::init()
{
std::fill(m_mags, m_mags + m_flen, 0);
std::fill(m_tmp, m_tmp + m_flen, 0);
m_magAvg = 0;
}
void FFTNoiseReduction::push(cmplx data, int index)
{
m_mags[index] = std::abs(data);
if ((m_scheme == SchemeAverage) || (m_scheme == SchemeAvgStdDev)) {
m_magAvg += m_mags[index];
}
}
void FFTNoiseReduction::calc()
{
if (m_scheme == SchemeAverage)
{
m_magAvg /= m_flen;
m_magAvg = m_expFilter.push(m_magAvg);
}
if (m_scheme == SchemeAvgStdDev)
{
m_magAvg /= m_flen;
auto variance_func = [this](float accumulator, const float& val) {
return accumulator + ((val - m_magAvg)*(val - m_magAvg) / (m_flen - 1));
};
float var = std::accumulate(m_mags, m_mags + m_flen, 0.0, variance_func);
m_magThr = (m_sigmaFactor/2.0)*std::sqrt(var) + m_magAvg;
m_magThr = m_expFilter.push(m_magThr);
}
else if (m_scheme == SchemePeaks)
{
std::copy(m_mags, m_mags + m_flen, m_tmp);
std::sort(m_tmp, m_tmp + m_flen);
m_magThr = m_tmp[m_flen - m_nbPeaks];
}
}
bool FFTNoiseReduction::cut(int index)
{
if (m_scheme == SchemeAverage)
{
return m_mags[index] < m_aboveAvgFactor * m_magAvg;
}
else if ((m_scheme == SchemePeaks) || (m_scheme == SchemeAvgStdDev))
{
return m_mags[index] < m_magThr;
}
return false;
}
void FFTNoiseReduction::setScheme(Scheme scheme)
{
if (m_scheme != scheme) {
m_expFilter.reset();
}
m_scheme = scheme;
}
FFTNoiseReduction::ExponentialFilter::ExponentialFilter()
{
m_alpha = 1.0;
m_init = true;
}
float FFTNoiseReduction::ExponentialFilter::push(float newValue)
{
if (m_init)
{
m_prev = newValue;
m_init = false;
}
if (m_alpha == 1.0)
{
m_prev = newValue;
return newValue;
}
else
{
float result = m_alpha*m_prev + (1.0 - m_alpha)*newValue;
m_prev = result;
return result;
}
}
void FFTNoiseReduction::ExponentialFilter::reset()
{
m_init = true;
}
void FFTNoiseReduction::ExponentialFilter::setAlpha(float alpha)
{
m_alpha = alpha < 0.0f ? 0.0f : alpha > 1.0f ? 1.0f : alpha;
qDebug("FFTNoiseReduction::ExponentialFilter::setAlpha: %f", m_alpha);
m_init = true;
}

72
sdrbase/dsp/fftnr.h Normal file
View File

@ -0,0 +1,72 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// Helper class for noise reduction //
// //
// 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 _FFTNR_H
#define _FFTNR_H
#include <complex>
#include "export.h"
class SDRBASE_API FFTNoiseReduction {
public:
typedef std::complex<float> cmplx;
enum Scheme {
SchemeAverage,
SchemeAvgStdDev,
SchemePeaks
};
FFTNoiseReduction(int len);
~FFTNoiseReduction();
void init(); //!< call befor start of initial FFT scan
void push(cmplx data, int index); //!< Push FFT bin during initial FFT scan
void calc(); //!< calculate after initial FFT scan
bool cut(int index); //!< true if bin is to be zeroed else false (during second FFT scan)
void setAlpha(float alpha) { m_expFilter.setAlpha(alpha); }
void setScheme(Scheme scheme);
float m_aboveAvgFactor; //!< above average factor
float m_sigmaFactor; //!< sigma multiplicator for average + std deviation
int m_nbPeaks; //!< number of peaks (peaks scheme)
private:
class ExponentialFilter {
public:
ExponentialFilter();
float push(float newValue);
void reset();
void setAlpha(float alpha);
private:
bool m_init;
float m_alpha;
float m_prev;
};
Scheme m_scheme;
int m_flen; //!< FFT length
float *m_mags; //!< magnitudes (PSD)
float *m_tmp; //!< temporary buffer
float m_magAvg; //!< average of magnitudes
float m_magThr; //!< magnitude threshold (peaks scheme)
ExponentialFilter m_expFilter; //!< exponential filter for parameter smoothing
};
#endif

View File

@ -43,6 +43,7 @@ set(sdrgui_SOURCES
gui/featurelayout.cpp
gui/featurepresetsdialog.cpp
gui/fftdialog.cpp
gui/fftnrdialog.cpp
gui/fftwisdomdialog.cpp
gui/flowlayout.cpp
gui/framelesswindowresizer.cpp
@ -164,6 +165,7 @@ set(sdrgui_HEADERS
gui/featurelayout.h
gui/featurepresetsdialog.h
gui/fftdialog.h
gui/fftnrdialog.h
gui/fftwisdomdialog.h
gui/flowlayout.h
gui/framelesswindowresizer.h
@ -266,6 +268,7 @@ set(sdrgui_FORMS
gui/featureadddialog.ui
gui/featurepresetsdialog.ui
gui/fftdialog.ui
gui/fftnrdialog.ui
gui/fftwisdomdialog.ui
gui/glscopegui.ui
gui/glspectrumgui.ui

210
sdrgui/gui/fftnrdialog.cpp Normal file
View File

@ -0,0 +1,210 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "util/db.h"
#include "fftnrdialog.h"
#include "ui_fftnrdialog.h"
FFTNRDialog::FFTNRDialog(QWidget* parent) :
QDialog(parent),
ui(new Ui::FFTNRDialog)
{
ui->setupUi(this);
}
FFTNRDialog::~FFTNRDialog()
{
delete ui;
}
void FFTNRDialog::setScheme(FFTNoiseReduction::Scheme scheme)
{
ui->scheme->blockSignals(true);
ui->genParam->blockSignals(true);
switch (scheme)
{
case FFTNoiseReduction::Scheme::SchemeAverage:
ui->scheme->setCurrentIndex(0);
ui->genParam->setMinimum(200);
ui->genParam->setMaximum(990);
ui->genParam->setValue(m_aboveAvgFactor*10);
ui->genParam->setToolTip("Above average multiplier");
ui->genParamLabel->setText("Above avg mult");
ui->genParamValue->setText(tr("%1").arg(m_aboveAvgFactor, 0, 'f', 1));
break;
case FFTNoiseReduction::Scheme::SchemeAvgStdDev:
ui->scheme->setCurrentIndex(1);
ui->genParam->setMinimum(20);
ui->genParam->setMaximum(160);
ui->genParam->setValue(m_sigmaFactor*10);
ui->genParam->setToolTip("Standard deviation multiplier");
ui->genParamLabel->setText("Sigma multiplier");
ui->genParamValue->setText(tr("%1").arg(m_sigmaFactor, 0, 'f', 1));
break;
case FFTNoiseReduction::Scheme::SchemePeaks:
ui->scheme->setCurrentIndex(2);
ui->genParam->setMinimum(1);
ui->genParam->setMaximum(40);
ui->genParam->setValue(m_nbPeaks);
ui->genParam->setToolTip("Number of max peaks selected");
ui->genParamLabel->setText("Nb of peaks");
ui->genParamValue->setText(tr("%1").arg(m_nbPeaks));
break;
default:
break;
}
ui->scheme->blockSignals(false);
ui->genParam->blockSignals(false);
m_scheme = scheme;
}
void FFTNRDialog::setAboveAvgFactor(float aboveAvgFactor)
{
if (aboveAvgFactor < 20.0f)
{
m_aboveAvgFactor = 20.0f;
emit valueChanged(ChangedAboveAvgFactor);
}
else if (aboveAvgFactor > 99.0f)
{
m_aboveAvgFactor = 99.0f;
emit valueChanged(ChangedAboveAvgFactor);
}
else{
m_aboveAvgFactor = aboveAvgFactor;
}
if (m_scheme == FFTNoiseReduction::Scheme::SchemeAverage)
{
ui->genParam->blockSignals(true);
ui->genParam->setValue(m_aboveAvgFactor*10);
ui->genParamValue->setText(tr("%1").arg(m_aboveAvgFactor, 0, 'f', 1));
ui->genParam->blockSignals(false);
}
}
void FFTNRDialog::setSigmaFactor(float sigmaFactor)
{
if (sigmaFactor < 2.0f)
{
m_sigmaFactor = 2.0f;
emit valueChanged(ChangedSigmaFactor);
}
else if (sigmaFactor > 16.0f)
{
m_sigmaFactor = 16.0f;
emit valueChanged(ChangedSigmaFactor);
}
else{
m_sigmaFactor = sigmaFactor;
}
if (m_scheme == FFTNoiseReduction::Scheme::SchemeAvgStdDev)
{
ui->genParam->blockSignals(true);
ui->genParam->setValue(m_sigmaFactor*10);
ui->genParamValue->setText(tr("%1").arg(m_sigmaFactor, 0, 'f', 1));
ui->genParam->blockSignals(false);
}
}
void FFTNRDialog::setNbPeaks(int nbPeaks)
{
if (nbPeaks < 1)
{
m_nbPeaks = 1;
emit valueChanged(ChangedNbPeaks);
}
else if (nbPeaks > 40)
{
m_nbPeaks = 40;
emit valueChanged(ChangedNbPeaks);
}
else{
m_nbPeaks = nbPeaks;
}
if (m_scheme == FFTNoiseReduction::Scheme::SchemePeaks)
{
ui->genParam->blockSignals(true);
ui->genParam->setValue(m_nbPeaks);
ui->genParamValue->setText(tr("%1").arg(m_nbPeaks));
ui->genParam->blockSignals(false);
}
}
void FFTNRDialog::setAlpha(float alpha, int fftLength, int sampleRate)
{
m_alpha = alpha < 0.0f ? 0.0f : alpha > 0.99999 ? 0.99999f : alpha;
m_flen = fftLength;
m_sampleRate = sampleRate;
int alphaDisplay = -round(CalcDb::dbPower(1.0f - m_alpha));
ui->alpha->blockSignals(true);
ui->alpha->setValue(alphaDisplay);
ui->alpha->blockSignals(false);
ui->alphaValue->setText(tr("%1").arg(alphaDisplay));
ui->alphaValue->setToolTip(tr("dB(1 - alpha) alpha=%1").arg(m_alpha, 0, 'f', 5));
float t = m_flen;
t /= m_sampleRate;
float tau = -(t / log(m_alpha));
ui->tauText->setText(tr("%1").arg(tau, 0, 'f', 3));
}
void FFTNRDialog::on_scheme_currentIndexChanged(int index)
{
setScheme((FFTNoiseReduction::Scheme) index);
emit valueChanged(ChangedScheme);
}
void FFTNRDialog::on_genParam_valueChanged(int value)
{
switch (m_scheme)
{
case FFTNoiseReduction::Scheme::SchemeAverage:
m_aboveAvgFactor = value / 10.0f;
ui->genParamValue->setText(tr("%1").arg(m_aboveAvgFactor, 0, 'f', 1));
emit valueChanged(ChangedAboveAvgFactor);
break;
case FFTNoiseReduction::Scheme::SchemeAvgStdDev:
m_sigmaFactor = value / 10.0f;
ui->genParamValue->setText(tr("%1").arg(m_sigmaFactor, 0, 'f', 1));
emit valueChanged(ChangedSigmaFactor);
break;
case FFTNoiseReduction::Scheme::SchemePeaks:
m_nbPeaks = value;
ui->genParamValue->setText(tr("%1").arg(m_nbPeaks));
emit valueChanged(ChangedNbPeaks);
break;
default:
break;
}
}
void FFTNRDialog::on_alpha_valueChanged(int value)
{
m_alpha = 1.0 - CalcDb::powerFromdB(-value);
ui->alphaValue->setText(tr("%1").arg(value));
ui->alphaValue->setToolTip(tr("dB(1 - alpha) alpha=%1").arg(m_alpha, 0, 'f', 5));
float t = m_flen;
t /= m_sampleRate;
float tau = -(t / log(m_alpha));
ui->tauText->setText(tr("%1").arg(tau, 0, 'f', 3));
emit valueChanged(ChangedAlpha);
}

75
sdrgui/gui/fftnrdialog.h Normal file
View File

@ -0,0 +1,75 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRGUI_GUI_FFTNRDIALOG_H_
#define SDRGUI_GUI_FFTNRDIALOG_H_
#include <QDialog>
#include "dsp/fftnr.h"
#include "export.h"
namespace Ui {
class FFTNRDialog;
}
class SDRGUI_API FFTNRDialog : public QDialog {
Q_OBJECT
public:
enum ValueChanged {
ChangedScheme,
ChangedAboveAvgFactor,
ChangedSigmaFactor,
ChangedNbPeaks,
ChangedAlpha
};
explicit FFTNRDialog(QWidget* parent = nullptr);
~FFTNRDialog();
void setScheme(FFTNoiseReduction::Scheme scheme);
void setAboveAvgFactor(float aboveAvgFactor);
void setSigmaFactor(float sigmaFactor);
void setNbPeaks(int nbPeaks);
void setAlpha(float alpha, int fftLength, int sampleRate);
FFTNoiseReduction::Scheme getScheme() const { return m_scheme; }
float getAboveAvgFactor() const { return m_aboveAvgFactor; }
float getSigmaFactor() const { return m_sigmaFactor; }
int getNbPeaks() const { return m_nbPeaks; }
float getAlpha() const { return m_alpha; }
signals:
void valueChanged(int valueChanged);
private:
Ui::FFTNRDialog *ui;
FFTNoiseReduction::Scheme m_scheme;
float m_aboveAvgFactor; //!< above average factor
float m_sigmaFactor; //!< sigma multiplicator for average + std deviation
int m_nbPeaks; //!< number of peaks (peaks scheme)
float m_alpha; //!< parameter EMA alpha factor
int m_flen; //!< FFT filter FFT length used to display time constant
int m_sampleRate; //!< Sample rate used to display time constant
void updateScheme();
private slots:
void on_scheme_currentIndexChanged(int index);
void on_genParam_valueChanged(int value);
void on_alpha_valueChanged(int value);
};
#endif

320
sdrgui/gui/fftnrdialog.ui Normal file
View File

@ -0,0 +1,320 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FFTNRDialog</class>
<widget class="QDialog" name="FFTNRDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>365</width>
<height>112</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>FFT Noise reduction</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="nrSetup">
<item>
<widget class="QLabel" name="schemeLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Path to fftwf-wisdom executable</string>
</property>
<property name="text">
<string>Scheme</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="scheme">
<property name="toolTip">
<string>Select noise reduction scheme</string>
</property>
<item>
<property name="text">
<string>Average</string>
</property>
</item>
<item>
<property name="text">
<string>Avg Std Dev</string>
</property>
</item>
<item>
<property name="text">
<string>Peaks</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="genParamLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Path to fftwf-wisdom executable</string>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="genParam">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string/>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>26</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>13</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="genParamValue">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>28</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Path to fftwf-wisdom executable</string>
</property>
<property name="text">
<string>20.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="averagingLayout">
<item>
<widget class="QLabel" name="alphaLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Alpha</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="alpha">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>-db(1 - alpha) alpha is the smoothing constant of the exponential filter </string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>40</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>13</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="alphaValue">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>-dB(1 - alpha) alpha=...</string>
</property>
<property name="text">
<string>00</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="tauLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Path to fftwf-wisdom executable</string>
</property>
<property name="text">
<string>Tau</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="tauText">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Time constant of the smoothing filter in seconds</string>
</property>
<property name="text">
<string>0.000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</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>
<include location="../resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>FFTNRDialog</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>FFTNRDialog</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>