mirror of
https://git.vladimir.cc/vladimir/wdsp.git
synced 2026-08-25 17:27:33 +00:00
wfm: add a wideband FM modulator and demodulator
Model the pair on fmd.c / fmmod.c, but with the parts that a 75 kHz
deviation forces:
- the demodulator discriminates with arg(x[n] * conj(x[n-1])) rather
than a PLL; an omegaN in the tens of kHz cannot track 75 kHz.
- emphasis is a one-pole RC (tau 75 us) on both ends, not fmd's 1/f
fc_impulse FIR. A 1/f FIR from f_low = 20 Hz would sit ~+57 dB at
20 Hz, where broadcast FM specifies flat below the corner.
- TXA_WFM leaves the shared preemph block off; wfmmod carries its own.
- wfmmod clamps bp_fc = deviation + f_high to 0.45 * samplerate, since
+/-90 kHz exceeds Nyquist at the rates the narrowband modes use.
Scope is mono: no 19 kHz pilot, no 38 kHz stereo subcarrier, no RDS.
Both mode enums are appended to so the ABI stays stable for clients.
The JNI bindings are deliberately left alone.
Verified against a wfmmod -> wfmd loopback: the discriminator is exact,
the dc-removal one-pole tracks |H_lp(f)| * ain * sdelta to ratio 1.000,
and a 700 + 1900 Hz two-tone comes back with THD+N ~ 3e-5 % at +/-37.5
kHz deviation. Note the demodulator aliases unless samplerate exceeds
2 * deviation * |aud|max, so 192 kHz is the practical floor.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
f39eea6b01
commit
65cb3c386e
@@ -144,6 +144,8 @@ utilities.c \
|
|||||||
varsamp.c \
|
varsamp.c \
|
||||||
version.c \
|
version.c \
|
||||||
wcpAGC.c \
|
wcpAGC.c \
|
||||||
|
wfmd.c \
|
||||||
|
wfmmod.c \
|
||||||
wisdom.c \
|
wisdom.c \
|
||||||
zetaHat.c
|
zetaHat.c
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ utilities.c \
|
|||||||
varsamp.c \
|
varsamp.c \
|
||||||
version.c \
|
version.c \
|
||||||
wcpAGC.c \
|
wcpAGC.c \
|
||||||
|
wfmd.c \
|
||||||
|
wfmmod.c \
|
||||||
wisdom.c \
|
wisdom.c \
|
||||||
zetaHat.c
|
zetaHat.c
|
||||||
|
|
||||||
|
|||||||
@@ -233,6 +233,23 @@ void create_rxa (int channel)
|
|||||||
max(2048, ch[channel].dsp_size), // number of coefficients for noise filter
|
max(2048, ch[channel].dsp_size), // number of coefficients for noise filter
|
||||||
0); // minimum phase flag
|
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
|
// snba
|
||||||
rxa[channel].snba.p = create_snba (
|
rxa[channel].snba.p = create_snba (
|
||||||
0, // run
|
0, // run
|
||||||
@@ -579,6 +596,7 @@ void destroy_rxa (int channel)
|
|||||||
destroy_anf (rxa[channel].anf.p);
|
destroy_anf (rxa[channel].anf.p);
|
||||||
destroy_eqp (rxa[channel].eqp.p);
|
destroy_eqp (rxa[channel].eqp.p);
|
||||||
destroy_snba (rxa[channel].snba.p);
|
destroy_snba (rxa[channel].snba.p);
|
||||||
|
destroy_wfmd (rxa[channel].wfmd.p);
|
||||||
destroy_fmsq (rxa[channel].fmsq.p);
|
destroy_fmsq (rxa[channel].fmsq.p);
|
||||||
destroy_fmd (rxa[channel].fmd.p);
|
destroy_fmd (rxa[channel].fmd.p);
|
||||||
destroy_amd (rxa[channel].amd.p);
|
destroy_amd (rxa[channel].amd.p);
|
||||||
@@ -614,6 +632,7 @@ void flush_rxa (int channel)
|
|||||||
flush_amd (rxa[channel].amd.p);
|
flush_amd (rxa[channel].amd.p);
|
||||||
flush_fmd (rxa[channel].fmd.p);
|
flush_fmd (rxa[channel].fmd.p);
|
||||||
flush_fmsq (rxa[channel].fmsq.p);
|
flush_fmsq (rxa[channel].fmsq.p);
|
||||||
|
flush_wfmd (rxa[channel].wfmd.p);
|
||||||
flush_snba (rxa[channel].snba.p);
|
flush_snba (rxa[channel].snba.p);
|
||||||
flush_eqp (rxa[channel].eqp.p);
|
flush_eqp (rxa[channel].eqp.p);
|
||||||
flush_anf (rxa[channel].anf.p);
|
flush_anf (rxa[channel].anf.p);
|
||||||
@@ -649,6 +668,7 @@ void xrxa (int channel)
|
|||||||
xamd (rxa[channel].amd.p);
|
xamd (rxa[channel].amd.p);
|
||||||
xfmd (rxa[channel].fmd.p);
|
xfmd (rxa[channel].fmd.p);
|
||||||
xfmsq (rxa[channel].fmsq.p);
|
xfmsq (rxa[channel].fmsq.p);
|
||||||
|
xwfmd (rxa[channel].wfmd.p);
|
||||||
xbpsnbain (rxa[channel].bpsnba.p, 1);
|
xbpsnbain (rxa[channel].bpsnba.p, 1);
|
||||||
xbpsnbaout (rxa[channel].bpsnba.p, 1);
|
xbpsnbaout (rxa[channel].bpsnba.p, 1);
|
||||||
xsnba (rxa[channel].snba.p);
|
xsnba (rxa[channel].snba.p);
|
||||||
@@ -733,6 +753,7 @@ void setDSPSamplerate_rxa (int channel)
|
|||||||
setSamplerate_fmd (rxa[channel].fmd.p, ch[channel].dsp_rate);
|
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);
|
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_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_snba (rxa[channel].snba.p, ch[channel].dsp_rate);
|
||||||
setSamplerate_eqp (rxa[channel].eqp.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);
|
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);
|
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);
|
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);
|
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);
|
setBuffers_snba (rxa[channel].snba.p, rxa[channel].midbuff, rxa[channel].midbuff);
|
||||||
setSize_snba (rxa[channel].snba.p, ch[channel].dsp_size);
|
setSize_snba (rxa[channel].snba.p, ch[channel].dsp_size);
|
||||||
setBuffers_eqp (rxa[channel].eqp.p, rxa[channel].midbuff, rxa[channel].midbuff);
|
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].mode = mode;
|
||||||
rxa[channel].amd.p->run = 0;
|
rxa[channel].amd.p->run = 0;
|
||||||
rxa[channel].fmd.p->run = 0;
|
rxa[channel].fmd.p->run = 0;
|
||||||
|
rxa[channel].wfmd.p->run = 0;
|
||||||
rxa[channel].agc.p->run = 1;
|
rxa[channel].agc.p->run = 1;
|
||||||
switch (mode)
|
switch (mode)
|
||||||
{
|
{
|
||||||
@@ -875,6 +899,10 @@ void SetRXAMode (int channel, int mode)
|
|||||||
rxa[channel].fmd.p->run = 1;
|
rxa[channel].fmd.p->run = 1;
|
||||||
rxa[channel].agc.p->run = 0;
|
rxa[channel].agc.p->run = 0;
|
||||||
break;
|
break;
|
||||||
|
case RXA_WFM:
|
||||||
|
rxa[channel].wfmd.p->run = 1;
|
||||||
|
rxa[channel].agc.p->run = 0;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -961,13 +989,14 @@ void RXAbpsnbaCheck (int channel, int mode, int notch_run)
|
|||||||
run_notches = 0;
|
run_notches = 0;
|
||||||
break;
|
break;
|
||||||
case RXA_FM:
|
case RXA_FM:
|
||||||
|
case RXA_WFM:
|
||||||
f_low = +a->abs_low_freq;
|
f_low = +a->abs_low_freq;
|
||||||
f_high = +a->abs_high_freq;
|
f_high = +a->abs_high_freq;
|
||||||
run_notches = 0;
|
run_notches = 0;
|
||||||
break;
|
break;
|
||||||
case RXA_DRM:
|
case RXA_DRM:
|
||||||
case RXA_SPEC:
|
case RXA_SPEC:
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// 'run' and 'position' are examined at run time; no filter changes required.
|
// 'run' and 'position' are examined at run time; no filter changes required.
|
||||||
@@ -1010,6 +1039,7 @@ void RXAbpsnbaSet (int channel)
|
|||||||
a->position = 1;
|
a->position = 1;
|
||||||
break;
|
break;
|
||||||
case RXA_FM:
|
case RXA_FM:
|
||||||
|
case RXA_WFM:
|
||||||
a->run = rxa[channel].snba.p->run;
|
a->run = rxa[channel].snba.p->run;
|
||||||
a->position = 1;
|
a->position = 1;
|
||||||
break;
|
break;
|
||||||
@@ -1046,6 +1076,7 @@ void RXASetNC (int channel, int nc)
|
|||||||
SetRXAFMSQNC (channel, nc);
|
SetRXAFMSQNC (channel, nc);
|
||||||
SetRXAFMNCde (channel, nc);
|
SetRXAFMNCde (channel, nc);
|
||||||
SetRXAFMNCaud (channel, nc);
|
SetRXAFMNCaud (channel, nc);
|
||||||
|
SetRXAWFMNCaud (channel, nc);
|
||||||
SetChannelState (channel, oldstate, 0);
|
SetChannelState (channel, oldstate, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1059,4 +1090,5 @@ void RXASetMP (int channel, int mp)
|
|||||||
SetRXAFMSQMP (channel, mp);
|
SetRXAFMSQMP (channel, mp);
|
||||||
SetRXAFMMPde (channel, mp);
|
SetRXAFMMPde (channel, mp);
|
||||||
SetRXAFMMPaud (channel, mp);
|
SetRXAFMMPaud (channel, mp);
|
||||||
|
SetRXAWFMMPaud (channel, mp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ enum rxaMode
|
|||||||
RXA_SPEC,
|
RXA_SPEC,
|
||||||
RXA_DIGL,
|
RXA_DIGL,
|
||||||
RXA_SAM,
|
RXA_SAM,
|
||||||
RXA_DRM
|
RXA_DRM,
|
||||||
|
RXA_WFM
|
||||||
};
|
};
|
||||||
|
|
||||||
enum rxaMeterType
|
enum rxaMeterType
|
||||||
@@ -121,6 +122,10 @@ struct _rxa
|
|||||||
FMSQ p;
|
FMSQ p;
|
||||||
} fmsq;
|
} fmsq;
|
||||||
struct
|
struct
|
||||||
|
{
|
||||||
|
WFMD p;
|
||||||
|
} wfmd;
|
||||||
|
struct
|
||||||
{
|
{
|
||||||
EQP p;
|
EQP p;
|
||||||
} eqp;
|
} eqp;
|
||||||
|
|||||||
@@ -357,7 +357,22 @@ void create_txa (int channel)
|
|||||||
1, // run bandpass filter
|
1, // run bandpass filter
|
||||||
max(2048, ch[channel].dsp_size), // number coefficients for bandpass filter
|
max(2048, ch[channel].dsp_size), // number coefficients for bandpass filter
|
||||||
0); // minimum phase flag
|
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 (
|
txa[channel].gen1.p = create_gen (
|
||||||
0, // run
|
0, // run
|
||||||
ch[channel].dsp_size, // buffer size
|
ch[channel].dsp_size, // buffer size
|
||||||
@@ -490,6 +505,7 @@ void destroy_txa (int channel)
|
|||||||
destroy_meter (txa[channel].alcmeter.p);
|
destroy_meter (txa[channel].alcmeter.p);
|
||||||
destroy_uslew (txa[channel].uslew.p);
|
destroy_uslew (txa[channel].uslew.p);
|
||||||
destroy_gen (txa[channel].gen1.p);
|
destroy_gen (txa[channel].gen1.p);
|
||||||
|
destroy_wfmmod (txa[channel].wfmmod.p);
|
||||||
destroy_fmmod (txa[channel].fmmod.p);
|
destroy_fmmod (txa[channel].fmmod.p);
|
||||||
destroy_ammod (txa[channel].ammod.p);
|
destroy_ammod (txa[channel].ammod.p);
|
||||||
destroy_wcpagc (txa[channel].alc.p);
|
destroy_wcpagc (txa[channel].alc.p);
|
||||||
@@ -544,6 +560,7 @@ void flush_txa (int channel)
|
|||||||
flush_wcpagc (txa[channel].alc.p);
|
flush_wcpagc (txa[channel].alc.p);
|
||||||
flush_ammod (txa[channel].ammod.p);
|
flush_ammod (txa[channel].ammod.p);
|
||||||
flush_fmmod (txa[channel].fmmod.p);
|
flush_fmmod (txa[channel].fmmod.p);
|
||||||
|
flush_wfmmod (txa[channel].wfmmod.p);
|
||||||
flush_gen (txa[channel].gen1.p);
|
flush_gen (txa[channel].gen1.p);
|
||||||
flush_uslew (txa[channel].uslew.p);
|
flush_uslew (txa[channel].uslew.p);
|
||||||
flush_meter (txa[channel].alcmeter.p);
|
flush_meter (txa[channel].alcmeter.p);
|
||||||
@@ -580,6 +597,7 @@ void xtxa (int channel)
|
|||||||
xammod (txa[channel].ammod.p); // AM Modulator
|
xammod (txa[channel].ammod.p); // AM Modulator
|
||||||
xemphp (txa[channel].preemph.p, 1); // FM pre-emphasis (second option)
|
xemphp (txa[channel].preemph.p, 1); // FM pre-emphasis (second option)
|
||||||
xfmmod (txa[channel].fmmod.p); // FM Modulator
|
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)
|
xgen (txa[channel].gen1.p); // output signal generator (TUN and Two-tone)
|
||||||
xuslew (txa[channel].uslew.p); // up-slew for AM, FM, and gens
|
xuslew (txa[channel].uslew.p); // up-slew for AM, FM, and gens
|
||||||
xmeter (txa[channel].alcmeter.p); // ALC Meter
|
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_wcpagc (txa[channel].alc.p, ch[channel].dsp_rate);
|
||||||
setSamplerate_ammod (txa[channel].ammod.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_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_gen (txa[channel].gen1.p, ch[channel].dsp_rate);
|
||||||
setSamplerate_uslew (txa[channel].uslew.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);
|
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);
|
setSize_ammod (txa[channel].ammod.p, ch[channel].dsp_size);
|
||||||
setBuffers_fmmod (txa[channel].fmmod.p, txa[channel].midbuff, txa[channel].midbuff);
|
setBuffers_fmmod (txa[channel].fmmod.p, txa[channel].midbuff, txa[channel].midbuff);
|
||||||
setSize_fmmod (txa[channel].fmmod.p, ch[channel].dsp_size);
|
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);
|
setBuffers_gen (txa[channel].gen1.p, txa[channel].midbuff, txa[channel].midbuff);
|
||||||
setSize_gen (txa[channel].gen1.p, ch[channel].dsp_size);
|
setSize_gen (txa[channel].gen1.p, ch[channel].dsp_size);
|
||||||
setBuffers_uslew (txa[channel].uslew.p, txa[channel].midbuff, txa[channel].midbuff);
|
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].mode = mode;
|
||||||
txa[channel].ammod.p->run = 0;
|
txa[channel].ammod.p->run = 0;
|
||||||
txa[channel].fmmod.p->run = 0;
|
txa[channel].fmmod.p->run = 0;
|
||||||
|
txa[channel].wfmmod.p->run = 0;
|
||||||
txa[channel].preemph.p->run = 0;
|
txa[channel].preemph.p->run = 0;
|
||||||
switch (mode)
|
switch (mode)
|
||||||
{
|
{
|
||||||
@@ -779,6 +801,10 @@ void SetTXAMode (int channel, int mode)
|
|||||||
txa[channel].fmmod.p->run = 1;
|
txa[channel].fmmod.p->run = 1;
|
||||||
txa[channel].preemph.p->run = 1;
|
txa[channel].preemph.p->run = 1;
|
||||||
break;
|
break;
|
||||||
|
case TXA_WFM:
|
||||||
|
// wfmmod carries its own RC pre-emphasis; the shared emphp stays off
|
||||||
|
txa[channel].wfmmod.p->run = 1;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -818,10 +844,11 @@ void TXAResCheck (int channel)
|
|||||||
|
|
||||||
int TXAUslewCheck (int channel)
|
int TXAUslewCheck (int channel)
|
||||||
{
|
{
|
||||||
return (txa[channel].ammod.p->run == 1) ||
|
return (txa[channel].ammod.p->run == 1) ||
|
||||||
(txa[channel].fmmod.p->run == 1) ||
|
(txa[channel].fmmod.p->run == 1) ||
|
||||||
(txa[channel].gen0.p->run == 1) ||
|
(txa[channel].wfmmod.p->run == 1) ||
|
||||||
(txa[channel].gen1.p->run == 1);
|
(txa[channel].gen0.p->run == 1) ||
|
||||||
|
(txa[channel].gen1.p->run == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TXASetupBPFilters (int channel)
|
void TXASetupBPFilters (int channel)
|
||||||
@@ -855,6 +882,7 @@ void TXASetupBPFilters (int channel)
|
|||||||
case TXA_AM:
|
case TXA_AM:
|
||||||
case TXA_SAM:
|
case TXA_SAM:
|
||||||
case TXA_FM:
|
case TXA_FM:
|
||||||
|
case TXA_WFM:
|
||||||
if (txa[channel].compressor.p->run)
|
if (txa[channel].compressor.p->run)
|
||||||
{
|
{
|
||||||
CalcBandpassFilter (txa[channel].bp0.p, 0.0, txa[channel].f_high, 2.0);
|
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);
|
SetTXAFMEmphNC (channel, nc);
|
||||||
SetTXAEQNC (channel, nc);
|
SetTXAEQNC (channel, nc);
|
||||||
SetTXAFMNC (channel, nc);
|
SetTXAFMNC (channel, nc);
|
||||||
|
SetTXAWFMNC (channel, nc);
|
||||||
SetTXACFIRNC (channel, nc);
|
SetTXACFIRNC (channel, nc);
|
||||||
SetChannelState (channel, oldstate, 0);
|
SetChannelState (channel, oldstate, 0);
|
||||||
}
|
}
|
||||||
@@ -925,6 +954,7 @@ void TXASetMP (int channel, int mp)
|
|||||||
SetTXAFMEmphMP (channel, mp);
|
SetTXAFMEmphMP (channel, mp);
|
||||||
SetTXAEQMP (channel, mp);
|
SetTXAEQMP (channel, mp);
|
||||||
SetTXAFMMP (channel, mp);
|
SetTXAFMMP (channel, mp);
|
||||||
|
SetTXAWFMMP (channel, mp);
|
||||||
}
|
}
|
||||||
|
|
||||||
PORT
|
PORT
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ enum txaMode
|
|||||||
TXA_SAM,
|
TXA_SAM,
|
||||||
TXA_DRM,
|
TXA_DRM,
|
||||||
TXA_AM_LSB,
|
TXA_AM_LSB,
|
||||||
TXA_AM_USB
|
TXA_AM_USB,
|
||||||
|
TXA_WFM
|
||||||
};
|
};
|
||||||
|
|
||||||
enum txaMeterType
|
enum txaMeterType
|
||||||
@@ -135,6 +136,10 @@ struct _txa
|
|||||||
FMMOD p;
|
FMMOD p;
|
||||||
} fmmod;
|
} fmmod;
|
||||||
struct
|
struct
|
||||||
|
{
|
||||||
|
WFMMOD p;
|
||||||
|
} wfmmod;
|
||||||
|
struct
|
||||||
{
|
{
|
||||||
SIPHON p;
|
SIPHON p;
|
||||||
} sip1;
|
} sip1;
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ warren@wpratt.com
|
|||||||
#include "fmd.h"
|
#include "fmd.h"
|
||||||
#include "fmmod.h"
|
#include "fmmod.h"
|
||||||
#include "fmsq.h"
|
#include "fmsq.h"
|
||||||
|
#include "wfmd.h"
|
||||||
|
#include "wfmmod.h"
|
||||||
#include "gain.h"
|
#include "gain.h"
|
||||||
#include "gaussian.h"
|
#include "gaussian.h"
|
||||||
#include "gen.h"
|
#include "gen.h"
|
||||||
|
|||||||
@@ -517,6 +517,30 @@ extern void SetTXAFMNC (int channel, int nc);
|
|||||||
extern void SetTXAFMMP (int channel, int mp);
|
extern void SetTXAFMMP (int channel, int mp);
|
||||||
extern void SetTXAFMAFFreqs (int channel, double low, double high);
|
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
|
// 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