Skip to content

Commit ad41542

Browse files
committed
dsp: default to 48 khz dsp bw to improve p25p2 timing recovery for difficult qpsk lsm systems
1 parent 60ab0f0 commit ad41542

20 files changed

Lines changed: 415 additions & 162 deletions

File tree

apps/dsd-cli/main.c

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ bootstrap_interactive(dsd_opts* opts, dsd_state* state) {
279279
int dev = prompt_int("RTL device index", 0, 0, 255);
280280
int gain = prompt_int("RTL gain (dB)", 22, 0, 60);
281281
int ppm = prompt_int("PPM error", 0, -200, 200);
282-
int bw = prompt_int("DSP bandwidth (kHz: 4,6,8,12,16,24)", 12, 4, 24);
282+
int bw = prompt_int("DSP bandwidth (kHz: 4,6,8,12,16,24,48)", 48, 4, 48);
283283
int sql = prompt_int("Squelch (0=off; negative dB ok via CLI later)", 0, -1000, 100000);
284284
int vol = prompt_int("Volume multiplier (1..3)", 1, 1, 3);
285285
snprintf(opts->audio_in_dev, sizeof opts->audio_in_dev, "rtl:%d:%s:%d:%d:%d:%d:%d", dev, freq, gain, ppm,
@@ -303,7 +303,7 @@ bootstrap_interactive(dsd_opts* opts, dsd_state* state) {
303303
} else {
304304
int gain = prompt_int("RTL gain (dB)", 22, 0, 60);
305305
int ppm = prompt_int("PPM error", 0, -200, 200);
306-
int bw = prompt_int("DSP bandwidth (kHz: 4,6,8,12,16,24)", 12, 4, 24);
306+
int bw = prompt_int("DSP bandwidth (kHz: 4,6,8,12,16,24,48)", 48, 4, 48);
307307
int sql = prompt_int("Squelch (0=off)", 0, -1000, 100000);
308308
int vol = prompt_int("Volume multiplier (1..3)", 1, 1, 3);
309309
snprintf(opts->audio_in_dev, sizeof opts->audio_in_dev, "rtltcp:%s:%d:%s:%d:%d:%d:%d:%d", host, port,
@@ -462,7 +462,7 @@ bootstrap_interactive(dsd_opts* opts, dsd_state* state) {
462462
opts->frame_ysf = 0;
463463
opts->frame_m17 = 0;
464464
state->samplesPerSymbol = 20;
465-
state->symbolCenter = 10;
465+
state->symbolCenter = 9; /* (sps-1)/2 */
466466
opts->mod_c4fm = 1;
467467
opts->mod_qpsk = 0;
468468
opts->mod_gfsk = 0;
@@ -600,7 +600,7 @@ bootstrap_interactive(dsd_opts* opts, dsd_state* state) {
600600
opts->frame_ysf = 0;
601601
opts->frame_m17 = 0;
602602
state->samplesPerSymbol = 20; // same as NXDN48
603-
state->symbolCenter = 10; // same as NXDN48
603+
state->symbolCenter = 9; // (sps-1)/2, same as NXDN48
604604
opts->mod_c4fm = 1;
605605
opts->mod_qpsk = 0;
606606
opts->mod_gfsk = 0;
@@ -1489,7 +1489,7 @@ initOpts(dsd_opts* opts) {
14891489
opts->input_volume_multiplier = 1;
14901490
opts->rtl_udp_port =
14911491
0; //set UDP port for RTL remote -- 0 by default, will be making this optional for some external/legacy use cases (edacs-fm, etc)
1492-
opts->rtl_dsp_bw_khz = 24; // DSP baseband kHz (4,6,8,12,16,24). Not tuner IF BW.
1492+
opts->rtl_dsp_bw_khz = 48; // DSP baseband kHz (4,6,8,12,16,24,48). Not tuner IF BW.
14931493
opts->rtlsdr_ppm_error = 0; //initialize ppm with 0 value;
14941494
opts->rtlsdr_center_freq =
14951495
850000000; //set to an initial value (if user is using a channel map, then they won't need to specify anything other than -i rtl if desired)
@@ -2396,7 +2396,7 @@ usage() {
23962396
printf(" freq <num> RTL-SDR Frequency (851800000 or 851.8M) \n");
23972397
printf(" gain <num> RTL-SDR Device Gain (0-49)(default = 0; Hardware AGC recommended)\n");
23982398
printf(" ppm <num> RTL-SDR PPM Error (default = 0)\n");
2399-
printf(" bw <num> RTL-SDR DSP Bandwidth (kHz) (default 24). Allowed: 4,6,8,12,16,24.\n");
2399+
printf(" bw <num> RTL-SDR DSP Bandwidth (kHz) (default 48). Allowed: 4,6,8,12,16,24,48.\n");
24002400
printf(" Note: This is the DSP baseband used to derive capture rate;\n");
24012401
printf(" it is NOT the tuner IF filter.\n");
24022402
printf(" sq <val> RTL-SDR Squelch Threshold (Optional)\n");
@@ -3267,10 +3267,10 @@ main(int argc, char** argv) {
32673267
curr = strtok_r(NULL, ":", &saveptr); // bw (kHz)
32683268
if (curr != NULL) {
32693269
int bw = atoi(curr);
3270-
if (bw == 4 || bw == 6 || bw == 8 || bw == 12 || bw == 16 || bw == 24) {
3270+
if (bw == 4 || bw == 6 || bw == 8 || bw == 12 || bw == 16 || bw == 24 || bw == 48) {
32713271
opts.rtl_dsp_bw_khz = bw;
32723272
} else {
3273-
opts.rtl_dsp_bw_khz = 12;
3273+
opts.rtl_dsp_bw_khz = 48;
32743274
}
32753275
} else {
32763276
goto RTLTCPEND;
@@ -3382,13 +3382,13 @@ main(int argc, char** argv) {
33823382
if (curr != NULL) {
33833383
int bw = 0;
33843384
bw = atoi(curr);
3385-
//check for proper values (6,8,12,24)
3386-
if (bw == 4 || bw == 6 || bw == 8 || bw == 12 || bw == 16
3387-
|| bw == 24) //testing 4 and 16 as well for weak and/or nxdn48 systems
3385+
//check for proper values (4,6,8,12,16,24,48)
3386+
if (bw == 4 || bw == 6 || bw == 8 || bw == 12 || bw == 16 || bw == 24
3387+
|| bw == 48) // testing 4 and 16 as well for weak and/or nxdn48 systems
33883388
{
33893389
opts.rtl_dsp_bw_khz = bw;
33903390
} else {
3391-
opts.rtl_dsp_bw_khz = 12; // safe default -- provides best performance on most systems
3391+
opts.rtl_dsp_bw_khz = 48; // default baseband when input omits/invalid
33923392
}
33933393
} else {
33943394
goto RTLEND;

docs/cli.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ Notes
164164
- Tune controls: `-E` disable group calls, `-p` disable private calls, `-e` enable data calls, `--enc-lockout` do not tune encrypted P25 calls, `--enc-follow` allow encrypted (default)
165165
- Hold talkgroup: `-I <dec>`
166166
- rigctl over TCP: `-U <port>` (SDR++ default 4532)
167-
- Set rigctl bandwidth (Hz): `-B <hertz>` (e.g., 7000–24000 by mode)
167+
- Set rigctl bandwidth (Hz): `-B <hertz>` (e.g., 7000–48000 by mode)
168168
- Hang time after voice/sync loss (seconds): `-t <secs>`
169169
- Env (advanced): When P25 Phase 1 voice error rate is elevated, extend hangtime to reduce VC↔CC thrash:
170170
- `DSD_NEO_P25P1_ERR_HOLD_PCT=<percent>` (default 8.0)

include/dsd-neo/core/opts.h

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ struct dsd_opts {
118118
/* Generic input volume multiplier for non-RTL inputs (Pulse/WAV/TCP/UDP). */
119119
int input_volume_multiplier;
120120
int rtl_udp_port;
121-
/* Base DSP bandwidth for RTL path in kHz (4,6,8,12,16,24). Influences capture rate planning.
121+
/* Base DSP bandwidth for RTL path in kHz (4,6,8,12,16,24,48). Influences capture rate planning.
122122
Not the hardware tuner IF bandwidth. */
123123
int rtl_dsp_bw_khz;
124124
int rtl_bias_tee; /* 1 to enable RTL-SDR bias tee (if supported) */
@@ -284,3 +284,63 @@ struct dsd_opts {
284284
char mbe_out_path[2048]; //1024
285285
char dsp_out_file[2048];
286286
};
287+
288+
/**
289+
* @brief Compute samples-per-symbol for a given symbol rate and sample rate.
290+
*
291+
* Dynamically computes SPS based on the actual demodulator output sample rate.
292+
* When demod_rate_hz is provided (>0), it takes precedence over rtl_dsp_bw_khz
293+
* to correctly handle cases where a resampler changes the effective rate.
294+
*
295+
* @param opts Decoder options containing rtl_dsp_bw_khz fallback (may be NULL).
296+
* @param sym_rate_hz Symbol rate in Hz (e.g., 4800 for P25P1, 6000 for P25P2).
297+
* @param demod_rate_hz Actual demodulator output rate in Hz (0 to use rtl_dsp_bw_khz).
298+
* @return Computed samples per symbol, clamped to [2, 64].
299+
*/
300+
static inline int
301+
dsd_opts_compute_sps_rate(const dsd_opts* opts, int sym_rate_hz, int demod_rate_hz) {
302+
int fs_hz = 48000; /* default fallback */
303+
if (demod_rate_hz > 0) {
304+
fs_hz = demod_rate_hz;
305+
} else if (opts && opts->rtl_dsp_bw_khz > 0) {
306+
fs_hz = opts->rtl_dsp_bw_khz * 1000;
307+
}
308+
int sps = (fs_hz + (sym_rate_hz / 2)) / sym_rate_hz; /* round(fs/sym_rate) */
309+
if (sps < 2) {
310+
sps = 2;
311+
} else if (sps > 64) {
312+
sps = 64;
313+
}
314+
return sps;
315+
}
316+
317+
/**
318+
* @brief Compute samples-per-symbol for a given symbol rate and DSP bandwidth.
319+
*
320+
* Convenience wrapper that uses rtl_dsp_bw_khz from opts. For cases where the
321+
* actual demodulator output rate may differ (e.g., resampler active), prefer
322+
* dsd_opts_compute_sps_rate() with the actual rate.
323+
*
324+
* @param opts Decoder options containing rtl_dsp_bw_khz (may be NULL).
325+
* @param sym_rate_hz Symbol rate in Hz (e.g., 4800 for P25P1, 6000 for P25P2).
326+
* @return Computed samples per symbol, clamped to [2, 64].
327+
*/
328+
static inline int
329+
dsd_opts_compute_sps(const dsd_opts* opts, int sym_rate_hz) {
330+
return dsd_opts_compute_sps_rate(opts, sym_rate_hz, 0);
331+
}
332+
333+
/**
334+
* @brief Compute symbol center index for a given SPS value.
335+
*
336+
* The symbol center is the optimal sample index within the symbol period
337+
* for slicing. Uses (sps - 1) / 2 which correctly handles both even and
338+
* odd SPS values (e.g., SPS=5 -> 2, SPS=8 -> 3, SPS=10 -> 4).
339+
*
340+
* @param sps Samples per symbol.
341+
* @return Symbol center index.
342+
*/
343+
static inline int
344+
dsd_opts_symbol_center(int sps) {
345+
return (sps - 1) / 2;
346+
}

include/dsd-neo/dsp/costas.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,14 @@ typedef struct {
5050
*
5151
* The FLL uses band-edge filters to estimate frequency error before timing recovery.
5252
* This is critical for initial frequency acquisition on channel retunes.
53+
*
54+
* Filter size = 2*sps+1. At 48 kHz DSP bandwidth:
55+
* - P25P1: SPS=10 -> 21 taps
56+
* - P25P2: SPS=8 -> 17 taps
57+
* - NXDN: SPS=20 -> 41 taps
58+
* Size 48 supports SPS up to 23 (e.g., future 2400 sym/s modes at higher rates).
5359
*/
54-
#define FLL_BAND_EDGE_MAX_TAPS 32
60+
#define FLL_BAND_EDGE_MAX_TAPS 48
5561

5662
typedef struct {
5763
float phase; /* NCO phase accumulator (radians) */

include/dsd-neo/dsp/demod_state.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,14 @@ struct demod_state {
145145
float hb_hist_i[10][HB_TAPS_MAX - 1];
146146
float hb_hist_q[10][HB_TAPS_MAX - 1];
147147

148-
/* Fixed channel low-pass (post-HB) to bound noise bandwidth at higher Fs */
148+
/* Fixed channel low-pass (post-HB) to bound noise bandwidth at higher Fs.
149+
* At 48 kHz with 1200 Hz transition, Blackman needs up to 135 taps (hist = 134).
150+
* Size 144 provides headroom for higher sample rates. */
149151
int channel_lpf_enable; /* gate */
150152
int channel_lpf_hist_len;
151-
int channel_lpf_profile; /* see DSD_CH_LPF_PROFILE_* */
152-
float channel_lpf_hist_i[64]; /* sized for up to 63-tap symmetric FIR (tap-1) */
153-
float channel_lpf_hist_q[64];
153+
int channel_lpf_profile; /* see DSD_CH_LPF_PROFILE_* */
154+
float channel_lpf_hist_i[144]; /* sized for up to 144-tap symmetric FIR (tap-1) */
155+
float channel_lpf_hist_q[144];
154156
float channel_pwr; /* mean power (RMS^2 proxy) measured after channel LPF */
155157
float channel_squelch_level; /* squelch threshold (linear power); 0 = disabled */
156158
int channel_squelched; /* 1 if squelched this block, 0 otherwise */

include/dsd-neo/dsp/ted.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ extern "C" {
2222
typedef struct {
2323
int enabled;
2424
int force; /* allow forcing TED even for FM/C4FM paths */
25-
int sps; /* nominal samples per symbol (e.g., 5 for 4800 sym/s at 24k) */
25+
int sps; /* nominal samples per symbol. At 48 kHz: P25P1=10, P25P2=8, NXDN=20 */
2626
/* OP25-compatible Gardner parameters (from p25_demodulator.py) */
2727
float gain_mu; /* mu loop gain, default 0.025 (OP25 default) */
2828
float gain_omega; /* omega loop gain, default 0.1 * gain_mu^2 */

include/dsd-neo/ui/menu_services.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ int svc_rtl_set_freq(dsd_opts* opts, uint32_t hz);
184184
int svc_rtl_set_gain(dsd_opts* opts, int value);
185185
/** @brief Set RTL PPM correction (clamped to ±200). */
186186
int svc_rtl_set_ppm(dsd_opts* opts, int ppm);
187-
/** @brief Set RTL DSP baseband bandwidth (kHz), clamping to allowed set and restarting if needed. */
187+
/** @brief Set RTL DSP baseband bandwidth (kHz: 4,6,8,12,16,24,48), clamping and restarting if needed. */
188188
int svc_rtl_set_bandwidth(dsd_opts* opts, int khz);
189189
/** @brief Set RTL squelch threshold in dB, converting to power units. */
190190
int svc_rtl_set_sql_db(dsd_opts* opts, double dB);

src/dsp/demod_pipeline.cpp

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,25 @@ assume_aligned_ptr(const T* p, size_t /*align_unused*/) {
8080

8181
/* HB_TAPS and hb_q15_taps provided by dsp/halfband.h */
8282

83-
/* Fixed channel low-pass for high-rate (24 kHz) mode.
83+
/* Fixed channel low-pass for high-rate mode.
8484
*
85-
* Profile 0 (wide/analog): ~8 kHz cutoff @ 24 kHz Fs.
86-
* Designed as Blackman-windowed sinc, 63 taps; passband ripple ~0 dB through 6 kHz,
87-
* ~-6 dB at 8 kHz, stopband < -65 dB by 9 kHz.
85+
* Profile 0 (wide/analog): target ~8 kHz cutoff.
86+
* Profile 1 (digital-narrow): target ~5 kHz cutoff.
87+
* Profiles 2/3 (OP25) are generated dynamically per sample rate.
8888
*
89-
* Profile 1 (digital-narrow): ~5 kHz cutoff @ 24 kHz Fs.
90-
* Designed as Blackman-windowed sinc, 63 taps; passband ~0 dB through ~3.6 kHz,
91-
* ~-3 dB at 4.8 kHz, stopband < -60 dB by 6 kHz; tailored for 4.8 ksps 4FSK/CQPSK. */
92-
static const int kChannelLpfTaps = 63;
89+
* Legacy 63-tap Blackman prototypes are kept as fallback; preferred taps are
90+
* generated per sample rate to preserve the intended spectral shape at any Fs.
91+
*
92+
* At 48 kHz with 1200 Hz transition width:
93+
* - Hamming: ntaps = (53 * 48000) / (22 * 1200) = 97
94+
* - Blackman: ntaps = (74 * 48000) / (22 * 1200) = 135
95+
* Size 144 provides headroom for higher sample rates. */
96+
static const int kChannelLpfTaps = 144;
9397
static const int kChannelLpfHistLen = kChannelLpfTaps - 1;
94-
static const float channel_lpf_wide[kChannelLpfTaps] = {
98+
/* Legacy fallback filters are 63 taps (designed for 24 kHz). Only used when
99+
* dynamic filter generation fails; prefer dynamically generated taps. */
100+
static const int kChannelLpfFallbackTaps = 63;
101+
static const float channel_lpf_wide[kChannelLpfFallbackTaps] = {
95102
0.0f,
96103
0.0f,
97104
-1.0f / 32768.0f,
@@ -159,7 +166,7 @@ static const float channel_lpf_wide[kChannelLpfTaps] = {
159166

160167
/* Digital-narrow profile taps (fc≈5 kHz @ 24 kHz, 63 taps). Designed as
161168
Blackman-windowed sinc and normalized to unity DC gain. */
162-
static const float channel_lpf_digital[kChannelLpfTaps] = {
169+
static const float channel_lpf_digital[kChannelLpfFallbackTaps] = {
163170
0.0f,
164171
0.0f,
165172
0.0f,
@@ -353,11 +360,18 @@ audio_polydecim_process(struct demod_state* d, const float* in, int in_len, floa
353360

354361
/* ---------------- Fixed channel LPF (complex, no decimation) ----------------- */
355362

363+
/* Dynamically generated wide/digital taps (per-rate); fall back to legacy static prototypes on error. */
364+
static float s_channel_wide_taps[kChannelLpfTaps];
365+
static float s_channel_digital_taps[kChannelLpfTaps];
366+
static int s_channel_wide_ntaps = 0;
367+
static int s_channel_digital_ntaps = 0;
368+
static double s_channel_taps_sample_rate = 0.0;
369+
356370
/* OP25-compatible dynamically generated filter taps (cached).
357371
* Generated once per sample rate using GNU Radio's firdes.low_pass() algorithm.
358-
* Max tap count for Hamming window at 24kHz with 1200Hz transition: 49 taps. */
359-
static float s_op25_tdma_taps[128];
360-
static float s_op25_fdma_taps[128];
372+
* At 48 kHz with 1200 Hz transition, Hamming: ntaps = (53 * 48000) / (22 * 1200) = 97. */
373+
static float s_op25_tdma_taps[kChannelLpfTaps];
374+
static float s_op25_fdma_taps[kChannelLpfTaps];
361375
static int s_op25_tdma_ntaps = 0;
362376
static int s_op25_fdma_ntaps = 0;
363377
static double s_op25_taps_sample_rate = 0.0;
@@ -379,14 +393,16 @@ channel_lpf_ensure_op25_taps(double sample_rate) {
379393
}
380394

381395
/* Generate TDMA filter: firdes.low_pass(1.0, sample_rate, 9600, 1200, WIN_HAMMING) */
382-
s_op25_tdma_ntaps = dsd_firdes_low_pass(1.0, sample_rate, 9600.0, 1200.0, DSD_WIN_HAMMING, s_op25_tdma_taps, 128);
396+
s_op25_tdma_ntaps =
397+
dsd_firdes_low_pass(1.0, sample_rate, 9600.0, 1200.0, DSD_WIN_HAMMING, s_op25_tdma_taps, kChannelLpfTaps);
383398
if (s_op25_tdma_ntaps < 0) {
384399
s_op25_tdma_ntaps = 0;
385400
fprintf(stderr, "[channel_lpf] Failed to generate OP25 TDMA filter for Fs=%.0f\n", sample_rate);
386401
}
387402

388403
/* Generate FDMA filter: firdes.low_pass(1.0, sample_rate, 7000, 1200, WIN_HAMMING) */
389-
s_op25_fdma_ntaps = dsd_firdes_low_pass(1.0, sample_rate, 7000.0, 1200.0, DSD_WIN_HAMMING, s_op25_fdma_taps, 128);
404+
s_op25_fdma_ntaps =
405+
dsd_firdes_low_pass(1.0, sample_rate, 7000.0, 1200.0, DSD_WIN_HAMMING, s_op25_fdma_taps, kChannelLpfTaps);
390406
if (s_op25_fdma_ntaps < 0) {
391407
s_op25_fdma_ntaps = 0;
392408
fprintf(stderr, "[channel_lpf] Failed to generate OP25 FDMA filter for Fs=%.0f\n", sample_rate);
@@ -403,6 +419,39 @@ channel_lpf_ensure_op25_taps(double sample_rate) {
403419
}
404420
}
405421

422+
/**
423+
* @brief Ensure base wide/digital channel LPF taps are generated for the given sample rate.
424+
*
425+
* Matches the legacy 24 kHz designs by keeping the same absolute cutoffs:
426+
* - Wide: cutoff ~8 kHz, transition ~1.2 kHz (Blackman)
427+
* - Digital: cutoff ~5 kHz, transition ~1.2 kHz (Blackman)
428+
*
429+
* Falls back to the legacy static prototypes if generation fails.
430+
*/
431+
static void
432+
channel_lpf_ensure_base_taps(double sample_rate) {
433+
if (sample_rate <= 0.0) {
434+
return;
435+
}
436+
if (sample_rate == s_channel_taps_sample_rate && s_channel_wide_ntaps > 0 && s_channel_digital_ntaps > 0) {
437+
return; /* Already generated for this sample rate */
438+
}
439+
440+
s_channel_wide_ntaps =
441+
dsd_firdes_low_pass(1.0, sample_rate, 8000.0, 1200.0, DSD_WIN_BLACKMAN, s_channel_wide_taps, kChannelLpfTaps);
442+
if (s_channel_wide_ntaps < 0) {
443+
s_channel_wide_ntaps = 0;
444+
}
445+
446+
s_channel_digital_ntaps = dsd_firdes_low_pass(1.0, sample_rate, 5000.0, 1200.0, DSD_WIN_BLACKMAN,
447+
s_channel_digital_taps, kChannelLpfTaps);
448+
if (s_channel_digital_ntaps < 0) {
449+
s_channel_digital_ntaps = 0;
450+
}
451+
452+
s_channel_taps_sample_rate = sample_rate;
453+
}
454+
406455
static void
407456
channel_lpf_apply(struct demod_state* d) {
408457
if (!d || !d->channel_lpf_enable || d->lp_len < 2) {
@@ -415,8 +464,14 @@ channel_lpf_apply(struct demod_state* d) {
415464
/* Select filter taps based on profile */
416465
switch (d->channel_lpf_profile) {
417466
case DSD_CH_LPF_PROFILE_DIGITAL:
418-
taps = channel_lpf_digital;
419-
taps_len = kChannelLpfTaps;
467+
channel_lpf_ensure_base_taps((double)d->rate_out);
468+
if (s_channel_digital_ntaps > 0) {
469+
taps = s_channel_digital_taps;
470+
taps_len = s_channel_digital_ntaps;
471+
} else {
472+
taps = channel_lpf_digital; /* fallback (63 taps, 24 kHz design) */
473+
taps_len = kChannelLpfFallbackTaps;
474+
}
420475
break;
421476
case DSD_CH_LPF_PROFILE_OP25_TDMA:
422477
/* OP25-compatible TDMA filter: 9600 Hz cutoff, 1200 Hz transition, Hamming */
@@ -425,9 +480,9 @@ channel_lpf_apply(struct demod_state* d) {
425480
taps = s_op25_tdma_taps;
426481
taps_len = s_op25_tdma_ntaps;
427482
} else {
428-
/* Fallback to wide if generation failed */
483+
/* Fallback to wide if generation failed (63 taps, 24 kHz design) */
429484
taps = channel_lpf_wide;
430-
taps_len = kChannelLpfTaps;
485+
taps_len = kChannelLpfFallbackTaps;
431486
}
432487
break;
433488
case DSD_CH_LPF_PROFILE_OP25_FDMA:
@@ -437,15 +492,21 @@ channel_lpf_apply(struct demod_state* d) {
437492
taps = s_op25_fdma_taps;
438493
taps_len = s_op25_fdma_ntaps;
439494
} else {
440-
/* Fallback to wide if generation failed */
495+
/* Fallback to wide if generation failed (63 taps, 24 kHz design) */
441496
taps = channel_lpf_wide;
442-
taps_len = kChannelLpfTaps;
497+
taps_len = kChannelLpfFallbackTaps;
443498
}
444499
break;
445500
case DSD_CH_LPF_PROFILE_WIDE:
446501
default:
447-
taps = channel_lpf_wide;
448-
taps_len = kChannelLpfTaps;
502+
channel_lpf_ensure_base_taps((double)d->rate_out);
503+
if (s_channel_wide_ntaps > 0) {
504+
taps = s_channel_wide_taps;
505+
taps_len = s_channel_wide_ntaps;
506+
} else {
507+
taps = channel_lpf_wide; /* fallback (63 taps, 24 kHz design) */
508+
taps_len = kChannelLpfFallbackTaps;
509+
}
449510
break;
450511
}
451512

0 commit comments

Comments
 (0)