1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-12-23 01:55:48 -05: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) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_spectrumVis(SDR_RX_SCALEF),
m_basebandSampleRate(0)
{
setObjectName(m_channelId);
m_thread = new QThread(this);
m_basebandSink = new FreqTrackerBaseband();
m_basebandSink->setSpectrumSink(&m_spectrumVis);
propagateMessageQueue(getInputMessageQueue());
m_basebandSink->moveToThread(m_thread);

View File

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

View File

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

View File

@ -91,6 +91,8 @@ bool FreqTrackerGUI::handleMessage(const Message& message)
m_basebandSampleRate = cfg.getSampleRate();
int sinkSampleRate = m_basebandSampleRate / (1<<m_settings.m_log2Decim);
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) {
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;
int sinkSampleRate = m_basebandSampleRate / (1<<m_settings.m_log2Decim);
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) {
ui->rfBW->setMaximum(sinkSampleRate/100);
@ -288,6 +292,7 @@ FreqTrackerGUI::FreqTrackerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, B
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_channelMarker(this),
m_pllChannelMarker(this),
m_basebandSampleRate(0),
m_doApplySettings(true),
m_squelchOpen(false),
@ -300,6 +305,8 @@ FreqTrackerGUI::FreqTrackerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, B
m_freqTracker = reinterpret_cast<FreqTracker*>(rxChannel);
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
@ -318,10 +325,23 @@ FreqTrackerGUI::FreqTrackerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, B
setTitleColor(m_channelMarker.getColor());
m_settings.setChannelMarker(&m_channelMarker);
m_settings.setSpectrumGUI(ui->spectrumGUI);
m_deviceUISet->addChannelMarker(&m_channelMarker);
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(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
@ -445,6 +465,7 @@ void FreqTrackerGUI::tick()
int freq = m_freqTracker->getAvgDeltaFreq();
QLocale loc;
ui->trackingFrequencyText->setText(tr("%1 Hz").arg(loc.toString(freq)));
m_pllChannelMarker.setCenterFrequency(freq);
if (m_settings.m_tracking) {
ui->tracking->setToolTip("Tracking on");

View File

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

View File

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

View File

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

View File

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

View File

@ -22,6 +22,7 @@
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/fftfilt.h"
#include "dsp/spectrumvis.h"
#include "util/db.h"
#include "util/stepfunctions.h"
#include "util/messagequeue.h"
@ -33,6 +34,8 @@ FreqTrackerSink::FreqTrackerSink() :
m_channelSampleRate(48000),
m_inputFrequencyOffset(0),
m_sinkSampleRate(48000),
m_spectrumSink(nullptr),
m_sampleBufferCount(0),
m_squelchOpen(false),
m_squelchGate(0),
m_magsqSum(0.0f),
@ -52,6 +55,8 @@ FreqTrackerSink::FreqTrackerSink() :
m_timer = &DSPEngine::instance()->getMasterTimer();
#endif
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_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;
int n_out;
m_sampleBuffer[m_sampleBufferCount++] = Sample(ci.real(), ci.imag());
if (m_settings.m_rrc)
{
@ -168,7 +174,15 @@ void FreqTrackerSink::processOneSample(Complex &ci)
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
@ -215,6 +229,10 @@ void FreqTrackerSink::applyChannelSettings(int sinkSampleRate, int channelSample
if (useInterpolator) {
setInterpolator();
}
m_sampleBufferSize = m_sinkSampleRate / 20; // 50 ms
m_sampleBuffer.resize(m_sampleBufferSize);
m_sampleBufferCount = 0;
}
void FreqTrackerSink::applySettings(const FreqTrackerSettings& settings, bool force)

View File

@ -36,6 +36,7 @@
#include "freqtrackersettings.h"
class SpectrumVis;
class fftfilt;
class MessageQueue;
class QTimer;
@ -48,6 +49,7 @@ public:
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 applyChannelSettings(int sinkSampleRate, int channelSampleRate, int channelFrequencyOffset, bool force = false);
void setMessageQueueToInput(MessageQueue *messageQueue) { m_messageQueueToInput = messageQueue;}
@ -99,6 +101,11 @@ private:
int m_inputFrequencyOffset;
uint32_t m_sinkSampleRate;
SpectrumVis* m_spectrumSink;
SampleVector m_sampleBuffer;
unsigned int m_sampleBufferCount;
unsigned int m_sampleBufferSize;
NCOF m_nco;
PhaseLockComplex m_pll;
FreqLockComplex m_fll;
@ -138,4 +145,4 @@ private slots:
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>
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.