1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-25 09:18:54 -05:00
sdrangel/wdsp/ssql.cpp

378 lines
12 KiB
C++
Raw Permalink Normal View History

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;
2024-08-03 05:05:12 -04:00
ring.resize(rsize);
2024-07-31 18:31:28 -04:00
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
2024-08-03 05:05:12 -04:00
out[0] = (float) std::min (1.0, (double)rcount / div); // calculate the output sample
2024-07-31 18:31:28 -04:00
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
2024-08-03 05:05:12 -04:00
out[i] = (float) 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-08-03 05:05:12 -04:00
double delta;
double 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-08-03 05:05:12 -04:00
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);
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);
2024-08-03 05:05:12 -04:00
cup.resize(ntup + 1);
cdown.resize(ntdown + 1);
2024-07-31 20:01:09 -04:00
compute_slews();
2024-06-16 05:31:13 -04:00
// control
2024-08-03 05:05:12 -04:00
state = SSQLState::MUTED;
2024-07-31 20:01:09 -04:00
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-08-03 05:05:12 -04:00
delete filt;
delete cvtr;
delete dcbl;
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-08-03 05:05:12 -04:00
std::fill(b1.begin(), b1.end(), 0);
2024-07-31 20:01:09 -04:00
dcbl->flush();
2024-08-03 05:05:12 -04:00
std::fill(ibuff.begin(), ibuff.end(), 0);
std::fill(ftovbuff.begin(), ftovbuff.end(), 0);
2024-07-31 20:01:09 -04:00
cvtr->flush();
2024-08-03 05:05:12 -04:00
std::fill(lpbuff.begin(), lpbuff.end(), 0);
2024-07-31 20:01:09 -04:00
filt->flush();
2024-08-03 05:05:12 -04:00
std::fill(wdbuff.begin(), wdbuff.end(), 0);
std::fill(tr_signal.begin(), tr_signal.end(), 0);
2024-06-16 05:31:13 -04:00
}
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
{
2024-08-03 05:05:12 -04:00
case SSQLState::MUTED:
2024-07-31 20:01:09 -04:00
if (tr_signal[i] == 1)
2024-06-16 05:31:13 -04:00
{
2024-08-03 05:05:12 -04:00
state = SSQLState::INCREASE;
2024-07-31 20:01:09 -04:00
count = ntup;
2024-06-16 05:31:13 -04:00
}
2024-08-03 05:05:12 -04:00
out[2 * i + 0] = (float) (muted_gain * in[2 * i + 0]);
out[2 * i + 1] = (float) (muted_gain * in[2 * i + 1]);
2024-06-16 05:31:13 -04:00
break;
2024-08-03 05:05:12 -04:00
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]);
2024-07-31 20:01:09 -04:00
if (count-- == 0)
2024-08-03 05:05:12 -04:00
state = SSQLState::UNMUTED;
2024-06-16 05:31:13 -04:00
break;
2024-08-03 05:05:12 -04:00
case SSQLState::UNMUTED:
2024-07-31 20:01:09 -04:00
if (tr_signal[i] == 0)
2024-06-16 05:31:13 -04:00
{
2024-08-03 05:05:12 -04:00
state = SSQLState::DECREASE;
2024-07-31 20:01:09 -04:00
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;
2024-08-03 05:05:12 -04:00
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]);
2024-07-31 20:01:09 -04:00
if (count-- == 0)
2024-08-03 05:05:12 -04:00
state = SSQLState::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