Skip to content

Band-limit batch resampling (Stage 17) - #42

Open
robmorgan wants to merge 1 commit into
mainfrom
fix/batch-resampler-antialiasing
Open

Band-limit batch resampling (Stage 17)#42
robmorgan wants to merge 1 commit into
mainfrom
fix/batch-resampler-antialiasing

Conversation

@robmorgan

Copy link
Copy Markdown
Owner

Closes the last confirmed defect from the 2026-08-05 quality review (D5): the batch windowed-sinc resampler had no anti-aliasing on downsample.

Changes

  • resample_sinc cutoff-scales when downsampling: stopband at the output Nyquist, using the streaming kernel's margin policy (stopband edge at the fold, not the −6 dB point — the first attempt put the cutoff exactly at the fold and left 23 kHz content in the transition band at ~−10 dB, re-learning the lesson cutoff_for_step already encodes). Tap span widens to keep the zero-crossing count under the dilated kernel.
  • Kaiser window → per-call lookup table: bessel_i0 leaves the per-tap loop.
  • AudioBuffer::resample: unfiltered cubic → the band-limited sinc.

Measured

Case Before After
18 kHz tone, 2:1 downsample, alias rejection 1.9 dB (folds at full level) 89.8 dB
23 kHz through 48→44.1 kHz images into audible band (0.33) gated < 0.03
pitch_shift SFDR, in-band content 69.3 dB 69.3 dB (unchanged)

Honest scope note on the last row: pitch_shift's pipeline only downsamples on pitch-up, where foldable content maps above Nyquist anyway — D5's practical weight was in the public resample APIs (resample_sinc, AudioBuffer::resample), not the shifter. Recorded in the ROADMAP Stage 17 note.

Gates

test_resample_sinc_antialiases_downsampling (≥ 60 dB, fails pre-fix at 1.9) in resample.rs; test_resample_rejects_above_target_nyquist in types.rs. Full suites, clippy -D warnings incl. qa-harnesses, fmt, docs, desktop all green.

🤖 Generated with Claude Code

resample_sinc now scales its kernel cutoff when downsampling — stopband
at the OUTPUT Nyquist with the same margin policy as the streaming
kernel (stopband edge at the fold, not the -6 dB point; a cutoff exactly
at the fold left near-Nyquist content in the transition band at ~-10 dB).
Tap span widens to keep the zero-crossing count under the dilated
kernel, and the Kaiser window moved to a per-call lookup table so
bessel_i0 leaves the per-tap loop. AudioBuffer::resample switches from
unfiltered cubic to the band-limited sinc.

Measured: 18 kHz tone downsampled 2:1 folded at essentially full level
before (1.9 dB rejection) and measures 89.8 dB down after; a 23 kHz
tone through 48 -> 44.1 kHz conversion no longer images into the
audible band. Honest scope note: pitch_shift itself measured 69.3 dB
SFDR before AND after on in-band content - its pipeline only
downsamples on pitch-up, where foldable content maps above Nyquist
anyway - so the audible beneficiaries are the public resample APIs
(review finding D5's practical weight was there, not in the shifter).

New gates: downsample alias rejection >= 60 dB (resample.rs), 48->44.1
ultrasonic-fold bound (types.rs). Full suites, clippy, docs, desktop
green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant