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:
@@ -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,13 +989,14 @@ 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;
|
||||
break;
|
||||
case RXA_DRM:
|
||||
case RXA_SPEC:
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
// 'run' and 'position' are examined at run time; no filter changes required.
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user