1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-09-27 15:26:33 -04:00

Frequency Tracker: add spectrum display. Implements #665

This commit is contained in:
f4exb 2020-10-25 00:46:49 +02:00
parent 86ce2ca843
commit b04bb24146
13 changed files with 82 additions and 15 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

View File

@ -55,12 +55,14 @@ const int FreqTracker::m_udpBlockSize = 512;
FreqTracker::FreqTracker(DeviceAPI *deviceAPI) : FreqTracker::FreqTracker(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI), m_deviceAPI(deviceAPI),
m_spectrumVis(SDR_RX_SCALEF),
m_basebandSampleRate(0) m_basebandSampleRate(0)
{ {
setObjectName(m_channelId); setObjectName(m_channelId);
m_thread = new QThread(this); m_thread = new QThread(this);
m_basebandSink = new FreqTrackerBaseband(); m_basebandSink = new FreqTrackerBaseband();
m_basebandSink->setSpectrumSink(&m_spectrumVis);
propagateMessageQueue(getInputMessageQueue()); propagateMessageQueue(getInputMessageQueue());
m_basebandSink->moveToThread(m_thread); m_basebandSink->moveToThread(m_thread);

View File

@ -24,6 +24,7 @@
#include <QMutex> #include <QMutex>
#include "dsp/basebandsamplesink.h" #include "dsp/basebandsamplesink.h"
#include "dsp/spectrumvis.h"
#include "channel/channelapi.h" #include "channel/channelapi.h"
#include "util/message.h" #include "util/message.h"
@ -109,6 +110,7 @@ public:
const QStringList& channelSettingsKeys, const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response); SWGSDRangel::SWGChannelSettings& response);
SpectrumVis *getSpectrumVis() { return &m_spectrumVis; }
uint32_t getSampleRate() const { return m_basebandSink->getSampleRate(); } uint32_t getSampleRate() const { return m_basebandSink->getSampleRate(); }
double getMagSq() const { return m_basebandSink->getMagSq(); } double getMagSq() const { return m_basebandSink->getMagSq(); }
bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); } bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); }
@ -131,6 +133,7 @@ private:
QThread *m_thread; QThread *m_thread;
FreqTrackerBaseband* m_basebandSink; FreqTrackerBaseband* m_basebandSink;
FreqTrackerSettings m_settings; FreqTrackerSettings m_settings;
SpectrumVis m_spectrumVis;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
static const int m_udpBlockSize; static const int m_udpBlockSize;
QNetworkAccessManager *m_networkManager; QNetworkAccessManager *m_networkManager;

View File

@ -28,6 +28,7 @@
#include "freqtrackersink.h" #include "freqtrackersink.h"
class DownChannelizer; class DownChannelizer;
class SpectrumVis;
class FreqTrackerBaseband : public QObject class FreqTrackerBaseband : public QObject
{ {
@ -61,6 +62,7 @@ public:
void reset(); void reset();
void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
void setSpectrumSink(SpectrumVis* spectrumSink) { m_spectrumVis = spectrumSink; m_sink.setSpectrumSink(spectrumSink); }
int getChannelSampleRate() const; int getChannelSampleRate() const;
void setBasebandSampleRate(int sampleRate); void setBasebandSampleRate(int sampleRate);
void setMessageQueueToInput(MessageQueue *messageQueue) { m_sink.setMessageQueueToInput(messageQueue); } void setMessageQueueToInput(MessageQueue *messageQueue) { m_sink.setMessageQueueToInput(messageQueue); }
@ -80,6 +82,7 @@ private:
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
FreqTrackerSettings m_settings; FreqTrackerSettings m_settings;
unsigned int m_basebandSampleRate; unsigned int m_basebandSampleRate;
SpectrumVis *m_spectrumVis;
QMutex m_mutex; QMutex m_mutex;
bool handleMessage(const Message& cmd); bool handleMessage(const Message& cmd);

View File

@ -91,6 +91,8 @@ bool FreqTrackerGUI::handleMessage(const Message& message)
m_basebandSampleRate = cfg.getSampleRate(); m_basebandSampleRate = cfg.getSampleRate();
int sinkSampleRate = m_basebandSampleRate / (1<<m_settings.m_log2Decim); int sinkSampleRate = m_basebandSampleRate / (1<<m_settings.m_log2Decim);
ui->channelSampleRateText->setText(tr("%1k").arg(QString::number(sinkSampleRate / 1000.0f, 'g', 5))); ui->channelSampleRateText->setText(tr("%1k").arg(QString::number(sinkSampleRate / 1000.0f, 'g', 5)));
ui->glSpectrum->setSampleRate(sinkSampleRate);
m_pllChannelMarker.setBandwidth(sinkSampleRate/1000);
if (sinkSampleRate > 1000) { if (sinkSampleRate > 1000) {
ui->rfBW->setMaximum(sinkSampleRate/100); ui->rfBW->setMaximum(sinkSampleRate/100);
@ -151,6 +153,8 @@ void FreqTrackerGUI::on_log2Decim_currentIndexChanged(int index)
m_settings.m_log2Decim = index < 0 ? 0 : index > 6 ? 6 : index; m_settings.m_log2Decim = index < 0 ? 0 : index > 6 ? 6 : index;
int sinkSampleRate = m_basebandSampleRate / (1<<m_settings.m_log2Decim); int sinkSampleRate = m_basebandSampleRate / (1<<m_settings.m_log2Decim);
ui->channelSampleRateText->setText(tr("%1k").arg(QString::number(sinkSampleRate / 1000.0f, 'g', 5))); ui->channelSampleRateText->setText(tr("%1k").arg(QString::number(sinkSampleRate / 1000.0f, 'g', 5)));
ui->glSpectrum->setSampleRate(sinkSampleRate);
m_pllChannelMarker.setBandwidth(sinkSampleRate/1000);
if (sinkSampleRate > 1000) { if (sinkSampleRate > 1000) {
ui->rfBW->setMaximum(sinkSampleRate/100); ui->rfBW->setMaximum(sinkSampleRate/100);
@ -288,6 +292,7 @@ FreqTrackerGUI::FreqTrackerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, B
m_pluginAPI(pluginAPI), m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet), m_deviceUISet(deviceUISet),
m_channelMarker(this), m_channelMarker(this),
m_pllChannelMarker(this),
m_basebandSampleRate(0), m_basebandSampleRate(0),
m_doApplySettings(true), m_doApplySettings(true),
m_squelchOpen(false), m_squelchOpen(false),
@ -300,6 +305,8 @@ FreqTrackerGUI::FreqTrackerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, B
m_freqTracker = reinterpret_cast<FreqTracker*>(rxChannel); m_freqTracker = reinterpret_cast<FreqTracker*>(rxChannel);
m_freqTracker->setMessageQueueToGUI(getInputMessageQueue()); m_freqTracker->setMessageQueueToGUI(getInputMessageQueue());
m_spectrumVis = m_freqTracker->getSpectrumVis();
m_spectrumVis->setGLSpectrum(ui->glSpectrum);
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms
@ -318,10 +325,23 @@ FreqTrackerGUI::FreqTrackerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, B
setTitleColor(m_channelMarker.getColor()); setTitleColor(m_channelMarker.getColor());
m_settings.setChannelMarker(&m_channelMarker); m_settings.setChannelMarker(&m_channelMarker);
m_settings.setSpectrumGUI(ui->spectrumGUI);
m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addChannelMarker(&m_channelMarker);
m_deviceUISet->addRollupWidget(this); m_deviceUISet->addRollupWidget(this);
ui->glSpectrum->setCenterFrequency(0);
m_pllChannelMarker.blockSignals(true);
m_pllChannelMarker.setColor(Qt::gray);
m_pllChannelMarker.setCenterFrequency(0);
m_pllChannelMarker.setBandwidth(35);
m_pllChannelMarker.setTitle("Tracker");
m_pllChannelMarker.setMovable(false);
m_pllChannelMarker.blockSignals(false);
m_pllChannelMarker.setVisible(true);
ui->glSpectrum->addChannelMarker(&m_pllChannelMarker);
ui->spectrumGUI->setBuddies(m_spectrumVis, ui->glSpectrum);
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
@ -445,6 +465,7 @@ void FreqTrackerGUI::tick()
int freq = m_freqTracker->getAvgDeltaFreq(); int freq = m_freqTracker->getAvgDeltaFreq();
QLocale loc; QLocale loc;
ui->trackingFrequencyText->setText(tr("%1 Hz").arg(loc.toString(freq))); ui->trackingFrequencyText->setText(tr("%1 Hz").arg(loc.toString(freq)));
m_pllChannelMarker.setCenterFrequency(freq);
if (m_settings.m_tracking) { if (m_settings.m_tracking) {
ui->tracking->setToolTip("Tracking on"); ui->tracking->setToolTip("Tracking on");

View File

@ -28,9 +28,9 @@
class PluginAPI; class PluginAPI;
class DeviceUISet; class DeviceUISet;
class FreqTracker; class FreqTracker;
class BasebandSampleSink; class BasebandSampleSink;
class SpectrumVis;
namespace Ui { namespace Ui {
class FreqTrackerGUI; class FreqTrackerGUI;
@ -57,11 +57,13 @@ private:
PluginAPI* m_pluginAPI; PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet; DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker; ChannelMarker m_channelMarker;
ChannelMarker m_pllChannelMarker;
FreqTrackerSettings m_settings; FreqTrackerSettings m_settings;
int m_basebandSampleRate; int m_basebandSampleRate;
bool m_doApplySettings; bool m_doApplySettings;
FreqTracker* m_freqTracker; FreqTracker* m_freqTracker;
SpectrumVis* m_spectrumVis;
bool m_squelchOpen; bool m_squelchOpen;
uint32_t m_tickCount; uint32_t m_tickCount;
MessageQueue m_inputMessageQueue; MessageQueue m_inputMessageQueue;

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>360</width> <width>400</width>
<height>327</height> <height>327</height>
</rect> </rect>
</property> </property>
@ -18,16 +18,10 @@
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>360</width> <width>400</width>
<height>100</height> <height>100</height>
</size> </size>
</property> </property>
<property name="maximumSize">
<size>
<width>360</width>
<height>16777215</height>
</size>
</property>
<property name="font"> <property name="font">
<font> <font>
<family>Liberation Sans</family> <family>Liberation Sans</family>
@ -45,7 +39,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>358</width> <width>401</width>
<height>140</height> <height>140</height>
</rect> </rect>
</property> </property>
@ -686,7 +680,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>150</y> <y>150</y>
<width>351</width> <width>391</width>
<height>171</height> <height>171</height>
</rect> </rect>
</property> </property>

View File

@ -23,7 +23,8 @@
#include "freqtrackersettings.h" #include "freqtrackersettings.h"
FreqTrackerSettings::FreqTrackerSettings() : FreqTrackerSettings::FreqTrackerSettings() :
m_channelMarker(0) m_channelMarker(0),
m_spectrumGUI(0)
{ {
resetToDefaults(); resetToDefaults();
} }
@ -57,6 +58,10 @@ QByteArray FreqTrackerSettings::serialize() const
s.writeS32(1, m_inputFrequencyOffset); s.writeS32(1, m_inputFrequencyOffset);
s.writeS32(2, m_rfBandwidth/100); s.writeS32(2, m_rfBandwidth/100);
s.writeU32(3, m_log2Decim); s.writeU32(3, m_log2Decim);
if (m_spectrumGUI) {
s.writeBlob(4, m_spectrumGUI->serialize());
}
s.writeS32(5, m_squelch); s.writeS32(5, m_squelch);
if (m_channelMarker) { if (m_channelMarker) {
@ -105,7 +110,13 @@ bool FreqTrackerSettings::deserialize(const QByteArray& data)
m_rfBandwidth = 100 * tmp; m_rfBandwidth = 100 * tmp;
d.readU32(3, &utmp, 0); d.readU32(3, &utmp, 0);
m_log2Decim = utmp > 6 ? 6 : utmp; m_log2Decim = utmp > 6 ? 6 : utmp;
d.readS32(4, &tmp, 20);
if (m_spectrumGUI)
{
d.readBlob(4, &bytetmp);
m_spectrumGUI->deserialize(bytetmp);
}
d.readS32(5, &tmp, -40); d.readS32(5, &tmp, -40);
m_squelch = tmp; m_squelch = tmp;
d.readBlob(6, &bytetmp); d.readBlob(6, &bytetmp);

View File

@ -41,6 +41,7 @@ struct FreqTrackerSettings
quint32 m_rgbColor; quint32 m_rgbColor;
QString m_title; QString m_title;
Serializable *m_channelMarker; Serializable *m_channelMarker;
Serializable *m_spectrumGUI;
float m_alphaEMA; //!< alpha factor for delta frequency EMA float m_alphaEMA; //!< alpha factor for delta frequency EMA
bool m_tracking; bool m_tracking;
TrackerType m_trackerType; TrackerType m_trackerType;
@ -58,6 +59,7 @@ struct FreqTrackerSettings
FreqTrackerSettings(); FreqTrackerSettings();
void resetToDefaults(); void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; }
QByteArray serialize() const; QByteArray serialize() const;
bool deserialize(const QByteArray& data); bool deserialize(const QByteArray& data);
}; };

View File

@ -22,6 +22,7 @@
#include "dsp/dspengine.h" #include "dsp/dspengine.h"
#include "dsp/dspcommands.h" #include "dsp/dspcommands.h"
#include "dsp/fftfilt.h" #include "dsp/fftfilt.h"
#include "dsp/spectrumvis.h"
#include "util/db.h" #include "util/db.h"
#include "util/stepfunctions.h" #include "util/stepfunctions.h"
#include "util/messagequeue.h" #include "util/messagequeue.h"
@ -33,6 +34,8 @@ FreqTrackerSink::FreqTrackerSink() :
m_channelSampleRate(48000), m_channelSampleRate(48000),
m_inputFrequencyOffset(0), m_inputFrequencyOffset(0),
m_sinkSampleRate(48000), m_sinkSampleRate(48000),
m_spectrumSink(nullptr),
m_sampleBufferCount(0),
m_squelchOpen(false), m_squelchOpen(false),
m_squelchGate(0), m_squelchGate(0),
m_magsqSum(0.0f), m_magsqSum(0.0f),
@ -52,6 +55,8 @@ FreqTrackerSink::FreqTrackerSink() :
m_timer = &DSPEngine::instance()->getMasterTimer(); m_timer = &DSPEngine::instance()->getMasterTimer();
#endif #endif
m_magsq = 0.0; m_magsq = 0.0;
m_sampleBufferSize = m_sinkSampleRate / 20; // 50 ms
m_sampleBuffer.resize(m_sampleBufferSize);
m_rrcFilter = new fftfilt(m_settings.m_rfBandwidth / m_sinkSampleRate, 2*1024); m_rrcFilter = new fftfilt(m_settings.m_rfBandwidth / m_sinkSampleRate, 2*1024);
m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain
@ -102,6 +107,7 @@ void FreqTrackerSink::processOneSample(Complex &ci)
{ {
fftfilt::cmplx *sideband; fftfilt::cmplx *sideband;
int n_out; int n_out;
m_sampleBuffer[m_sampleBufferCount++] = Sample(ci.real(), ci.imag());
if (m_settings.m_rrc) if (m_settings.m_rrc)
{ {
@ -168,7 +174,15 @@ void FreqTrackerSink::processOneSample(Complex &ci)
m_pll.feed(re, im); m_pll.feed(re, im);
} }
} }
} }
if (m_spectrumSink && (m_sampleBufferCount == m_sampleBufferSize))
{
m_spectrumSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), false);
m_sampleBufferCount = 0;
}
} }
Real FreqTrackerSink::getFrequency() const Real FreqTrackerSink::getFrequency() const
@ -215,6 +229,10 @@ void FreqTrackerSink::applyChannelSettings(int sinkSampleRate, int channelSample
if (useInterpolator) { if (useInterpolator) {
setInterpolator(); setInterpolator();
} }
m_sampleBufferSize = m_sinkSampleRate / 20; // 50 ms
m_sampleBuffer.resize(m_sampleBufferSize);
m_sampleBufferCount = 0;
} }
void FreqTrackerSink::applySettings(const FreqTrackerSettings& settings, bool force) void FreqTrackerSink::applySettings(const FreqTrackerSettings& settings, bool force)

View File

@ -36,6 +36,7 @@
#include "freqtrackersettings.h" #include "freqtrackersettings.h"
class SpectrumVis;
class fftfilt; class fftfilt;
class MessageQueue; class MessageQueue;
class QTimer; class QTimer;
@ -48,6 +49,7 @@ public:
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void setSpectrumSink(SpectrumVis* spectrumSink) { m_spectrumSink = spectrumSink; }
void applySettings(const FreqTrackerSettings& settings, bool force = false); void applySettings(const FreqTrackerSettings& settings, bool force = false);
void applyChannelSettings(int sinkSampleRate, int channelSampleRate, int channelFrequencyOffset, bool force = false); void applyChannelSettings(int sinkSampleRate, int channelSampleRate, int channelFrequencyOffset, bool force = false);
void setMessageQueueToInput(MessageQueue *messageQueue) { m_messageQueueToInput = messageQueue;} void setMessageQueueToInput(MessageQueue *messageQueue) { m_messageQueueToInput = messageQueue;}
@ -99,6 +101,11 @@ private:
int m_inputFrequencyOffset; int m_inputFrequencyOffset;
uint32_t m_sinkSampleRate; uint32_t m_sinkSampleRate;
SpectrumVis* m_spectrumSink;
SampleVector m_sampleBuffer;
unsigned int m_sampleBufferCount;
unsigned int m_sampleBufferSize;
NCOF m_nco; NCOF m_nco;
PhaseLockComplex m_pll; PhaseLockComplex m_pll;
FreqLockComplex m_fll; FreqLockComplex m_fll;
@ -138,4 +145,4 @@ private slots:
void tick(); void tick();
}; };
#endif // INCLUDE_FREQTRACKERSINK_H #endif // INCLUDE_FREQTRACKERSINK_H

View File

@ -98,4 +98,8 @@ This is the squelch threshold in dB. The average total power received in the sig
<h4>10: Squelch time gate</h4> <h4>10: Squelch time gate</h4>
Number of milliseconds following squelch gate opening after which the signal is declared open. 0 means squelch is declared open with no delay and is suitable for burst signals. The value can be varied in steps of 10 ms from 0 to 990 ms. Number of milliseconds following squelch gate opening after which the signal is declared open. 0 means squelch is declared open with no delay and is suitable for burst signals. The value can be varied in steps of 10 ms from 0 to 990 ms.
<h4>11: Channel spectrum</h4>
This is the spectrum display of the tracker channel. When the tracker is locked to the signal the center of the channel should fall almost in the middle of the signal spectrum (ideally in the middle when the tracker error is zero). Thus the locking can be followed dynamically and it can be more reliable than the lock indicator. A channel marker shows the tracker offset from the channel center frequency (tracker error). Its width is the tracker error tolerance but is hardly visible since it is 1/1000th of the channel width. Controls on the bottom of the panel are identical to the ones of the main spectrum display.