1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-26 01:39:05 -05:00

Local Sink: FFT filter bands

This commit is contained in:
f4exb 2022-12-10 19:02:38 +01:00
parent 4d1ab5d413
commit bf765a00ec
11 changed files with 563 additions and 46 deletions

View File

@ -76,6 +76,7 @@ bool LocalSinkGUI::handleMessage(const Message& message)
m_basebandSampleRate = notif.getSampleRate(); m_basebandSampleRate = notif.getSampleRate();
updateAbsoluteCenterFrequency(); updateAbsoluteCenterFrequency();
displayRateAndShift(); displayRateAndShift();
displayFFTBand();
return true; return true;
} }
else if (LocalSink::MsgConfigureLocalSink::match(message)) else if (LocalSink::MsgConfigureLocalSink::match(message))
@ -106,6 +107,8 @@ LocalSinkGUI::LocalSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
ui(new Ui::LocalSinkGUI), ui(new Ui::LocalSinkGUI),
m_pluginAPI(pluginAPI), m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet), m_deviceUISet(deviceUISet),
m_currentBandIndex(-1),
m_showFilterHighCut(false),
m_deviceCenterFrequency(0), m_deviceCenterFrequency(0),
m_basebandSampleRate(0), m_basebandSampleRate(0),
m_tickCount(0) m_tickCount(0)
@ -194,9 +197,15 @@ void LocalSinkGUI::displaySettings()
ui->localDevicePlay->setChecked(m_settings.m_play); ui->localDevicePlay->setChecked(m_settings.m_play);
ui->decimationFactor->setCurrentIndex(m_settings.m_log2Decim); ui->decimationFactor->setCurrentIndex(m_settings.m_log2Decim);
ui->dsp->setChecked(m_settings.m_dsp); ui->dsp->setChecked(m_settings.m_dsp);
ui->gain->setValue(m_settings.m_gaindB);
ui->gainText->setText(tr("%1").arg(m_settings.m_gaindB)); ui->gainText->setText(tr("%1").arg(m_settings.m_gaindB));
ui->fft->setChecked(m_settings.m_fftOn);
ui->fftSize->setCurrentIndex(m_settings.m_log2FFT-6);
ui->fftWindow->setCurrentIndex((int) m_settings.m_fftWindow);
ui->filterF2orW->setChecked(m_showFilterHighCut);
applyDecimation(); applyDecimation();
updateIndexLabel(); updateIndexLabel();
displayFFTBand(false);
getRollupContents()->restoreState(m_rollupState); getRollupContents()->restoreState(m_rollupState);
blockApplySettings(false); blockApplySettings(false);
@ -215,6 +224,51 @@ void LocalSinkGUI::displayRateAndShift()
m_channelMarker.setBandwidth(channelSampleRate); m_channelMarker.setBandwidth(channelSampleRate);
} }
void LocalSinkGUI::displayFFTBand(bool blockApplySettings)
{
if (blockApplySettings) {
this->blockApplySettings(true);
}
ui->bandIndex->setMaximum(m_settings.m_fftBands.size() != 0 ? m_settings.m_fftBands.size() - 1 : 0);
ui->bandIndex->setEnabled(m_settings.m_fftBands.size() != 0);
ui->f1->setEnabled(m_settings.m_fftBands.size() != 0);
ui->bandWidth->setEnabled(m_settings.m_fftBands.size() != 0);
if ((m_settings.m_fftBands.size() != 0) && (m_currentBandIndex < 0)) {
m_currentBandIndex = 0;
}
if (m_currentBandIndex >= 0)
{
ui->bandIndex->setValue(m_currentBandIndex);
m_currentBandIndex = ui->bandIndex->value();
ui->bandIndexText->setText(tr("%1").arg(m_currentBandIndex));
ui->f1->setValue(m_settings.m_fftBands[m_currentBandIndex].first*1000);
ui->bandWidth->setValue(m_settings.m_fftBands[m_currentBandIndex].second*1000);
double channelSampleRate = ((double) m_basebandSampleRate) / (1<<m_settings.m_log2Decim);
double f1 = (m_settings.m_fftBands[m_currentBandIndex].first)*channelSampleRate;
double w = (m_settings.m_fftBands[m_currentBandIndex].second)*channelSampleRate;
ui->f1Text->setText(displayScaled(f1, 5));
if (m_showFilterHighCut)
{
ui->bandwidthText->setToolTip("Filter high cut frequency");
double f2 = f1 + w;
ui->bandwidthText->setText(displayScaled(f2, 5));
}
else
{
ui->bandwidthText->setToolTip("Filter width");
ui->bandwidthText->setText(displayScaled(w, 5));
}
}
if (blockApplySettings) {
this->blockApplySettings(false);
}
}
int LocalSinkGUI::getLocalDeviceIndexInCombo(int localDeviceIndex) int LocalSinkGUI::getLocalDeviceIndexInCombo(int localDeviceIndex)
{ {
int index = 0; int index = 0;
@ -363,6 +417,75 @@ void LocalSinkGUI::on_gain_valueChanged(int value)
applySettings(); applySettings();
} }
void LocalSinkGUI::on_fft_toggled(bool checked)
{
m_settings.m_fftOn = checked;
applySettings();
}
void LocalSinkGUI::on_fftBandAdd_clicked()
{
if (m_settings.m_fftBands.size() == m_settings.m_maxFFTBands) {
return;
}
m_settings.m_fftBands.push_back(std::pair<float,float>{-0.1f, 0.2f});
m_currentBandIndex = m_settings.m_fftBands.size()-1;
displayFFTBand();
applySettings();
}
void LocalSinkGUI::on_fftBandDel_clicked()
{
m_settings.m_fftBands.erase(m_settings.m_fftBands.begin() + m_currentBandIndex);
m_currentBandIndex--;
displayFFTBand();
applySettings();
}
void LocalSinkGUI::on_bandIndex_valueChanged(int value)
{
ui->bandIndexText->setText(tr("%1").arg(value));
m_currentBandIndex = value;
displayFFTBand();
}
void LocalSinkGUI::on_f1_valueChanged(int value)
{
float f1 = value / 1000.0f;
m_settings.m_fftBands[m_currentBandIndex].first = f1;
float maxWidth = 0.5f - f1;
if (m_settings.m_fftBands[m_currentBandIndex].second > maxWidth) {
m_settings.m_fftBands[m_currentBandIndex].second = maxWidth;
}
displayFFTBand();
applySettings();
}
void LocalSinkGUI::on_bandWidth_valueChanged(int value)
{
float w = value / 1000.0f;
const float& f1 = m_settings.m_fftBands[m_currentBandIndex].first;
float maxWidth = 0.5f - f1;
if (w > maxWidth) {
m_settings.m_fftBands[m_currentBandIndex].second = maxWidth;
} else {
m_settings.m_fftBands[m_currentBandIndex].second = w;
}
displayFFTBand();
applySettings();
}
void LocalSinkGUI::on_filterF2orW_toggled(bool checked)
{
m_showFilterHighCut = checked;
displayFFTBand();
}
void LocalSinkGUI::applyDecimation() void LocalSinkGUI::applyDecimation()
{ {
uint32_t maxHash = 1; uint32_t maxHash = 1;
@ -386,6 +509,7 @@ void LocalSinkGUI::applyPosition()
updateAbsoluteCenterFrequency(); updateAbsoluteCenterFrequency();
displayRateAndShift(); displayRateAndShift();
displayFFTBand();
applySettings(); applySettings();
} }
@ -403,7 +527,14 @@ void LocalSinkGUI::makeUIConnections()
QObject::connect(ui->localDevice, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &LocalSinkGUI::on_localDevice_currentIndexChanged); QObject::connect(ui->localDevice, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &LocalSinkGUI::on_localDevice_currentIndexChanged);
QObject::connect(ui->localDevicePlay, &ButtonSwitch::toggled, this, &LocalSinkGUI::on_localDevicePlay_toggled); QObject::connect(ui->localDevicePlay, &ButtonSwitch::toggled, this, &LocalSinkGUI::on_localDevicePlay_toggled);
QObject::connect(ui->dsp, &ButtonSwitch::toggled, this, &LocalSinkGUI::on_dsp_toggled); QObject::connect(ui->dsp, &ButtonSwitch::toggled, this, &LocalSinkGUI::on_dsp_toggled);
QObject::connect(ui->gain, &QSlider::valueChanged, this, &LocalSinkGUI::on_gain_valueChanged); QObject::connect(ui->gain, &QDial::valueChanged, this, &LocalSinkGUI::on_gain_valueChanged);
QObject::connect(ui->fft, &ButtonSwitch::toggled, this, &LocalSinkGUI::on_fft_toggled);
QObject::connect(ui->fftBandAdd, &QPushButton::clicked, this, &LocalSinkGUI::on_fftBandAdd_clicked);
QObject::connect(ui->fftBandDel, &QPushButton::clicked, this, &LocalSinkGUI::on_fftBandDel_clicked);
QObject::connect(ui->bandIndex, &QSlider::valueChanged, this, &LocalSinkGUI::on_bandIndex_valueChanged);
QObject::connect(ui->f1, &QDial::valueChanged, this, &LocalSinkGUI::on_f1_valueChanged);
QObject::connect(ui->bandWidth, &QDial::valueChanged, this, &LocalSinkGUI::on_bandWidth_valueChanged);
QObject::connect(ui->filterF2orW, &ButtonSwitch::toggled, this, &LocalSinkGUI::on_filterF2orW_toggled);
} }
void LocalSinkGUI::updateAbsoluteCenterFrequency() void LocalSinkGUI::updateAbsoluteCenterFrequency()
@ -411,3 +542,20 @@ void LocalSinkGUI::updateAbsoluteCenterFrequency()
int shift = m_shiftFrequencyFactor * m_basebandSampleRate; int shift = m_shiftFrequencyFactor * m_basebandSampleRate;
setStatusFrequency(m_deviceCenterFrequency + shift); setStatusFrequency(m_deviceCenterFrequency + shift);
} }
QString LocalSinkGUI::displayScaled(int64_t value, int precision)
{
int64_t posValue = (value < 0) ? -value : value;
if (posValue < 1000) {
return tr("%1").arg(QString::number(value, 'g', precision));
} else if (posValue < 1000000) {
return tr("%1k").arg(QString::number(value / 1000.0, 'g', precision));
} else if (posValue < 1000000000) {
return tr("%1M").arg(QString::number(value / 1000000.0, 'g', precision));
} else if (posValue < 1000000000000) {
return tr("%1%2").arg(QString::number(value / 1000000000.0, 'g', precision)).arg("G");
} else {
return tr("%1").arg(QString::number(value, 'e', precision));
}
}

View File

@ -68,6 +68,8 @@ private:
ChannelMarker m_channelMarker; ChannelMarker m_channelMarker;
RollupState m_rollupState; RollupState m_rollupState;
LocalSinkSettings m_settings; LocalSinkSettings m_settings;
int m_currentBandIndex;
bool m_showFilterHighCut;
qint64 m_deviceCenterFrequency; qint64 m_deviceCenterFrequency;
int m_basebandSampleRate; int m_basebandSampleRate;
double m_shiftFrequencyFactor; //!< Channel frequency shift factor double m_shiftFrequencyFactor; //!< Channel frequency shift factor
@ -86,11 +88,13 @@ private:
void applySettings(bool force = false); void applySettings(bool force = false);
void displaySettings(); void displaySettings();
void displayRateAndShift(); void displayRateAndShift();
void displayFFTBand(bool blockApplySettings = true);
bool handleMessage(const Message& message); bool handleMessage(const Message& message);
void makeUIConnections(); void makeUIConnections();
void updateAbsoluteCenterFrequency(); void updateAbsoluteCenterFrequency();
void updateDeviceSetList(const QList<int>& deviceSetIndexes); void updateDeviceSetList(const QList<int>& deviceSetIndexes);
int getLocalDeviceIndexInCombo(int localDeviceIndex); int getLocalDeviceIndexInCombo(int localDeviceIndex);
QString displayScaled(int64_t value, int precision);
void leaveEvent(QEvent*); void leaveEvent(QEvent*);
void enterEvent(EnterEventType*); void enterEvent(EnterEventType*);
@ -106,6 +110,13 @@ private slots:
void on_localDevicePlay_toggled(bool checked); void on_localDevicePlay_toggled(bool checked);
void on_dsp_toggled(bool checked); void on_dsp_toggled(bool checked);
void on_gain_valueChanged(int value); void on_gain_valueChanged(int value);
void on_fft_toggled(bool checked);
void on_fftBandAdd_clicked();
void on_fftBandDel_clicked();
void on_bandIndex_valueChanged(int value);
void on_f1_valueChanged(int value);
void on_bandWidth_valueChanged(int value);
void on_filterF2orW_toggled(bool checked);
void onWidgetRolled(QWidget* widget, bool rollDown); void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p); void onMenuDialogCalled(const QPoint& p);
void tick(); void tick();

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>452</width> <width>480</width>
<height>467</height> <height>467</height>
</rect> </rect>
</property> </property>
@ -18,7 +18,7 @@
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>414</width> <width>480</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -36,7 +36,7 @@
<rect> <rect>
<x>1</x> <x>1</x>
<y>1</y> <y>1</y>
<width>451</width> <width>481</width>
<height>141</height> <height>141</height>
</rect> </rect>
</property> </property>
@ -277,6 +277,12 @@
</item> </item>
<item> <item>
<widget class="ButtonSwitch" name="localDevicePlay"> <widget class="ButtonSwitch" name="localDevicePlay">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip"> <property name="toolTip">
<string>Start/Stop processing</string> <string>Start/Stop processing</string>
</property> </property>
@ -292,6 +298,15 @@
</item> </item>
<item> <item>
<widget class="ButtonSwitch" name="dsp"> <widget class="ButtonSwitch" name="dsp">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Toggle DSP processing</string>
</property>
<property name="text"> <property name="text">
<string>DSP</string> <string>DSP</string>
</property> </property>
@ -353,6 +368,15 @@
</item> </item>
<item> <item>
<widget class="ButtonSwitch" name="fft"> <widget class="ButtonSwitch" name="fft">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Toggle FFT filter</string>
</property>
<property name="text"> <property name="text">
<string>FFT</string> <string>FFT</string>
</property> </property>
@ -372,12 +396,6 @@
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip"> <property name="toolTip">
<string>FFT size</string> <string>FFT size</string>
</property> </property>
@ -440,14 +458,8 @@
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip"> <property name="toolTip">
<string>FFT filter window function</string> <string>FFT filter bands window function</string>
</property> </property>
<property name="sizeAdjustPolicy"> <property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum> <enum>QComboBox::AdjustToContents</enum>
@ -499,6 +511,22 @@
</item> </item>
</widget> </widget>
</item> </item>
<item>
<widget class="ButtonSwitch" name="fftFilterReverse">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Reverse filter bands (pass &lt;-&gt; cut)</string>
</property>
<property name="text">
<string>R</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
@ -517,9 +545,12 @@
<item> <item>
<layout class="QHBoxLayout" name="fftLayout"> <layout class="QHBoxLayout" name="fftLayout">
<item> <item>
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="fftBandLabel">
<property name="toolTip">
<string>FFT filter bands</string>
</property>
<property name="text"> <property name="text">
<string>Band</string> <string>FFT</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -551,6 +582,131 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<layout class="QVBoxLayout" name="fftBandAddDel">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="fftBandAdd">
<property name="maximumSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="palette">
<palette>
<active>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>190</red>
<green>190</green>
<blue>190</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>10</pointsize>
</font>
</property>
<property name="toolTip">
<string>Add a new FFT band</string>
</property>
<property name="text">
<string>+</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="fftBandDel">
<property name="maximumSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="palette">
<palette>
<active>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>190</red>
<green>190</green>
<blue>190</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>10</pointsize>
</font>
</property>
<property name="toolTip">
<string>Remove current FFT band</string>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<widget class="Line" name="line"> <widget class="Line" name="line">
<property name="orientation"> <property name="orientation">
@ -572,17 +728,20 @@
<property name="text"> <property name="text">
<string>00</string> <string>00</string>
</property> </property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="f1Label"> <widget class="QLabel" name="f1Label">
<property name="text"> <property name="text">
<string>f1</string> <string>F1</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QDial" name="f1Bin"> <widget class="QDial" name="f1">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>24</width> <width>24</width>
@ -596,13 +755,13 @@
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Frequency limit #1 bin</string> <string>Filter low cut frequency</string>
</property> </property>
<property name="minimum"> <property name="minimum">
<number>0</number> <number>-500</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>1023</number> <number>500</number>
</property> </property>
<property name="pageStep"> <property name="pageStep">
<number>1</number> <number>1</number>
@ -618,7 +777,7 @@
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Frequency limit #1 frequency</string> <string>Filter low cut frequency</string>
</property> </property>
<property name="text"> <property name="text">
<string>100.00k</string> <string>100.00k</string>
@ -636,14 +795,14 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="f2Label"> <widget class="QLabel" name="widthLabel">
<property name="text"> <property name="text">
<string>f2</string> <string>W</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QDial" name="f2Bin"> <widget class="QDial" name="bandWidth">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>24</width> <width>24</width>
@ -657,13 +816,13 @@
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Frequency limit #2 bin</string> <string>FFT band width</string>
</property> </property>
<property name="minimum"> <property name="minimum">
<number>0</number> <number>0</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>1023</number> <number>1000</number>
</property> </property>
<property name="pageStep"> <property name="pageStep">
<number>1</number> <number>1</number>
@ -671,7 +830,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="f2Text"> <widget class="QLabel" name="bandwidthText">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>50</width> <width>50</width>
@ -679,7 +838,7 @@
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Frequency limit #2 frequency</string> <string>Filter width</string>
</property> </property>
<property name="text"> <property name="text">
<string>100.00k</string> <string>100.00k</string>
@ -689,6 +848,30 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QToolButton" name="filterF2orW">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Filter width / high cut display toggle</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_2head_h.png</normaloff>
<normalon>:/arrow_down.png</normalon>:/arrow_2head_h.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer_3"> <spacer name="horizontalSpacer_3">
<property name="orientation"> <property name="orientation">

View File

@ -40,6 +40,9 @@ void LocalSinkSettings::resetToDefaults()
m_play = false; m_play = false;
m_dsp = false; m_dsp = false;
m_gaindB = 0; m_gaindB = 0;
m_fftOn = false;
m_log2FFT = 10;
m_fftWindow = FFTWindow::Function::Bartlett;
m_streamIndex = 0; m_streamIndex = 0;
m_useReverseAPI = false; m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1"; m_reverseAPIAddress = "127.0.0.1";
@ -84,6 +87,23 @@ QByteArray LocalSinkSettings::serialize() const
s.writeBlob(21, m_spectrumGUI->serialize()); s.writeBlob(21, m_spectrumGUI->serialize());
} }
s.writeBool(22, m_fftOn);
s.writeU32(23, (int) m_fftWindow);
s.writeU32(99, m_fftBands.size());
int i = 0;
for (auto fftBand : m_fftBands)
{
s.writeFloat(100 + 2*i, fftBand.first);
s.writeFloat(101 + 2*i, fftBand.second);
i++;
if (i == m_maxFFTBands) {
break;
}
}
return s.final(); return s.final();
} }
@ -99,7 +119,7 @@ bool LocalSinkSettings::deserialize(const QByteArray& data)
if(d.getVersion() == 1) if(d.getVersion() == 1)
{ {
uint32_t tmp; uint32_t utmp;
QString strtmp; QString strtmp;
QByteArray bytetmp; QByteArray bytetmp;
@ -115,20 +135,20 @@ bool LocalSinkSettings::deserialize(const QByteArray& data)
d.readString(6, &m_title, "Local sink"); d.readString(6, &m_title, "Local sink");
d.readBool(7, &m_useReverseAPI, false); d.readBool(7, &m_useReverseAPI, false);
d.readString(8, &m_reverseAPIAddress, "127.0.0.1"); d.readString(8, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(9, &tmp, 0); d.readU32(9, &utmp, 0);
if ((tmp > 1023) && (tmp < 65535)) { if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = tmp; m_reverseAPIPort = utmp;
} else { } else {
m_reverseAPIPort = 8888; m_reverseAPIPort = 8888;
} }
d.readU32(10, &tmp, 0); d.readU32(10, &utmp, 0);
m_reverseAPIDeviceIndex = tmp > 99 ? 99 : tmp; m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(11, &tmp, 0); d.readU32(11, &utmp, 0);
m_reverseAPIChannelIndex = tmp > 99 ? 99 : tmp; m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
d.readU32(12, &tmp, 0); d.readU32(12, &utmp, 0);
m_log2Decim = tmp > 6 ? 6 : tmp; m_log2Decim = utmp > 6 ? 6 : utmp;
d.readU32(13, &m_filterChainHash, 0); d.readU32(13, &m_filterChainHash, 0);
d.readS32(14, &m_streamIndex, 0); d.readS32(14, &m_streamIndex, 0);
@ -150,6 +170,24 @@ bool LocalSinkSettings::deserialize(const QByteArray& data)
m_spectrumGUI->deserialize(bytetmp); m_spectrumGUI->deserialize(bytetmp);
} }
d.readBool(22, &m_fftOn, false);
d.readU32(23, &utmp, 0);
m_fftWindow = (utmp > (uint32_t) FFTWindow::Function::BlackmanHarris7) ?
FFTWindow::Function::BlackmanHarris7 :
(FFTWindow::Function) utmp;
uint32_t nbBands;
d.readU32(99, &nbBands, 0);
m_fftBands.clear();
for (uint32_t i = 0; i < std::min(nbBands, m_maxFFTBands); i++)
{
float f1, w;
d.readFloat(100 + 2*i, &f1, 0);
d.readFloat(101 + 2*i, &w, 0);
m_fftBands.push_back(std::pair<float, float>{f1, w});
}
return true; return true;
} }
else else

View File

@ -21,6 +21,8 @@
#include <QByteArray> #include <QByteArray>
#include <QString> #include <QString>
#include "dsp/fftwindow.h"
class Serializable; class Serializable;
struct LocalSinkSettings struct LocalSinkSettings
@ -33,6 +35,11 @@ struct LocalSinkSettings
bool m_play; bool m_play;
bool m_dsp; bool m_dsp;
int m_gaindB; int m_gaindB;
bool m_fftOn;
uint32_t m_log2FFT;
FFTWindow::Function m_fftWindow;
static const uint32_t m_maxFFTBands = 20;
std::vector<std::pair<float, float>> m_fftBands;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx). int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI; bool m_useReverseAPI;
QString m_reverseAPIAddress; QString m_reverseAPIAddress;

View File

@ -22,6 +22,7 @@
#include "dsp/devicesamplesource.h" #include "dsp/devicesamplesource.h"
#include "dsp/hbfilterchainconverter.h" #include "dsp/hbfilterchainconverter.h"
#include "dsp/spectrumvis.h" #include "dsp/spectrumvis.h"
#include "dsp/fftfilt.h"
#include "util/db.h" #include "util/db.h"
#include "localsinkworker.h" #include "localsinkworker.h"
@ -39,16 +40,49 @@ LocalSinkSink::LocalSinkSink() :
m_deviceSampleRate(48000) m_deviceSampleRate(48000)
{ {
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(4000000)); m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(4000000));
// m_fftFilter = new fftfilt(0.1f, 0.4f, 1<<m_settings.m_log2FFT);
m_fftFilter = new fftfilt(1<<m_settings.m_log2FFT);
applySettings(m_settings, true); applySettings(m_settings, true);
} }
LocalSinkSink::~LocalSinkSink() LocalSinkSink::~LocalSinkSink()
{ {
delete m_fftFilter;
} }
void LocalSinkSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) void LocalSinkSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{ {
if (m_settings.m_dsp && (m_settings.m_gaindB != 0)) if (m_settings.m_dsp && m_settings.m_fftOn)
{
fftfilt::cmplx *rf;
int rf_out;
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
rf_out = m_fftFilter->runFilt(c, &rf); // filter RF
if (rf_out > 0)
{
m_spectrumBuffer.resize(rf_out);
std::transform(
rf,
rf + rf_out,
m_spectrumBuffer.begin(),
[this](const fftfilt::cmplx& c) -> Sample {
return Sample(c.real()*m_gain, c.imag()*m_gain);
}
);
if (m_running && m_deviceSource) {
m_deviceSource->getSampleFifo()->write(m_spectrumBuffer.begin(), m_spectrumBuffer.begin() + rf_out);
}
if (m_spectrumSink) {
m_spectrumSink->feed(m_spectrumBuffer.begin(), m_spectrumBuffer.begin() + rf_out, false);
}
}
}
}
else if (m_settings.m_dsp && (m_settings.m_gaindB != 0))
{ {
m_spectrumBuffer.resize(end - begin); m_spectrumBuffer.resize(end - begin);
std::transform( std::transform(
@ -74,11 +108,11 @@ void LocalSinkSink::feed(const SampleVector::const_iterator& begin, const Sample
if (m_running && m_deviceSource) { if (m_running && m_deviceSource) {
m_deviceSource->getSampleFifo()->write(begin, end); m_deviceSource->getSampleFifo()->write(begin, end);
} }
if (m_spectrumSink) {
m_spectrumSink->feed(begin, end, false);
}
} }
if (m_spectrumSink) {
m_spectrumSink->feed(begin, end, false);
}
// m_sampleFifo.write(begin, end); // m_sampleFifo.write(begin, end);
} }
@ -153,12 +187,22 @@ void LocalSinkSink::applySettings(const LocalSinkSettings& settings, bool force)
qDebug() << "LocalSinkSink::applySettings:" qDebug() << "LocalSinkSink::applySettings:"
<< " m_localDeviceIndex: " << settings.m_localDeviceIndex << " m_localDeviceIndex: " << settings.m_localDeviceIndex
<< " m_streamIndex: " << settings.m_streamIndex << " m_streamIndex: " << settings.m_streamIndex
<< " m_dsp: " << settings.m_dsp
<< " m_gaindB: " << settings.m_gaindB
<< " m_fftOn: " << settings.m_fftOn
<< " force: " << force; << " force: " << force;
if ((settings.m_gaindB != m_settings.m_gaindB) || force) { if ((settings.m_gaindB != m_settings.m_gaindB) || force) {
m_gain = CalcDb::powerFromdB(settings.m_gaindB/2.0); // Amplitude gain m_gain = CalcDb::powerFromdB(settings.m_gaindB/2.0); // Amplitude gain
} }
if ((settings.m_fftOn != m_settings.m_fftOn) || force)
{
if (settings.m_fftOn) {
m_fftFilter->create_filter(m_settings.m_fftBands, true, FFTWindow::Function::Rectangle);
}
}
m_settings = settings; m_settings = settings;
} }

View File

@ -28,6 +28,7 @@
class DeviceSampleSource; class DeviceSampleSource;
class LocalSinkWorker; class LocalSinkWorker;
class SpectrumVis; class SpectrumVis;
class fftfilt;
class LocalSinkSink : public QObject, public ChannelSampleSink { class LocalSinkSink : public QObject, public ChannelSampleSink {
Q_OBJECT Q_OBJECT
@ -54,6 +55,7 @@ private:
SampleVector m_spectrumBuffer; SampleVector m_spectrumBuffer;
bool m_running; bool m_running;
float m_gain; //!< Amplitude gain float m_gain; //!< Amplitude gain
fftfilt* m_fftFilter;
uint64_t m_centerFrequency; uint64_t m_centerFrequency;
int64_t m_frequencyOffset; int64_t m_frequencyOffset;

View File

@ -34,6 +34,7 @@
#include <cstdlib> #include <cstdlib>
#include <cmath> #include <cmath>
#include <typeinfo> #include <typeinfo>
#include <array>
#include <stdio.h> #include <stdio.h>
#include <sys/types.h> #include <sys/types.h>
@ -156,6 +157,87 @@ void fftfilt::create_filter(float f1, float f2, FFTWindow::Function wf)
} }
} }
void fftfilt::create_filter(const std::vector<std::pair<float, float>>& limits, bool pass, FFTWindow::Function wf)
{
// initialize the filter canvas
std::vector<int> canvas(flen, pass ? 0 : 1);
fftfilt::cmplx* xfilter = new fftfilt::cmplx[flen];
for (const auto& fs : limits)
{
const float& f1 = fs.first + 0.5;
const float& w = fs.second > 0.0 ? fs.second : 0.0;
const float& f2 = f1 + w;
for (int i = 0; i < flen; i++)
{
if (pass) // pass
{
if ((i >= f1*flen) && (i <= f2*flen)) {
canvas[i] = 1;
}
}
else // reject
{
if ((i >= f1*flen) && (i <= f2*flen)) {
canvas[i] = 0;
}
}
}
}
std::vector<std::pair<int,int>> indexes;
int c = 0;
for (int i = 0; i < flen; i++)
{
if ((canvas[i] == 1) && (c == 0)) {
indexes.push_back(std::pair<int,int>{i, 0});
}
if ((canvas[i] == 0) && (c == 1)) {
indexes.back().second = i;
}
xfilter[i] = cmplx(canvas[i], 0);
c = canvas[i];
}
// Apply window
for (const auto& wband : indexes)
{
FFTWindow fwin;
fwin.create(wf, wband.second - wband.first);
fwin.apply(&xfilter[wband.first]);
}
// Rearrange
std::copy(&xfilter[flen2], &xfilter[flen-1], filter);
std::copy(&xfilter[0], &xfilter[flen2-1], &filter[flen2]);
// // normalize the output filter for unity gain
// float scale = 0, mag;
// for (int i = 0; i < flen2; i++)
// {
// mag = abs(filter[i]);
// if (mag > scale) {
// scale = mag;
// }
// }
// if (scale != 0)
// {
// for (int i = 0; i < flen; i++) {
// filter[i] /= scale;
// }
// }
delete[] xfilter;
}
// Double the size of FFT used for equivalent SSB filter or assume FFT is half the size of the one used for SSB // Double the size of FFT used for equivalent SSB filter or assume FFT is half the size of the one used for SSB
void fftfilt::create_dsb_filter(float f2, FFTWindow::Function wf) void fftfilt::create_dsb_filter(float f2, FFTWindow::Function wf)
{ {

View File

@ -27,6 +27,7 @@ public:
// f1 < f2 ==> bandpass // f1 < f2 ==> bandpass
// f1 > f2 ==> band reject // f1 > f2 ==> band reject
void create_filter(float f1, float f2, FFTWindow::Function wf = FFTWindow::Blackman); void create_filter(float f1, float f2, FFTWindow::Function wf = FFTWindow::Blackman);
void create_filter(const std::vector<std::pair<float, float>>& limits, bool pass = true, FFTWindow::Function wf = FFTWindow::Blackman);
void create_dsb_filter(float f2, FFTWindow::Function wf = FFTWindow::Blackman); 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_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 void create_rrc_filter(float fb, float a); //!< root raised cosine. fb is half the band pass

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

View File

@ -1,5 +1,6 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file>arrow_2head_h.png</file>
<file>truncate.png</file> <file>truncate.png</file>
<file>caliper.png</file> <file>caliper.png</file>
<file>flip_windows.png</file> <file>flip_windows.png</file>