/*************************************************************************** * Copyright (C) 2025 by Federico Amedeo Izzo IU2NUO, * * Niccolò Izzo IU2KIN * * Frederik Saraci IU2NRO * * Silvano Seva IU2KWO * * * * 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; either version 3 of the License, or * * (at your option) any later version. * * * * 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 for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see * ***************************************************************************/ #ifndef CTCSS_DETECTOR_H #define CTCSS_DETECTOR_H #ifndef __cplusplus #error This header is C++ only! #endif #include #include #include #include "core/goertzel.hpp" /* * Goertzel filter coefficients for the 50 CTCSS tones, computed for a sampling * frequency of 2kHz. */ static constexpr std::array < float, 50 > ctcssCoeffs2k = { 1.955858f, 1.952788f, 1.949194f, 1.945616f, 1.941767f, 1.937634f, 1.933200f, 1.928450f, 1.923195f, 1.917936f, 1.911955f, 1.907097f, 1.902113f, 1.895202f, 1.887648f, 1.879838f, 1.871331f, 1.862315f, 1.852531f, 1.842180f, 1.830988f, 1.818907f, 1.806161f, 1.792725f, 1.778002f, 1.762507f, 1.753218f, 1.745912f, 1.735704f, 1.728163f, 1.717311f, 1.709207f, 1.697685f, 1.688992f, 1.676770f, 1.667463f, 1.654514f, 1.644208f, 1.630498f, 1.619878f, 1.605010f, 1.593692f, 1.577610f, 1.548608f, 1.517951f, 1.503955f, 1.485167f, 1.450171f, 1.412880f, 1.395880f }; /** * CTCSS detector class, based on the modified Goertzel filter. * The detector is designed to be called recursively: each time the number of * accumulated samples reaches the detection window, the energy of each CTCSS * frequency is evaluated to find the one with the maximum value. If the ratio * between the peak energy and the average of all the energies is above the * threshold, the tone corresponding to the peak is considered detected. */ class CtcssDetector { public: /** * Constructor. * * @param coeffs: Goertzel filter coefficients. * @param window: size of the detection window. * @param threshold: detection threshold. */ CtcssDetector(const std::array< float, 50 >& coeffs, const uint32_t window, const float threshold) : threshold(threshold), window(window), goertzel(coeffs), totSamples(0), overThresh(false) { } /** * Destructor. */ ~CtcssDetector() { } /** * Update the internal states of the Goertzel filter. * * @param samples: pointer to new input values. * @param numSamples: number of new input values. */ void update(const int16_t *samples, const size_t numSamples) { goertzel.samples(samples, numSamples); totSamples += numSamples; if(totSamples >= window) { analyze(); goertzel.reset(); totSamples = 0; } } /** * Check if a CTCSS tone is being detected. * * @param toneIdx: index of the CTCSS tone. * @return true if the CTCSS tone is active. */ bool toneDetected(const uint8_t toneIdx) { return (toneIdx == activeToneIdx) && (overThresh == true); } /** * Reset detector state. */ void reset() { goertzel.reset(); totSamples = 0; overThresh = false; } private: /** * Compute signal power and determine if a CTCSS tone is active. */ void analyze() { float avgPower = 0.0f; float maxPower = 0.0f; for(size_t i = 0; i < 50; i++) { float power = goertzel.power(i); avgPower += power; if(maxPower < power) { maxPower = power; activeToneIdx = i; } } avgPower /= 50.0f; overThresh = (maxPower >= (avgPower * threshold)) ? true : false; } const float threshold; const uint32_t window; Goertzel< 50 > goertzel; uint32_t totSamples; uint8_t activeToneIdx; bool overThresh; }; #endif /* CTCSS_DETECTOR_H */