mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-12-23 01:55:48 -05:00
NFM demod: new discriminator and optional FM deviatoin based squelch
This commit is contained in:
parent
b805cc89c9
commit
2318419716
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -1,3 +1,9 @@
|
|||||||
|
sdrangel (3.3.0-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* NFM demod: new discriminator and optional FM deviatoin based squelch
|
||||||
|
|
||||||
|
-- Edouard Griffiths, F4EXB <f4exb06@gmail.com> Thu, 02 Mar 2017 23:14:18 +0100
|
||||||
|
|
||||||
sdrangel (3.2.0-1) unstable; urgency=medium
|
sdrangel (3.2.0-1) unstable; urgency=medium
|
||||||
|
|
||||||
* ATV demodulator for amateur Analog TV
|
* ATV demodulator for amateur Analog TV
|
||||||
|
@ -55,6 +55,7 @@ NFMDemod::NFMDemod() :
|
|||||||
m_config.m_afBandwidth = 3000;
|
m_config.m_afBandwidth = 3000;
|
||||||
m_config.m_fmDeviation = 2000;
|
m_config.m_fmDeviation = 2000;
|
||||||
m_config.m_squelchGate = 5; // 10s of ms at 48000 Hz sample rate. Corresponds to 2400 for AGC attack
|
m_config.m_squelchGate = 5; // 10s of ms at 48000 Hz sample rate. Corresponds to 2400 for AGC attack
|
||||||
|
m_config.m_deltaSquelch = false;
|
||||||
m_config.m_squelch = -30.0;
|
m_config.m_squelch = -30.0;
|
||||||
m_config.m_volume = 1.0;
|
m_config.m_volume = 1.0;
|
||||||
m_config.m_ctcssOn = false;
|
m_config.m_ctcssOn = false;
|
||||||
@ -68,7 +69,7 @@ NFMDemod::NFMDemod() :
|
|||||||
|
|
||||||
m_agcLevel = 1.0;
|
m_agcLevel = 1.0;
|
||||||
m_AGC.resize(m_squelchGate, m_agcLevel);
|
m_AGC.resize(m_squelchGate, m_agcLevel);
|
||||||
m_movingAverage.resize(16, 0);
|
m_movingAverage.resize(32, 0);
|
||||||
|
|
||||||
m_ctcssDetector.setCoefficients(3000, 6000.0); // 0.5s / 2 Hz resolution
|
m_ctcssDetector.setCoefficients(3000, 6000.0); // 0.5s / 2 Hz resolution
|
||||||
m_afSquelch.setCoefficients(24, 600, 48000.0, 200, 0); // 4000 Hz span, 250us, 100ms attack
|
m_afSquelch.setCoefficients(24, 600, 48000.0, 200, 0); // 4000 Hz span, 250us, 100ms attack
|
||||||
@ -87,6 +88,7 @@ void NFMDemod::configure(MessageQueue* messageQueue,
|
|||||||
int fmDeviation,
|
int fmDeviation,
|
||||||
Real volume,
|
Real volume,
|
||||||
int squelchGate,
|
int squelchGate,
|
||||||
|
bool deltaSquelch,
|
||||||
Real squelch,
|
Real squelch,
|
||||||
bool ctcssOn,
|
bool ctcssOn,
|
||||||
bool audioMute)
|
bool audioMute)
|
||||||
@ -96,6 +98,7 @@ void NFMDemod::configure(MessageQueue* messageQueue,
|
|||||||
fmDeviation,
|
fmDeviation,
|
||||||
volume,
|
volume,
|
||||||
squelchGate,
|
squelchGate,
|
||||||
|
deltaSquelch,
|
||||||
squelch,
|
squelch,
|
||||||
ctcssOn,
|
ctcssOn,
|
||||||
audioMute);
|
audioMute);
|
||||||
@ -154,8 +157,9 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto
|
|||||||
|
|
||||||
//double magsqRaw = m_AGC.getMagSq();
|
//double magsqRaw = m_AGC.getMagSq();
|
||||||
long double magsqRaw; // = ci.real()*ci.real() + c.imag()*c.imag();
|
long double magsqRaw; // = ci.real()*ci.real() + c.imag()*c.imag();
|
||||||
|
Real deviation;
|
||||||
|
|
||||||
Real demod = m_phaseDiscri.phaseDiscriminator3(ci, magsqRaw);
|
Real demod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation);
|
||||||
|
|
||||||
Real magsq = magsqRaw / (1<<30);
|
Real magsq = magsqRaw / (1<<30);
|
||||||
m_movingAverage.feed(magsq);
|
m_movingAverage.feed(magsq);
|
||||||
@ -174,20 +178,28 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto
|
|||||||
|
|
||||||
// AF processing
|
// AF processing
|
||||||
|
|
||||||
if (m_movingAverage.average() > m_squelchLevel)
|
if ( (m_running.m_deltaSquelch && ((deviation > m_squelchLevel) || (deviation < -m_squelchLevel))) ||
|
||||||
|
(!m_running.m_deltaSquelch && (m_movingAverage.average() < m_squelchLevel)) )
|
||||||
{
|
{
|
||||||
if (m_squelchCount < m_squelchGate)
|
if (m_squelchCount < m_squelchGate)
|
||||||
{
|
{
|
||||||
m_squelchCount++;
|
m_squelchCount = 0; // return to 0
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_squelchCount--; // grace period
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_squelchCount = 0;
|
if (m_squelchCount < m_squelchGate + 2)
|
||||||
|
{
|
||||||
|
m_squelchCount++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//squelchOpen = (getMag() > m_squelchLevel);
|
//squelchOpen = (getMag() > m_squelchLevel);
|
||||||
m_squelchOpen = m_squelchCount == m_squelchGate; // wait for AGC to stabilize
|
m_squelchOpen = m_squelchCount >= m_squelchGate; // wait for AGC to stabilize
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (m_afSquelch.analyze(demod))
|
if (m_afSquelch.analyze(demod))
|
||||||
@ -322,6 +334,7 @@ bool NFMDemod::handleMessage(const Message& cmd)
|
|||||||
m_config.m_fmDeviation = cfg.getFMDeviation();
|
m_config.m_fmDeviation = cfg.getFMDeviation();
|
||||||
m_config.m_volume = cfg.getVolume();
|
m_config.m_volume = cfg.getVolume();
|
||||||
m_config.m_squelchGate = cfg.getSquelchGate();
|
m_config.m_squelchGate = cfg.getSquelchGate();
|
||||||
|
m_config.m_deltaSquelch = cfg.getDeltaSquelch();
|
||||||
m_config.m_squelch = cfg.getSquelch();
|
m_config.m_squelch = cfg.getSquelch();
|
||||||
m_config.m_ctcssOn = cfg.getCtcssOn();
|
m_config.m_ctcssOn = cfg.getCtcssOn();
|
||||||
m_config.m_audioMute = cfg.getAudioMute();
|
m_config.m_audioMute = cfg.getAudioMute();
|
||||||
@ -332,8 +345,9 @@ bool NFMDemod::handleMessage(const Message& cmd)
|
|||||||
<< " m_afBandwidth: " << m_config.m_afBandwidth
|
<< " m_afBandwidth: " << m_config.m_afBandwidth
|
||||||
<< " m_fmDeviation: " << m_config.m_fmDeviation
|
<< " m_fmDeviation: " << m_config.m_fmDeviation
|
||||||
<< " m_volume: " << m_config.m_volume
|
<< " m_volume: " << m_config.m_volume
|
||||||
<< " m_squelchGate" << m_config.m_squelchGate
|
<< " m_squelchGate: " << m_config.m_squelchGate
|
||||||
<< " m_squelch: " << m_config.m_squelch
|
<< " m_deltaSquelch: " << m_config.m_deltaSquelch
|
||||||
|
<< " m_squelch: " << m_squelchLevel
|
||||||
<< " m_ctcssOn: " << m_config.m_ctcssOn
|
<< " m_ctcssOn: " << m_config.m_ctcssOn
|
||||||
<< " m_audioMute: " << m_config.m_audioMute;
|
<< " m_audioMute: " << m_config.m_audioMute;
|
||||||
|
|
||||||
@ -360,13 +374,13 @@ void NFMDemod::apply()
|
|||||||
m_interpolator.create(16, m_config.m_inputSampleRate, m_config.m_rfBandwidth / 2.2);
|
m_interpolator.create(16, m_config.m_inputSampleRate, m_config.m_rfBandwidth / 2.2);
|
||||||
m_interpolatorDistanceRemain = 0;
|
m_interpolatorDistanceRemain = 0;
|
||||||
m_interpolatorDistance = (Real) m_config.m_inputSampleRate / (Real) m_config.m_audioSampleRate;
|
m_interpolatorDistance = (Real) m_config.m_inputSampleRate / (Real) m_config.m_audioSampleRate;
|
||||||
m_phaseDiscri.setFMScaling(m_config.m_rfBandwidth / (float) m_config.m_fmDeviation);
|
m_phaseDiscri.setFMScaling((2.0f*m_config.m_rfBandwidth) / (float) m_config.m_fmDeviation);
|
||||||
m_settingsMutex.unlock();
|
m_settingsMutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_config.m_fmDeviation != m_running.m_fmDeviation)
|
if (m_config.m_fmDeviation != m_running.m_fmDeviation)
|
||||||
{
|
{
|
||||||
m_phaseDiscri.setFMScaling(m_config.m_rfBandwidth / (float) m_config.m_fmDeviation);
|
m_phaseDiscri.setFMScaling((2.0f*m_config.m_rfBandwidth) / (float) m_config.m_fmDeviation);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((m_config.m_afBandwidth != m_running.m_afBandwidth) ||
|
if ((m_config.m_afBandwidth != m_running.m_afBandwidth) ||
|
||||||
@ -384,12 +398,16 @@ void NFMDemod::apply()
|
|||||||
m_squelchCount = 0; // reset squelch open counter
|
m_squelchCount = 0; // reset squelch open counter
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_config.m_squelch != m_running.m_squelch)
|
if ((m_config.m_squelch != m_running.m_squelch) ||
|
||||||
|
(m_config.m_deltaSquelch != m_running.m_deltaSquelch))
|
||||||
{
|
{
|
||||||
// input is a value in tenths of dB
|
if (m_config.m_deltaSquelch) { // input is a value in negative millis
|
||||||
m_squelchLevel = std::pow(10.0, m_config.m_squelch / 10.0);
|
m_squelchLevel = - m_config.m_squelch / 1000.0;
|
||||||
|
} else { // input is a value in centi-Bels
|
||||||
|
m_squelchLevel = std::pow(10.0, m_config.m_squelch / 100.0);
|
||||||
|
}
|
||||||
//m_squelchLevel *= m_squelchLevel;
|
//m_squelchLevel *= m_squelchLevel;
|
||||||
m_afSquelch.setThreshold(m_squelchLevel);
|
//m_afSquelch.setThreshold(m_squelchLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_running.m_inputSampleRate = m_config.m_inputSampleRate;
|
m_running.m_inputSampleRate = m_config.m_inputSampleRate;
|
||||||
@ -398,6 +416,7 @@ void NFMDemod::apply()
|
|||||||
m_running.m_afBandwidth = m_config.m_afBandwidth;
|
m_running.m_afBandwidth = m_config.m_afBandwidth;
|
||||||
m_running.m_fmDeviation = m_config.m_fmDeviation;
|
m_running.m_fmDeviation = m_config.m_fmDeviation;
|
||||||
m_running.m_squelchGate = m_config.m_squelchGate;
|
m_running.m_squelchGate = m_config.m_squelchGate;
|
||||||
|
m_running.m_deltaSquelch = m_config.m_deltaSquelch;
|
||||||
m_running.m_squelch = m_config.m_squelch;
|
m_running.m_squelch = m_config.m_squelch;
|
||||||
m_running.m_volume = m_config.m_volume;
|
m_running.m_volume = m_config.m_volume;
|
||||||
m_running.m_audioSampleRate = m_config.m_audioSampleRate;
|
m_running.m_audioSampleRate = m_config.m_audioSampleRate;
|
||||||
|
@ -46,6 +46,7 @@ public:
|
|||||||
int fmDeviation,
|
int fmDeviation,
|
||||||
Real volume,
|
Real volume,
|
||||||
int squelchGate,
|
int squelchGate,
|
||||||
|
bool deltaSquelch,
|
||||||
Real squelch,
|
Real squelch,
|
||||||
bool ctcssOn,
|
bool ctcssOn,
|
||||||
bool audioMute);
|
bool audioMute);
|
||||||
@ -92,6 +93,7 @@ private:
|
|||||||
int getFMDeviation() const { return m_fmDeviation; }
|
int getFMDeviation() const { return m_fmDeviation; }
|
||||||
Real getVolume() const { return m_volume; }\
|
Real getVolume() const { return m_volume; }\
|
||||||
int getSquelchGate() const { return m_squelchGate; }
|
int getSquelchGate() const { return m_squelchGate; }
|
||||||
|
bool getDeltaSquelch() const { return m_deltaSquelch; }
|
||||||
Real getSquelch() const { return m_squelch; }
|
Real getSquelch() const { return m_squelch; }
|
||||||
bool getCtcssOn() const { return m_ctcssOn; }
|
bool getCtcssOn() const { return m_ctcssOn; }
|
||||||
bool getAudioMute() const { return m_audioMute; }
|
bool getAudioMute() const { return m_audioMute; }
|
||||||
@ -101,11 +103,21 @@ private:
|
|||||||
int fmDeviation,
|
int fmDeviation,
|
||||||
Real volume,
|
Real volume,
|
||||||
int squelchGate,
|
int squelchGate,
|
||||||
|
bool deltaSquelch,
|
||||||
Real squelch,
|
Real squelch,
|
||||||
bool ctcssOn,
|
bool ctcssOn,
|
||||||
bool audioMute)
|
bool audioMute)
|
||||||
{
|
{
|
||||||
return new MsgConfigureNFMDemod(rfBandwidth, afBandwidth, fmDeviation, volume, squelchGate, squelch, ctcssOn, audioMute);
|
return new MsgConfigureNFMDemod(
|
||||||
|
rfBandwidth,
|
||||||
|
afBandwidth,
|
||||||
|
fmDeviation,
|
||||||
|
volume,
|
||||||
|
squelchGate,
|
||||||
|
deltaSquelch,
|
||||||
|
squelch,
|
||||||
|
ctcssOn,
|
||||||
|
audioMute);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -114,6 +126,7 @@ private:
|
|||||||
int m_fmDeviation;
|
int m_fmDeviation;
|
||||||
Real m_volume;
|
Real m_volume;
|
||||||
int m_squelchGate;
|
int m_squelchGate;
|
||||||
|
bool m_deltaSquelch;
|
||||||
Real m_squelch;
|
Real m_squelch;
|
||||||
bool m_ctcssOn;
|
bool m_ctcssOn;
|
||||||
bool m_audioMute;
|
bool m_audioMute;
|
||||||
@ -123,6 +136,7 @@ private:
|
|||||||
int fmDeviation,
|
int fmDeviation,
|
||||||
Real volume,
|
Real volume,
|
||||||
int squelchGate,
|
int squelchGate,
|
||||||
|
bool deltaSquelch,
|
||||||
Real squelch,
|
Real squelch,
|
||||||
bool ctcssOn,
|
bool ctcssOn,
|
||||||
bool audioMute) :
|
bool audioMute) :
|
||||||
@ -132,6 +146,7 @@ private:
|
|||||||
m_fmDeviation(fmDeviation),
|
m_fmDeviation(fmDeviation),
|
||||||
m_volume(volume),
|
m_volume(volume),
|
||||||
m_squelchGate(squelchGate),
|
m_squelchGate(squelchGate),
|
||||||
|
m_deltaSquelch(deltaSquelch),
|
||||||
m_squelch(squelch),
|
m_squelch(squelch),
|
||||||
m_ctcssOn(ctcssOn),
|
m_ctcssOn(ctcssOn),
|
||||||
m_audioMute(audioMute)
|
m_audioMute(audioMute)
|
||||||
@ -156,6 +171,7 @@ private:
|
|||||||
Real m_afBandwidth;
|
Real m_afBandwidth;
|
||||||
int m_fmDeviation;
|
int m_fmDeviation;
|
||||||
int m_squelchGate;
|
int m_squelchGate;
|
||||||
|
bool m_deltaSquelch;
|
||||||
Real m_squelch;
|
Real m_squelch;
|
||||||
Real m_volume;
|
Real m_volume;
|
||||||
bool m_ctcssOn;
|
bool m_ctcssOn;
|
||||||
@ -170,6 +186,7 @@ private:
|
|||||||
m_afBandwidth(-1),
|
m_afBandwidth(-1),
|
||||||
m_fmDeviation(1),
|
m_fmDeviation(1),
|
||||||
m_squelchGate(1),
|
m_squelchGate(1),
|
||||||
|
m_deltaSquelch(false),
|
||||||
m_squelch(0),
|
m_squelch(0),
|
||||||
m_volume(0),
|
m_volume(0),
|
||||||
m_ctcssOn(false),
|
m_ctcssOn(false),
|
||||||
|
@ -89,6 +89,7 @@ QByteArray NFMDemodGUI::serialize() const
|
|||||||
s.writeBool(9, ui->ctcssOn->isChecked());
|
s.writeBool(9, ui->ctcssOn->isChecked());
|
||||||
s.writeBool(10, ui->audioMute->isChecked());
|
s.writeBool(10, ui->audioMute->isChecked());
|
||||||
s.writeS32(11, ui->squelchGate->value());
|
s.writeS32(11, ui->squelchGate->value());
|
||||||
|
s.writeBool(12, ui->deltaSquelch->isChecked());
|
||||||
return s.final();
|
return s.final();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,6 +137,8 @@ bool NFMDemodGUI::deserialize(const QByteArray& data)
|
|||||||
ui->audioMute->setChecked(boolTmp);
|
ui->audioMute->setChecked(boolTmp);
|
||||||
d.readS32(11, &tmp, 5);
|
d.readS32(11, &tmp, 5);
|
||||||
ui->squelchGate->setValue(tmp);
|
ui->squelchGate->setValue(tmp);
|
||||||
|
d.readBool(12, &boolTmp, false);
|
||||||
|
ui->deltaSquelch->setChecked(boolTmp);
|
||||||
|
|
||||||
blockApplySettings(false);
|
blockApplySettings(false);
|
||||||
m_channelMarker.blockSignals(false);
|
m_channelMarker.blockSignals(false);
|
||||||
@ -208,9 +211,33 @@ void NFMDemodGUI::on_squelchGate_valueChanged(int value)
|
|||||||
applySettings();
|
applySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NFMDemodGUI::on_deltaSquelch_toggled(bool checked)
|
||||||
|
{
|
||||||
|
if (ui->deltaSquelch->isChecked())
|
||||||
|
{
|
||||||
|
ui->squelchText->setText(QString("%1").arg((-ui->squelch->value()) / 10.0, 0, 'f', 1));
|
||||||
|
ui->squelchText->setToolTip(tr("Squelch deviation threshold (%)"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ui->squelchText->setText(QString("%1").arg(ui->squelch->value() / 10.0, 0, 'f', 1));
|
||||||
|
ui->squelchText->setToolTip(tr("Squelch power threshold (dB)"));
|
||||||
|
}
|
||||||
|
applySettings();
|
||||||
|
}
|
||||||
|
|
||||||
void NFMDemodGUI::on_squelch_valueChanged(int value)
|
void NFMDemodGUI::on_squelch_valueChanged(int value)
|
||||||
{
|
{
|
||||||
|
if (ui->deltaSquelch->isChecked())
|
||||||
|
{
|
||||||
|
ui->squelchText->setText(QString("%1").arg(-value / 10.0, 0, 'f', 1));
|
||||||
|
ui->squelchText->setToolTip(tr("Squelch deviation threshold (%)"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
ui->squelchText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
|
ui->squelchText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
|
||||||
|
ui->squelchText->setToolTip(tr("Squelch power threshold (dB)"));
|
||||||
|
}
|
||||||
applySettings();
|
applySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,6 +338,9 @@ NFMDemodGUI::NFMDemodGUI(PluginAPI* pluginAPI, DeviceSourceAPI *deviceAPI, QWidg
|
|||||||
m_deviceAPI->addChannelMarker(&m_channelMarker);
|
m_deviceAPI->addChannelMarker(&m_channelMarker);
|
||||||
m_deviceAPI->addRollupWidget(this);
|
m_deviceAPI->addRollupWidget(this);
|
||||||
|
|
||||||
|
QChar delta = QChar(0x94, 0x03);
|
||||||
|
ui->deltaSquelch->setText(delta);
|
||||||
|
|
||||||
applySettings();
|
applySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,7 +377,8 @@ void NFMDemodGUI::applySettings()
|
|||||||
m_fmDev[ui->rfBW->currentIndex()],
|
m_fmDev[ui->rfBW->currentIndex()],
|
||||||
ui->volume->value() / 10.0f,
|
ui->volume->value() / 10.0f,
|
||||||
ui->squelchGate->value(), // in 10ths of ms
|
ui->squelchGate->value(), // in 10ths of ms
|
||||||
ui->squelch->value() / 10.0f,
|
ui->deltaSquelch->isChecked(),
|
||||||
|
ui->squelch->value(), // -1000 -> 0
|
||||||
ui->ctcssOn->isChecked(),
|
ui->ctcssOn->isChecked(),
|
||||||
ui->audioMute->isChecked());
|
ui->audioMute->isChecked());
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ private slots:
|
|||||||
void on_afBW_valueChanged(int value);
|
void on_afBW_valueChanged(int value);
|
||||||
void on_volume_valueChanged(int value);
|
void on_volume_valueChanged(int value);
|
||||||
void on_squelchGate_valueChanged(int value);
|
void on_squelchGate_valueChanged(int value);
|
||||||
|
void on_deltaSquelch_toggled(bool checked);
|
||||||
void on_squelch_valueChanged(int value);
|
void on_squelch_valueChanged(int value);
|
||||||
void on_ctcss_currentIndexChanged(int index);
|
void on_ctcss_currentIndexChanged(int index);
|
||||||
void on_ctcssOn_toggled(bool checked);
|
void on_ctcssOn_toggled(bool checked);
|
||||||
|
@ -53,7 +53,16 @@
|
|||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>3</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="margin">
|
<property name="leftMargin">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
<number>2</number>
|
<number>2</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
@ -390,6 +399,25 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="ButtonSwitch" name="deltaSquelch">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>24</width>
|
||||||
|
<height>24</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Toggle frequency deviation (on) or channel power (off) based squelch</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>D</string>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QDial" name="squelch">
|
<widget class="QDial" name="squelch">
|
||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
@ -422,13 +450,13 @@
|
|||||||
<widget class="QLabel" name="squelchText">
|
<widget class="QLabel" name="squelchText">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>0</width>
|
<width>34</width>
|
||||||
<height>0</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>40</width>
|
<width>16777215</width>
|
||||||
<height>16777215</height>
|
<height>16777215</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
@ -596,6 +624,11 @@
|
|||||||
<header>gui/levelmeter.h</header>
|
<header>gui/levelmeter.h</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>ButtonSwitch</class>
|
||||||
|
<extends>QToolButton</extends>
|
||||||
|
<header>gui/buttonswitch.h</header>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../../../sdrbase/resources/res.qrc"/>
|
<include location="../../../sdrbase/resources/res.qrc"/>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
const PluginDescriptor NFMPlugin::m_pluginDescriptor = {
|
const PluginDescriptor NFMPlugin::m_pluginDescriptor = {
|
||||||
QString("NFM Demodulator"),
|
QString("NFM Demodulator"),
|
||||||
QString("3.2.0"),
|
QString("3.3.0"),
|
||||||
QString("(c) Edouard Griffiths, F4EXB"),
|
QString("(c) Edouard Griffiths, F4EXB"),
|
||||||
QString("https://github.com/f4exb/sdrangel"),
|
QString("https://github.com/f4exb/sdrangel"),
|
||||||
true,
|
true,
|
||||||
|
@ -51,6 +51,29 @@ public:
|
|||||||
return (std::atan2(d.imag(), d.real()) / M_PI) * m_fmScaling;
|
return (std::atan2(d.imag(), d.real()) / M_PI) * m_fmScaling;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discriminator with phase detection using atan2 and frequency by derivation.
|
||||||
|
* This yields a precise deviation to sample rate ratio: Sample rate => +/-1.0
|
||||||
|
*/
|
||||||
|
Real phaseDiscriminatorDelta(const Complex& sample, long double& magsq, Real& fmDev)
|
||||||
|
{
|
||||||
|
Real fltI = sample.real();
|
||||||
|
Real fltQ = sample.imag();
|
||||||
|
magsq = fltI*fltI + fltQ*fltQ;
|
||||||
|
|
||||||
|
Real curArg = atan2_approximation2((float) fltQ, (float) fltI);
|
||||||
|
fmDev = (curArg - m_prevArg) / M_PI;
|
||||||
|
m_prevArg = curArg;
|
||||||
|
|
||||||
|
if (fmDev < -1.0f) {
|
||||||
|
fmDev += 2.0f;
|
||||||
|
} else if (fmDev > 1.0f) {
|
||||||
|
fmDev -= 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmDev * m_fmScaling;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alternative without atan at the expense of a slight distorsion on very wideband signals
|
* Alternative without atan at the expense of a slight distorsion on very wideband signals
|
||||||
* http://www.embedded.com/design/configurable-systems/4212086/DSP-Tricks--Frequency-demodulation-algorithms-
|
* http://www.embedded.com/design/configurable-systems/4212086/DSP-Tricks--Frequency-demodulation-algorithms-
|
||||||
@ -73,14 +96,14 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Second alternative
|
* Second alternative
|
||||||
*/
|
*/
|
||||||
Real phaseDiscriminator3(const Complex& sample, long double& magsq)
|
Real phaseDiscriminator3(const Complex& sample, long double& magsq, Real& fltVal)
|
||||||
{
|
{
|
||||||
Real fltI = sample.real();
|
Real fltI = sample.real();
|
||||||
Real fltQ = sample.imag();
|
Real fltQ = sample.imag();
|
||||||
double fltNorm;
|
double fltNorm;
|
||||||
Real fltNormI;
|
Real fltNormI;
|
||||||
Real fltNormQ;
|
Real fltNormQ;
|
||||||
Real fltVal;
|
//Real fltVal;
|
||||||
|
|
||||||
magsq = fltI*fltI + fltQ*fltQ;
|
magsq = fltI*fltI + fltQ*fltQ;
|
||||||
fltNorm = std::sqrt(magsq);
|
fltNorm = std::sqrt(magsq);
|
||||||
@ -91,7 +114,7 @@ public:
|
|||||||
fltVal = m_fltPreviousI*(fltNormQ - m_fltPreviousQ2);
|
fltVal = m_fltPreviousI*(fltNormQ - m_fltPreviousQ2);
|
||||||
fltVal -= m_fltPreviousQ*(fltNormI - m_fltPreviousI2);
|
fltVal -= m_fltPreviousQ*(fltNormI - m_fltPreviousI2);
|
||||||
fltVal += 2.0f;
|
fltVal += 2.0f;
|
||||||
fltVal /= 2.0f; // normally it is /4
|
fltVal /= 4.0f; // normally it is /4
|
||||||
|
|
||||||
m_fltPreviousQ2 = m_fltPreviousQ;
|
m_fltPreviousQ2 = m_fltPreviousQ;
|
||||||
m_fltPreviousI2 = m_fltPreviousI;
|
m_fltPreviousI2 = m_fltPreviousI;
|
||||||
@ -110,7 +133,65 @@ private:
|
|||||||
Real m_fltPreviousQ;
|
Real m_fltPreviousQ;
|
||||||
Real m_fltPreviousI2;
|
Real m_fltPreviousI2;
|
||||||
Real m_fltPreviousQ2;
|
Real m_fltPreviousQ2;
|
||||||
|
Real m_prevArg;
|
||||||
|
|
||||||
|
float atan2_approximation1(float y, float x)
|
||||||
|
{
|
||||||
|
//http://pubs.opengroup.org/onlinepubs/009695399/functions/atan2.html
|
||||||
|
//Volkan SALMA
|
||||||
|
|
||||||
|
const float ONEQTR_PI = M_PI / 4.0;
|
||||||
|
const float THRQTR_PI = 3.0 * M_PI / 4.0;
|
||||||
|
float r, angle;
|
||||||
|
float abs_y = std::fabs(y) + 1e-10f; // kludge to prevent 0/0 condition
|
||||||
|
if ( x < 0.0f )
|
||||||
|
{
|
||||||
|
r = (x + abs_y) / (abs_y - x);
|
||||||
|
angle = THRQTR_PI;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r = (x - abs_y) / (x + abs_y);
|
||||||
|
angle = ONEQTR_PI;
|
||||||
|
}
|
||||||
|
angle += (0.1963f * r * r - 0.9817f) * r;
|
||||||
|
if ( y < 0.0f )
|
||||||
|
return( -angle ); // negate if in quad III or IV
|
||||||
|
else
|
||||||
|
return( angle );
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#define PI_FLOAT 3.14159265f
|
||||||
|
#define PIBY2_FLOAT 1.5707963f
|
||||||
|
// |error| < 0.005
|
||||||
|
float atan2_approximation2( float y, float x )
|
||||||
|
{
|
||||||
|
if ( x == 0.0f )
|
||||||
|
{
|
||||||
|
if ( y > 0.0f ) return PIBY2_FLOAT;
|
||||||
|
if ( y == 0.0f ) return 0.0f;
|
||||||
|
return -PIBY2_FLOAT;
|
||||||
|
}
|
||||||
|
float atan;
|
||||||
|
float z = y/x;
|
||||||
|
if ( std::fabs( z ) < 1.0f )
|
||||||
|
{
|
||||||
|
atan = z/(1.0f + 0.28f*z*z);
|
||||||
|
if ( x < 0.0f )
|
||||||
|
{
|
||||||
|
if ( y < 0.0f ) return atan - PI_FLOAT;
|
||||||
|
return atan + PI_FLOAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
atan = PIBY2_FLOAT - z/(z*z + 0.28f);
|
||||||
|
if ( y < 0.0f ) return atan - PI_FLOAT;
|
||||||
|
}
|
||||||
|
return atan;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* INCLUDE_DSP_PHASEDISCRI_H_ */
|
#endif /* INCLUDE_DSP_PHASEDISCRI_H_ */
|
||||||
|
@ -84,7 +84,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_2">
|
<widget class="QLabel" name="label_2">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string><html><head/><body><p>Version 3.2.0 - Copyright (C) 2015-2017 Edouard Griffiths, F4EXB. </p><p>Code at <a href="https://github.com/f4exb/sdrangel"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/f4exb/sdrangel</span></a></p><p>Many thanks to the original developers:</p><p>The osmocom developer team - especially horizon, Hoernchen &amp; tnt.</p><p>Christian Daniel from maintech GmbH.</p><p>John Greb (hexameron) for the contributions in <a href="https://github.com/hexameron/rtl-sdrangelove"><span style=" text-decoration: underline; color:#0000ff;">RTL-SDRangelove</span></a></p><p>The following rules apply to the SDRangel main application and libsdrbase:<br/>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; either version 2 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. You should have received a copy of the GNU General Public License along with this program. If not, see <a href="http://www.gnu.org/licenses/"><span style=" text-decoration: underline; color:#0000ff;">http://www.gnu.org/licenses/</span></a>.</p><p>For the license of installed plugins, look into the plugin list.</p></body></html></string>
|
<string><html><head/><body><p>Version 3.3.0 - Copyright (C) 2015-2017 Edouard Griffiths, F4EXB. </p><p>Code at <a href="https://github.com/f4exb/sdrangel"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/f4exb/sdrangel</span></a></p><p>Many thanks to the original developers:</p><p>The osmocom developer team - especially horizon, Hoernchen &amp; tnt.</p><p>Christian Daniel from maintech GmbH.</p><p>John Greb (hexameron) for the contributions in <a href="https://github.com/hexameron/rtl-sdrangelove"><span style=" text-decoration: underline; color:#0000ff;">RTL-SDRangelove</span></a></p><p>The following rules apply to the SDRangel main application and libsdrbase:<br/>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; either version 2 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. You should have received a copy of the GNU General Public License along with this program. If not, see <a href="http://www.gnu.org/licenses/"><span style=" text-decoration: underline; color:#0000ff;">http://www.gnu.org/licenses/</span></a>.</p><p>For the license of installed plugins, look into the plugin list.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="wordWrap">
|
<property name="wordWrap">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
@ -453,9 +453,9 @@ void MainWindow::createStatusBar()
|
|||||||
{
|
{
|
||||||
QString qtVersionStr = QString("Qt %1 ").arg(QT_VERSION_STR);
|
QString qtVersionStr = QString("Qt %1 ").arg(QT_VERSION_STR);
|
||||||
#if QT_VERSION >= 0x050400
|
#if QT_VERSION >= 0x050400
|
||||||
m_showSystemWidget = new QLabel("SDRangel v3.2.0 " + qtVersionStr + QSysInfo::prettyProductName(), this);
|
m_showSystemWidget = new QLabel("SDRangel v3.3.0 " + qtVersionStr + QSysInfo::prettyProductName(), this);
|
||||||
#else
|
#else
|
||||||
m_showSystemWidget = new QLabel("SDRangel v3.2.0 " + qtVersionStr, this);
|
m_showSystemWidget = new QLabel("SDRangel v3.3.0 " + qtVersionStr, this);
|
||||||
#endif
|
#endif
|
||||||
statusBar()->addPermanentWidget(m_showSystemWidget);
|
statusBar()->addPermanentWidget(m_showSystemWidget);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user