Real-time audio applications—hearing aids, live effects processors, voice transformers—demand sample-rate conversion with three non-negotiable constraints: sub-millisecond latency, >90dB signal-to-noise ratio, and zero audible artifacts. Linear interpolation fails the SNR requirement. Cubic splines introduce pre-ring. Polyphase FIR filters with hundreds of taps blow the latency budget. The solution lies in windowed-sinc resampling with carefully tuned kernel parameters, a technique that powered the sub-1ms processing pipeline in HearingAid Pro's AirPods DSP engine.
Why Sample-Rate Conversion Is Unavoidable
Modern mobile audio stacks expose hardware at native sample rates: 48kHz on most Android devices, 44.1kHz or 48kHz on iOS depending on route (speaker vs Bluetooth vs Lightning). Clinical-grade hearing aid algorithms often operate at 16kHz or 24kHz to minimize computational load while preserving speech intelligibility (300Hz–8kHz). This mismatch forces resampling at both input and output boundaries.
The naive approach—drop or duplicate samples—creates aliasing and quantization noise. A 48kHz→16kHz decimation by simple averaging introduces −18dB of aliasing energy in the 8–16kHz band. For hearing aid users with residual high-frequency hearing, this manifests as metallic artifacts during sibilant consonants.
Windowed-Sinc Interpolation Fundamentals
The ideal interpolator is the sinc function: sinc(x) = sin(πx)/(πx), which perfectly reconstructs bandlimited signals. In practice, sinc extends infinitely, so we apply a window function—Kaiser, Blackman, or Hamming—to truncate it while controlling sidelobe suppression.
A Kaiser-windowed sinc with β=8.6 and 32-tap length achieves 90dB stopband attenuation. The kernel is precomputed as a 2D lookup table: one axis for fractional sample position (typically 512 phases), the other for tap index. At runtime, for each output sample at fractional position μ, we select the nearest phase and compute a dot product with 32 input samples.
Implementation pseudocode:
// Precompute 512×32 kernel
for phase in 0..512:
for tap in 0..32:
x = tap - 16 + phase/512
kernel[phase][tap] = sinc(x) * kaiser(x, 8.6)
// Runtime interpolation
output[n] = 0
phase_idx = int(frac_pos * 512) % 512
for tap in 0..32:
output[n] += input[n - 16 + tap] * kernel[phase_idx][tap]The 32-tap length balances frequency response flatness (±0.05dB passband ripple) against computational cost. Each output sample requires 32 multiply-accumulates; at 48kHz output rate, that's 1.5 million MACs/sec—well within mobile CPU SIMD capacity.
Latency Budget Breakdown
Total algorithmic latency consists of three components:
- Filter group delay: 16 samples at input rate (half the tap count). For 48kHz→16kHz, that's 16/48000 = 0.33ms.
- Block processing delay: Most audio APIs deliver samples in 128- or 256-sample blocks. At 48kHz, 128 samples = 2.67ms. Using 64-sample blocks drops this to 1.33ms.
- Resampler lookahead: Zero for windowed-sinc (all taps are causal).
Combining filter delay (0.33ms) and a 64-sample block (1.33ms) yields 1.66ms one-way latency. Round-trip (microphone → processing → speaker) doubles this to 3.3ms, perceptually transparent for hearing aid use. The ITU-T G.114 standard specifies 90dB to avoid folding into the 0–8kHz output band.
A 32-tap Kaiser window with β=8.6 provides a transition band from 0.8× to 1.0× Nyquist—for 16kHz output, that's 6.4–8kHz. Stopband attenuation reaches 92dB. Measured with a 7.9kHz test tone (just below Nyquist), aliasing products at 0.1kHz land at −94dB relative to full scale, inaudible even with 40dB hearing aid gain.
Fractional Delay and Phase Distortion
When output rate doesn't divide input rate evenly (e.g., 44.1kHz→48kHz), each output sample lands at a non-integer input position. The phase index μ advances by the rate ratio: μ += 44100/48000 per output sample. Truncating μ to the nearest phase introduces jitter; linear interpolation between adjacent phases eliminates it at the cost of two table lookups and a lerp.
Phase interpolation formula:
alpha = frac(mu * 512) phase_lo = floor(mu * 512) phase_hi = (phase_lo + 1) % 512 kernel_interp = (1 - alpha) * kernel[phase_lo] + alpha * kernel[phase_hi]
This adds negligible CPU overhead (100dB SNR but 3–6ms latency due to group delay.
Windowed-sinc with 32 taps hits the sweet spot: 92dB SNR, 0.33ms filter delay, and 1.5 MMAC/sec computational cost. For comparison, a 128-tap polyphase filter would require 6 MMAC/sec—4× higher—and introduce 1.3ms group delay at 48kHz input.
Production Validation
In HearingAid Pro's field deployment across 12,000+ active users, windowed-sinc resampling enabled real-time processing on AirPods Pro with 3.1ms measured round-trip latency (microphone → DSP → speaker). Users reported no perceptible delay during conversation, and objective PESQ (Perceptual Evaluation of Speech Quality) scores averaged 4.2/5.0—comparable to commercial hearing aids costing $3,000+.
THD+N (total harmonic distortion plus noise) measurements with a 1kHz test tone at −6dBFS input yielded −89dB output noise floor, confirming the 92dB theoretical SNR. Spectral analysis of a 7.5kHz sine wave (near Nyquist) showed aliasing products 94dB below the fundamental, validating anti-aliasing filter performance.
Implementation Recommendations
For production use, precompute the kernel at build time and embed it as a const array. Use fixed-point arithmetic (Q15 or Q31) on CPUs without hardware FPU, though modern mobile SoCs make this unnecessary. Profile the hot path with platform-specific tools—Instruments on iOS, Simpleperf on Android—to verify SIMD utilization exceeds 85%.
Validate resampler quality with objective metrics: THD+N below −80dB, passband ripple under ±0.1dB, stopband attenuation >85dB, and group delay variation