2024-06-16 05:31:13 -04:00
|
|
|
/* 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"
|
2024-07-30 19:37:17 -04:00
|
|
|
#include "dbqlp.hpp"
|
2024-06-16 05:31:13 -04:00
|
|
|
|
|
|
|
namespace WDSP {
|
|
|
|
|
|
|
|
/********************************************************************************************************
|
|
|
|
* *
|
|
|
|
* Frequency to Voltage Converter *
|
|
|
|
* *
|
|
|
|
********************************************************************************************************/
|
|
|
|
|
2024-07-31 18:31:28 -04:00
|
|
|
FTOV::FTOV(
|
|
|
|
int _run,
|
|
|
|
int _size,
|
|
|
|
int _rate,
|
|
|
|
int _rsize,
|
|
|
|
double _fmax,
|
|
|
|
float* _in,
|
|
|
|
float* _out
|
|
|
|
)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 18:31:28 -04:00
|
|
|
run = _run;
|
|
|
|
size = _size;
|
|
|
|
rate = _rate;
|
|
|
|
rsize = _rsize;
|
|
|
|
fmax = _fmax;
|
|
|
|
in = _in;
|
|
|
|
out = _out;
|
|
|
|
eps = 0.01;
|
|
|
|
ring.resize(rsize); // (int*) malloc0 (rsize * sizeof (int));
|
|
|
|
rptr = 0;
|
|
|
|
inlast = 0.0;
|
|
|
|
rcount = 0;
|
|
|
|
div = fmax * 2.0 * rsize / rate; // fmax * 2 = zero-crossings/sec
|
2024-06-16 05:31:13 -04:00
|
|
|
// rsize / rate = sec of data in ring
|
|
|
|
// product is # zero-crossings in ring at fmax
|
|
|
|
}
|
|
|
|
|
2024-07-31 18:31:28 -04:00
|
|
|
void FTOV::flush()
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 18:31:28 -04:00
|
|
|
std::fill(ring.begin(), ring.end(), 0);
|
|
|
|
rptr = 0;
|
|
|
|
rcount = 0;
|
|
|
|
inlast = 0.0;
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
2024-07-31 18:31:28 -04:00
|
|
|
void FTOV::execute()
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
|
|
|
// 'ftov' does frequency to voltage conversion looking only at zero crossings of an
|
|
|
|
// AC (DC blocked) signal, i.e., ignoring signal amplitude.
|
2024-07-31 18:31:28 -04:00
|
|
|
if (run)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 18:31:28 -04:00
|
|
|
if (ring[rptr] == 1) // if current ring location is a '1' ...
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 18:31:28 -04:00
|
|
|
rcount--; // decrement the count
|
|
|
|
ring[rptr] = 0; // set the location to '0'
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
2024-07-31 18:31:28 -04:00
|
|
|
if ((inlast * in[0] < 0.0) && // different signs mean zero-crossing
|
|
|
|
(fabs (inlast - in[0]) > eps))
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 18:31:28 -04:00
|
|
|
ring[rptr] = 1; // set the ring location to '1'
|
|
|
|
rcount++; // increment the count
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
2024-07-31 18:31:28 -04:00
|
|
|
if (++rptr == rsize) rptr = 0; // increment and wrap the pointer as needed
|
|
|
|
out[0] = 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++)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 18:31:28 -04:00
|
|
|
if (ring[rptr] == 1) // if current ring location is '1' ...
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 18:31:28 -04:00
|
|
|
rcount--; // decrement the count
|
|
|
|
ring[rptr] = 0; // set the location to '0'
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
2024-07-31 18:31:28 -04:00
|
|
|
if ((in[i - 1] * in[i] < 0.0) && // different signs mean zero-crossing
|
|
|
|
(fabs (in[i - 1] - in[i]) > eps))
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 18:31:28 -04:00
|
|
|
ring[rptr] = 1; // set the ring location to '1'
|
|
|
|
rcount++; // increment the count
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
2024-07-31 18:31:28 -04:00
|
|
|
if (++rptr == rsize) rptr = 0; // increment and wrap the pointer as needed
|
|
|
|
out[i] = std::min(1.0, (double)rcount / div); // calculate the output sample
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*******************************************************************************************************/
|
|
|
|
/********************************** END Frequency to Voltage Converter *********************************/
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
void SSQL::compute_slews()
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-05 21:25:41 -04:00
|
|
|
double delta, theta;
|
2024-07-31 20:01:09 -04:00
|
|
|
delta = PI / (double) ntup;
|
2024-06-16 05:31:13 -04:00
|
|
|
theta = 0.0;
|
2024-07-31 20:01:09 -04:00
|
|
|
for (int i = 0; i <= ntup; i++)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
cup[i] = muted_gain + (1.0 - muted_gain) * 0.5 * (1.0 - cos(theta));
|
2024-06-16 05:31:13 -04:00
|
|
|
theta += delta;
|
|
|
|
}
|
2024-07-31 20:01:09 -04:00
|
|
|
delta = PI / (double)ntdown;
|
2024-06-16 05:31:13 -04:00
|
|
|
theta = 0.0;
|
2024-07-31 20:01:09 -04:00
|
|
|
for (int i = 0; i <= ntdown; i++)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
cdown[i] = muted_gain + (1.0 - muted_gain) * 0.5 * (1.0 + cos(theta));
|
2024-06-16 05:31:13 -04:00
|
|
|
theta += delta;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
void SSQL::calc()
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
b1 = new float[size * 2]; // (float*) malloc0 (size * sizeof (complex));
|
|
|
|
dcbl = new CBL(1, size, in, b1, 0, rate, 0.02);
|
|
|
|
ibuff = new float[size]; // (float*) malloc0 (size * sizeof (float));
|
|
|
|
ftovbuff = new float[size]; // (float*) malloc0(size * sizeof (float));
|
|
|
|
cvtr = new FTOV(1, size, rate, ftov_rsize, ftov_fmax, ibuff, ftovbuff);
|
|
|
|
lpbuff = new float[size]; // (float*) malloc0 (size * sizeof (float));
|
|
|
|
filt = new DBQLP(1, size, ftovbuff, lpbuff, rate, 11.3, 1.0, 1.0, 1);
|
|
|
|
wdbuff = new int[size]; // (int*) malloc0 (size * sizeof (int));
|
|
|
|
tr_signal = new int[size]; // (int*) malloc0 (size * sizeof (int));
|
2024-06-16 05:31:13 -04:00
|
|
|
// window detector
|
2024-07-31 20:01:09 -04:00
|
|
|
wdmult = exp (-1.0 / (rate * wdtau));
|
|
|
|
wdaverage = 0.0;
|
2024-06-16 05:31:13 -04:00
|
|
|
// trigger
|
2024-07-31 20:01:09 -04:00
|
|
|
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));
|
2024-06-16 05:31:13 -04:00
|
|
|
// level change
|
2024-07-31 20:01:09 -04:00
|
|
|
ntup = (int)(tup * rate);
|
|
|
|
ntdown = (int)(tdown * rate);
|
|
|
|
cup = new float[ntup + 1]; // (float*) malloc0 ((ntup + 1) * sizeof (float));
|
|
|
|
cdown = new float[ntdown + 1]; // (float*) malloc0 ((ntdown + 1) * sizeof (float));
|
|
|
|
compute_slews();
|
2024-06-16 05:31:13 -04:00
|
|
|
// control
|
2024-07-31 20:01:09 -04:00
|
|
|
state = 0;
|
|
|
|
count = 0;
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
void SSQL::decalc()
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
delete[] (tr_signal);
|
|
|
|
delete[] (wdbuff);
|
|
|
|
delete (filt);
|
|
|
|
delete[] (lpbuff);
|
|
|
|
delete (cvtr);
|
|
|
|
delete[] (ftovbuff);
|
|
|
|
delete[] (ibuff);
|
|
|
|
delete (dcbl);
|
|
|
|
delete[] (b1);
|
|
|
|
delete[] (cdown);
|
|
|
|
delete[] (cup);
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
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
|
2024-06-16 05:31:13 -04:00
|
|
|
)
|
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
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();
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
SSQL::~SSQL()
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
decalc();
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
void SSQL::flush()
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
std::fill(b1, b1 + size * 2, 0);
|
|
|
|
dcbl->flush();
|
|
|
|
memset (ibuff, 0, size * sizeof (float));
|
|
|
|
memset (ftovbuff, 0, size * sizeof (float));
|
|
|
|
cvtr->flush();
|
|
|
|
memset (lpbuff, 0, size * sizeof (float));
|
|
|
|
filt->flush();
|
|
|
|
memset (wdbuff, 0, size * sizeof (int));
|
|
|
|
memset (tr_signal, 0, size * sizeof (int));
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
enum _ssqlstate
|
|
|
|
{
|
|
|
|
MUTED,
|
|
|
|
INCREASE,
|
|
|
|
UNMUTED,
|
|
|
|
DECREASE
|
|
|
|
};
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
void SSQL::execute()
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
if (run)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
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);
|
2024-06-16 05:31:13 -04:00
|
|
|
// calculate the output of the window detector for each sample
|
2024-07-31 20:01:09 -04:00
|
|
|
for (int i = 0; i < size; i++)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
wdaverage = wdmult * wdaverage + (1.0 - wdmult) * lpbuff[i];
|
|
|
|
if ((lpbuff[i] - wdaverage) > wthresh || (wdaverage - lpbuff[i]) > wthresh)
|
|
|
|
wdbuff[i] = 0; // signal unmute
|
2024-06-16 05:31:13 -04:00
|
|
|
else
|
2024-07-31 20:01:09 -04:00
|
|
|
wdbuff[i] = 1; // signal mute
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
// calculate the trigger signal for each sample
|
2024-07-31 20:01:09 -04:00
|
|
|
for (int i = 0; i < size; i++)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
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
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
// execute state machine; calculate audio output
|
2024-07-31 20:01:09 -04:00
|
|
|
for (int i = 0; i < size; i++)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
switch (state)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
|
|
|
case MUTED:
|
2024-07-31 20:01:09 -04:00
|
|
|
if (tr_signal[i] == 1)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
state = INCREASE;
|
|
|
|
count = ntup;
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
2024-07-31 20:01:09 -04:00
|
|
|
out[2 * i + 0] = muted_gain * in[2 * i + 0];
|
|
|
|
out[2 * i + 1] = muted_gain * in[2 * i + 1];
|
2024-06-16 05:31:13 -04:00
|
|
|
break;
|
|
|
|
case INCREASE:
|
2024-07-31 20:01:09 -04:00
|
|
|
out[2 * i + 0] = in[2 * i + 0] * cup[ntup - count];
|
|
|
|
out[2 * i + 1] = in[2 * i + 1] * cup[ntup - count];
|
|
|
|
if (count-- == 0)
|
|
|
|
state = UNMUTED;
|
2024-06-16 05:31:13 -04:00
|
|
|
break;
|
|
|
|
case UNMUTED:
|
2024-07-31 20:01:09 -04:00
|
|
|
if (tr_signal[i] == 0)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
state = DECREASE;
|
|
|
|
count = ntdown;
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
2024-07-31 20:01:09 -04:00
|
|
|
out[2 * i + 0] = in[2 * i + 0];
|
|
|
|
out[2 * i + 1] = in[2 * i + 1];
|
2024-06-16 05:31:13 -04:00
|
|
|
break;
|
|
|
|
case DECREASE:
|
2024-07-31 20:01:09 -04:00
|
|
|
out[2 * i + 0] = in[2 * i + 0] * cdown[ntdown - count];
|
|
|
|
out[2 * i + 1] = in[2 * i + 1] * cdown[ntdown - count];
|
|
|
|
if (count-- == 0)
|
|
|
|
state = MUTED;
|
2024-06-16 05:31:13 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-07-31 20:01:09 -04:00
|
|
|
else if (in != out)
|
|
|
|
std::copy(in, in + size * 2, out);
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
void SSQL::setBuffers(float* _in, float* _out)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
decalc();
|
|
|
|
in = _in;
|
|
|
|
out = _out;
|
|
|
|
calc();
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
void SSQL::setSamplerate(int _rate)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
decalc();
|
|
|
|
rate = _rate;
|
|
|
|
calc();
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
void SSQL::setSize(int _size)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
decalc();
|
|
|
|
size = _size;
|
|
|
|
calc();
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/********************************************************************************************************
|
|
|
|
* *
|
|
|
|
* RXA Properties *
|
|
|
|
* *
|
|
|
|
********************************************************************************************************/
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
void SSQL::setRun(int _run)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
2024-07-31 20:01:09 -04:00
|
|
|
run = _run;
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
void SSQL::setThreshold(double _threshold)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
|
|
|
// 'threshold' should be between 0.0 and 1.0
|
|
|
|
// WU2O testing: 0.16 is a good default for 'threshold'; => 0.08 for 'wthresh'
|
2024-07-31 20:01:09 -04:00
|
|
|
wthresh = _threshold / 2.0;
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
void SSQL::setTauMute(double _tau_mute)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
|
|
|
// reasonable (wide) range is 0.1 to 2.0
|
|
|
|
// WU2O testing: 0.1 is good default value
|
2024-07-31 20:01:09 -04:00
|
|
|
tr_tau_mute = _tau_mute;
|
|
|
|
mute_mult = 1.0 - exp (-1.0 / (rate * tr_tau_mute));
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
2024-07-31 20:01:09 -04:00
|
|
|
void SSQL::setTauUnMute(double _tau_unmute)
|
2024-06-16 05:31:13 -04:00
|
|
|
{
|
|
|
|
// reasonable (wide) range is 0.1 to 1.0
|
|
|
|
// WU2O testing: 0.1 is good default value
|
2024-07-31 20:01:09 -04:00
|
|
|
tr_tau_unmute = _tau_unmute;
|
|
|
|
unmute_mult = 1.0 - exp (-1.0 / (rate * tr_tau_unmute));
|
2024-06-16 05:31:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace WDSP
|