/* rmatch.c This file is part of a program that implements a Software-Defined Radio. Copyright (C) 2017, 2018, 2022 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" MAV create_mav (int ringmin, int ringmax, double nom_value) { MAV a = (MAV) malloc0 (sizeof (mav)); a->ringmin = ringmin; a->ringmax = ringmax; a->nom_value = nom_value; a->ring = (int *) malloc0 (a->ringmax * sizeof (int)); a->mask = a->ringmax - 1; a->i = 0; a->load = 0; a->sum = 0; return a; } void destroy_mav (MAV a) { _aligned_free (a->ring); _aligned_free (a); } void flush_mav (MAV a) { memset (a->ring, 0, a->ringmax * sizeof (int)); a->i = 0; a->load = 0; a->sum = 0; } void xmav (MAV a, int input, double* output) { if (a->load >= a->ringmax) a->sum -= a->ring[a->i]; if (a->load < a->ringmax) a->load++; a->ring[a->i] = input; a->sum += a->ring[a->i]; if (a->load >= a->ringmin) *output = (double)a->sum / (double)a->load; else *output = a->nom_value; a->i = (a->i + 1) & a->mask; } AAMAV create_aamav (int ringmin, int ringmax, double nom_ratio) { AAMAV a = (AAMAV) malloc0 (sizeof (aamav)); a->ringmin = ringmin; a->ringmax = ringmax; a->nom_ratio = nom_ratio; a->ring = (int *) malloc0 (a->ringmax * sizeof (int)); a->mask = a->ringmax - 1; a->i = 0; a->load = 0; a->pos = 0; a->neg = 0; return a; } void destroy_aamav (AAMAV a) { _aligned_free (a->ring); _aligned_free (a); } void flush_aamav (AAMAV a) { memset (a->ring, 0, a->ringmax * sizeof (int)); a->i = 0; a->load = 0; a->pos = 0; a->neg = 0; } void xaamav (AAMAV a, int input, double* output) { if (a->load >= a->ringmax) { if (a->ring[a->i] >= 0) a->pos -= a->ring[a->i]; else a->neg += a->ring[a->i]; } if (a->load <= a->ringmax) a->load++; a->ring[a->i] = input; if (a->ring[a->i] >= 0) a->pos += a->ring[a->i]; else a->neg -= a->ring[a->i]; if (a->load >= a->ringmin) *output = (double)a->neg / (double)a->pos; else if (a->neg > 0 && a->pos > 0) { double frac = (double)a->load / (double)a->ringmin; *output = (1.0 - frac) * a->nom_ratio + frac * ((double)a->neg / (double)a->pos); } else *output = a->nom_ratio; a->i = (a->i + 1) & a->mask; } void calc_rmatch (RMATCH a) { int m; double theta, dtheta; int max_ring_insize; a->nom_ratio = (double)a->nom_outrate / (double)a->nom_inrate; max_ring_insize = (int)(1.0 + (double)a->insize * (1.05 * a->nom_ratio)); if (a->ringsize < 2 * max_ring_insize) a->ringsize = 2 * max_ring_insize; if (a->ringsize < 2 * a->outsize) a->ringsize = 2 * a->outsize; a->ring = (double *) malloc0 (a->ringsize * sizeof (complex)); a->rsize = a->ringsize; a->n_ring = a->rsize / 2; a->iin = a->rsize / 2; a->iout = 0; a->resout = (double *) malloc0 (max_ring_insize * sizeof (complex)); a->v = create_varsamp (1, a->insize, a->in, a->resout, a->nom_inrate, a->nom_outrate, a->fc_high, a->fc_low, a->R, a->gain, a->var, a->varmode); a->ffmav = create_aamav (a->ff_ringmin, a->ff_ringmax, a->nom_ratio); a->propmav = create_mav (a->prop_ringmin, a->prop_ringmax, 0.0); a->pr_gain = a->prop_gain * 48000.0 / (double)a->nom_outrate; // adjust gain for rate a->inv_nom_ratio = (double)a->nom_inrate / (double)a->nom_outrate; a->feed_forward = 1.0; a->av_deviation = 0.0; InitializeCriticalSectionAndSpinCount (&a->cs_ring, 2500); InitializeCriticalSectionAndSpinCount (&a->cs_var, 2500); a->ntslew = (int)(a->tslew * a->nom_outrate); if (a->ntslew + 1 > a->rsize / 2) a->ntslew = a->rsize / 2 - 1; a->cslew = (double *) malloc0 ((a->ntslew + 1) * sizeof (double)); dtheta = PI / (double)a->ntslew; theta = 0.0; for (m = 0; m <= a->ntslew; m++) { a->cslew[m] = 0.5 * (1.0 - cos (theta)); theta += dtheta; } a->baux = (double *) malloc0 (a->ringsize / 2 * sizeof (complex)); a->readsamps = 0; a->writesamps = 0; a->read_startup = (unsigned int)((double)a->nom_outrate * a->startup_delay); a->write_startup = (unsigned int)((double)a->nom_inrate * a->startup_delay); a->control_flag = 0; // diagnostics a->underflows = 0; a->overflows = 0; } void decalc_rmatch (RMATCH a) { _aligned_free (a->baux); _aligned_free (a->cslew); DeleteCriticalSection (&a->cs_var); DeleteCriticalSection (&a->cs_ring); destroy_mav (a->propmav); destroy_aamav (a->ffmav); destroy_varsamp (a->v); _aligned_free (a->resout); _aligned_free (a->ring); } RMATCH create_rmatch ( int run, // 0 - input and output calls do nothing; 1 - operates normally double* in, // pointer to input buffer double* out, // pointer to output buffer int insize, // size of input buffer int outsize, // size of output buffer int nom_inrate, // nominal input samplerate int nom_outrate, // nominal output samplerate double fc_high, // high cutoff frequency if lower than max double fc_low, // low cutoff frequency if higher than zero double gain, // gain to be applied during this process double startup_delay, // time (seconds) to delay before beginning measurements to control variable resampler int auto_ringsize, // 0 specified ringsize is used; 1 ringsize is auto-optimized - FEATURE NOT IMPLEMENTED!! int ringsize, // specified ringsize; max ringsize if 'auto' is enabled int R, // density factor for varsamp coefficients double var, // initial value of variable resampler ratio (value of ~1.0) int ffmav_min, // minimum feed-forward moving average size to put full weight on data in the ring int ffmav_max, // maximum feed-forward moving average size - MUST BE A POWER OF TWO! double ff_alpha, // feed-forward exponential averaging multiplier int prop_ringmin, // proportional feedback min moving average ringsize int prop_ringmax, // proportional feedback max moving average ringsize - MUST BE A POWER OF TWO! double prop_gain, // proportional feedback gain factor int varmode, // 0 - use same var for all samples of the buffer; 1 - interpolate from old_var to this var double tslew // slew/blend time (seconds) ) { RMATCH a = (RMATCH) malloc0 (sizeof (rmatch)); a->run = run; a->in = in; a->out = out; a->insize = insize; a->outsize = outsize; a->nom_inrate = nom_inrate; a->nom_outrate = nom_outrate; a->fc_high = fc_high; a->fc_low = fc_low; a->gain = gain; a->startup_delay = startup_delay; a->auto_ringsize = auto_ringsize; a->ringsize = ringsize; a->R = R; a->var = var; a->ff_ringmin = ffmav_min; a->ff_ringmax = ffmav_max; // must be a power of two a->ff_alpha = ff_alpha; a->prop_ringmin = prop_ringmin; a->prop_ringmax = prop_ringmax; // must be a power of two a->prop_gain = prop_gain; a->varmode = varmode; a->tslew = tslew; calc_rmatch(a); return a; } void destroy_rmatch (RMATCH a) { decalc_rmatch (a); _aligned_free (a); } void reset_rmatch (RMATCH a) { InterlockedBitTestAndReset (&a->run, 0); Sleep (10); decalc_rmatch(a); calc_rmatch (a); InterlockedBitTestAndSet (&a->run, 0); } void control (RMATCH a, int change) { { double current_ratio; xaamav (a->ffmav, change, ¤t_ratio); current_ratio *= a->inv_nom_ratio; a->feed_forward = a->ff_alpha * current_ratio + (1.0 - a->ff_alpha) * a->feed_forward; } { int deviation = a->n_ring - a->rsize / 2; xmav (a->propmav, deviation, &a->av_deviation); } EnterCriticalSection (&a->cs_var); a->var = a->feed_forward - a->pr_gain * a->av_deviation; if (a->var > 1.04) a->var = 1.04; if (a->var < 0.96) a->var = 0.96; LeaveCriticalSection (&a->cs_var); } void blend (RMATCH a) { int i, j; for (i = 0, j = a->iout; i <= a->ntslew; i++, j = (j + 1) % a->rsize) { a->ring[2 * j + 0] = a->cslew[i] * a->ring[2 * j + 0] + (1.0 - a->cslew[i]) * a->baux[2 * i + 0]; a->ring[2 * j + 1] = a->cslew[i] * a->ring[2 * j + 1] + (1.0 - a->cslew[i]) * a->baux[2 * i + 1]; } } void upslew (RMATCH a, int newsamps) { int i, j; i = 0; j = a->iin; while (a->ucnt >= 0 && i < newsamps) { a->ring[2 * j + 0] *= a->cslew[a->ntslew - a->ucnt]; a->ring[2 * j + 1] *= a->cslew[a->ntslew - a->ucnt]; a->ucnt--; i++; j = (j + 1) % a->rsize; } } PORT void xrmatchIN (void* b, double* in) { RMATCH a = (RMATCH)b; if (InterlockedAnd (&a->run, 1)) { int newsamps, first, second, ovfl; double var; a->v->in = a->in = in; EnterCriticalSection (&a->cs_var); if (!a->force) var = a->var; else var = a->fvar; LeaveCriticalSection (&a->cs_var); newsamps = xvarsamp (a->v, var); EnterCriticalSection (&a->cs_ring); a->n_ring += newsamps; if ((ovfl = a->n_ring - a->rsize) > 0) { InterlockedIncrement (&a->overflows); // a->n_ring = a->rsize / 2; a->n_ring = a->rsize; // if ((a->ntslew + 1) > (a->rsize - a->iout)) { first = a->rsize - a->iout; second = (a->ntslew + 1) - first; } else { first = a->ntslew + 1; second = 0; } memcpy (a->baux, a->ring + 2 * a->iout, first * sizeof (complex)); memcpy (a->baux + 2 * first, a->ring, second * sizeof (complex)); // a->iout = (a->iout + ovfl + a->rsize / 2) % a->rsize; a->iout = (a->iout + ovfl) % a->rsize; // } if (newsamps > (a->rsize - a->iin)) { first = a->rsize - a->iin; second = newsamps - first; } else { first = newsamps; second = 0; } memcpy (a->ring + 2 * a->iin, a->resout, first * sizeof (complex)); memcpy (a->ring, a->resout + 2 * first, second * sizeof (complex)); if (a->ucnt >= 0) upslew(a, newsamps); a->iin = (a->iin + newsamps) % a->rsize; if (ovfl > 0) blend (a); if (!a->control_flag) { a->writesamps += a->insize; if ((a->readsamps >= a->read_startup) && (a->writesamps >= a->write_startup)) a->control_flag = 1; } if (a->control_flag) control (a, a->insize); LeaveCriticalSection (&a->cs_ring); } } void dslew (RMATCH a) { int i, j, k, n; int zeros, first, second; if (a->n_ring > a->ntslew + 1) { i = (a->iout + (a->n_ring - (a->ntslew + 1))) % a->rsize; j = a->ntslew; k = a->ntslew + 1; n = a->n_ring - (a->ntslew + 1); } else { i = a->iout; j = a->ntslew; k = a->n_ring; n = 0; } while (k > 0 && j >= 0) { if (k == 1) { a->dlast[0] = a->ring[2 * i + 0]; a->dlast[1] = a->ring[2 * i + 1]; } a->ring[2 * i + 0] *= a->cslew[j]; a->ring[2 * i + 1] *= a->cslew[j]; i = (i + 1) % a->rsize; j--; k--; n++; } while (j >= 0) { a->ring[2 * i + 0] = a->dlast[0] * a->cslew[j]; a->ring[2 * i + 1] = a->dlast[1] * a->cslew[j]; i = (i + 1) % a->rsize; j--; n++; } // zeros = a->outsize + a->rsize / 2 - n; if ((zeros = a->outsize - n) > 0) // { // if (zeros > a->rsize - i) { first = a->rsize - i; second = zeros - first; } else { first = zeros; second = 0; } memset (a->ring + 2 * i, 0, first * sizeof (complex)); memset (a->ring, 0, second * sizeof (complex)); n += zeros; // } // // a->n_ring = a->outsize + a->rsize / 2; a->n_ring = n; // // a->iin = (a->iout + a->outsize + a->rsize/2) % a->rsize; a->iin = (a->iout + a->n_ring) % a->rsize; // } PORT void xrmatchOUT (void* b, double* out) { RMATCH a = (RMATCH)b; if (InterlockedAnd (&a->run, 1)) { int first, second; a->out = out; EnterCriticalSection (&a->cs_ring); if (a->n_ring < a->outsize) { dslew (a); a->ucnt = a->ntslew; InterlockedIncrement (&a->underflows); } if (a->outsize > (a->rsize - a->iout)) { first = a->rsize - a->iout; second = a->outsize - first; } else { first = a->outsize; second = 0; } memcpy (a->out, a->ring + 2 * a->iout, first * sizeof (complex)); memcpy (a->out + 2 * first, a->ring, second * sizeof (complex)); a->iout = (a->iout + a->outsize) % a->rsize; a->n_ring -= a->outsize; a->dlast[0] = a->out[2 * (a->outsize - 1) + 0]; a->dlast[1] = a->out[2 * (a->outsize - 1) + 1]; if (!a->control_flag) { a->readsamps += a->outsize; if ((a->readsamps >= a->read_startup) && (a->writesamps >= a->write_startup)) a->control_flag = 1; } if (a->control_flag) control (a, -(a->outsize)); LeaveCriticalSection (&a->cs_ring); } } PORT void getRMatchDiags (void* b, int* underflows, int* overflows, double* var, int* ringsize, int* nring) { RMATCH a = (RMATCH)b; *underflows = InterlockedAnd (&a->underflows, 0xFFFFFFFF); *overflows = InterlockedAnd (&a->overflows, 0xFFFFFFFF); EnterCriticalSection (&a->cs_var); *var = a->var; *ringsize = a->ringsize; *nring = a->n_ring; LeaveCriticalSection (&a->cs_var); } PORT void resetRMatchDiags (void* b) { RMATCH a = (RMATCH)b; InterlockedExchange (&a->underflows, 0); InterlockedExchange (&a->overflows, 0); } PORT void forceRMatchVar (void* b, int force, double fvar) { RMATCH a = (RMATCH)b; EnterCriticalSection (&a->cs_var); a->force = force; a->fvar = fvar; LeaveCriticalSection (&a->cs_var); } PORT void* create_rmatchV(int in_size, int out_size, int nom_inrate, int nom_outrate, int ringsize, double var) { return (void*)create_rmatch ( 1, // run 0, // input buffer, stuffed in other calls 0, // output buffer, stuffed in other calls in_size, // input buffer size (complex samples) out_size, // output buffer size (complex samples) nom_inrate, // nominal input sample-rate nom_outrate, // nominal output sample-rate 0.0, // fc_high (0.0 -> automatic) -1.0, // fc_low (-1.0 -> no low cutoff) 1.0, // gain 3.0, // startup delay (seconds) 1, // automatic ring-size [not implemented yet] ringsize, // ringsize 1024, // R, coefficient density var, // initial variable ratio 4096, // feed-forward moving average min size 262144, // feed-forward moving average max size - POWER OF TWO! 0.01, // feed-forward exponential smoothing 4096, // proportional feedback min moving av ringsize 16384, // proportional feedback max moving av ringsize - POWER OF TWO! 4.0e-06, // proportional feedback gain 1, // linearly interpolate cvar by sample 0.003 ); // slew time (seconds) } PORT void destroy_rmatchV (void* ptr) { RMATCH a = (RMATCH)ptr; destroy_rmatch (a); } PORT void setRMatchInsize (void* ptr, int insize) { RMATCH a = (RMATCH)ptr; InterlockedBitTestAndReset (&a->run, 0); Sleep (10); decalc_rmatch(a); a->insize = insize; calc_rmatch (a); InterlockedBitTestAndSet (&a->run, 0); } PORT void setRMatchOutsize (void* ptr, int outsize) { RMATCH a = (RMATCH)ptr; InterlockedBitTestAndReset (&a->run, 0); Sleep (10); decalc_rmatch(a); a->outsize = outsize; calc_rmatch (a); InterlockedBitTestAndSet (&a->run, 0); } PORT void setRMatchNomInrate (void* ptr, int nom_inrate) { RMATCH a = (RMATCH)ptr; InterlockedBitTestAndReset (&a->run, 0); Sleep (10); decalc_rmatch(a); a->nom_inrate = nom_inrate; calc_rmatch (a); InterlockedBitTestAndSet (&a->run, 0); } PORT void setRMatchNomOutrate (void* ptr, int nom_outrate) { RMATCH a = (RMATCH)ptr; InterlockedBitTestAndReset (&a->run, 0); Sleep (10); decalc_rmatch(a); a->nom_outrate = nom_outrate; calc_rmatch (a); InterlockedBitTestAndSet (&a->run, 0); } PORT void setRMatchRingsize (void* ptr, int ringsize) { RMATCH a = (RMATCH)ptr; InterlockedBitTestAndReset (&a->run, 0); Sleep (10); decalc_rmatch(a); a->ringsize = ringsize; calc_rmatch (a); InterlockedBitTestAndSet (&a->run, 0); } PORT void setRMatchFeedbackGain (void* b, double feedback_gain) { RMATCH a = (RMATCH)b; EnterCriticalSection(&a->cs_var); a->prop_gain = feedback_gain; a->pr_gain = a->prop_gain * 48000.0 / (double)a->nom_outrate; LeaveCriticalSection(&a->cs_var); } PORT void setRMatchSlewTime (void* b, double slew_time) { RMATCH a = (RMATCH)b; InterlockedBitTestAndReset(&a->run, 0); // turn OFF new data coming into the rmatch Sleep(10); // wait for processing to cease decalc_rmatch(a); // deallocate all memory EXCEPT the data structure holding all current parameters a->tslew = slew_time; // change the value of 'slew_time' calc_rmatch(a); // recalculate/reallocate everything in the RMATCH InterlockedBitTestAndSet(&a->run, 0); // turn ON the dataflow } PORT void setRMatchSlewTime1(void* b, double slew_time) { RMATCH a = (RMATCH)b; double theta, dtheta; int m; InterlockedBitTestAndReset(&a->run, 0); Sleep(10); _aligned_free(a->cslew); a->tslew = slew_time; a->ntslew = (int)(a->tslew * a->nom_outrate); if (a->ntslew + 1 > a->rsize / 2) a->ntslew = a->rsize / 2 - 1; a->cslew = (double*)malloc0((a->ntslew + 1) * sizeof(double)); dtheta = PI / (double)a->ntslew; theta = 0.0; for (m = 0; m <= a->ntslew; m++) { a->cslew[m] = 0.5 * (1.0 - cos(theta)); theta += dtheta; } InterlockedBitTestAndSet(&a->run, 0); } PORT void setRMatchPropRingMin(void* ptr, int prop_min) { RMATCH a = (RMATCH)ptr; InterlockedBitTestAndReset(&a->run, 0); Sleep(10); decalc_rmatch(a); a->prop_ringmin = prop_min; calc_rmatch(a); InterlockedBitTestAndSet(&a->run, 0); } PORT void setRMatchPropRingMax(void* ptr, int prop_max) { RMATCH a = (RMATCH)ptr; InterlockedBitTestAndReset(&a->run, 0); Sleep(10); decalc_rmatch(a); a->prop_ringmax = prop_max; // must be a power of two calc_rmatch(a); InterlockedBitTestAndSet(&a->run, 0); } PORT void setRMatchFFRingMin(void* ptr, int ff_ringmin) { RMATCH a = (RMATCH)ptr; InterlockedBitTestAndReset(&a->run, 0); Sleep(10); decalc_rmatch(a); a->ff_ringmin = ff_ringmin; calc_rmatch(a); InterlockedBitTestAndSet(&a->run, 0); } PORT void setRMatchFFRingMax(void* ptr, int ff_ringmax) { RMATCH a = (RMATCH)ptr; InterlockedBitTestAndReset(&a->run, 0); Sleep(10); decalc_rmatch(a); a->ff_ringmax = ff_ringmax; // must be a power of two calc_rmatch(a); InterlockedBitTestAndSet(&a->run, 0); } PORT void setRMatchFFAlpha(void* ptr, double ff_alpha) { RMATCH a = (RMATCH)ptr; InterlockedBitTestAndReset(&a->run, 0); Sleep (10); a->ff_alpha = ff_alpha; InterlockedBitTestAndSet(&a->run, 0); } PORT void getControlFlag(void* ptr, int* control_flag) { RMATCH a = (RMATCH)ptr; EnterCriticalSection(&a->cs_ring); *control_flag = a->control_flag; LeaveCriticalSection(&a->cs_ring); } // the following function is DEPRECATED // it is intended for Legacy PowerSDR use only PORT void* create_rmatchLegacyV(int in_size, int out_size, int nom_inrate, int nom_outrate, int ringsize) { return (void*)create_rmatch( 1, // run 0, // input buffer, stuffed in other calls 0, // output buffer, stuffed in other calls in_size, // input buffer size (complex samples) out_size, // output buffer size (complex samples) nom_inrate, // nominal input sample-rate nom_outrate, // nominal output sample-rate 0.0, // fc_high (0.0 -> automatic) -1.0, // fc_low (-1.0 -> no low cutoff) 1.0, // gain 3.0, // startup delay (seconds) 1, // automatic ring-size [not implemented yet] ringsize, // ringsize 1024, // R, coefficient density 1.0, // initial variable ratio 4096, // feed-forward moving average min size 262144, // feed-forward moving average max size - POWER OF TWO! 0.01, // feed-forward exponential smoothing 4096, // proportional feedback min moving av ringsize 16384, // proportional feedback max moving av ringsize - POWER OF TWO! 1.0e-06, // proportional feedback gain ***W4WMT - reduce loop gain a bit for PowerSDR to help Primary buffers > 512 0, // linearly interpolate cvar by sample ***W4WMT - set varmode = 0 for PowerSDR (doesn't work otherwise!?!) 0.003); // slew time (seconds) }