mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-31 04:50:29 -04: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/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; | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user