mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-31 13:00:26 -04:00 
			
		
		
		
	
		
			
	
	
		
			407 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			407 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|  | /*  fmd.c
 | ||
|  | 
 | ||
|  | This file is part of a program that implements a Software-Defined Radio. | ||
|  | 
 | ||
|  | Copyright (C) 2013, 2023 Warren Pratt, NR0V | ||
|  | Copyright (C) 2024 Edouard Griffiths, F4EXB Adapted to SDRangel | ||
|  | 
 | ||
|  | 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 2 | ||
|  | 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, write to the Free Software | ||
|  | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. | ||
|  | 
 | ||
|  | The author can be reached by email at | ||
|  | 
 | ||
|  | warren@wpratt.com | ||
|  | 
 | ||
|  | */ | ||
|  | 
 | ||
|  | #include "comm.hpp"
 | ||
|  | #include "iir.hpp"
 | ||
|  | #include "firmin.hpp"
 | ||
|  | #include "fcurve.hpp"
 | ||
|  | #include "fir.hpp"
 | ||
|  | #include "wcpAGC.hpp"
 | ||
|  | #include "fmd.hpp"
 | ||
|  | #include "RXA.hpp"
 | ||
|  | 
 | ||
|  | namespace WDSP { | ||
|  | 
 | ||
|  | void FMD::calc_fmd (FMD *a) | ||
|  | { | ||
|  |     // pll
 | ||
|  |     a->omega_min = TWOPI * a->fmin / a->rate; | ||
|  |     a->omega_max = TWOPI * a->fmax / a->rate; | ||
|  |     a->g1 = 1.0 - exp(-2.0 * a->omegaN * a->zeta / a->rate); | ||
|  |     a->g2 = -a->g1 + 2.0 * (1 - exp(-a->omegaN * a->zeta / a->rate) * cos(a->omegaN / a->rate * sqrt(1.0 - a->zeta * a->zeta))); | ||
|  |     a->phs = 0.0; | ||
|  |     a->fil_out = 0.0; | ||
|  |     a->omega = 0.0; | ||
|  |     a->pllpole = a->omegaN * sqrt(2.0 * a->zeta * a->zeta + 1.0 + sqrt((2.0 * a->zeta * a->zeta + 1.0) * (2.0 * a->zeta * a->zeta + 1.0) + 1)) / TWOPI; | ||
|  |     // dc removal
 | ||
|  |     a->mtau = exp(-1.0 / (a->rate * a->tau)); | ||
|  |     a->onem_mtau = 1.0 - a->mtau; | ||
|  |     a->fmdc = 0.0; | ||
|  |     // pll audio gain
 | ||
|  |     a->again = a->rate / (a->deviation * TWOPI); | ||
|  |     // CTCSS Removal
 | ||
|  |     a->sntch = SNOTCH::create_snotch(1, a->size, a->out, a->out, (int)a->rate, a->ctcss_freq, 0.0002); | ||
|  |     // detector limiter
 | ||
|  |     a->plim = WCPAGC::create_wcpagc ( | ||
|  |         1,                                          // run - always ON
 | ||
|  |         5,                                          // mode
 | ||
|  |         1,                                          // 0 for max(I,Q), 1 for envelope
 | ||
|  |         a->out,                                     // input buff pointer
 | ||
|  |         a->out,                                     // output buff pointer
 | ||
|  |         a->size,                                    // io_buffsize
 | ||
|  |         (int)a->rate,                               // sample rate
 | ||
|  |         0.001,                                      // tau_attack
 | ||
|  |         0.008,                                      // tau_decay
 | ||
|  |         4,                                          // n_tau
 | ||
|  |         a->lim_gain,                                // max_gain (sets threshold, initial value)
 | ||
|  |         1.0,                                        // var_gain / slope
 | ||
|  |         1.0,                                        // fixed_gain
 | ||
|  |         1.0,                                        // max_input
 | ||
|  |         0.9,                                        // out_targ
 | ||
|  |         0.250,                                      // tau_fast_backaverage
 | ||
|  |         0.004,                                      // tau_fast_decay
 | ||
|  |         4.0,                                        // pop_ratio
 | ||
|  |         0,                                          // hang_enable
 | ||
|  |         0.500,                                      // tau_hang_backmult
 | ||
|  |         0.500,                                      // hangtime
 | ||
|  |         2.000,                                      // hang_thresh
 | ||
|  |         0.100);                                     // tau_hang_decay
 | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::decalc_fmd (FMD *a) | ||
|  | { | ||
|  |     WCPAGC::destroy_wcpagc(a->plim); | ||
|  |     SNOTCH::destroy_snotch(a->sntch); | ||
|  | } | ||
|  | 
 | ||
|  | FMD* FMD::create_fmd( | ||
|  |     int run, | ||
|  |     int size, | ||
|  |     double* in, | ||
|  |     double* out, | ||
|  |     int rate, | ||
|  |     double deviation, | ||
|  |     double f_low, | ||
|  |     double f_high, | ||
|  |     double fmin, | ||
|  |     double fmax, | ||
|  |     double zeta, | ||
|  |     double omegaN, | ||
|  |     double tau, | ||
|  |     double afgain, | ||
|  |     int sntch_run, | ||
|  |     double ctcss_freq, | ||
|  |     int nc_de, | ||
|  |     int mp_de, | ||
|  |     int nc_aud, | ||
|  |     int mp_aud | ||
|  | ) | ||
|  | { | ||
|  |     FMD *a = new FMD; | ||
|  |     double* impulse; | ||
|  |     a->run = run; | ||
|  |     a->size = size; | ||
|  |     a->in = in; | ||
|  |     a->out = out; | ||
|  |     a->rate = (double)rate; | ||
|  |     a->deviation = deviation; | ||
|  |     a->f_low = f_low; | ||
|  |     a->f_high = f_high; | ||
|  |     a->fmin = fmin; | ||
|  |     a->fmax = fmax; | ||
|  |     a->zeta = zeta; | ||
|  |     a->omegaN = omegaN; | ||
|  |     a->tau = tau; | ||
|  |     a->afgain = afgain; | ||
|  |     a->sntch_run = sntch_run; | ||
|  |     a->ctcss_freq = ctcss_freq; | ||
|  |     a->nc_de = nc_de; | ||
|  |     a->mp_de = mp_de; | ||
|  |     a->nc_aud = nc_aud; | ||
|  |     a->mp_aud = mp_aud; | ||
|  |     a->lim_run = 0; | ||
|  |     a->lim_pre_gain = 0.4; | ||
|  |     a->lim_gain = 2.5; | ||
|  |     calc_fmd (a); | ||
|  |     // de-emphasis filter
 | ||
|  |     a->audio = new double[a->size * 2]; // (double *) malloc0 (a->size * sizeof (complex));
 | ||
|  |     impulse = FCurve::fc_impulse (a->nc_de, a->f_low, a->f_high, +20.0 * log10(a->f_high / a->f_low), 0.0, 1, a->rate, 1.0 / (2.0 * a->size), 0, 0); | ||
|  |     a->pde = FIRCORE::create_fircore (a->size, a->audio, a->out, a->nc_de, a->mp_de, impulse); | ||
|  |     delete[] (impulse); | ||
|  |     // audio filter
 | ||
|  |     impulse = FIR::fir_bandpass(a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size)); | ||
|  |     a->paud = FIRCORE::create_fircore (a->size, a->out, a->out, a->nc_aud, a->mp_aud, impulse); | ||
|  |     delete[] (impulse); | ||
|  |     return a; | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::destroy_fmd (FMD *a) | ||
|  | { | ||
|  |     FIRCORE::destroy_fircore (a->paud); | ||
|  |     FIRCORE::destroy_fircore (a->pde); | ||
|  |     delete[] (a->audio); | ||
|  |     decalc_fmd (a); | ||
|  |     delete (a); | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::flush_fmd (FMD *a) | ||
|  | { | ||
|  |     memset (a->audio, 0, a->size * sizeof (dcomplex)); | ||
|  |     FIRCORE::flush_fircore (a->pde); | ||
|  |     FIRCORE::flush_fircore (a->paud); | ||
|  |     a->phs = 0.0; | ||
|  |     a->fil_out = 0.0; | ||
|  |     a->omega = 0.0; | ||
|  |     a->fmdc = 0.0; | ||
|  |     SNOTCH::flush_snotch (a->sntch); | ||
|  |     WCPAGC::flush_wcpagc (a->plim); | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::xfmd (FMD *a) | ||
|  | { | ||
|  |     if (a->run) | ||
|  |     { | ||
|  |         int i; | ||
|  |         double det, del_out; | ||
|  |         double vco[2], corr[2]; | ||
|  |         for (i = 0; i < a->size; i++) | ||
|  |         { | ||
|  |             // pll
 | ||
|  |             vco[0]  = cos (a->phs); | ||
|  |             vco[1]  = sin (a->phs); | ||
|  |             corr[0] = + a->in[2 * i + 0] * vco[0] + a->in[2 * i + 1] * vco[1]; | ||
|  |             corr[1] = - a->in[2 * i + 0] * vco[1] + a->in[2 * i + 1] * vco[0]; | ||
|  |             if ((corr[0] == 0.0) && (corr[1] == 0.0)) corr[0] = 1.0; | ||
|  |             det = atan2 (corr[1], corr[0]); | ||
|  |             del_out = a->fil_out; | ||
|  |             a->omega += a->g2 * det; | ||
|  |             if (a->omega < a->omega_min) a->omega = a->omega_min; | ||
|  |             if (a->omega > a->omega_max) a->omega = a->omega_max; | ||
|  |             a->fil_out = a->g1 * det + a->omega; | ||
|  |             a->phs += del_out; | ||
|  |             while (a->phs >= TWOPI) a->phs -= TWOPI; | ||
|  |             while (a->phs < 0.0) a->phs += TWOPI; | ||
|  |             // dc removal, gain, & demod output
 | ||
|  |             a->fmdc = a->mtau * a->fmdc + a->onem_mtau * a->fil_out; | ||
|  |             a->audio[2 * i + 0] = a->again * (a->fil_out - a->fmdc); | ||
|  |             a->audio[2 * i + 1] = a->audio[2 * i + 0]; | ||
|  |         } | ||
|  |         // de-emphasis
 | ||
|  |         FIRCORE::xfircore (a->pde); | ||
|  |         // audio filter
 | ||
|  |         FIRCORE::xfircore (a->paud); | ||
|  |         // CTCSS Removal
 | ||
|  |         SNOTCH::xsnotch (a->sntch); | ||
|  |         if (a->lim_run) | ||
|  |         { | ||
|  |             for (i = 0; i < 2 * a->size; i++) | ||
|  |                 a->out[i] *= a->lim_pre_gain; | ||
|  |             WCPAGC::xwcpagc (a->plim); | ||
|  |         } | ||
|  |     } | ||
|  |     else if (a->in != a->out) | ||
|  |         memcpy (a->out, a->in, a->size * sizeof (dcomplex)); | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::setBuffers_fmd (FMD *a, double* in, double* out) | ||
|  | { | ||
|  |     decalc_fmd (a); | ||
|  |     a->in = in; | ||
|  |     a->out = out; | ||
|  |     calc_fmd (a); | ||
|  |     FIRCORE::setBuffers_fircore (a->pde,  a->audio, a->out); | ||
|  |     FIRCORE::setBuffers_fircore (a->paud, a->out, a->out); | ||
|  |     WCPAGC::setBuffers_wcpagc (a->plim, a->out, a->out); | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::setSamplerate_fmd (FMD *a, int rate) | ||
|  | { | ||
|  |     double* impulse; | ||
|  |     decalc_fmd (a); | ||
|  |     a->rate = rate; | ||
|  |     calc_fmd (a); | ||
|  |     // de-emphasis filter
 | ||
|  |     impulse = FCurve::fc_impulse (a->nc_de, a->f_low, a->f_high, +20.0 * log10(a->f_high / a->f_low), 0.0, 1, a->rate, 1.0 / (2.0 * a->size), 0, 0); | ||
|  |     FIRCORE::setImpulse_fircore (a->pde, impulse, 1); | ||
|  |     delete[] (impulse); | ||
|  |     // audio filter
 | ||
|  |     impulse = FIR::fir_bandpass(a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size)); | ||
|  |     FIRCORE::setImpulse_fircore (a->paud, impulse, 1); | ||
|  |     delete[] (impulse); | ||
|  |     WCPAGC::setSamplerate_wcpagc (a->plim, (int)a->rate); | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::setSize_fmd (FMD *a, int size) | ||
|  | { | ||
|  |     double* impulse; | ||
|  |     decalc_fmd (a); | ||
|  |     delete[] (a->audio); | ||
|  |     a->size = size; | ||
|  |     calc_fmd (a); | ||
|  |     a->audio = new double[a->size * 2]; // (double *) malloc0 (a->size * sizeof (complex));
 | ||
|  |     // de-emphasis filter
 | ||
|  |     FIRCORE::destroy_fircore (a->pde); | ||
|  |     impulse = FCurve::fc_impulse (a->nc_de, a->f_low, a->f_high, +20.0 * log10(a->f_high / a->f_low), 0.0, 1, a->rate, 1.0 / (2.0 * a->size), 0, 0); | ||
|  |     a->pde = FIRCORE::create_fircore (a->size, a->audio, a->out, a->nc_de, a->mp_de, impulse); | ||
|  |     delete[] (impulse); | ||
|  |     // audio filter
 | ||
|  |     FIRCORE::destroy_fircore (a->paud); | ||
|  |     impulse = FIR::fir_bandpass(a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size)); | ||
|  |     a->paud = FIRCORE::create_fircore (a->size, a->out, a->out, a->nc_aud, a->mp_aud, impulse); | ||
|  |     delete[] (impulse); | ||
|  |     WCPAGC::setSize_wcpagc (a->plim, a->size); | ||
|  | } | ||
|  | 
 | ||
|  | /********************************************************************************************************
 | ||
|  | *                                                                                                       * | ||
|  | *                                           RXA Properties                                              * | ||
|  | *                                                                                                       * | ||
|  | ********************************************************************************************************/ | ||
|  | 
 | ||
|  | void FMD::SetFMDeviation (RXA& rxa, double deviation) | ||
|  | { | ||
|  |     FMD *a; | ||
|  |     rxa.csDSP.lock(); | ||
|  |     a = rxa.fmd.p; | ||
|  |     a->deviation = deviation; | ||
|  |     a->again = a->rate / (a->deviation * TWOPI); | ||
|  |     rxa.csDSP.unlock(); | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::SetCTCSSFreq (RXA& rxa, double freq) | ||
|  | { | ||
|  |     FMD *a; | ||
|  |     rxa.csDSP.lock(); | ||
|  |     a = rxa.fmd.p; | ||
|  |     a->ctcss_freq = freq; | ||
|  |     SNOTCH::SetSNCTCSSFreq (a->sntch, a->ctcss_freq); | ||
|  |     rxa.csDSP.unlock(); | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::SetCTCSSRun (RXA& rxa, int run) | ||
|  | { | ||
|  |     FMD *a; | ||
|  |     rxa.csDSP.lock(); | ||
|  |     a = rxa.fmd.p; | ||
|  |     a->sntch_run = run; | ||
|  |     SNOTCH::SetSNCTCSSRun (a->sntch, a->sntch_run); | ||
|  |     rxa.csDSP.unlock(); | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::SetFMNCde (RXA& rxa, int nc) | ||
|  | { | ||
|  |     FMD *a; | ||
|  |     double* impulse; | ||
|  |     rxa.csDSP.lock(); | ||
|  |     a = rxa.fmd.p; | ||
|  |     if (a->nc_de != nc) | ||
|  |     { | ||
|  |         a->nc_de = nc; | ||
|  |         impulse = FCurve::fc_impulse (a->nc_de, a->f_low, a->f_high, +20.0 * log10(a->f_high / a->f_low), 0.0, 1, a->rate, 1.0 / (2.0 * a->size), 0, 0); | ||
|  |         FIRCORE::setNc_fircore (a->pde, a->nc_de, impulse); | ||
|  |         delete[] (impulse); | ||
|  |     } | ||
|  |     rxa.csDSP.unlock(); | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::SetFMMPde (RXA& rxa, int mp) | ||
|  | { | ||
|  |     FMD *a; | ||
|  |     a = rxa.fmd.p; | ||
|  |     if (a->mp_de != mp) | ||
|  |     { | ||
|  |         a->mp_de = mp; | ||
|  |         FIRCORE::setMp_fircore (a->pde, a->mp_de); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::SetFMNCaud (RXA& rxa, int nc) | ||
|  | { | ||
|  |     FMD *a; | ||
|  |     double* impulse; | ||
|  |     rxa.csDSP.lock(); | ||
|  |     a = rxa.fmd.p; | ||
|  |     if (a->nc_aud != nc) | ||
|  |     { | ||
|  |         a->nc_aud = nc; | ||
|  |         impulse = FIR::fir_bandpass(a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size)); | ||
|  |         FIRCORE::setNc_fircore (a->paud, a->nc_aud, impulse); | ||
|  |         delete[] (impulse); | ||
|  |     } | ||
|  |     rxa.csDSP.unlock(); | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::SetFMMPaud (RXA& rxa, int mp) | ||
|  | { | ||
|  |     FMD *a; | ||
|  |     a = rxa.fmd.p; | ||
|  |     if (a->mp_aud != mp) | ||
|  |     { | ||
|  |         a->mp_aud = mp; | ||
|  |         FIRCORE::setMp_fircore (a->paud, a->mp_aud); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::SetFMLimRun (RXA& rxa, int run) | ||
|  | { | ||
|  |     FMD *a; | ||
|  |     a = rxa.fmd.p; | ||
|  |     rxa.csDSP.lock(); | ||
|  |     if (a->lim_run != run) | ||
|  |     { | ||
|  |         a->lim_run = run; | ||
|  |     } | ||
|  |     rxa.csDSP.unlock(); | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::SetFMLimGain (RXA& rxa, double gaindB) | ||
|  | { | ||
|  |     double gain = pow(10.0, gaindB / 20.0); | ||
|  |     FMD *a = rxa.fmd.p; | ||
|  |     rxa.csDSP.lock(); | ||
|  |     if (a->lim_gain != gain) | ||
|  |     { | ||
|  |         decalc_fmd(a); | ||
|  |         a->lim_gain = gain; | ||
|  |         calc_fmd(a); | ||
|  |     } | ||
|  |     rxa.csDSP.unlock(); | ||
|  | } | ||
|  | 
 | ||
|  | void FMD::SetFMAFFilter(RXA& rxa, double low, double high) | ||
|  | { | ||
|  |     FMD *a = rxa.fmd.p; | ||
|  |     double* impulse; | ||
|  |     rxa.csDSP.lock(); | ||
|  |     if (a->f_low != low || a->f_high != high) | ||
|  |     { | ||
|  |         a->f_low = low; | ||
|  |         a->f_high = high; | ||
|  |         // de-emphasis filter
 | ||
|  |         impulse = FCurve::fc_impulse (a->nc_de, a->f_low, a->f_high, +20.0 * log10(a->f_high / a->f_low), 0.0, 1, a->rate, 1.0 / (2.0 * a->size), 0, 0); | ||
|  |         FIRCORE::setImpulse_fircore (a->pde, impulse, 1); | ||
|  |         delete[] (impulse); | ||
|  |         // audio filter
 | ||
|  |         impulse = FIR::fir_bandpass (a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size)); | ||
|  |         FIRCORE::setImpulse_fircore (a->paud, impulse, 1); | ||
|  |         delete[] (impulse); | ||
|  |     } | ||
|  |     rxa.csDSP.unlock(); | ||
|  | } | ||
|  | 
 | ||
|  | } // namespace WDSP
 |