mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-12-24 19:00:36 -05:00
NFM demod: revised AF squelch completely
This commit is contained in:
parent
34942340a3
commit
4246fb6381
@ -55,8 +55,8 @@ set(sdrbase_SOURCES
|
|||||||
sdrbase/audio/audiofifo.cpp
|
sdrbase/audio/audiofifo.cpp
|
||||||
sdrbase/audio/audiooutput.cpp
|
sdrbase/audio/audiooutput.cpp
|
||||||
|
|
||||||
sdrbase/dsp/agc.cpp
|
|
||||||
sdrbase/dsp/afsquelch.cpp
|
sdrbase/dsp/afsquelch.cpp
|
||||||
|
sdrbase/dsp/agc.cpp
|
||||||
sdrbase/dsp/channelizer.cpp
|
sdrbase/dsp/channelizer.cpp
|
||||||
sdrbase/dsp/channelmarker.cpp
|
sdrbase/dsp/channelmarker.cpp
|
||||||
sdrbase/dsp/ctcssdetector.cpp
|
sdrbase/dsp/ctcssdetector.cpp
|
||||||
@ -127,6 +127,7 @@ set(sdrbase_HEADERS
|
|||||||
include-gpl/audio/audiofifo.h
|
include-gpl/audio/audiofifo.h
|
||||||
include-gpl/audio/audiooutput.h
|
include-gpl/audio/audiooutput.h
|
||||||
|
|
||||||
|
include-gpl/dsp/afsquelch.h
|
||||||
include-gpl/dsp/channelizer.h
|
include-gpl/dsp/channelizer.h
|
||||||
include/dsp/channelmarker.h
|
include/dsp/channelmarker.h
|
||||||
include-gpl/dsp/complex.h
|
include-gpl/dsp/complex.h
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#define INCLUDE_GPL_DSP_AFSQUELCH_H_
|
#define INCLUDE_GPL_DSP_AFSQUELCH_H_
|
||||||
|
|
||||||
#include "dsp/dsptypes.h"
|
#include "dsp/dsptypes.h"
|
||||||
|
#include "dsp/movingaverage.h"
|
||||||
|
|
||||||
/** AFSquelch: AF squelch class based on the Modified Goertzel
|
/** AFSquelch: AF squelch class based on the Modified Goertzel
|
||||||
* algorithm.
|
* algorithm.
|
||||||
@ -28,26 +29,24 @@ public:
|
|||||||
AFSquelch();
|
AFSquelch();
|
||||||
// allows user defined tone pair
|
// allows user defined tone pair
|
||||||
AFSquelch(unsigned int nbTones,
|
AFSquelch(unsigned int nbTones,
|
||||||
const Real *tones,
|
const Real *tones);
|
||||||
int samplesAttack = 0,
|
|
||||||
int samplesDecay = 0);
|
|
||||||
virtual ~AFSquelch();
|
virtual ~AFSquelch();
|
||||||
|
|
||||||
// setup the basic parameters and coefficients
|
// setup the basic parameters and coefficients
|
||||||
void setCoefficients(
|
void setCoefficients(
|
||||||
int N, // the algorithm "block" size
|
int N, //!< the algorithm "block" size
|
||||||
int SampleRate, // input signal sample rate
|
unsigned int nbAvg, //!< averaging size
|
||||||
int _samplesAttack, // number of results before squelch opens
|
int SampleRate, //!< input signal sample rate
|
||||||
int _samplesDecay); // number of results keeping squelch open
|
int _samplesAttack, //!< number of results before squelch opens
|
||||||
|
int _samplesDecay); //!< number of results keeping squelch open
|
||||||
|
|
||||||
// set the detection threshold
|
// set the detection threshold
|
||||||
void setThreshold(double _threshold) {
|
void setThreshold(double _threshold);
|
||||||
m_threshold = _threshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
// analyze a sample set and optionally filter
|
// analyze a sample set and optionally filter
|
||||||
// the tone frequencies.
|
// the tone frequencies.
|
||||||
bool analyze(Real *sample); // input signal sample
|
bool analyze(Real sample); // input signal sample
|
||||||
|
bool evaluate(); // evaluate result
|
||||||
|
|
||||||
// get the tone set
|
// get the tone set
|
||||||
const Real *getToneSet() const
|
const Real *getToneSet() const
|
||||||
@ -64,9 +63,9 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
void feedback(Real sample);
|
void feedback(Real sample);
|
||||||
void feedForward();
|
void feedForward();
|
||||||
void evaluate();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
unsigned int m_nbAvg; //!< number of power samples taken for moving average
|
||||||
int m_N;
|
int m_N;
|
||||||
int m_sampleRate;
|
int m_sampleRate;
|
||||||
int m_samplesProcessed;
|
int m_samplesProcessed;
|
||||||
@ -84,6 +83,7 @@ private:
|
|||||||
double *m_u0;
|
double *m_u0;
|
||||||
double *m_u1;
|
double *m_u1;
|
||||||
double *m_power;
|
double *m_power;
|
||||||
|
std::vector<MovingAverage<Real> > m_movingAverages;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,6 +51,11 @@ public:
|
|||||||
return m_sum / (float) m_history.size();
|
return m_sum / (float) m_history.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Type sum() const
|
||||||
|
{
|
||||||
|
return m_sum;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::vector<Type> m_history;
|
std::vector<Type> m_history;
|
||||||
Type m_sum;
|
Type m_sum;
|
||||||
|
@ -33,7 +33,7 @@ MESSAGE_CLASS_DEFINITION(NFMDemod::MsgConfigureNFMDemod, Message)
|
|||||||
NFMDemod::NFMDemod() :
|
NFMDemod::NFMDemod() :
|
||||||
m_ctcssIndex(0),
|
m_ctcssIndex(0),
|
||||||
m_sampleCount(0),
|
m_sampleCount(0),
|
||||||
m_squelchOpen(false),
|
m_afSquelch(2, afSqTones),
|
||||||
m_audioFifo(4, 48000),
|
m_audioFifo(4, 48000),
|
||||||
m_settingsMutex(QMutex::Recursive)
|
m_settingsMutex(QMutex::Recursive)
|
||||||
{
|
{
|
||||||
@ -53,11 +53,11 @@ NFMDemod::NFMDemod() :
|
|||||||
m_audioBuffer.resize(1<<14);
|
m_audioBuffer.resize(1<<14);
|
||||||
m_audioBufferFill = 0;
|
m_audioBufferFill = 0;
|
||||||
|
|
||||||
m_movingAverage.resize(240, 0);
|
|
||||||
m_agcLevel = 1.0;
|
m_agcLevel = 1.0;
|
||||||
m_AGC.resize(240, m_agcLevel);
|
m_AGC.resize(240, m_agcLevel);
|
||||||
|
|
||||||
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, 1200, 48000.0, 4, 0); // 4000 Hz span, 250us
|
||||||
|
|
||||||
DSPEngine::instance()->addAudioSink(&m_audioFifo);
|
DSPEngine::instance()->addAudioSink(&m_audioFifo);
|
||||||
}
|
}
|
||||||
@ -114,6 +114,7 @@ Real angleDist(Real a, Real b)
|
|||||||
|
|
||||||
void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
|
void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
|
||||||
{
|
{
|
||||||
|
bool squelchOpen;
|
||||||
Complex ci;
|
Complex ci;
|
||||||
|
|
||||||
m_settingsMutex.lock();
|
m_settingsMutex.lock();
|
||||||
@ -169,7 +170,13 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto
|
|||||||
|
|
||||||
// AF processing
|
// AF processing
|
||||||
|
|
||||||
if (m_AGC.getAverage() > m_squelchLevel)
|
if (m_afSquelch.analyze(demod))
|
||||||
|
{
|
||||||
|
squelchOpen = m_afSquelch.evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (squelchOpen)
|
||||||
|
//if (m_AGC.getAverage() > m_squelchLevel)
|
||||||
{
|
{
|
||||||
if (m_running.m_ctcssOn)
|
if (m_running.m_ctcssOn)
|
||||||
{
|
{
|
||||||
@ -343,10 +350,9 @@ void NFMDemod::apply()
|
|||||||
if (m_config.m_squelch != m_running.m_squelch)
|
if (m_config.m_squelch != m_running.m_squelch)
|
||||||
{
|
{
|
||||||
// input is a power level in dB
|
// input is a power level in dB
|
||||||
// m_squelchLevel = pow(10.0, m_config.m_squelch / 10.0);
|
m_squelchLevel = pow(10.0, m_config.m_squelch / 10.0);
|
||||||
m_squelchLevel = pow(10.0, m_config.m_squelch / 20.0); // to magnitude
|
|
||||||
|
|
||||||
//m_squelchLevel *= m_squelchLevel;
|
//m_squelchLevel *= m_squelchLevel;
|
||||||
|
m_afSquelch.setThreshold(m_squelchLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_running.m_inputSampleRate = m_config.m_inputSampleRate;
|
m_running.m_inputSampleRate = m_config.m_inputSampleRate;
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
#include "dsp/interpolator.h"
|
#include "dsp/interpolator.h"
|
||||||
#include "dsp/lowpass.h"
|
#include "dsp/lowpass.h"
|
||||||
#include "dsp/bandpass.h"
|
#include "dsp/bandpass.h"
|
||||||
#include "dsp/movingaverage.h"
|
#include "dsp/afsquelch.h"
|
||||||
#include "dsp/agc.h"
|
#include "dsp/agc.h"
|
||||||
#include "dsp/ctcssdetector.h"
|
#include "dsp/ctcssdetector.h"
|
||||||
#include "dsp/afsquelch.h"
|
#include "dsp/afsquelch.h"
|
||||||
@ -155,14 +155,12 @@ private:
|
|||||||
int m_sampleCount;
|
int m_sampleCount;
|
||||||
|
|
||||||
double m_squelchLevel;
|
double m_squelchLevel;
|
||||||
//int m_squelchState;
|
|
||||||
bool m_squelchOpen;
|
|
||||||
|
|
||||||
Real m_lastArgument;
|
Real m_lastArgument;
|
||||||
Complex m_m1Sample;
|
Complex m_m1Sample;
|
||||||
Complex m_m2Sample;
|
Complex m_m2Sample;
|
||||||
MovingAverage<Real> m_movingAverage;
|
|
||||||
MagAGC m_AGC;
|
MagAGC m_AGC;
|
||||||
|
AFSquelch m_afSquelch;
|
||||||
Real m_agcLevel; // AGC will aim to this level
|
Real m_agcLevel; // AGC will aim to this level
|
||||||
Real m_agcFloor; // AGC will not go below this level
|
Real m_agcFloor; // AGC will not go below this level
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@
|
|||||||
<item row="4" column="4">
|
<item row="4" column="4">
|
||||||
<widget class="QSlider" name="squelch">
|
<widget class="QSlider" name="squelch">
|
||||||
<property name="minimum">
|
<property name="minimum">
|
||||||
<number>-100</number>
|
<number>-30</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
@ -140,7 +140,7 @@
|
|||||||
<number>1</number>
|
<number>1</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="value">
|
<property name="value">
|
||||||
<number>-40</number>
|
<number>-20</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
|
@ -131,7 +131,7 @@ bool AirspyInput::start(int device)
|
|||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_sampleFifo.setSize(96000 * 4))
|
if (!m_sampleFifo.setSize(1<<19))
|
||||||
{
|
{
|
||||||
qCritical("AirspyInput::start: could not allocate SampleFifo");
|
qCritical("AirspyInput::start: could not allocate SampleFifo");
|
||||||
return false;
|
return false;
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "dsp/afsquelch.h"
|
#include "dsp/afsquelch.h"
|
||||||
|
|
||||||
AFSquelch::AFSquelch() :
|
AFSquelch::AFSquelch() :
|
||||||
|
m_nbAvg(128),
|
||||||
m_N(0),
|
m_N(0),
|
||||||
m_sampleRate(0),
|
m_sampleRate(0),
|
||||||
m_samplesProcessed(0),
|
m_samplesProcessed(0),
|
||||||
@ -36,20 +37,22 @@ AFSquelch::AFSquelch() :
|
|||||||
m_u0 = new double[m_nTones];
|
m_u0 = new double[m_nTones];
|
||||||
m_u1 = new double[m_nTones];
|
m_u1 = new double[m_nTones];
|
||||||
m_power = new double[m_nTones];
|
m_power = new double[m_nTones];
|
||||||
|
m_movingAverages.resize(m_nTones, MovingAverage<Real>(m_nbAvg, 0.0));
|
||||||
|
|
||||||
m_toneSet[0] = 2000.0;
|
m_toneSet[0] = 2000.0;
|
||||||
m_toneSet[1] = 10000.0;
|
m_toneSet[1] = 10000.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
AFSquelch::AFSquelch(unsigned int nbTones, const Real *tones, int samplesAttack, int samplesDecay) :
|
AFSquelch::AFSquelch(unsigned int nbTones, const Real *tones) :
|
||||||
m_N(0),
|
m_N(0),
|
||||||
|
m_nbAvg(0),
|
||||||
m_sampleRate(0),
|
m_sampleRate(0),
|
||||||
m_samplesProcessed(0),
|
m_samplesProcessed(0),
|
||||||
m_maxPowerIndex(0),
|
m_maxPowerIndex(0),
|
||||||
m_nTones(nbTones),
|
m_nTones(nbTones),
|
||||||
m_samplesAttack(samplesAttack),
|
m_samplesAttack(0),
|
||||||
m_attackCount(0),
|
m_attackCount(0),
|
||||||
m_samplesDecay(samplesDecay),
|
m_samplesDecay(0),
|
||||||
m_decayCount(0),
|
m_decayCount(0),
|
||||||
m_isOpen(false),
|
m_isOpen(false),
|
||||||
m_threshold(0.0)
|
m_threshold(0.0)
|
||||||
@ -79,12 +82,14 @@ AFSquelch::~AFSquelch()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AFSquelch::setCoefficients(int _N, int _samplerate, int _samplesAttack, int _samplesDecay )
|
void AFSquelch::setCoefficients(int _N, unsigned int nbAvg, int _samplerate, int _samplesAttack, int _samplesDecay )
|
||||||
{
|
{
|
||||||
m_N = _N; // save the basic parameters for use during analysis
|
m_N = _N; // save the basic parameters for use during analysis
|
||||||
|
m_nbAvg = nbAvg;
|
||||||
m_sampleRate = _samplerate;
|
m_sampleRate = _samplerate;
|
||||||
m_samplesAttack = _samplesAttack;
|
m_samplesAttack = _samplesAttack;
|
||||||
m_samplesDecay = _samplesDecay;
|
m_samplesDecay = _samplesDecay;
|
||||||
|
m_movingAverages.resize(m_nTones, MovingAverage<Real>(m_nbAvg, 0.0));
|
||||||
|
|
||||||
// for each of the frequencies (tones) of interest calculate
|
// for each of the frequencies (tones) of interest calculate
|
||||||
// k and the associated filter coefficient as per the Goertzel
|
// k and the associated filter coefficient as per the Goertzel
|
||||||
@ -102,10 +107,10 @@ void AFSquelch::setCoefficients(int _N, int _samplerate, int _samplesAttack, int
|
|||||||
|
|
||||||
|
|
||||||
// Analyze an input signal
|
// Analyze an input signal
|
||||||
bool AFSquelch::analyze(Real *sample)
|
bool AFSquelch::analyze(Real sample)
|
||||||
{
|
{
|
||||||
|
|
||||||
feedback(*sample); // Goertzel feedback
|
feedback(sample); // Goertzel feedback
|
||||||
m_samplesProcessed += 1;
|
m_samplesProcessed += 1;
|
||||||
|
|
||||||
if (m_samplesProcessed == m_N) // completed a block of N
|
if (m_samplesProcessed == m_N) // completed a block of N
|
||||||
@ -140,6 +145,7 @@ void AFSquelch::feedForward()
|
|||||||
for (int j = 0; j < m_nTones; ++j)
|
for (int j = 0; j < m_nTones; ++j)
|
||||||
{
|
{
|
||||||
m_power[j] = (m_u0[j] * m_u0[j]) + (m_u1[j] * m_u1[j]) - (m_coef[j] * m_u0[j] * m_u1[j]);
|
m_power[j] = (m_u0[j] * m_u0[j]) + (m_u1[j] * m_u1[j]) - (m_coef[j] * m_u0[j] * m_u1[j]);
|
||||||
|
m_movingAverages[j].feed(m_power[j]);
|
||||||
m_u0[j] = m_u1[j] = 0.0; // reset for next block.
|
m_u0[j] = m_u1[j] = 0.0; // reset for next block.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,6 +158,7 @@ void AFSquelch::reset()
|
|||||||
for (int j = 0; j < m_nTones; ++j)
|
for (int j = 0; j < m_nTones; ++j)
|
||||||
{
|
{
|
||||||
m_power[j] = m_u0[j] = m_u1[j] = 0.0; // reset
|
m_power[j] = m_u0[j] = m_u1[j] = 0.0; // reset
|
||||||
|
m_movingAverages[j].fill(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_samplesProcessed = 0;
|
m_samplesProcessed = 0;
|
||||||
@ -160,7 +167,7 @@ void AFSquelch::reset()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AFSquelch::evaluate()
|
bool AFSquelch::evaluate()
|
||||||
{
|
{
|
||||||
double maxPower = 0.0;
|
double maxPower = 0.0;
|
||||||
double minPower;
|
double minPower;
|
||||||
@ -168,8 +175,9 @@ void AFSquelch::evaluate()
|
|||||||
|
|
||||||
for (int j = 0; j < m_nTones; ++j)
|
for (int j = 0; j < m_nTones; ++j)
|
||||||
{
|
{
|
||||||
if (m_power[j] > maxPower) {
|
if (m_movingAverages[j].sum() > maxPower)
|
||||||
maxPower = m_power[j];
|
{
|
||||||
|
maxPower = m_movingAverages[j].sum();
|
||||||
maxIndex = j;
|
maxIndex = j;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,14 +186,14 @@ void AFSquelch::evaluate()
|
|||||||
|
|
||||||
for (int j = 0; j < m_nTones; ++j)
|
for (int j = 0; j < m_nTones; ++j)
|
||||||
{
|
{
|
||||||
if (m_power[j] < minPower) {
|
if (m_movingAverages[j].sum() < minPower) {
|
||||||
minPower = m_power[j];
|
minPower = m_movingAverages[j].sum();
|
||||||
minIndex = j;
|
minIndex = j;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// principle is to open if power is uneven because noise gives even power
|
// principle is to open if power is uneven because noise gives even power
|
||||||
bool open = ((maxPower - minPower) > m_threshold); // && (minIndex > maxIndex);
|
bool open = (minPower/maxPower < m_threshold) && (minIndex > maxIndex);
|
||||||
|
|
||||||
if (open)
|
if (open)
|
||||||
{
|
{
|
||||||
@ -213,4 +221,12 @@ void AFSquelch::evaluate()
|
|||||||
m_attackCount = 0;
|
m_attackCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return m_isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AFSquelch::setThreshold(double threshold)
|
||||||
|
{
|
||||||
|
qDebug("AFSquelch::setThreshold: threshold: %f", threshold);
|
||||||
|
m_threshold = threshold;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user