/////////////////////////////////////////////////////////////////////////////////// // 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 . // /////////////////////////////////////////////////////////////////////////////////// #include #include "dsp/afsquelch.h" AFSquelch::AFSquelch() : m_nbAvg(128), 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) { 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]; m_movingAverages.resize(m_nTones, MovingAverage(m_nbAvg, 0.0)); m_toneSet[0] = 2000.0; m_toneSet[1] = 10000.0; } 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(0), m_attackCount(0), m_samplesDecay(0), m_decayCount(0), m_isOpen(false), m_threshold(0.0) { 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) { m_toneSet[j] = tones[j]; } } AFSquelch::~AFSquelch() { delete[] m_k; delete[] m_coef; delete[] m_toneSet; delete[] m_u0; delete[] m_u1; delete[] m_power; } 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(m_nbAvg, 0.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. for (int j = 0; j < m_nTones; ++j) { 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 m_samplesProcessed += 1; if (m_samplesProcessed == m_N) // completed a block of N { feedForward(); // calculate the power at each tone m_samplesProcessed = 0; return true; // have a result } else { return false; } } void AFSquelch::feedback(Real in) { double t; // feedback for each tone for (int j = 0; j < m_nTones; ++j) { t = m_u0[j]; m_u0[j] = in + (m_coef[j] * m_u0[j]) - m_u1[j]; m_u1[j] = t; } } 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. } evaluate(); } 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; m_maxPowerIndex = 0; m_isOpen = false; } bool AFSquelch::evaluate() { double maxPower = 0.0; double minPower; int minIndex = 0, maxIndex = 0; for (int j = 0; j < m_nTones; ++j) { if (m_movingAverages[j].sum() > maxPower) { maxPower = m_movingAverages[j].sum(); maxIndex = j; } } minPower = maxPower; 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); if (open) { if ((m_samplesAttack > 0) && (m_attackCount < m_samplesAttack)) { m_isOpen = false; m_attackCount++; } else { m_isOpen = true; m_decayCount = 0; } } else { if ((m_samplesDecay > 0) && (m_decayCount < m_samplesDecay)) { m_isOpen = true; m_decayCount++; } else { m_isOpen = false; m_attackCount = 0; } } return m_isOpen; } void AFSquelch::setThreshold(double threshold) { qDebug("AFSquelch::setThreshold: threshold: %f", threshold); m_threshold = threshold; }