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