1
0
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:
f4exb 2015-09-13 11:56:14 +02:00
parent 34942340a3
commit 4246fb6381
8 changed files with 64 additions and 38 deletions

View File

@ -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

View File

@ -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;
}; };

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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>

View File

@ -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;

View File

@ -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;
} }