1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-15 12:51:49 -05:00

NFM demod: fixed AF squelch setting according to audio sample rate. Fixed discriminator. Added details to documentation.

This commit is contained in:
f4exb 2018-04-14 21:45:45 +02:00
parent 9196c6f0c7
commit 9c7026ae5e
5 changed files with 67 additions and 32 deletions

View File

@ -44,6 +44,7 @@ const QString NFMDemod::m_channelIdURI = "de.maintech.sdrangelove.channel.nfm";
const QString NFMDemod::m_channelId = "NFMDemod"; const QString NFMDemod::m_channelId = "NFMDemod";
static const double afSqTones[2] = {1000.0, 6000.0}; // {1200.0, 8000.0}; static const double afSqTones[2] = {1000.0, 6000.0}; // {1200.0, 8000.0};
static const double afSqTones_lowrate[2] = {1000.0, 3500.0};
const int NFMDemod::m_udpBlockSize = 512; const int NFMDemod::m_udpBlockSize = 512;
NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) :
@ -64,7 +65,7 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) :
m_magsqSum(0.0f), m_magsqSum(0.0f),
m_magsqPeak(0.0f), m_magsqPeak(0.0f),
m_magsqCount(0), m_magsqCount(0),
m_afSquelch(2, afSqTones), m_afSquelch(),
m_audioFifo(48000), m_audioFifo(48000),
m_settingsMutex(QMutex::Recursive) m_settingsMutex(QMutex::Recursive)
{ {
@ -78,9 +79,11 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) :
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue());
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
m_discriCompensation = (m_audioSampleRate/48000.0f);
m_discriCompensation *= sqrt(m_discriCompensation);
m_ctcssDetector.setCoefficients(m_audioSampleRate/16, m_audioSampleRate/8.0f); // 0.5s / 2 Hz resolution m_ctcssDetector.setCoefficients(m_audioSampleRate/16, m_audioSampleRate/8.0f); // 0.5s / 2 Hz resolution
m_afSquelch.setCoefficients(m_audioSampleRate/2000, 600, m_audioSampleRate, 200, 0); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay m_afSquelch.setCoefficients(m_audioSampleRate/2000, 600, m_audioSampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
m_lowpass.create(301, m_audioSampleRate, 250.0); m_lowpass.create(301, m_audioSampleRate, 250.0);
@ -137,7 +140,6 @@ Real angleDist(Real a, Real b)
void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused)))
{ {
Complex ci; Complex ci;
float f = (m_audioSampleRate / 48000.0f);
if (!m_running) { if (!m_running) {
return; return;
@ -176,7 +178,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto
if (m_settings.m_deltaSquelch) if (m_settings.m_deltaSquelch)
{ {
if (m_afSquelch.analyze(demod * f)) { if (m_afSquelch.analyze(demod * m_discriCompensation)) {
m_afSquelchOpen = m_afSquelch.evaluate() ? m_squelchGate + m_squelchDecay : 0; m_afSquelchOpen = m_afSquelch.evaluate() ? m_squelchGate + m_squelchDecay : 0;
} }
@ -219,7 +221,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto
{ {
if (m_settings.m_ctcssOn) if (m_settings.m_ctcssOn)
{ {
Real ctcss_sample = m_lowpass.filter(demod * f); Real ctcss_sample = m_lowpass.filter(demod * m_discriCompensation);
if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k
{ {
@ -259,7 +261,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto
} }
else else
{ {
demod = m_bandpass.filter(demod * f); demod = m_bandpass.filter(demod * m_discriCompensation);
Real squelchFactor = StepFunctions::smootherstep((Real) (m_squelchCount - m_squelchGate) / (Real) m_squelchDecay); Real squelchFactor = StepFunctions::smootherstep((Real) (m_squelchCount - m_squelchGate) / (Real) m_squelchDecay);
sample = demod * m_settings.m_volume * squelchFactor; sample = demod * m_settings.m_volume * squelchFactor;
} }
@ -404,6 +406,7 @@ void NFMDemod::applyAudioSampleRate(int sampleRate)
m_inputMessageQueue.push(channelConfigMsg); m_inputMessageQueue.push(channelConfigMsg);
m_settingsMutex.lock(); m_settingsMutex.lock();
m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.2f); m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.2f);
m_interpolatorDistanceRemain = 0; m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate;
@ -413,9 +416,20 @@ void NFMDemod::applyAudioSampleRate(int sampleRate)
m_squelchDecay = (sampleRate / 100); // decay is fixed at 10ms m_squelchDecay = (sampleRate / 100); // decay is fixed at 10ms
m_squelchCount = 0; // reset squelch open counter m_squelchCount = 0; // reset squelch open counter
m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution
m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
if (sampleRate < 16000) {
m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones_lowrate); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
} else {
m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
}
m_discriCompensation = (sampleRate/48000.0f);
m_discriCompensation *= sqrt(m_discriCompensation);
m_phaseDiscri.setFMScaling(sampleRate / static_cast<float>(m_settings.m_fmDeviation)); m_phaseDiscri.setFMScaling(sampleRate / static_cast<float>(m_settings.m_fmDeviation));
m_audioFifo.setSize(sampleRate); m_audioFifo.setSize(sampleRate);
m_settingsMutex.unlock(); m_settingsMutex.unlock();
m_audioSampleRate = sampleRate; m_audioSampleRate = sampleRate;

View File

@ -179,6 +179,7 @@ private:
int m_inputFrequencyOffset; int m_inputFrequencyOffset;
NFMDemodSettings m_settings; NFMDemodSettings m_settings;
uint32_t m_audioSampleRate; uint32_t m_audioSampleRate;
float m_discriCompensation; //!< compensation factor that depends on audio rate (1 for 48 kS/s)
bool m_running; bool m_running;
NCO m_nco; NCO m_nco;

View File

@ -24,7 +24,11 @@ Average total power in dB relative to a +/- 1.0 amplitude signal received in the
<h3>4: RF bandwidth</h3> <h3>4: RF bandwidth</h3>
This is the bandwidth in kHz of the channel signal before demodulation. It can be set in steps as 5, 6.25, 8.33, 10, 12.5, 15, 20, 25 and 40 kHz. This is the bandwidth in kHz of the channel signal before demodulation. It can be set in steps as 5, 6.25, 8.33, 10, 12.5, 15, 20, 25 and 40 kHz. The expected one side frequency deviation is 0.4 times the bandwidth.
&#9758; The demodulation is done at the channel sample rate which is guaranteed not to be lower than the requested audio sample rate but can possibly be equal to it. This means that for correct operaton in any case you must ensure that the sample rate of the audio device is not lower than the Nyquist rate required to process this channel bandwidth.
&#9758; The channel sample rate is always the baseband signal rate divided by an integer power of two so depending on the baseband sample rate obtained from the sampling device you could also guarantee a minimal channel bandwidth. For example with a 125 kS/s baseband sample rate and a 8 kS/s audio sample rate the channel sample rate cannot be lower than 125/8 = 15.625 kS/s (125/16 = 7.8125 kS/s is too small) which is still OK for 5 or 6.25 kHz channel bandwidths.
<h3>5: AF bandwidth</h3> <h3>5: AF bandwidth</h3>
@ -32,7 +36,7 @@ This is the bandwidth of the audio signal in kHz (i.e. after demodulation). It c
<h3>6: Volume</h3> <h3>6: Volume</h3>
This is the volume of the audio signal from 0.0 (mute) to 10.0 (maximum). It can be varied continuously in 0.1 steps using the dial button. This is the volume of the audio signal from 0.0 (mute) to 4.0 (maximum). It can be varied continuously in 0.1 steps using the dial button.
<h3>7: Delta/Level squelch</h3> <h3>7: Delta/Level squelch</h3>
@ -40,7 +44,21 @@ Use this button to toggle between AF (on) and RF power (off) based squelch.
<h3>8: Squelch threshold</h3> <h3>8: Squelch threshold</h3>
This is the squelch threshold in dB. The average total power received in the signal bandwidth before demodulation is compared to this value and the squelch input is open above this value. It can be varied continuously in 0.1 dB steps from 0.0 to -100.0 dB using the dial button. <h4>Power threshold mode</h4>
Case when the delta/Level squelch control (7) is off (power). This is the squelch threshold in dB. The average total power received in the signal bandwidth before demodulation is compared to this value and the squelch input is open above this value. It can be varied continuously in 0.1 dB steps from 0.0 to -100.0 dB using the dial button.
<h4>Audio frequency delta mode</h4>
Case when the delta/Level squelch control (7) is on (delta). In this mode the squelch compares the power of the demodulated audio signal in a low frequency band and a high frequency band. In the absence of signal the discriminator response is nearly flat and the power in the two bands is more or less balanced. In the presence of a signal the lower band will receive more power than the higher band. The squelch does the ratio of both powers and the squelch is opened if this ratio is lower than the threshold given in percent.
A ratio of 1 (100%) will always open the squelch and a ratio of 0 will always close it. The value can be varied to detect more distorted and thus weak signals towards the higher values. The button rotation runs from higher to lower as you turn it clockwise thus giving the same feel as in power mode. The best ratio for a standard NFM transmission is ~40%.
The distinct advantage of this type of squelch is that it guarantees the quality level of the audio signal (optimized for voice) thus remaining closed for too noisy signals received on marginal conditions or bursts of noise independently of the signal power.
&#9758; The signal used is the one before AF filtering and the bands are centered around 1000 Hz for the lower band and 6000 Hz for the higher band. This means that it will not work if your audio device runs at 8000 or 11025 Hz. You will need at least a 16000 Hz sample rate. Choose power squelch for lower audio rates.
&#9758; The chosen bands around 1000 and 6000 Hz are optimized for standard voice signals in the 300-3000 Hz range.
<h3>9: Squelch gate</h3> <h3>9: Squelch gate</h3>

View File

@ -20,14 +20,14 @@
#undef M_PI #undef M_PI
#define M_PI 3.14159265358979323846 #define M_PI 3.14159265358979323846
AFSquelch::AFSquelch(unsigned int nbTones, const double *tones) : AFSquelch::AFSquelch() :
m_nbAvg(128), m_nbAvg(128),
m_N(0), m_N(24),
m_sampleRate(0), m_sampleRate(48000),
m_samplesProcessed(0), m_samplesProcessed(0),
m_samplesAvgProcessed(0), m_samplesAvgProcessed(0),
m_maxPowerIndex(0), m_maxPowerIndex(0),
m_nTones(nbTones), m_nTones(2),
m_samplesAttack(0), m_samplesAttack(0),
m_attackCount(0), m_attackCount(0),
m_samplesDecay(0), m_samplesDecay(0),
@ -46,9 +46,9 @@ AFSquelch::AFSquelch(unsigned int nbTones, const double *tones) :
for (unsigned int j = 0; j < m_nTones; ++j) for (unsigned int j = 0; j < m_nTones; ++j)
{ {
m_toneSet[j] = tones[j]; m_toneSet[j] = j == 0 ? 1000.0 : 6000.0;
m_k[j] = ((double)m_N * m_toneSet[j]) / (double)m_sampleRate; m_k[j] = ((double)m_N * m_toneSet[j]) / (double) m_sampleRate;
m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double)m_sampleRate); m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double) m_sampleRate);
m_u0[j] = 0.0; m_u0[j] = 0.0;
m_u1[j] = 0.0; m_u1[j] = 0.0;
m_power[j] = 0.0; m_power[j] = 0.0;
@ -67,19 +67,19 @@ AFSquelch::~AFSquelch()
delete[] m_power; delete[] m_power;
} }
void AFSquelch::setCoefficients( void AFSquelch::setCoefficients(
unsigned int N, unsigned int N,
unsigned int nbAvg, unsigned int nbAvg,
unsigned int _samplerate, unsigned int sampleRate,
unsigned int _samplesAttack, unsigned int samplesAttack,
unsigned int _samplesDecay) unsigned int samplesDecay,
const double *tones)
{ {
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_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<double>(m_nbAvg, 0.0)); m_movingAverages.resize(m_nTones, MovingAverage<double>(m_nbAvg, 0.0));
m_samplesProcessed = 0; m_samplesProcessed = 0;
m_samplesAvgProcessed = 0; m_samplesAvgProcessed = 0;
@ -97,8 +97,10 @@ void AFSquelch::setCoefficients(
// for later display. The tone set is specified in the // for later display. The tone set is specified in the
// constructor. Notice that the resulting coefficients are // constructor. Notice that the resulting coefficients are
// independent of N. // independent of N.
for (unsigned int j = 0; j < m_nTones; ++j) for (unsigned int j = 0; j < m_nTones; ++j)
{ {
m_toneSet[j] = tones[j] < ((double) m_sampleRate) * 0.4 ? tones[j] : ((double) m_sampleRate) * 0.4; // guarantee 80% Nyquist rate
m_k[j] = ((double)m_N * m_toneSet[j]) / (double)m_sampleRate; m_k[j] = ((double)m_N * m_toneSet[j]) / (double)m_sampleRate;
m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double)m_sampleRate); m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double)m_sampleRate);
m_u0[j] = 0.0; m_u0[j] = 0.0;

View File

@ -26,18 +26,18 @@
*/ */
class SDRBASE_API AFSquelch { class SDRBASE_API AFSquelch {
public: public:
// allows user defined tone pair // constructor with default values
AFSquelch(unsigned int nbTones, AFSquelch();
const double *tones);
virtual ~AFSquelch(); virtual ~AFSquelch();
// setup the basic parameters and coefficients // setup the basic parameters and coefficients
void setCoefficients( void setCoefficients(
unsigned int N, //!< the algorithm "block" size unsigned int N, //!< the algorithm "block" size
unsigned int nbAvg, //!< averaging size unsigned int nbAvg, //!< averaging size
unsigned int SampleRate, //!< input signal sample rate unsigned int sampleRate, //!< input signal sample rate
unsigned int _samplesAttack, //!< number of results before squelch opens unsigned int samplesAttack, //!< number of results before squelch opens
unsigned int _samplesDecay); //!< number of results keeping squelch open unsigned int samplesDecay, //!< number of results keeping squelch open
const double *tones); //!< center frequency of tones tested
// set the detection threshold // set the detection threshold
void setThreshold(double _threshold); void setThreshold(double _threshold);