mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-11-04 05:30:32 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			378 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			378 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*  ssql.c
 | 
						|
 | 
						|
This file is part of a program that implements a Software-Defined Radio.
 | 
						|
 | 
						|
Copyright (C) 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@pratt.one
 | 
						|
 | 
						|
*/
 | 
						|
 | 
						|
#include "comm.hpp"
 | 
						|
#include "cblock.hpp"
 | 
						|
#include "ssql.hpp"
 | 
						|
#include "dbqlp.hpp"
 | 
						|
 | 
						|
namespace WDSP {
 | 
						|
 | 
						|
/********************************************************************************************************
 | 
						|
*                                                                                                       *
 | 
						|
*                                      Frequency to Voltage Converter                                   *
 | 
						|
*                                                                                                       *
 | 
						|
********************************************************************************************************/
 | 
						|
 | 
						|
FTOV::FTOV(
 | 
						|
    int _run,
 | 
						|
    int _size,
 | 
						|
    int _rate,
 | 
						|
    int _rsize,
 | 
						|
    double _fmax,
 | 
						|
    float* _in,
 | 
						|
    float* _out
 | 
						|
)
 | 
						|
{
 | 
						|
    run = _run;
 | 
						|
    size = _size;
 | 
						|
    rate = _rate;
 | 
						|
    rsize = _rsize;
 | 
						|
    fmax = _fmax;
 | 
						|
    in = _in;
 | 
						|
    out = _out;
 | 
						|
    eps = 0.01;
 | 
						|
    ring.resize(rsize);
 | 
						|
    rptr = 0;
 | 
						|
    inlast = 0.0;
 | 
						|
    rcount = 0;
 | 
						|
    div = fmax * 2.0 * rsize / rate;                // fmax * 2 = zero-crossings/sec
 | 
						|
                                                                // rsize / rate = sec of data in ring
 | 
						|
                                                                // product is # zero-crossings in ring at fmax
 | 
						|
}
 | 
						|
 | 
						|
void FTOV::flush()
 | 
						|
{
 | 
						|
    std::fill(ring.begin(), ring.end(), 0);
 | 
						|
    rptr = 0;
 | 
						|
    rcount = 0;
 | 
						|
    inlast = 0.0;
 | 
						|
}
 | 
						|
 | 
						|
void FTOV::execute()
 | 
						|
{
 | 
						|
    // 'ftov' does frequency to voltage conversion looking only at zero crossings of an
 | 
						|
    //     AC (DC blocked) signal, i.e., ignoring signal amplitude.
 | 
						|
    if (run)
 | 
						|
    {
 | 
						|
        if (ring[rptr] == 1)                              // if current ring location is a '1' ...
 | 
						|
        {
 | 
						|
            rcount--;                                        // decrement the count
 | 
						|
            ring[rptr] = 0;                               // set the location to '0'
 | 
						|
        }
 | 
						|
        if ((inlast * in[0] < 0.0) &&                     // different signs mean zero-crossing
 | 
						|
            (fabs (inlast - in[0]) > eps))
 | 
						|
        {
 | 
						|
            ring[rptr] = 1;                               // set the ring location to '1'
 | 
						|
            rcount++;                                        // increment the count
 | 
						|
        }
 | 
						|
        if (++rptr == rsize) rptr = 0;                 // increment and wrap the pointer as needed
 | 
						|
        out[0] = (float) std::min (1.0, (double)rcount / div);      // calculate the output sample
 | 
						|
        inlast = in[size - 1];                         // save the last input sample for next buffer
 | 
						|
        for (int i = 1; i < size; i++)
 | 
						|
        {
 | 
						|
            if (ring[rptr] == 1)                          // if current ring location is '1' ...
 | 
						|
            {
 | 
						|
                rcount--;                                    // decrement the count
 | 
						|
                ring[rptr] = 0;                           // set the location to '0'
 | 
						|
            }
 | 
						|
            if ((in[i - 1] * in[i] < 0.0) &&              // different signs mean zero-crossing
 | 
						|
                (fabs (in[i - 1] - in[i]) > eps))
 | 
						|
            {
 | 
						|
                ring[rptr] = 1;                           // set the ring location to '1'
 | 
						|
                rcount++;                                    // increment the count
 | 
						|
            }
 | 
						|
            if (++rptr == rsize) rptr = 0;             // increment and wrap the pointer as needed
 | 
						|
            out[i] = (float) std::min(1.0, (double)rcount / div);   // calculate the output sample
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
/*******************************************************************************************************/
 | 
						|
/********************************** END Frequency to Voltage Converter *********************************/
 | 
						|
 | 
						|
 | 
						|
 | 
						|
void SSQL::compute_slews()
 | 
						|
{
 | 
						|
    double delta;
 | 
						|
    double theta;
 | 
						|
    delta = PI / (double) ntup;
 | 
						|
    theta = 0.0;
 | 
						|
    for (int i = 0; i <= ntup; i++)
 | 
						|
    {
 | 
						|
        cup[i] = muted_gain + (1.0 - muted_gain) * 0.5 * (1.0 - cos(theta));
 | 
						|
        theta += delta;
 | 
						|
    }
 | 
						|
    delta = PI / (double)ntdown;
 | 
						|
    theta = 0.0;
 | 
						|
    for (int i = 0; i <= ntdown; i++)
 | 
						|
    {
 | 
						|
        cdown[i] = muted_gain + (1.0 - muted_gain) * 0.5 * (1.0 + cos(theta));
 | 
						|
        theta += delta;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void SSQL::calc()
 | 
						|
{
 | 
						|
    b1.resize(size * 2);
 | 
						|
    dcbl = new CBL(1, size, in, b1.data(), 0, rate, 0.02);
 | 
						|
    ibuff.resize(size);
 | 
						|
    ftovbuff.resize(size);
 | 
						|
    cvtr = new FTOV(
 | 
						|
        1,
 | 
						|
        size,
 | 
						|
        rate,
 | 
						|
        ftov_rsize,
 | 
						|
        ftov_fmax,
 | 
						|
        ibuff.data(),
 | 
						|
        ftovbuff.data()
 | 
						|
    );
 | 
						|
    lpbuff.resize(size);
 | 
						|
    filt = new DBQLP(
 | 
						|
        1,
 | 
						|
        size,
 | 
						|
        ftovbuff.data(),
 | 
						|
        lpbuff.data(),
 | 
						|
        rate,
 | 
						|
        11.3,
 | 
						|
        1.0,
 | 
						|
        1.0,
 | 
						|
        1
 | 
						|
    );
 | 
						|
    wdbuff.resize(size);
 | 
						|
    tr_signal.resize(size);
 | 
						|
    // window detector
 | 
						|
    wdmult = exp (-1.0 / (rate * wdtau));
 | 
						|
    wdaverage = 0.0;
 | 
						|
    // trigger
 | 
						|
    tr_voltage = tr_thresh;
 | 
						|
    mute_mult = 1.0 - exp (-1.0 / (rate * tr_tau_mute));
 | 
						|
    unmute_mult = 1.0 - exp (-1.0 / (rate * tr_tau_unmute));
 | 
						|
    // level change
 | 
						|
    ntup = (int)(tup * rate);
 | 
						|
    ntdown = (int)(tdown * rate);
 | 
						|
    cup.resize(ntup + 1);
 | 
						|
    cdown.resize(ntdown + 1);
 | 
						|
    compute_slews();
 | 
						|
    // control
 | 
						|
    state = SSQLState::MUTED;
 | 
						|
    count = 0;
 | 
						|
}
 | 
						|
 | 
						|
void SSQL::decalc()
 | 
						|
{
 | 
						|
    delete filt;
 | 
						|
    delete cvtr;
 | 
						|
    delete dcbl;
 | 
						|
}
 | 
						|
 | 
						|
SSQL::SSQL(
 | 
						|
    int _run,
 | 
						|
    int _size,
 | 
						|
    float* _in,
 | 
						|
    float* _out,
 | 
						|
    int _rate,
 | 
						|
    double _tup,
 | 
						|
    double _tdown,
 | 
						|
    double _muted_gain,
 | 
						|
    double _tau_mute,
 | 
						|
    double _tau_unmute,
 | 
						|
    double _wthresh,
 | 
						|
    double _tr_thresh,
 | 
						|
    int _rsize,
 | 
						|
    double _fmax
 | 
						|
)
 | 
						|
{
 | 
						|
    run = _run;
 | 
						|
    size = _size;
 | 
						|
    in = _in;
 | 
						|
    out = _out;
 | 
						|
    rate = _rate;
 | 
						|
    tup = _tup;
 | 
						|
    tdown = _tdown;
 | 
						|
    muted_gain = _muted_gain;
 | 
						|
    tr_tau_mute = _tau_mute;
 | 
						|
    tr_tau_unmute = _tau_unmute;
 | 
						|
    wthresh = _wthresh;           // PRIMARY SQUELCH THRESHOLD CONTROL
 | 
						|
    tr_thresh = _tr_thresh;       // value between tr_ss_unmute and tr_ss_mute, default = 0.8197
 | 
						|
    tr_ss_mute = 1.0;
 | 
						|
    tr_ss_unmute = 0.3125;
 | 
						|
    wdtau = 0.5;
 | 
						|
    ftov_rsize = _rsize;
 | 
						|
    ftov_fmax = _fmax;
 | 
						|
    calc();
 | 
						|
}
 | 
						|
 | 
						|
SSQL::~SSQL()
 | 
						|
{
 | 
						|
    decalc();
 | 
						|
}
 | 
						|
 | 
						|
void SSQL::flush()
 | 
						|
{
 | 
						|
    std::fill(b1.begin(), b1.end(), 0);
 | 
						|
    dcbl->flush();
 | 
						|
    std::fill(ibuff.begin(), ibuff.end(), 0);
 | 
						|
    std::fill(ftovbuff.begin(), ftovbuff.end(), 0);
 | 
						|
    cvtr->flush();
 | 
						|
    std::fill(lpbuff.begin(), lpbuff.end(), 0);
 | 
						|
    filt->flush();
 | 
						|
    std::fill(wdbuff.begin(), wdbuff.end(), 0);
 | 
						|
    std::fill(tr_signal.begin(), tr_signal.end(), 0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void SSQL::execute()
 | 
						|
{
 | 
						|
    if (run)
 | 
						|
    {
 | 
						|
        dcbl->execute();                                         // dc block the input signal
 | 
						|
        for (int i = 0; i < size; i++)                       // extract 'I' component
 | 
						|
            ibuff[i] = b1[2 * i];
 | 
						|
        cvtr->execute();                                        // convert frequency to voltage, ignoring amplitude
 | 
						|
        // WriteAudioWDSP(20.0, rate, size, ftovbuff, 4, 0.99);
 | 
						|
        filt->execute();                                       // low-pass filter
 | 
						|
        // WriteAudioWDSP(20.0, rate, size, lpbuff, 4, 0.99);
 | 
						|
        // calculate the output of the window detector for each sample
 | 
						|
        for (int i = 0; i < size; i++)
 | 
						|
        {
 | 
						|
            wdaverage = wdmult * wdaverage + (1.0 - wdmult) * lpbuff[i];
 | 
						|
            if ((lpbuff[i] - wdaverage) > wthresh || (wdaverage - lpbuff[i]) > wthresh)
 | 
						|
                wdbuff[i] = 0;       // signal unmute
 | 
						|
            else
 | 
						|
                wdbuff[i] = 1;       // signal mute
 | 
						|
        }
 | 
						|
        // calculate the trigger signal for each sample
 | 
						|
        for (int i = 0; i < size; i++)
 | 
						|
        {
 | 
						|
            if (wdbuff[i] == 0)
 | 
						|
                tr_voltage += (tr_ss_unmute - tr_voltage) * unmute_mult;
 | 
						|
            if (wdbuff[i] == 1)
 | 
						|
                tr_voltage += (tr_ss_mute - tr_voltage) * mute_mult;
 | 
						|
            if (tr_voltage > tr_thresh) tr_signal[i] = 0;  // muted
 | 
						|
            else                              tr_signal[i] = 1;  // unmuted
 | 
						|
        }
 | 
						|
        // execute state machine; calculate audio output
 | 
						|
        for (int i = 0; i < size; i++)
 | 
						|
        {
 | 
						|
            switch (state)
 | 
						|
            {
 | 
						|
            case SSQLState::MUTED:
 | 
						|
                if (tr_signal[i] == 1)
 | 
						|
                {
 | 
						|
                    state = SSQLState::INCREASE;
 | 
						|
                    count = ntup;
 | 
						|
                }
 | 
						|
                out[2 * i + 0] = (float) (muted_gain * in[2 * i + 0]);
 | 
						|
                out[2 * i + 1] = (float) (muted_gain * in[2 * i + 1]);
 | 
						|
                break;
 | 
						|
            case SSQLState::INCREASE:
 | 
						|
                out[2 * i + 0] = (float) (in[2 * i + 0] * cup[ntup - count]);
 | 
						|
                out[2 * i + 1] = (float) (in[2 * i + 1] * cup[ntup - count]);
 | 
						|
                if (count-- == 0)
 | 
						|
                    state = SSQLState::UNMUTED;
 | 
						|
                break;
 | 
						|
            case SSQLState::UNMUTED:
 | 
						|
                if (tr_signal[i] == 0)
 | 
						|
                {
 | 
						|
                    state = SSQLState::DECREASE;
 | 
						|
                    count = ntdown;
 | 
						|
                }
 | 
						|
                out[2 * i + 0] = in[2 * i + 0];
 | 
						|
                out[2 * i + 1] = in[2 * i + 1];
 | 
						|
                break;
 | 
						|
            case SSQLState::DECREASE:
 | 
						|
                out[2 * i + 0] = (float) (in[2 * i + 0] * cdown[ntdown - count]);
 | 
						|
                out[2 * i + 1] = (float) (in[2 * i + 1] * cdown[ntdown - count]);
 | 
						|
                if (count-- == 0)
 | 
						|
                    state = SSQLState::MUTED;
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else if (in != out)
 | 
						|
        std::copy(in, in + size * 2, out);
 | 
						|
}
 | 
						|
 | 
						|
void SSQL::setBuffers(float* _in, float* _out)
 | 
						|
{
 | 
						|
    decalc();
 | 
						|
    in = _in;
 | 
						|
    out = _out;
 | 
						|
    calc();
 | 
						|
}
 | 
						|
 | 
						|
void SSQL::setSamplerate(int _rate)
 | 
						|
{
 | 
						|
    decalc();
 | 
						|
    rate = _rate;
 | 
						|
    calc();
 | 
						|
}
 | 
						|
 | 
						|
void SSQL::setSize(int _size)
 | 
						|
{
 | 
						|
    decalc();
 | 
						|
    size = _size;
 | 
						|
    calc();
 | 
						|
}
 | 
						|
 | 
						|
/********************************************************************************************************
 | 
						|
*                                                                                                       *
 | 
						|
*                                           RXA Properties                                              *
 | 
						|
*                                                                                                       *
 | 
						|
********************************************************************************************************/
 | 
						|
 | 
						|
void SSQL::setRun(int _run)
 | 
						|
{
 | 
						|
    run = _run;
 | 
						|
}
 | 
						|
 | 
						|
void SSQL::setThreshold(double _threshold)
 | 
						|
{
 | 
						|
    // 'threshold' should be between 0.0 and 1.0
 | 
						|
    // WU2O testing:  0.16 is a good default for 'threshold'; => 0.08 for 'wthresh'
 | 
						|
    wthresh = _threshold / 2.0;
 | 
						|
}
 | 
						|
 | 
						|
void SSQL::setTauMute(double _tau_mute)
 | 
						|
{
 | 
						|
    // reasonable (wide) range is 0.1 to 2.0
 | 
						|
    // WU2O testing:  0.1 is good default value
 | 
						|
    tr_tau_mute = _tau_mute;
 | 
						|
    mute_mult = 1.0 - exp (-1.0 / (rate * tr_tau_mute));
 | 
						|
}
 | 
						|
 | 
						|
void SSQL::setTauUnMute(double _tau_unmute)
 | 
						|
{
 | 
						|
    // reasonable (wide) range is 0.1 to 1.0
 | 
						|
    // WU2O testing:  0.1 is good default value
 | 
						|
    tr_tau_unmute = _tau_unmute;
 | 
						|
    unmute_mult = 1.0 - exp (-1.0 / (rate * tr_tau_unmute));
 | 
						|
}
 | 
						|
 | 
						|
} // namespace WDSP
 |