sdrangel/sdrbase/dsp/afsquelch.cpp

234 lines
5.7 KiB
C++
Raw Normal View History

///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Edouard Griffiths, F4EXB. //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <cmath>
#include "dsp/afsquelch.h"
AFSquelch::AFSquelch() :
m_nbAvg(128),
2015-09-12 10:34:57 -04:00
m_N(0),
m_sampleRate(0),
m_samplesProcessed(0),
m_maxPowerIndex(0),
m_nTones(2),
m_samplesAttack(0),
m_attackCount(0),
m_samplesDecay(0),
m_decayCount(0),
m_isOpen(false),
m_threshold(0.0)
{
2015-09-12 10:34:57 -04:00
m_k = new double[m_nTones];
m_coef = new double[m_nTones];
m_toneSet = new Real[m_nTones];
m_u0 = new double[m_nTones];
m_u1 = new double[m_nTones];
m_power = new double[m_nTones];
2017-05-12 08:41:27 -04:00
m_movingAverages.resize(m_nTones, MovingAverage<double>(m_nbAvg, 0.0));
2015-09-12 10:34:57 -04:00
m_toneSet[0] = 2000.0;
m_toneSet[1] = 10000.0;
}
AFSquelch::AFSquelch(unsigned int nbTones, const Real *tones) :
2015-09-12 10:34:57 -04:00
m_N(0),
m_nbAvg(0),
2015-09-12 10:34:57 -04:00
m_sampleRate(0),
m_samplesProcessed(0),
m_maxPowerIndex(0),
m_nTones(nbTones),
m_samplesAttack(0),
2015-09-12 10:34:57 -04:00
m_attackCount(0),
m_samplesDecay(0),
2015-09-12 10:34:57 -04:00
m_decayCount(0),
m_isOpen(false),
m_threshold(0.0)
{
2015-09-12 10:34:57 -04:00
m_k = new double[m_nTones];
m_coef = new double[m_nTones];
m_toneSet = new Real[m_nTones];
m_u0 = new double[m_nTones];
m_u1 = new double[m_nTones];
m_power = new double[m_nTones];
for (int j = 0; j < m_nTones; ++j)
{
2015-09-12 10:34:57 -04:00
m_toneSet[j] = tones[j];
}
}
AFSquelch::~AFSquelch()
{
2015-09-12 10:34:57 -04:00
delete[] m_k;
delete[] m_coef;
delete[] m_toneSet;
delete[] m_u0;
delete[] m_u1;
delete[] m_power;
}
2016-02-28 04:53:37 -05:00
void AFSquelch::setCoefficients(int N, unsigned int nbAvg, int _samplerate, int _samplesAttack, int _samplesDecay )
{
2016-02-28 04:53:37 -05:00
m_N = N; // save the basic parameters for use during analysis
m_nbAvg = nbAvg;
2015-09-12 10:34:57 -04:00
m_sampleRate = _samplerate;
m_samplesAttack = _samplesAttack;
m_samplesDecay = _samplesDecay;
2017-05-12 08:41:27 -04:00
m_movingAverages.resize(m_nTones, MovingAverage<double>(m_nbAvg, 1.0));
// for each of the frequencies (tones) of interest calculate
// k and the associated filter coefficient as per the Goertzel
// algorithm. Note: we are using a real value (as apposed to
// an integer as described in some references. k is retained
// for later display. The tone set is specified in the
// constructor. Notice that the resulting coefficients are
// independent of N.
2015-09-12 10:34:57 -04:00
for (int j = 0; j < m_nTones; ++j)
{
2015-09-12 10:34:57 -04:00
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);
}
}
// Analyze an input signal
bool AFSquelch::analyze(Real sample)
{
feedback(sample); // Goertzel feedback
2015-09-12 10:34:57 -04:00
m_samplesProcessed += 1;
2015-09-12 10:34:57 -04:00
if (m_samplesProcessed == m_N) // completed a block of N
{
feedForward(); // calculate the power at each tone
2015-09-12 10:34:57 -04:00
m_samplesProcessed = 0;
return true; // have a result
}
else
{
return false;
}
}
void AFSquelch::feedback(Real in)
{
double t;
// feedback for each tone
2015-09-12 10:34:57 -04:00
for (int j = 0; j < m_nTones; ++j)
{
2015-09-12 10:34:57 -04:00
t = m_u0[j];
m_u0[j] = in + (m_coef[j] * m_u0[j]) - m_u1[j];
m_u1[j] = t;
}
}
void AFSquelch::feedForward()
{
2015-09-12 10:34:57 -04:00
for (int j = 0; j < m_nTones; ++j)
{
2015-09-12 10:34:57 -04:00
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]);
2015-09-12 10:34:57 -04:00
m_u0[j] = m_u1[j] = 0.0; // reset for next block.
}
evaluate();
}
void AFSquelch::reset()
{
2015-09-12 10:34:57 -04:00
for (int j = 0; j < m_nTones; ++j)
{
2015-09-12 10:34:57 -04:00
m_power[j] = m_u0[j] = m_u1[j] = 0.0; // reset
m_movingAverages[j].fill(0.0);
}
2015-09-12 10:34:57 -04:00
m_samplesProcessed = 0;
m_maxPowerIndex = 0;
m_isOpen = false;
}
bool AFSquelch::evaluate()
{
2017-05-12 08:41:27 -04:00
double maxPower = 1.0;
double minPower;
int minIndex = 0, maxIndex = 0;
2015-09-12 10:34:57 -04:00
for (int j = 0; j < m_nTones; ++j)
{
if (m_movingAverages[j].sum() > maxPower)
{
maxPower = m_movingAverages[j].sum();
maxIndex = j;
}
}
minPower = maxPower;
2015-09-12 10:34:57 -04:00
for (int j = 0; j < m_nTones; ++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 = (minPower/maxPower < m_threshold) && (minIndex > maxIndex);
2017-05-12 08:41:27 -04:00
//qDebug("AFSquelch::evaluate: %g : %g", minPower/maxPower, m_threshold);
if (open)
{
if ((m_samplesAttack > 0) && (m_attackCount < m_samplesAttack))
{
2015-09-12 10:34:57 -04:00
m_isOpen = false;
m_attackCount++;
}
else
{
2015-09-12 10:34:57 -04:00
m_isOpen = true;
m_decayCount = 0;
}
}
else
{
if ((m_samplesDecay > 0) && (m_decayCount < m_samplesDecay))
{
2015-09-12 10:34:57 -04:00
m_isOpen = true;
m_decayCount++;
}
else
{
2015-09-12 10:34:57 -04:00
m_isOpen = false;
m_attackCount = 0;
}
}
return m_isOpen;
}
void AFSquelch::setThreshold(double threshold)
{
qDebug("AFSquelch::setThreshold: threshold: %f", threshold);
m_threshold = threshold;
}