/* 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); }