Merge branch 'wfm' into cpu-optimization
This commit is contained in:
@@ -144,6 +144,8 @@ utilities.c \
|
||||
varsamp.c \
|
||||
version.c \
|
||||
wcpAGC.c \
|
||||
wfmd.c \
|
||||
wfmmod.c \
|
||||
wisdom.c \
|
||||
zetaHat.c
|
||||
|
||||
|
||||
@@ -119,6 +119,8 @@ utilities.c \
|
||||
varsamp.c \
|
||||
version.c \
|
||||
wcpAGC.c \
|
||||
wfmd.c \
|
||||
wfmmod.c \
|
||||
wisdom.c \
|
||||
zetaHat.c
|
||||
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ SOURCES = \
|
||||
matchedCW.c meter.c meterlog10.c nbp.c nob.c nobII.c osctrl.c \
|
||||
patchpanel.c resample.c rmatch.c rnnr.c RXA.c sbnr.c sender.c \
|
||||
shift.c siphon.c slew.c snb.c ssql.c syncbuffs.c TXA.c \
|
||||
utilities.c varsamp.c version.c wcpAGC.c wisdom.c zetaHat.c
|
||||
utilities.c varsamp.c version.c wcpAGC.c wfmd.c wfmmod.c wisdom.c zetaHat.c
|
||||
|
||||
OBJS := $(addprefix $(OBJDIR)/, $(SOURCES:.c=.o))
|
||||
|
||||
|
||||
@@ -233,6 +233,23 @@ void create_rxa (int channel)
|
||||
max(2048, ch[channel].dsp_size), // number of coefficients for noise filter
|
||||
0); // minimum phase flag
|
||||
|
||||
// WFM demod
|
||||
rxa[channel].wfmd.p = create_wfmd (
|
||||
0, // run
|
||||
ch[channel].dsp_size, // buffer size
|
||||
rxa[channel].midbuff, // pointer to input buffer
|
||||
rxa[channel].midbuff, // pointer to output buffer
|
||||
ch[channel].dsp_rate, // sample rate
|
||||
75000.0, // deviation
|
||||
20.0, // f_low
|
||||
15000.0, // f_high
|
||||
0.02, // tau - for dc removal
|
||||
1, // run de-emphasis
|
||||
75.0e-6, // de-emphasis time constant
|
||||
0.5, // audio gain
|
||||
max(2048, ch[channel].dsp_size), // # coefs for audio cutoff filter
|
||||
0); // min phase flag for audio cutoff filter
|
||||
|
||||
// snba
|
||||
rxa[channel].snba.p = create_snba (
|
||||
0, // run
|
||||
@@ -579,6 +596,7 @@ void destroy_rxa (int channel)
|
||||
destroy_anf (rxa[channel].anf.p);
|
||||
destroy_eqp (rxa[channel].eqp.p);
|
||||
destroy_snba (rxa[channel].snba.p);
|
||||
destroy_wfmd (rxa[channel].wfmd.p);
|
||||
destroy_fmsq (rxa[channel].fmsq.p);
|
||||
destroy_fmd (rxa[channel].fmd.p);
|
||||
destroy_amd (rxa[channel].amd.p);
|
||||
@@ -614,6 +632,7 @@ void flush_rxa (int channel)
|
||||
flush_amd (rxa[channel].amd.p);
|
||||
flush_fmd (rxa[channel].fmd.p);
|
||||
flush_fmsq (rxa[channel].fmsq.p);
|
||||
flush_wfmd (rxa[channel].wfmd.p);
|
||||
flush_snba (rxa[channel].snba.p);
|
||||
flush_eqp (rxa[channel].eqp.p);
|
||||
flush_anf (rxa[channel].anf.p);
|
||||
@@ -649,6 +668,7 @@ void xrxa (int channel)
|
||||
xamd (rxa[channel].amd.p);
|
||||
xfmd (rxa[channel].fmd.p);
|
||||
xfmsq (rxa[channel].fmsq.p);
|
||||
xwfmd (rxa[channel].wfmd.p);
|
||||
xbpsnbain (rxa[channel].bpsnba.p, 1);
|
||||
xbpsnbaout (rxa[channel].bpsnba.p, 1);
|
||||
xsnba (rxa[channel].snba.p);
|
||||
@@ -733,6 +753,7 @@ void setDSPSamplerate_rxa (int channel)
|
||||
setSamplerate_fmd (rxa[channel].fmd.p, ch[channel].dsp_rate);
|
||||
setBuffers_fmsq (rxa[channel].fmsq.p, rxa[channel].midbuff, rxa[channel].midbuff, rxa[channel].fmd.p->audio);
|
||||
setSamplerate_fmsq (rxa[channel].fmsq.p, ch[channel].dsp_rate);
|
||||
setSamplerate_wfmd (rxa[channel].wfmd.p, ch[channel].dsp_rate);
|
||||
setSamplerate_snba (rxa[channel].snba.p, ch[channel].dsp_rate);
|
||||
setSamplerate_eqp (rxa[channel].eqp.p, ch[channel].dsp_rate);
|
||||
setSamplerate_anf (rxa[channel].anf.p, ch[channel].dsp_rate);
|
||||
@@ -794,6 +815,8 @@ void setDSPBuffsize_rxa (int channel)
|
||||
setSize_fmd (rxa[channel].fmd.p, ch[channel].dsp_size);
|
||||
setBuffers_fmsq (rxa[channel].fmsq.p, rxa[channel].midbuff, rxa[channel].midbuff, rxa[channel].fmd.p->audio);
|
||||
setSize_fmsq (rxa[channel].fmsq.p, ch[channel].dsp_size);
|
||||
setBuffers_wfmd (rxa[channel].wfmd.p, rxa[channel].midbuff, rxa[channel].midbuff);
|
||||
setSize_wfmd (rxa[channel].wfmd.p, ch[channel].dsp_size);
|
||||
setBuffers_snba (rxa[channel].snba.p, rxa[channel].midbuff, rxa[channel].midbuff);
|
||||
setSize_snba (rxa[channel].snba.p, ch[channel].dsp_size);
|
||||
setBuffers_eqp (rxa[channel].eqp.p, rxa[channel].midbuff, rxa[channel].midbuff);
|
||||
@@ -857,6 +880,7 @@ void SetRXAMode (int channel, int mode)
|
||||
rxa[channel].mode = mode;
|
||||
rxa[channel].amd.p->run = 0;
|
||||
rxa[channel].fmd.p->run = 0;
|
||||
rxa[channel].wfmd.p->run = 0;
|
||||
rxa[channel].agc.p->run = 1;
|
||||
switch (mode)
|
||||
{
|
||||
@@ -875,6 +899,10 @@ void SetRXAMode (int channel, int mode)
|
||||
rxa[channel].fmd.p->run = 1;
|
||||
rxa[channel].agc.p->run = 0;
|
||||
break;
|
||||
case RXA_WFM:
|
||||
rxa[channel].wfmd.p->run = 1;
|
||||
rxa[channel].agc.p->run = 0;
|
||||
break;
|
||||
default:
|
||||
|
||||
break;
|
||||
@@ -961,6 +989,7 @@ void RXAbpsnbaCheck (int channel, int mode, int notch_run)
|
||||
run_notches = 0;
|
||||
break;
|
||||
case RXA_FM:
|
||||
case RXA_WFM:
|
||||
f_low = +a->abs_low_freq;
|
||||
f_high = +a->abs_high_freq;
|
||||
run_notches = 0;
|
||||
@@ -1010,6 +1039,7 @@ void RXAbpsnbaSet (int channel)
|
||||
a->position = 1;
|
||||
break;
|
||||
case RXA_FM:
|
||||
case RXA_WFM:
|
||||
a->run = rxa[channel].snba.p->run;
|
||||
a->position = 1;
|
||||
break;
|
||||
@@ -1046,6 +1076,7 @@ void RXASetNC (int channel, int nc)
|
||||
SetRXAFMSQNC (channel, nc);
|
||||
SetRXAFMNCde (channel, nc);
|
||||
SetRXAFMNCaud (channel, nc);
|
||||
SetRXAWFMNCaud (channel, nc);
|
||||
SetChannelState (channel, oldstate, 0);
|
||||
}
|
||||
|
||||
@@ -1059,4 +1090,5 @@ void RXASetMP (int channel, int mp)
|
||||
SetRXAFMSQMP (channel, mp);
|
||||
SetRXAFMMPde (channel, mp);
|
||||
SetRXAFMMPaud (channel, mp);
|
||||
SetRXAWFMMPaud (channel, mp);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ enum rxaMode
|
||||
RXA_SPEC,
|
||||
RXA_DIGL,
|
||||
RXA_SAM,
|
||||
RXA_DRM
|
||||
RXA_DRM,
|
||||
RXA_WFM
|
||||
};
|
||||
|
||||
enum rxaMeterType
|
||||
@@ -121,6 +122,10 @@ struct _rxa
|
||||
FMSQ p;
|
||||
} fmsq;
|
||||
struct
|
||||
{
|
||||
WFMD p;
|
||||
} wfmd;
|
||||
struct
|
||||
{
|
||||
EQP p;
|
||||
} eqp;
|
||||
|
||||
@@ -358,6 +358,21 @@ void create_txa (int channel)
|
||||
max(2048, ch[channel].dsp_size), // number coefficients for bandpass filter
|
||||
0); // minimum phase flag
|
||||
|
||||
txa[channel].wfmmod.p = create_wfmmod (
|
||||
0, // run - OFF by default
|
||||
ch[channel].dsp_size, // size
|
||||
txa[channel].midbuff, // pointer to input buffer
|
||||
txa[channel].midbuff, // pointer to output buffer
|
||||
ch[channel].dsp_rate, // samplerate
|
||||
75000.0, // deviation
|
||||
20.0, // low cutoff frequency
|
||||
15000.0, // high cutoff frequency
|
||||
1, // run pre-emphasis
|
||||
75.0e-6, // pre-emphasis time constant
|
||||
1, // run bandpass filter
|
||||
max(2048, ch[channel].dsp_size), // number coefficients for bandpass filter
|
||||
0); // minimum phase flag
|
||||
|
||||
txa[channel].gen1.p = create_gen (
|
||||
0, // run
|
||||
ch[channel].dsp_size, // buffer size
|
||||
@@ -490,6 +505,7 @@ void destroy_txa (int channel)
|
||||
destroy_meter (txa[channel].alcmeter.p);
|
||||
destroy_uslew (txa[channel].uslew.p);
|
||||
destroy_gen (txa[channel].gen1.p);
|
||||
destroy_wfmmod (txa[channel].wfmmod.p);
|
||||
destroy_fmmod (txa[channel].fmmod.p);
|
||||
destroy_ammod (txa[channel].ammod.p);
|
||||
destroy_wcpagc (txa[channel].alc.p);
|
||||
@@ -544,6 +560,7 @@ void flush_txa (int channel)
|
||||
flush_wcpagc (txa[channel].alc.p);
|
||||
flush_ammod (txa[channel].ammod.p);
|
||||
flush_fmmod (txa[channel].fmmod.p);
|
||||
flush_wfmmod (txa[channel].wfmmod.p);
|
||||
flush_gen (txa[channel].gen1.p);
|
||||
flush_uslew (txa[channel].uslew.p);
|
||||
flush_meter (txa[channel].alcmeter.p);
|
||||
@@ -580,6 +597,7 @@ void xtxa (int channel)
|
||||
xammod (txa[channel].ammod.p); // AM Modulator
|
||||
xemphp (txa[channel].preemph.p, 1); // FM pre-emphasis (second option)
|
||||
xfmmod (txa[channel].fmmod.p); // FM Modulator
|
||||
xwfmmod (txa[channel].wfmmod.p); // WFM Modulator (pre-emphasis is internal)
|
||||
xgen (txa[channel].gen1.p); // output signal generator (TUN and Two-tone)
|
||||
xuslew (txa[channel].uslew.p); // up-slew for AM, FM, and gens
|
||||
xmeter (txa[channel].alcmeter.p); // ALC Meter
|
||||
@@ -653,6 +671,7 @@ void setDSPSamplerate_txa (int channel)
|
||||
setSamplerate_wcpagc (txa[channel].alc.p, ch[channel].dsp_rate);
|
||||
setSamplerate_ammod (txa[channel].ammod.p, ch[channel].dsp_rate);
|
||||
setSamplerate_fmmod (txa[channel].fmmod.p, ch[channel].dsp_rate);
|
||||
setSamplerate_wfmmod (txa[channel].wfmmod.p, ch[channel].dsp_rate);
|
||||
setSamplerate_gen (txa[channel].gen1.p, ch[channel].dsp_rate);
|
||||
setSamplerate_uslew (txa[channel].uslew.p, ch[channel].dsp_rate);
|
||||
setSamplerate_meter (txa[channel].alcmeter.p, ch[channel].dsp_rate);
|
||||
@@ -723,6 +742,8 @@ void setDSPBuffsize_txa (int channel)
|
||||
setSize_ammod (txa[channel].ammod.p, ch[channel].dsp_size);
|
||||
setBuffers_fmmod (txa[channel].fmmod.p, txa[channel].midbuff, txa[channel].midbuff);
|
||||
setSize_fmmod (txa[channel].fmmod.p, ch[channel].dsp_size);
|
||||
setBuffers_wfmmod (txa[channel].wfmmod.p, txa[channel].midbuff, txa[channel].midbuff);
|
||||
setSize_wfmmod (txa[channel].wfmmod.p, ch[channel].dsp_size);
|
||||
setBuffers_gen (txa[channel].gen1.p, txa[channel].midbuff, txa[channel].midbuff);
|
||||
setSize_gen (txa[channel].gen1.p, ch[channel].dsp_size);
|
||||
setBuffers_uslew (txa[channel].uslew.p, txa[channel].midbuff, txa[channel].midbuff);
|
||||
@@ -758,6 +779,7 @@ void SetTXAMode (int channel, int mode)
|
||||
txa[channel].mode = mode;
|
||||
txa[channel].ammod.p->run = 0;
|
||||
txa[channel].fmmod.p->run = 0;
|
||||
txa[channel].wfmmod.p->run = 0;
|
||||
txa[channel].preemph.p->run = 0;
|
||||
switch (mode)
|
||||
{
|
||||
@@ -779,6 +801,10 @@ void SetTXAMode (int channel, int mode)
|
||||
txa[channel].fmmod.p->run = 1;
|
||||
txa[channel].preemph.p->run = 1;
|
||||
break;
|
||||
case TXA_WFM:
|
||||
// wfmmod carries its own RC pre-emphasis; the shared emphp stays off
|
||||
txa[channel].wfmmod.p->run = 1;
|
||||
break;
|
||||
default:
|
||||
|
||||
break;
|
||||
@@ -820,6 +846,7 @@ int TXAUslewCheck (int channel)
|
||||
{
|
||||
return (txa[channel].ammod.p->run == 1) ||
|
||||
(txa[channel].fmmod.p->run == 1) ||
|
||||
(txa[channel].wfmmod.p->run == 1) ||
|
||||
(txa[channel].gen0.p->run == 1) ||
|
||||
(txa[channel].gen1.p->run == 1);
|
||||
}
|
||||
@@ -855,6 +882,7 @@ void TXASetupBPFilters (int channel)
|
||||
case TXA_AM:
|
||||
case TXA_SAM:
|
||||
case TXA_FM:
|
||||
case TXA_WFM:
|
||||
if (txa[channel].compressor.p->run)
|
||||
{
|
||||
CalcBandpassFilter (txa[channel].bp0.p, 0.0, txa[channel].f_high, 2.0);
|
||||
@@ -914,6 +942,7 @@ void TXASetNC (int channel, int nc)
|
||||
SetTXAFMEmphNC (channel, nc);
|
||||
SetTXAEQNC (channel, nc);
|
||||
SetTXAFMNC (channel, nc);
|
||||
SetTXAWFMNC (channel, nc);
|
||||
SetTXACFIRNC (channel, nc);
|
||||
SetChannelState (channel, oldstate, 0);
|
||||
}
|
||||
@@ -925,6 +954,7 @@ void TXASetMP (int channel, int mp)
|
||||
SetTXAFMEmphMP (channel, mp);
|
||||
SetTXAEQMP (channel, mp);
|
||||
SetTXAFMMP (channel, mp);
|
||||
SetTXAWFMMP (channel, mp);
|
||||
}
|
||||
|
||||
PORT
|
||||
|
||||
@@ -43,7 +43,8 @@ enum txaMode
|
||||
TXA_SAM,
|
||||
TXA_DRM,
|
||||
TXA_AM_LSB,
|
||||
TXA_AM_USB
|
||||
TXA_AM_USB,
|
||||
TXA_WFM
|
||||
};
|
||||
|
||||
enum txaMeterType
|
||||
@@ -135,6 +136,10 @@ struct _txa
|
||||
FMMOD p;
|
||||
} fmmod;
|
||||
struct
|
||||
{
|
||||
WFMMOD p;
|
||||
} wfmmod;
|
||||
struct
|
||||
{
|
||||
SIPHON p;
|
||||
} sip1;
|
||||
|
||||
@@ -138,6 +138,7 @@ CALCC create_calcc (int channel, int runcal, int size, int rate, int ints, int s
|
||||
a->stbl = stbl;
|
||||
a->npsamps = npsamps;
|
||||
a->alpha = alpha;
|
||||
a->outlier_sigma = 0.0;
|
||||
|
||||
a->info = (int *) malloc0 (16 * sizeof (int));
|
||||
a->binfo = (int *) malloc0 (16 * sizeof (int));
|
||||
@@ -321,6 +322,125 @@ void rxscheck (int rints, double* tvec, double* coef, int* info)
|
||||
if (out < 0.00) *info |= 0x0020;
|
||||
}
|
||||
|
||||
// Yurij_eu2av: fallback rx_scale estimator. It averages the top few
|
||||
// amplitude intervals (ignoring overrange samples) and linearly extrapolates
|
||||
// to full TX scale (env_TX = 1/hw_scale). Used only if the cubic xbuilder
|
||||
// fit fails or is rejected by rxscheck.
|
||||
static int estimate_rx_scale_from_top_intervals(CALCC a, double* rx_scale_out)
|
||||
{
|
||||
const int n_top = 4;
|
||||
double sx[4], sy[4], sw[4];
|
||||
int valid = 0;
|
||||
int b, j;
|
||||
for (b = a->ints - 1; b >= 0 && valid < n_top; b--)
|
||||
{
|
||||
int base = b * a->spi;
|
||||
double sum_x = 0.0, sum_y = 0.0;
|
||||
int n = 0;
|
||||
for (j = 0; j < a->spi; j++)
|
||||
{
|
||||
int k = base + j;
|
||||
double nx = a->env_TX[k] * a->hw_scale;
|
||||
if (nx > 1.0 || nx < 0.0) continue;
|
||||
if (a->env_TX[k] < 1.0e-30 || a->env_RX[k] < 1.0e-30) continue;
|
||||
sum_x += a->env_TX[k];
|
||||
sum_y += a->env_RX[k];
|
||||
n++;
|
||||
}
|
||||
if (n == 0) continue;
|
||||
sx[valid] = sum_x / (double)n;
|
||||
sy[valid] = sum_y / (double)n;
|
||||
sw[valid] = (double)n;
|
||||
valid++;
|
||||
}
|
||||
if (valid < 2) return -1;
|
||||
{
|
||||
double s_w = 0.0, s_x = 0.0, s_y = 0.0, s_xx = 0.0, s_xy = 0.0;
|
||||
double det, aa, bb, target_x, y_at_target;
|
||||
int i;
|
||||
for (i = 0; i < valid; i++)
|
||||
{
|
||||
double w = sw[i];
|
||||
s_w += w;
|
||||
s_x += w * sx[i];
|
||||
s_y += w * sy[i];
|
||||
s_xx += w * sx[i] * sx[i];
|
||||
s_xy += w * sx[i] * sy[i];
|
||||
}
|
||||
det = s_w * s_xx - s_x * s_x;
|
||||
if (fabs(det) < 1e-30) return -1;
|
||||
bb = (s_w * s_xy - s_x * s_y) / det;
|
||||
aa = (s_y - bb * s_x) / s_w;
|
||||
target_x = 1.0 / a->hw_scale;
|
||||
y_at_target = aa + bb * target_x;
|
||||
if (y_at_target <= 1e-15) return -1;
|
||||
*rx_scale_out = 1.0 / y_at_target;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Yurij_eu2av: robust outlier rejection for the cubic-spline xbuilder.
|
||||
// Fits rx = k*tx through the origin via median ratio, then rejects points
|
||||
// whose residual exceeds sigma * MAD.
|
||||
static int cmp_double(const void* a, const void* b)
|
||||
{
|
||||
double da = *(const double*)a;
|
||||
double db = *(const double*)b;
|
||||
if (da < db) return -1;
|
||||
if (da > db) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static double median_double(double* v, int n)
|
||||
{
|
||||
if (n <= 0) return 0.0;
|
||||
if (n % 2 == 1)
|
||||
return v[n / 2];
|
||||
else
|
||||
return 0.5 * (v[n / 2 - 1] + v[n / 2]);
|
||||
}
|
||||
|
||||
static int reject_outliers(double* tx, double* rx, int n, double sigma)
|
||||
{
|
||||
const int min_points = 32;
|
||||
int i, keep = 0;
|
||||
double* ratios;
|
||||
double* absres;
|
||||
double med_ratio, med_absres, thr;
|
||||
|
||||
if (n < min_points || sigma <= 0.0) return n;
|
||||
|
||||
ratios = (double*)malloc0(n * sizeof(double));
|
||||
for (i = 0; i < n; i++)
|
||||
ratios[i] = (tx[i] > 1.0e-30) ? rx[i] / tx[i] : 0.0;
|
||||
qsort(ratios, n, sizeof(double), cmp_double);
|
||||
med_ratio = median_double(ratios, n);
|
||||
_aligned_free(ratios);
|
||||
|
||||
if (fabs(med_ratio) < 1.0e-30) return n;
|
||||
|
||||
absres = (double*)malloc0(n * sizeof(double));
|
||||
for (i = 0; i < n; i++)
|
||||
absres[i] = fabs(rx[i] - med_ratio * tx[i]);
|
||||
qsort(absres, n, sizeof(double), cmp_double);
|
||||
med_absres = median_double(absres, n);
|
||||
_aligned_free(absres);
|
||||
|
||||
if (med_absres < 1.0e-30) return n;
|
||||
|
||||
thr = sigma * med_absres;
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
if (fabs(rx[i] - med_ratio * tx[i]) <= thr)
|
||||
{
|
||||
tx[keep] = tx[i];
|
||||
rx[keep] = rx[i];
|
||||
keep++;
|
||||
}
|
||||
}
|
||||
return (keep >= min_points) ? keep : n;
|
||||
}
|
||||
|
||||
void calc (CALCC a)
|
||||
{
|
||||
int i;
|
||||
@@ -336,21 +456,60 @@ void calc (CALCC a)
|
||||
double tvec[3];
|
||||
double txrxcoefs[4 * 2];
|
||||
double rx_scale;
|
||||
int xb_ok = 0;
|
||||
double* tx_filt;
|
||||
double* rx_filt;
|
||||
int n_filt = 0;
|
||||
if (a->ints < 16) rints = 1;
|
||||
else rints = 2;
|
||||
ix = rints - 1;
|
||||
for (i = 0; i <= rints; i++)
|
||||
tvec[i] = (double)i / (double)rints / a->hw_scale;
|
||||
dx = tvec[rints] - tvec[rints - 1];
|
||||
xbuilder(a->ccbld, a->nsamps, a->env_TX, a->env_RX, rints, tvec, &(a->binfo[0]), txrxcoefs, a->ptol);
|
||||
|
||||
// Yurij_eu2av: build a filtered dataset with overrange samples removed
|
||||
// before running xbuilder. Overrange env_TX*hw_scale > 1.0 can distort
|
||||
// the cubic fit and produce an incorrect rx_scale.
|
||||
tx_filt = (double*)malloc0(a->nsamps * sizeof(double));
|
||||
rx_filt = (double*)malloc0(a->nsamps * sizeof(double));
|
||||
for (i = 0; i < a->nsamps; i++)
|
||||
{
|
||||
double nx = a->env_TX[i] * a->hw_scale;
|
||||
if (nx > 1.0 || nx < 0.0) continue;
|
||||
if (a->env_TX[i] < 1.0e-30 || a->env_RX[i] < 1.0e-30) continue;
|
||||
tx_filt[n_filt] = a->env_TX[i];
|
||||
rx_filt[n_filt] = a->env_RX[i];
|
||||
n_filt++;
|
||||
}
|
||||
|
||||
// Yurij_eu2av: optional outlier rejection before cubic-spline fit.
|
||||
if (a->outlier_sigma > 0.0)
|
||||
n_filt = reject_outliers(tx_filt, rx_filt, n_filt, a->outlier_sigma);
|
||||
|
||||
xbuilder(a->ccbld, n_filt, tx_filt, rx_filt, rints, tvec, &(a->binfo[0]), txrxcoefs, a->ptol);
|
||||
rxscheck (rints, tvec, txrxcoefs, &a->binfo[7]);
|
||||
if ((a->binfo[0] == 0) && (a->binfo[7] == 0))
|
||||
{
|
||||
rx_scale = 1.0 / (txrxcoefs[4 * ix + 0] + dx * (txrxcoefs[4 * ix + 1] + dx * (txrxcoefs[4 * ix + 2] + dx * txrxcoefs[4 * ix + 3])));
|
||||
else
|
||||
xb_ok = 1;
|
||||
}
|
||||
else if (estimate_rx_scale_from_top_intervals(a, &rx_scale) == 0)
|
||||
{
|
||||
// Yurij_eu2av: xbuilder failed, but the bucket-average fallback
|
||||
// gave a usable rx_scale. Keep binfo[0] bit 0 set for diagnostics.
|
||||
a->binfo[0] |= 0x0001;
|
||||
xb_ok = 1;
|
||||
}
|
||||
|
||||
_aligned_free(tx_filt);
|
||||
_aligned_free(rx_filt);
|
||||
|
||||
if (!xb_ok)
|
||||
{
|
||||
a->scOK = 0;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (a->stbl && _InterlockedAnd (&a->ctrl.running, 1))
|
||||
a->rx_scale = a->alpha * a->rx_scale + (1.0 - a->alpha) * rx_scale;
|
||||
else
|
||||
@@ -1046,6 +1205,16 @@ void SetPSPtol (int channel, double ptol)
|
||||
LeaveCriticalSection (&txa[channel].calcc.cs_update);
|
||||
}
|
||||
|
||||
PORT
|
||||
void SetPSOutlierSigma (int channel, double sigma)
|
||||
{
|
||||
// Yurij_eu2av: 0.0 disables the pre-xbuilder outlier filter.
|
||||
if (sigma < 0.0) sigma = 0.0;
|
||||
EnterCriticalSection (&txa[channel].calcc.cs_update);
|
||||
txa[channel].calcc.p->outlier_sigma = sigma;
|
||||
LeaveCriticalSection (&txa[channel].calcc.cs_update);
|
||||
}
|
||||
|
||||
PORT
|
||||
void GetPSDisp (int channel, double* x, double* ym, double* yc, double* ys, double* cm, double* cc, double* cs)
|
||||
{
|
||||
|
||||
@@ -48,6 +48,7 @@ typedef struct _calcc
|
||||
double hw_scale;
|
||||
double rx_scale;
|
||||
double alpha;
|
||||
double outlier_sigma;
|
||||
|
||||
int tsamps;
|
||||
double* env_TX;
|
||||
|
||||
@@ -82,6 +82,8 @@ warren@wpratt.com
|
||||
#include "fmd.h"
|
||||
#include "fmmod.h"
|
||||
#include "fmsq.h"
|
||||
#include "wfmd.h"
|
||||
#include "wfmmod.h"
|
||||
#include "gain.h"
|
||||
#include "gaussian.h"
|
||||
#include "gen.h"
|
||||
|
||||
@@ -277,6 +277,7 @@ extern void SetPSHWPeak (int channel, double peak);
|
||||
extern void GetPSHWPeak (int channel, double* peak);
|
||||
extern void GetPSMaxTX (int channel, double* maxtx);
|
||||
extern void SetPSPtol (int channel, double ptol);
|
||||
extern void SetPSOutlierSigma (int channel, double sigma);
|
||||
extern void GetPSDisp (int channel, double* x, double* ym, double* yc, double* ys, double* cm, double* cc, double* cs);
|
||||
extern void SetPSFeedbackRate (int channel, int rate);
|
||||
extern void SetPSPinMode (int channel, int pin);
|
||||
@@ -517,6 +518,30 @@ extern void SetTXAFMNC (int channel, int nc);
|
||||
extern void SetTXAFMMP (int channel, int mp);
|
||||
extern void SetTXAFMAFFreqs (int channel, double low, double high);
|
||||
|
||||
//
|
||||
// Interfaces from wfmd.c
|
||||
//
|
||||
|
||||
extern void SetRXAWFMDeviation (int channel, double deviation);
|
||||
extern void SetRXAWFMNCaud (int channel, int nc);
|
||||
extern void SetRXAWFMMPaud (int channel, int mp);
|
||||
extern void SetRXAWFMAFFilter (int channel, double low, double high);
|
||||
extern void SetRXAWFMDeemphRun (int channel, int run);
|
||||
extern void SetRXAWFMDeemphTau (int channel, double tau);
|
||||
extern void SetRXAWFMLimRun (int channel, int run);
|
||||
extern void SetRXAWFMLimGain (int channel, double gaindB);
|
||||
|
||||
//
|
||||
// Interfaces from wfmmod.c
|
||||
//
|
||||
|
||||
extern void SetTXAWFMDeviation (int channel, double deviation);
|
||||
extern void SetTXAWFMNC (int channel, int nc);
|
||||
extern void SetTXAWFMMP (int channel, int mp);
|
||||
extern void SetTXAWFMAFFreqs (int channel, double low, double high);
|
||||
extern void SetTXAWFMPreEmphRun (int channel, int run);
|
||||
extern void SetTXAWFMPreEmphTau (int channel, double tau);
|
||||
|
||||
//
|
||||
// Interfaces from fmsq.c
|
||||
//
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
/* wfmd.c
|
||||
|
||||
This file is part of a program that implements a Software-Defined Radio.
|
||||
|
||||
Copyright (C) 2013, 2023 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"
|
||||
|
||||
void calc_wfmd (WFMD a)
|
||||
{
|
||||
// discriminator
|
||||
a->pre_i = 0.0;
|
||||
a->pre_q = 0.0;
|
||||
a->again = a->rate / (a->deviation * TWOPI);
|
||||
// dc removal
|
||||
a->mtau = exp(-1.0 / (a->rate * a->tau));
|
||||
a->onem_mtau = 1.0 - a->mtau;
|
||||
a->fmdc = 0.0;
|
||||
// de-emphasis
|
||||
if (a->tau_de > 0.0) a->mde = exp(-1.0 / (a->rate * a->tau_de));
|
||||
else a->mde = 0.0;
|
||||
a->onem_mde = 1.0 - a->mde;
|
||||
a->deemph_z = 0.0;
|
||||
// detector limiter
|
||||
a->plim = create_wcpagc (
|
||||
1, // run - always ON
|
||||
5, // mode
|
||||
1, // 0 for max(I,Q), 1 for envelope
|
||||
a->out, // input buff pointer
|
||||
a->out, // output buff pointer
|
||||
a->size, // io_buffsize
|
||||
(int)a->rate, // sample rate
|
||||
0.001, // tau_attack
|
||||
0.008, // tau_decay
|
||||
4, // n_tau
|
||||
a->lim_gain, // max_gain (sets threshold, initial value)
|
||||
1.0, // var_gain / slope
|
||||
1.0, // fixed_gain
|
||||
1.0, // max_input
|
||||
0.9, // out_targ
|
||||
0.250, // tau_fast_backaverage
|
||||
0.004, // tau_fast_decay
|
||||
4.0, // pop_ratio
|
||||
0, // hang_enable
|
||||
0.500, // tau_hang_backmult
|
||||
0.500, // hangtime
|
||||
2.000, // hang_thresh
|
||||
0.100); // tau_hang_decay
|
||||
}
|
||||
|
||||
void decalc_wfmd (WFMD a)
|
||||
{
|
||||
destroy_wcpagc(a->plim);
|
||||
}
|
||||
|
||||
WFMD create_wfmd (int run, int size, double* in, double* out, int rate, double deviation, double f_low, double f_high,
|
||||
double tau, int deemph_run, double tau_de, double afgain, int nc_aud, int mp_aud)
|
||||
{
|
||||
WFMD a = (WFMD) malloc0 (sizeof (wfmd));
|
||||
double* impulse;
|
||||
a->run = run;
|
||||
a->size = size;
|
||||
a->in = in;
|
||||
a->out = out;
|
||||
a->rate = (double)rate;
|
||||
a->deviation = deviation;
|
||||
a->f_low = f_low;
|
||||
a->f_high = f_high;
|
||||
a->tau = tau;
|
||||
a->deemph_run = deemph_run;
|
||||
a->tau_de = tau_de;
|
||||
a->afgain = afgain;
|
||||
a->nc_aud = nc_aud;
|
||||
a->mp_aud = mp_aud;
|
||||
a->lim_run = 0;
|
||||
a->lim_pre_gain = 0.4;
|
||||
a->lim_gain = 2.5;
|
||||
calc_wfmd (a);
|
||||
a->audio = (double *) malloc0 (a->size * sizeof (complex));
|
||||
// audio filter
|
||||
impulse = fir_bandpass(a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size));
|
||||
a->paud = create_fircore (a->size, a->audio, a->out, a->nc_aud, a->mp_aud, impulse);
|
||||
_aligned_free (impulse);
|
||||
return a;
|
||||
}
|
||||
|
||||
void destroy_wfmd (WFMD a)
|
||||
{
|
||||
destroy_fircore (a->paud);
|
||||
_aligned_free (a->audio);
|
||||
decalc_wfmd (a);
|
||||
_aligned_free (a);
|
||||
}
|
||||
|
||||
void flush_wfmd (WFMD a)
|
||||
{
|
||||
memset (a->audio, 0, a->size * sizeof (complex));
|
||||
flush_fircore (a->paud);
|
||||
a->pre_i = 0.0;
|
||||
a->pre_q = 0.0;
|
||||
a->fmdc = 0.0;
|
||||
a->deemph_z = 0.0;
|
||||
flush_wcpagc (a->plim);
|
||||
}
|
||||
|
||||
void xwfmd (WFMD a)
|
||||
{
|
||||
if (a->run)
|
||||
{
|
||||
int i;
|
||||
double si, sq, cr, ci, det, aud;
|
||||
for (i = 0; i < a->size; i++)
|
||||
{
|
||||
// quadrature discriminator: det = arg (x[n] * conj (x[n-1]))
|
||||
si = a->in[2 * i + 0];
|
||||
sq = a->in[2 * i + 1];
|
||||
cr = + si * a->pre_i + sq * a->pre_q;
|
||||
ci = - si * a->pre_q + sq * a->pre_i;
|
||||
a->pre_i = si;
|
||||
a->pre_q = sq;
|
||||
det = atan2 (ci, cr);
|
||||
// dc removal, gain, & demod output
|
||||
a->fmdc = a->mtau * a->fmdc + a->onem_mtau * det;
|
||||
aud = a->again * (det - a->fmdc);
|
||||
// de-emphasis
|
||||
if (a->deemph_run)
|
||||
{
|
||||
a->deemph_z = a->mde * a->deemph_z + a->onem_mde * aud;
|
||||
aud = a->deemph_z;
|
||||
}
|
||||
a->audio[2 * i + 0] = aud;
|
||||
a->audio[2 * i + 1] = aud;
|
||||
}
|
||||
// audio filter
|
||||
xfircore (a->paud);
|
||||
if (a->lim_run)
|
||||
{
|
||||
for (i = 0; i < 2 * a->size; i++)
|
||||
a->out[i] *= a->lim_pre_gain;
|
||||
xwcpagc (a->plim);
|
||||
}
|
||||
}
|
||||
else if (a->in != a->out)
|
||||
memcpy (a->out, a->in, a->size * sizeof (complex));
|
||||
}
|
||||
|
||||
void setBuffers_wfmd (WFMD a, double* in, double* out)
|
||||
{
|
||||
decalc_wfmd (a);
|
||||
a->in = in;
|
||||
a->out = out;
|
||||
calc_wfmd (a);
|
||||
setBuffers_fircore (a->paud, a->audio, a->out);
|
||||
setBuffers_wcpagc (a->plim, a->out, a->out);
|
||||
}
|
||||
|
||||
void setSamplerate_wfmd (WFMD a, int rate)
|
||||
{
|
||||
double* impulse;
|
||||
decalc_wfmd (a);
|
||||
a->rate = rate;
|
||||
calc_wfmd (a);
|
||||
// audio filter
|
||||
impulse = fir_bandpass(a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size));
|
||||
setImpulse_fircore (a->paud, impulse, 1);
|
||||
_aligned_free (impulse);
|
||||
setSamplerate_wcpagc (a->plim, (int)a->rate);
|
||||
}
|
||||
|
||||
void setSize_wfmd (WFMD a, int size)
|
||||
{
|
||||
double* impulse;
|
||||
decalc_wfmd (a);
|
||||
_aligned_free (a->audio);
|
||||
a->size = size;
|
||||
calc_wfmd (a);
|
||||
a->audio = (double *) malloc0 (a->size * sizeof (complex));
|
||||
// audio filter
|
||||
destroy_fircore (a->paud);
|
||||
impulse = fir_bandpass(a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size));
|
||||
a->paud = create_fircore (a->size, a->audio, a->out, a->nc_aud, a->mp_aud, impulse);
|
||||
_aligned_free (impulse);
|
||||
setSize_wcpagc (a->plim, a->size);
|
||||
}
|
||||
|
||||
/********************************************************************************************************
|
||||
* *
|
||||
* RXA Properties *
|
||||
* *
|
||||
********************************************************************************************************/
|
||||
|
||||
PORT
|
||||
void SetRXAWFMDeviation (int channel, double deviation)
|
||||
{
|
||||
WFMD a;
|
||||
EnterCriticalSection (&ch[channel].csDSP);
|
||||
a = rxa[channel].wfmd.p;
|
||||
a->deviation = deviation;
|
||||
a->again = a->rate / (a->deviation * TWOPI);
|
||||
LeaveCriticalSection (&ch[channel].csDSP);
|
||||
}
|
||||
|
||||
PORT
|
||||
void SetRXAWFMNCaud (int channel, int nc)
|
||||
{
|
||||
WFMD a;
|
||||
double* impulse;
|
||||
EnterCriticalSection (&ch[channel].csDSP);
|
||||
a = rxa[channel].wfmd.p;
|
||||
if (a->nc_aud != nc)
|
||||
{
|
||||
a->nc_aud = nc;
|
||||
impulse = fir_bandpass(a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size));
|
||||
setNc_fircore (a->paud, a->nc_aud, impulse);
|
||||
_aligned_free (impulse);
|
||||
}
|
||||
LeaveCriticalSection (&ch[channel].csDSP);
|
||||
}
|
||||
|
||||
PORT
|
||||
void SetRXAWFMMPaud (int channel, int mp)
|
||||
{
|
||||
WFMD a;
|
||||
a = rxa[channel].wfmd.p;
|
||||
if (a->mp_aud != mp)
|
||||
{
|
||||
a->mp_aud = mp;
|
||||
setMp_fircore (a->paud, a->mp_aud);
|
||||
}
|
||||
}
|
||||
|
||||
PORT
|
||||
void SetRXAWFMAFFilter (int channel, double low, double high)
|
||||
{
|
||||
WFMD a = rxa[channel].wfmd.p;
|
||||
double* impulse;
|
||||
EnterCriticalSection (&ch[channel].csDSP);
|
||||
if (a->f_low != low || a->f_high != high)
|
||||
{
|
||||
a->f_low = low;
|
||||
a->f_high = high;
|
||||
impulse = fir_bandpass (a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size));
|
||||
setImpulse_fircore (a->paud, impulse, 1);
|
||||
_aligned_free (impulse);
|
||||
}
|
||||
LeaveCriticalSection (&ch[channel].csDSP);
|
||||
}
|
||||
|
||||
PORT
|
||||
void SetRXAWFMDeemphRun (int channel, int run)
|
||||
{
|
||||
WFMD a = rxa[channel].wfmd.p;
|
||||
EnterCriticalSection (&ch[channel].csDSP);
|
||||
if (a->deemph_run != run)
|
||||
{
|
||||
a->deemph_run = run;
|
||||
a->deemph_z = 0.0;
|
||||
}
|
||||
LeaveCriticalSection (&ch[channel].csDSP);
|
||||
}
|
||||
|
||||
PORT
|
||||
void SetRXAWFMDeemphTau (int channel, double tau)
|
||||
{
|
||||
WFMD a = rxa[channel].wfmd.p;
|
||||
EnterCriticalSection (&ch[channel].csDSP);
|
||||
if (a->tau_de != tau && tau > 0.0)
|
||||
{
|
||||
a->tau_de = tau;
|
||||
a->mde = exp(-1.0 / (a->rate * a->tau_de));
|
||||
a->onem_mde = 1.0 - a->mde;
|
||||
a->deemph_z = 0.0;
|
||||
}
|
||||
LeaveCriticalSection (&ch[channel].csDSP);
|
||||
}
|
||||
|
||||
PORT
|
||||
void SetRXAWFMLimRun (int channel, int run)
|
||||
{
|
||||
WFMD a = rxa[channel].wfmd.p;
|
||||
EnterCriticalSection (&ch[channel].csDSP);
|
||||
if (a->lim_run != run)
|
||||
{
|
||||
a->lim_run = run;
|
||||
}
|
||||
LeaveCriticalSection (&ch[channel].csDSP);
|
||||
}
|
||||
|
||||
PORT
|
||||
void SetRXAWFMLimGain (int channel, double gaindB)
|
||||
{
|
||||
double gain = pow(10.0, gaindB / 20.0);
|
||||
WFMD a = rxa[channel].wfmd.p;
|
||||
EnterCriticalSection (&ch[channel].csDSP);
|
||||
if (a->lim_gain != gain)
|
||||
{
|
||||
decalc_wfmd (a);
|
||||
a->lim_gain = gain;
|
||||
calc_wfmd (a);
|
||||
}
|
||||
LeaveCriticalSection (&ch[channel].csDSP);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/* wfmd.h
|
||||
|
||||
This file is part of a program that implements a Software-Defined Radio.
|
||||
|
||||
Copyright (C) 2013 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
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _wfmd_h
|
||||
#define _wfmd_h
|
||||
#include "firmin.h"
|
||||
#include "wcpAGC.h"
|
||||
typedef struct _wfmd
|
||||
{
|
||||
int run;
|
||||
int size;
|
||||
double* in;
|
||||
double* out;
|
||||
double rate;
|
||||
double f_low; // audio low cutoff
|
||||
double f_high; // audio high cutoff
|
||||
// quadrature discriminator
|
||||
double deviation; // peak deviation, Hz
|
||||
double again; // discriminator output gain
|
||||
double pre_i; // previous sample, I
|
||||
double pre_q; // previous sample, Q
|
||||
// for dc removal
|
||||
double tau;
|
||||
double mtau;
|
||||
double onem_mtau;
|
||||
double fmdc;
|
||||
// de-emphasis, single-pole RC
|
||||
int deemph_run;
|
||||
double tau_de; // 75.0e-6 (Americas) or 50.0e-6 (elsewhere)
|
||||
double mde;
|
||||
double onem_mde;
|
||||
double deemph_z;
|
||||
// for audio filter
|
||||
double* audio;
|
||||
FIRCORE paud;
|
||||
int nc_aud;
|
||||
int mp_aud;
|
||||
double afgain;
|
||||
// detector limiter
|
||||
WCPAGC plim;
|
||||
int lim_run;
|
||||
double lim_gain;
|
||||
double lim_pre_gain;
|
||||
} wfmd, *WFMD;
|
||||
|
||||
extern WFMD create_wfmd ( int run, int size, double* in, double* out, int rate, double deviation,
|
||||
double f_low, double f_high, double tau, int deemph_run, double tau_de, double afgain,
|
||||
int nc_aud, int mp_aud);
|
||||
|
||||
extern void destroy_wfmd (WFMD a);
|
||||
|
||||
extern void flush_wfmd (WFMD a);
|
||||
|
||||
extern void xwfmd (WFMD a);
|
||||
|
||||
extern void setBuffers_wfmd (WFMD a, double* in, double* out);
|
||||
|
||||
extern void setSamplerate_wfmd (WFMD a, int rate);
|
||||
|
||||
extern void setSize_wfmd (WFMD a, int size);
|
||||
|
||||
// RXA Properties
|
||||
|
||||
extern __declspec (dllexport) void SetRXAWFMDeviation (int channel, double deviation);
|
||||
|
||||
extern __declspec (dllexport) void SetRXAWFMNCaud (int channel, int nc);
|
||||
|
||||
extern __declspec (dllexport) void SetRXAWFMMPaud (int channel, int mp);
|
||||
|
||||
extern __declspec (dllexport) void SetRXAWFMAFFilter (int channel, double low, double high);
|
||||
|
||||
extern __declspec (dllexport) void SetRXAWFMDeemphRun (int channel, int run);
|
||||
|
||||
extern __declspec (dllexport) void SetRXAWFMDeemphTau (int channel, double tau);
|
||||
|
||||
extern __declspec (dllexport) void SetRXAWFMLimRun (int channel, int run);
|
||||
|
||||
extern __declspec (dllexport) void SetRXAWFMLimGain (int channel, double gaindB);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,250 @@
|
||||
/* wfmmod.c
|
||||
|
||||
This file is part of a program that implements a Software-Defined Radio.
|
||||
|
||||
Copyright (C) 2013, 2016, 2023 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"
|
||||
|
||||
// the modulated spectrum spans +/-(deviation + f_high); at the sample rates used for
|
||||
// narrowband modes that exceeds Nyquist, so the bandpass degenerates to a passthrough.
|
||||
static double bpfc_wfmmod (double samplerate, double deviation, double f_high)
|
||||
{
|
||||
double fc = deviation + f_high;
|
||||
double max_fc = 0.45 * samplerate;
|
||||
if (fc > max_fc) fc = max_fc;
|
||||
return fc;
|
||||
}
|
||||
|
||||
void calc_wfmmod (WFMMOD a)
|
||||
{
|
||||
// pre-emphasis
|
||||
if (a->tau_pre > 0.0) a->pmult = exp(-1.0 / (a->samplerate * a->tau_pre));
|
||||
else a->pmult = 0.0;
|
||||
a->pnorm = 1.0 / (1.0 - a->pmult);
|
||||
a->pre_z = 0.0;
|
||||
// mod
|
||||
a->sphase = 0.0;
|
||||
a->sdelta = TWOPI * a->deviation / a->samplerate;
|
||||
// bandpass
|
||||
a->bp_fc = bpfc_wfmmod (a->samplerate, a->deviation, a->f_high);
|
||||
}
|
||||
|
||||
WFMMOD create_wfmmod (int run, int size, double* in, double* out, int rate, double dev, double f_low, double f_high,
|
||||
int pre_run, double tau_pre, int bp_run, int nc, int mp)
|
||||
{
|
||||
WFMMOD a = (WFMMOD) malloc0 (sizeof (wfmmod));
|
||||
double* impulse;
|
||||
a->run = run;
|
||||
a->size = size;
|
||||
a->in = in;
|
||||
a->out = out;
|
||||
a->samplerate = (double)rate;
|
||||
a->deviation = dev;
|
||||
a->f_low = f_low;
|
||||
a->f_high = f_high;
|
||||
a->pre_run = pre_run;
|
||||
a->tau_pre = tau_pre;
|
||||
a->bp_run = bp_run;
|
||||
a->nc = nc;
|
||||
a->mp = mp;
|
||||
calc_wfmmod (a);
|
||||
impulse = fir_bandpass(a->nc, -a->bp_fc, +a->bp_fc, a->samplerate, 0, 1, 1.0 / (2 * a->size));
|
||||
a->p = create_fircore (a->size, a->out, a->out, a->nc, a->mp, impulse);
|
||||
_aligned_free (impulse);
|
||||
return a;
|
||||
}
|
||||
|
||||
void destroy_wfmmod (WFMMOD a)
|
||||
{
|
||||
destroy_fircore (a->p);
|
||||
_aligned_free (a);
|
||||
}
|
||||
|
||||
void flush_wfmmod (WFMMOD a)
|
||||
{
|
||||
a->pre_z = 0.0;
|
||||
a->sphase = 0.0;
|
||||
flush_fircore (a->p);
|
||||
}
|
||||
|
||||
void xwfmmod (WFMMOD a)
|
||||
{
|
||||
int i;
|
||||
double aud, dp;
|
||||
if (a->run)
|
||||
{
|
||||
for (i = 0; i < a->size; i++)
|
||||
{
|
||||
aud = a->in[2 * i + 0];
|
||||
if (a->pre_run)
|
||||
{
|
||||
dp = a->pnorm * (aud - a->pmult * a->pre_z);
|
||||
a->pre_z = aud;
|
||||
aud = dp;
|
||||
}
|
||||
dp = aud * a->sdelta;
|
||||
a->sphase += dp;
|
||||
// at the wide deviation, |dp| exceeds TWOPI once samplerate < deviation,
|
||||
// so one subtraction is not enough to bring sphase back into range
|
||||
while (a->sphase >= TWOPI) a->sphase -= TWOPI;
|
||||
while (a->sphase < 0.0 ) a->sphase += TWOPI;
|
||||
a->out[2 * i + 0] = 0.7071 * cos (a->sphase);
|
||||
a->out[2 * i + 1] = 0.7071 * sin (a->sphase);
|
||||
}
|
||||
if (a->bp_run)
|
||||
xfircore (a->p);
|
||||
}
|
||||
else if (a->in != a->out)
|
||||
memcpy (a->out, a->in, a->size * sizeof (complex));
|
||||
}
|
||||
|
||||
void setBuffers_wfmmod (WFMMOD a, double* in, double* out)
|
||||
{
|
||||
a->in = in;
|
||||
a->out = out;
|
||||
calc_wfmmod (a);
|
||||
setBuffers_fircore (a->p, a->out, a->out);
|
||||
}
|
||||
|
||||
void setSamplerate_wfmmod (WFMMOD a, int rate)
|
||||
{
|
||||
double* impulse;
|
||||
a->samplerate = rate;
|
||||
calc_wfmmod (a);
|
||||
impulse = fir_bandpass(a->nc, -a->bp_fc, +a->bp_fc, a->samplerate, 0, 1, 1.0 / (2 * a->size));
|
||||
setImpulse_fircore (a->p, impulse, 1);
|
||||
_aligned_free (impulse);
|
||||
}
|
||||
|
||||
void setSize_wfmmod (WFMMOD a, int size)
|
||||
{
|
||||
double* impulse;
|
||||
a->size = size;
|
||||
calc_wfmmod (a);
|
||||
setSize_fircore (a->p, a->size);
|
||||
impulse = fir_bandpass(a->nc, -a->bp_fc, +a->bp_fc, a->samplerate, 0, 1, 1.0 / (2 * a->size));
|
||||
setImpulse_fircore (a->p, impulse, 1);
|
||||
_aligned_free (impulse);
|
||||
}
|
||||
|
||||
/********************************************************************************************************
|
||||
* *
|
||||
* TXA Properties *
|
||||
* *
|
||||
********************************************************************************************************/
|
||||
|
||||
PORT
|
||||
void SetTXAWFMDeviation (int channel, double deviation)
|
||||
{
|
||||
WFMMOD a = txa[channel].wfmmod.p;
|
||||
double bp_fc = bpfc_wfmmod (a->samplerate, deviation, a->f_high);
|
||||
double* impulse = fir_bandpass (a->nc, -bp_fc, +bp_fc, a->samplerate, 0, 1, 1.0 / (2 * a->size));
|
||||
setImpulse_fircore (a->p, impulse, 0);
|
||||
_aligned_free (impulse);
|
||||
EnterCriticalSection (&ch[channel].csDSP);
|
||||
a->deviation = deviation;
|
||||
// mod
|
||||
a->sphase = 0.0;
|
||||
a->sdelta = TWOPI * a->deviation / a->samplerate;
|
||||
// bandpass
|
||||
a->bp_fc = bp_fc;
|
||||
setUpdate_fircore (a->p);
|
||||
LeaveCriticalSection (&ch[channel].csDSP);
|
||||
}
|
||||
|
||||
PORT
|
||||
void SetTXAWFMNC (int channel, int nc)
|
||||
{
|
||||
WFMMOD a;
|
||||
double* impulse;
|
||||
EnterCriticalSection (&ch[channel].csDSP);
|
||||
a = txa[channel].wfmmod.p;
|
||||
if (a->nc != nc)
|
||||
{
|
||||
a->nc = nc;
|
||||
impulse = fir_bandpass (a->nc, -a->bp_fc, +a->bp_fc, a->samplerate, 0, 1, 1.0 / (2 * a->size));
|
||||
setNc_fircore (a->p, a->nc, impulse);
|
||||
_aligned_free (impulse);
|
||||
}
|
||||
LeaveCriticalSection (&ch[channel].csDSP);
|
||||
}
|
||||
|
||||
PORT
|
||||
void SetTXAWFMMP (int channel, int mp)
|
||||
{
|
||||
WFMMOD a;
|
||||
a = txa[channel].wfmmod.p;
|
||||
if (a->mp != mp)
|
||||
{
|
||||
a->mp = mp;
|
||||
setMp_fircore (a->p, a->mp);
|
||||
}
|
||||
}
|
||||
|
||||
PORT
|
||||
void SetTXAWFMAFFreqs (int channel, double low, double high)
|
||||
{
|
||||
WFMMOD a;
|
||||
double* impulse;
|
||||
EnterCriticalSection (&ch[channel].csDSP);
|
||||
a = txa[channel].wfmmod.p;
|
||||
if (a->f_low != low || a->f_high != high)
|
||||
{
|
||||
a->f_low = low;
|
||||
a->f_high = high;
|
||||
a->bp_fc = bpfc_wfmmod (a->samplerate, a->deviation, a->f_high);
|
||||
impulse = fir_bandpass (a->nc, -a->bp_fc, +a->bp_fc, a->samplerate, 0, 1, 1.0 / (2 * a->size));
|
||||
setImpulse_fircore (a->p, impulse, 1);
|
||||
_aligned_free (impulse);
|
||||
}
|
||||
LeaveCriticalSection (&ch[channel].csDSP);
|
||||
}
|
||||
|
||||
PORT
|
||||
void SetTXAWFMPreEmphRun (int channel, int run)
|
||||
{
|
||||
WFMMOD a = txa[channel].wfmmod.p;
|
||||
EnterCriticalSection (&ch[channel].csDSP);
|
||||
if (a->pre_run != run)
|
||||
{
|
||||
a->pre_run = run;
|
||||
a->pre_z = 0.0;
|
||||
}
|
||||
LeaveCriticalSection (&ch[channel].csDSP);
|
||||
}
|
||||
|
||||
PORT
|
||||
void SetTXAWFMPreEmphTau (int channel, double tau)
|
||||
{
|
||||
WFMMOD a = txa[channel].wfmmod.p;
|
||||
EnterCriticalSection (&ch[channel].csDSP);
|
||||
if (a->tau_pre != tau && tau > 0.0)
|
||||
{
|
||||
a->tau_pre = tau;
|
||||
a->pmult = exp(-1.0 / (a->samplerate * a->tau_pre));
|
||||
a->pnorm = 1.0 / (1.0 - a->pmult);
|
||||
a->pre_z = 0.0;
|
||||
}
|
||||
LeaveCriticalSection (&ch[channel].csDSP);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/* wfmmod.h
|
||||
|
||||
This file is part of a program that implements a Software-Defined Radio.
|
||||
|
||||
Copyright (C) 2013, 2016, 2023 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
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _wfmmod_h
|
||||
#define _wfmmod_h
|
||||
#include "firmin.h"
|
||||
typedef struct _wfmmod
|
||||
{
|
||||
int run;
|
||||
int size;
|
||||
double* in;
|
||||
double* out;
|
||||
double samplerate;
|
||||
double deviation;
|
||||
double f_low;
|
||||
double f_high;
|
||||
// pre-emphasis, single-pole RC; inverse of the receiver's de-emphasis
|
||||
int pre_run;
|
||||
double tau_pre; // 75.0e-6 (Americas) or 50.0e-6 (elsewhere)
|
||||
double pmult;
|
||||
double pnorm;
|
||||
double pre_z;
|
||||
// mod
|
||||
double sphase;
|
||||
double sdelta;
|
||||
// bandpass
|
||||
int bp_run;
|
||||
double bp_fc;
|
||||
int nc;
|
||||
int mp;
|
||||
FIRCORE p;
|
||||
}wfmmod, *WFMMOD;
|
||||
|
||||
extern WFMMOD create_wfmmod (int run, int size, double* in, double* out, int rate, double dev, double f_low, double f_high,
|
||||
int pre_run, double tau_pre, int bp_run, int nc, int mp);
|
||||
|
||||
extern void destroy_wfmmod (WFMMOD a);
|
||||
|
||||
extern void flush_wfmmod (WFMMOD a);
|
||||
|
||||
extern void xwfmmod (WFMMOD a);
|
||||
|
||||
extern void setBuffers_wfmmod (WFMMOD a, double* in, double* out);
|
||||
|
||||
extern void setSamplerate_wfmmod (WFMMOD a, int rate);
|
||||
|
||||
extern void setSize_wfmmod (WFMMOD a, int size);
|
||||
|
||||
// TXA Properties
|
||||
|
||||
extern __declspec (dllexport) void SetTXAWFMDeviation (int channel, double deviation);
|
||||
|
||||
extern __declspec (dllexport) void SetTXAWFMNC (int channel, int nc);
|
||||
|
||||
extern __declspec (dllexport) void SetTXAWFMMP (int channel, int mp);
|
||||
|
||||
extern __declspec (dllexport) void SetTXAWFMAFFreqs (int channel, double low, double high);
|
||||
|
||||
extern __declspec (dllexport) void SetTXAWFMPreEmphRun (int channel, int run);
|
||||
|
||||
extern __declspec (dllexport) void SetTXAWFMPreEmphTau (int channel, double tau);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user