wdsp/dexp.c
Uladzimir Karpenka 89c8a0e2b5 first commit
2026-06-01 15:58:45 +03:00

717 lines
18 KiB
C

/* dexp.c
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2018, 2019 Warren Pratt, NR0V
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.h"
DEXP pdexp[4];
DELRING calc_delring (int rsize, int size, int delay, double* in, double* out)
{
DELRING a = (DELRING) malloc0 (sizeof (delring));
a->rsize = rsize;
a->size = size;
a->rdelay = delay;
a->in = in;
a->out = out;
a->ring = (double *) malloc0 (a->rsize * sizeof (complex));
a->inptr = a->rdelay;
a->outptr = 0;
return a;
}
void decalc_delring (DELRING a)
{
_aligned_free (a->ring);
_aligned_free (a);
}
void flush_delring (DELRING a)
{
memset (a->ring, 0, a->rsize * sizeof (complex));
a->inptr = a->rdelay;
a->outptr = 0;
}
void xdelring (DELRING a)
{
int first, second;
// copy in
if (a->size > (a->rsize - a->inptr))
{
first = a->rsize - a->inptr;
second = a->size - first;
}
else
{
first = a->size;
second = 0;
}
memcpy (a->ring + 2 * a->inptr, a->in, first * sizeof (complex));
memcpy (a->ring, a->in + 2 * first, second * sizeof (complex));
a->inptr = (a->inptr + a->size) % a->rsize;
// copy out
if (a->size > (a->rsize - a->outptr))
{
first = a->rsize - a->outptr;
second = a->size - first;
}
else
{
first = a->size;
second = 0;
}
memcpy (a->out, a->ring + 2 * a->outptr, first * sizeof (complex));
memcpy (a->out + 2 * first, a->ring, second * sizeof (complex));
a->outptr = (a->outptr + a->size) % a->rsize;
}
void calc_slews (DEXP a)
{
int i;
double delta, theta;
delta = PI / (double)a->nattack;
theta = 0.0;
for (i = 0; i <= a->nattack; i++)
{
a->cattack[i] = a->low_gain + (1.0 - a->low_gain) * 0.5 * (1.0 - cos (theta));
theta += delta;
}
delta = PI / (double)a->ndecay;
theta = 0.0;
for (i = 0; i <= a->ndecay; i++)
{
a->cdecay[i] = a->low_gain + (1.0 - a->low_gain) * 0.5 * (1.0 + cos (theta));
theta += delta;
}
}
void calc_buffs (DEXP a)
{
a->trigsig = (double *)malloc0 (2 * a->size * sizeof(complex)); // allow for double-sized output of filter
a->delsig = (double *)malloc0 ( a->size * sizeof(complex));
a->audbuffer = (double *)malloc0 ( a->size * sizeof(complex));
}
void decalc_buffs (DEXP a)
{
_aligned_free (a->audbuffer);
_aligned_free (a->delsig);
_aligned_free (a->trigsig);
}
void calc_dexp (DEXP a)
{
// trigger signal preparation
a->avm = exp(-1.0 / (a->rate * a->dettau));
a->onem_avm = 1.0 - a->avm;
a->avsig = 0.0;
// level change
a->nattack = (int)(a->tattack * a->rate);
a->ndecay = (int)(a->tdecay * a->rate);
a->cattack = (double *)malloc0((a->nattack + 1) * sizeof(double));
a->cdecay = (double *)malloc0((a->ndecay + 1) * sizeof(double));
a->low_gain = 1.0 / a->exp_ratio;
calc_slews(a);
// control
a->state = 0;
a->count = 0;
a->hold_thresh = a->hysteresis_ratio * a->attack_thresh; // hysteresis ratio < 1.0
a->nhold = (int)(a->thold * a->rate);
// vox
a->vox_count = (int)(a->audelay * a->rate);
// audio delay
a->audring = calc_delring ((int)a->rate, a->size, (int)(a->audelay * a->rate), a->audbuffer, a->out);
}
void decalc_dexp (DEXP a)
{
decalc_delring (a->audring);
_aligned_free (a->cdecay);
_aligned_free (a->cattack);
}
void calc_filter (DEXP a)
{
double* impulse;
// 2.0 gain on filter is somewhat arbitrarily chosen to get trigger input similar to that without the filter, knowing
// that for any reasonable use of the filter there will be a reduction in trigger signal.
impulse = fir_bandpass (a->nc, a->low_cut, a->high_cut, a->rate, a->wintype, 1, 2.0/(double)(2 * a->size));
// print_impulse ("scf.txt", a->nc, impulse, 1, 0);
a->p = create_fircore (a->size, a->in, a->trigsig, a->nc, 1, impulse);
_aligned_free (impulse);
a->scdring = calc_delring (a->size + a->nc / 2, a->size, a->nc / 64, a->in, a->delsig);
}
void decalc_filter (DEXP a)
{
destroy_fircore (a->p);
decalc_delring (a->scdring);
}
void calc_antivox(DEXP a)
{
a->antivox_mult = exp(-1.0 / (a->antivox_rate * a->antivox_tau));
a->antivox_onemmult = 1.0 - a->antivox_mult;
a->antivox_data = (double *) malloc0 (a->antivox_size * sizeof (complex));
}
void decalc_antivox(DEXP a)
{
_aligned_free (a->antivox_data);
}
PORT
void create_dexp (int id, int run_dexp, int size, double* in, double* out, int rate, double dettau, double tattack, double tdecay,
double thold, double exp_ratio, double hyst_ratio, double attack_thresh, int nc, int wtype, double lowcut, double highcut,
int run_filt, int run_vox, int run_audelay, double audelay, void (__stdcall *pushvox)(int id, int active),
int antivox_run, int antivox_size, int antivox_rate, double antivox_gain, double antivox_tau)
{
DEXP a = (DEXP) malloc0 (sizeof (dexp));
a->id = id;
a->run_dexp = run_dexp;
a->size = size;
a->in = in;
a->out = out;
a->rate = (double)rate;
a->dettau = dettau;
a->tattack = tattack;
a->tdecay = tdecay;
a->thold = thold;
a->exp_ratio = exp_ratio;
a->hysteresis_ratio = hyst_ratio;
a->attack_thresh = attack_thresh;
a->nc = nc;
a->wintype = wtype;
a->low_cut = lowcut;
a->high_cut = highcut;
a->run_filt = run_filt;
a->run_vox = run_vox;
a->run_audelay = run_audelay;
a->audelay = audelay;
a->pushvox = pushvox;
a->antivox_run = antivox_run;
a->antivox_size = antivox_size;
a->antivox_rate = (double)antivox_rate;
a->antivox_gain = antivox_gain;
a->antivox_tau = antivox_tau;
calc_buffs (a);
calc_dexp (a);
calc_filter (a);
calc_antivox (a);
InitializeCriticalSectionAndSpinCount(&a->cs_update, 2500);
pdexp[id] = a;
return;
}
PORT
void destroy_dexp (int id)
{
DEXP a = pdexp[id];
DeleteCriticalSection (&a->cs_update);
decalc_antivox (a);
decalc_filter (a);
decalc_dexp (a);
decalc_buffs (a);
_aligned_free (a);
}
PORT
void flush_dexp (int id)
{
DEXP a = pdexp[id];
memset (a->audbuffer, 0, a->size * sizeof (complex));
memset (a->trigsig, 0, a->size * sizeof (complex));
memset (a->delsig, 0, a->size * sizeof (complex));
a->avsig = 0.0;
a->state = 0;
a->count = 0;
flush_fircore (a->p);
flush_delring (a->scdring);
flush_delring (a->audring);
}
enum _dexpstate
{
DEXP_LOW,
DEXP_ATTACK,
DEXP_HIGH,
DEXP_HOLD,
DEXP_DECAY
};
PORT
void xdexp (int id)
{
DEXP a = pdexp[id];
int i;
double sig, gain, asig;
double max = 0.0;
EnterCriticalSection (&a->cs_update);
// ******* BEGIN SIDE-CHANNEL FILTER *******
if (a->run_filt)
{
xdelring (a->scdring); // input is 'a->in'; output is 'a->delsig'
xfircore (a->p); // input is 'a->in'; output is 'a->trigsig'
}
else
{
memcpy (a->delsig, a->in, a->size * sizeof (complex));
memcpy (a->trigsig, a->in, a->size * sizeof (complex));
}
// ******* END SIDE-CHANNEL FILTER *******
// ******* CALCULATE ANTIVOX LEVEL *******
if (a->state == DEXP_LOW && a->antivox_new != 0)
{
// if VOX is currently NOT triggered, and, if we have new antivox data to process
for (i = 0; i < a->antivox_size; i++)
{
sig = sqrt (a->antivox_data[2 * i + 0] * a->antivox_data[2 * i + 0] + a->antivox_data[2 * i + 1] * a->antivox_data[2 * i + 1]);
a->antivox_level = a->antivox_mult * a->antivox_level + a->antivox_onemmult * sig;
}
// set the new_data flag to zero
a->antivox_new = 0;
}
// ******* END CALCULATE ANTIVOX LEVEL *******
// ******* BEGIN DEXP *******
// uses 'a->trigsig' as trigger signal; uses 'a->delsig' as audio input
// 'a->audbuffer' is audio output
// DEXP code runs continuously so it can be used to trigger VOX also.
for (i = 0; i < a->size; i++)
{
sig = sqrt (a->trigsig[2 * i + 0] * a->trigsig[2 * i + 0] + a->trigsig[2 * i + 1] * a->trigsig[2 * i + 1]);
a->avsig = a->avm * a->avsig + a->onem_avm * sig;
if (a->avsig > max) max = a->avsig;
switch (a->state)
{
case DEXP_LOW:
if (a->antivox_run)
asig = a->avsig - a->antivox_gain * a->antivox_level;
else
asig = a->avsig;
if (asig > a->attack_thresh)
{
a->state = DEXP_ATTACK;
a->count = a->nattack;
}
a->audbuffer[2 * i + 0] = a->low_gain * a->delsig[2 * i + 0];
a->audbuffer[2 * i + 1] = a->low_gain * a->delsig[2 * i + 1];
// ******* BEGIN VOX *******
// If we're going to attack, turn on VOX immediately.
// Prepare 'vox_count' for the next turnoff too.
if (a->run_vox && a->state == DEXP_ATTACK)
{
(a->pushvox)(a->id, 1);
// Set vox_count for delay IF the audio delay is also enabled.
if (a->run_audelay)
a->vox_count = (int)(a->audelay * a->rate);
else
a->vox_count = 1;
}
// If we're sitting in this state and the delayed vox count expires, turn OFF VOX.
else if (a->run_vox && --(a->vox_count) == 0)
(a->pushvox)(a->id, 0);
// Don't let 'vox_count' keep counting down.
else if (a->vox_count < 0)
a->vox_count = 0;
// ******* END VOX *******
break;
case DEXP_ATTACK:
gain = a->cattack[a->nattack - a->count];
a->audbuffer[2 * i + 0] = a->delsig[2 * i + 0] * gain;
a->audbuffer[2 * i + 1] = a->delsig[2 * i + 1] * gain;
if (a->count-- == 0)
a->state = DEXP_HIGH;
break;
case DEXP_HIGH:
if (a->avsig < a->hold_thresh)
{
a->state = DEXP_HOLD;
a->count = a->nhold;
}
a->audbuffer[2 * i + 0] = a->delsig[2 * i + 0];
a->audbuffer[2 * i + 1] = a->delsig[2 * i + 1];
break;
case DEXP_HOLD:
a->audbuffer[2 * i + 0] = a->delsig[2 * i + 0];
a->audbuffer[2 * i + 1] = a->delsig[2 * i + 1];
if (a->avsig > a->attack_thresh)
a->state = DEXP_HIGH;
else if (a->count-- == 0)
{
a->state = DEXP_DECAY;
a->count = a->ndecay;
}
break;
case DEXP_DECAY:
gain = a->cdecay[a->ndecay - a->count];
a->audbuffer[2 * i + 0] = a->delsig[2 * i + 0] * gain;
a->audbuffer[2 * i + 1] = a->delsig[2 * i + 1] * gain;
if (a->count-- == 0)
a->state = DEXP_LOW;
break;
}
}
a->peak = max;
// If DEXP functionality is set to OFF, copy its input to overwrite its output.
if (!a->run_dexp)
memcpy (a->audbuffer, a->delsig, a->size * sizeof (complex));
// ******* END DEXP *******
// ******* BEGIN AUDIO DELAY *******
if (a->run_audelay)
xdelring (a->audring); // uses 'a->audbuffer' as audio input; uses 'a->out' as audio output
else
memcpy (a->out, a->audbuffer, a->size * sizeof (complex));
// ******* END AUDIO DELAY *******
LeaveCriticalSection (&a->cs_update);
}
PORT
void SendCBPushDexpVox (int id, void (__stdcall *pushvox)(int id, int active))
{
// Call to set the address of the callback to operate VOX.
DEXP a = pdexp[id];
a->pushvox = pushvox;
}
PORT
void SetDEXPRun (int id, int run)
{
// run != 0, puts dexp in the audio processing path; otherwise, it's only used to trigger VOX
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
a->run_dexp = run;
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPSize (int id, int size)
{
// There are some constraints on the input/output buffer sizes.
// * must be a power-of-two
// * must be less than or equal to 'nc', the number of filter coefficients, which is also a power-of-two
// * must be less than 'rate' samples because of the sizing of 'audring'
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_filter (a);
decalc_dexp (a);
decalc_buffs (a);
a->size = size;
calc_buffs (a);
calc_dexp (a);
calc_filter (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPIOBuffers (int id, double* in, double* out)
{
// Sets the input/output buffers. They can be the same.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_filter (a);
decalc_dexp (a);
a->in = in;
a->out = out;
calc_dexp (a);
calc_filter (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPRate (int id, double rate)
{
// Sets the sample rate.
// This is used for timing and filter calculations as well as sizing 'audring'.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_filter (a);
decalc_dexp (a);
a->rate = rate;
calc_dexp (a);
calc_filter (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPDetectorTau (int id, double tau)
{
// Time-constant for smoothing the signal for detection (seconds).
// 0.01 seconds is a good starting point to try.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_dexp (a);
a->dettau = tau;
calc_dexp (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPAttackTime (int id, double time)
{
// Set attack time, seconds.
// 0.002 - 0.100 should be a good range.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_dexp (a);
a->tattack = time;
calc_dexp (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPReleaseTime (int id, double time)
{
// Set release time, seconds.
// 0.002 - 0.999 should be a good range.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_dexp (a);
a->tdecay = time;
calc_dexp (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPHoldTime (int id, double time)
{
// Set hold time, seconds.
// 0.000 - 2.000 should be a good range.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_dexp (a);
a->thold = time;
calc_dexp (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPExpansionRatio (int id, double ratio)
{
// Set expansion ratio. High_gain = 1.0; Low_gain = 1.0/exp_ratio.
// Range of 1.0 - 30.0 should be good. Could use dB: 0.0 - 30.0dB.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_dexp (a);
a->exp_ratio = ratio;
calc_dexp (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPHysteresisRatio (int id, double ratio)
{
// Set Hysteresis Ratio. Hold_thresh = hysteresis_ratio * Attack_thresh.
// Expose to operator in dB: 0.0dB - 9.9dB should be good (1.000 - 0.320).
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_dexp (a);
a->hysteresis_ratio = ratio;
calc_dexp (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPAttackThreshold (int id, double thresh)
{
// Set attack threshold.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_dexp (a);
a->attack_thresh = thresh;
calc_dexp (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPFilterTaps (int id, int taps)
{
// Set number of taps. Must be a power of two and an even multiple of 'size'.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_filter (a);
a->nc = taps;
calc_filter (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPWindowType (int id, int type)
{
// Set filter window type.
// * 0 - 4-term Blackman-Harris.
// * 1 - 7-term Blackman-Harris.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_filter (a);
a->wintype = type;
calc_filter (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPLowCut (int id, double lowcut)
{
// Set side-channel filter low_cut (Hertz).
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_filter (a);
a->low_cut = lowcut;
calc_filter (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPHighCut (int id, double highcut)
{
// Set side-channel filter high_cut (Hertz).
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_filter (a);
a->high_cut = highcut;
calc_filter (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPRunSideChannelFilter (int id, int run)
{
// Turn OFF/ON the side-channel filter and its compensating delay.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
a->run_filt = run;
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPRunVox (int id, int run)
{
// Turn OFF/ON calls to 'pushvox(...)'.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
a->run_vox = run;
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPRunAudioDelay (int id, int run)
{
// Turn OFF/ON audio delay line.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
a->run_audelay = run;
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetDEXPAudioDelay (int id, double delay)
{
// Set the audio delay, seconds.
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_dexp (a);
a->audelay = delay;
calc_dexp (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void GetDEXPPeakSignal (int id, double* peak)
{
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
*peak = a->peak;
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetAntiVOXRun (int id, int run)
{
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
a->antivox_run = run;
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetAntiVOXSize (int id, int size)
{
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_antivox(a);
a->antivox_size = size;
calc_antivox(a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetAntiVOXRate (int id, double rate)
{
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_antivox(a);
a->antivox_rate = rate;
calc_antivox(a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetAntiVOXGain (int id, double gain)
{
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
a->antivox_gain = gain;
LeaveCriticalSection (&a->cs_update);
}
PORT
void SetAntiVOXDetectorTau (int id, double tau)
{
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
decalc_antivox (a);
a->antivox_tau = tau;
calc_antivox (a);
LeaveCriticalSection (&a->cs_update);
}
PORT
void SendAntiVOXData (int id, int nsamples, double* data)
{
// note: 'nsamples' is not used as it has been previously specified
DEXP a = pdexp[id];
EnterCriticalSection (&a->cs_update);
memcpy (a->antivox_data, data, a->antivox_size * sizeof (complex));
a->antivox_new = 1;
LeaveCriticalSection (&a->cs_update);
}