NFM demodulation without using atan and smooth squelch with AGC suppressing most clicks on low level signals and hiss on carrier tails. Only useful modulation comes through

This commit is contained in:
f4exb 2015-05-14 17:19:06 +02:00
parent 51396e01ac
commit 8948928ca0
4 changed files with 39 additions and 7 deletions

View File

@ -80,6 +80,7 @@ Done since the fork
- Added AM demod so now you can listen to air traffic!
- Added the possibility to change the brightness and/or color of the grid.
- Make the low cutoff frequency of the SSB filter variable so it can be used for CW also.
- NFM demodulation without using atan and smooth squelch with AGC suppressing most clicks on low level signals and hiss on carrier tails. Only useful modulation comes through.
=====
To Do

View File

@ -18,26 +18,33 @@ public:
m_squelch(false),
m_fill(0),
m_cutoff(0),
m_clip(0),
m_moving_average()
{}
SimpleAGC(int historySize, Real initial, Real cutoff) :
SimpleAGC(int historySize, Real initial, Real cutoff=0, Real clip=0) :
m_squelch(false),
m_fill(initial),
m_cutoff(cutoff),
m_clip(clip),
m_moving_average(historySize, initial)
{}
void resize(int historySize, Real initial, Real cutoff)
void resize(int historySize, Real initial, Real cutoff=0, Real clip=0)
{
m_fill = initial;
m_cutoff = cutoff;
m_clip = clip;
m_moving_average.resize(historySize, initial);
}
Real getValue()
{
return m_moving_average.average();
if (m_moving_average.average() > m_clip) {
return m_moving_average.average();
} else {
return m_clip;
}
}
void feed(Real value)
@ -60,10 +67,11 @@ public:
}
private:
bool m_squelch;
Real m_fill;
Real m_cutoff;
MovingAverage m_moving_average;
bool m_squelch; // open for processing
Real m_fill; // refill average at this level
Real m_cutoff; // consider samples only above this level
Real m_clip; // never go below this level
MovingAverage m_moving_average; // Averaging engine. The stack length conditions the smoothness of AGC.
};

View File

@ -43,6 +43,8 @@ NFMDemod::NFMDemod(AudioFifo* audioFifo, SampleSink* sampleSink) :
m_audioBufferFill = 0;
m_movingAverage.resize(16, 0);
m_agcLevel = 0.003;
m_AGC.resize(4096, m_agcLevel, 0, 0.1*m_agcLevel);
}
NFMDemod::~NFMDemod()
@ -108,6 +110,9 @@ void NFMDemod::feed(SampleVector::const_iterator begin, SampleVector::const_iter
if(m_squelchState > 0) {
m_squelchState--;
m_AGC.feed(abs(ci));
ci *= (m_agcLevel / m_AGC.getValue());
// demod
/*
Real argument = arg(ci);
@ -115,9 +120,12 @@ void NFMDemod::feed(SampleVector::const_iterator begin, SampleVector::const_iter
m_lastArgument = argument;
*/
/*
// Original NFM
Complex d = conj(m_m1Sample) * ci;
Real demod = atan2(d.imag(), d.real());
demod /= M_PI;
*/
/*
Real argument1 = arg(ci);//atan2(ci.imag(), ci.real());
@ -126,6 +134,14 @@ void NFMDemod::feed(SampleVector::const_iterator begin, SampleVector::const_iter
m_lastSample = Complex(argument1, 0);
*/
// Alternative without atan - needs AGC
// http://www.embedded.com/design/configurable-systems/4212086/DSP-Tricks--Frequency-demodulation-algorithms-
Real ip = ci.real() - m_m2Sample.real();
Real qp = ci.imag() - m_m2Sample.imag();
Real h1 = m_m1Sample.real() * qp;
Real h2 = m_m1Sample.imag() * ip;
Real demod = (h1 - h2) * 10000;
m_m2Sample = m_m1Sample;
m_m1Sample = ci;
@ -133,15 +149,18 @@ void NFMDemod::feed(SampleVector::const_iterator begin, SampleVector::const_iter
demod = m_lowpass.filter(demod);
/*
if(demod < -1)
demod = -1;
else if(demod > 1)
demod = 1;
*/
demod *= m_running.m_volume;
sample = demod * 32700;
} else {
m_AGC.close();
sample = 0;
}

View File

@ -24,6 +24,7 @@
#include "dsp/interpolator.h"
#include "dsp/lowpass.h"
#include "dsp/movingaverage.h"
#include "dsp/agc.h"
#include "audio/audiofifo.h"
#include "util/message.h"
@ -119,6 +120,9 @@ private:
Complex m_m1Sample;
Complex m_m2Sample;
MovingAverage m_movingAverage;
SimpleAGC m_AGC;
Real m_agcLevel; // AGC will aim to this level
Real m_agcFloor; // AGC will not go below this level
AudioVector m_audioBuffer;
uint m_audioBufferFill;