Sample rate conversion (resampling) changes a signal from one sample rate to another. This is essential when combining audio from different sources, preparing data for systems that expect a specific rate, or reducing storage by downsampling.
miniDSP provides a high-quality offline polyphase sinc resampler that handles arbitrary rate ratios (e.g., 44100 Hz to 48000 Hz) with >100 dB stopband attenuation using default parameters.
Mathematical building blocks
Bessel I₀
The zeroth-order modified Bessel function of the first kind appears in the Kaiser window formula. It is computed via the convergent power series:
\[I_0(x) = \sum_{k=0}^{\infty} \left[\frac{(x/2)^k}{k!}\right]^2
\]
Reading the formula in C:
double sum = 1.0;
double term = 1.0;
double half_x = x / 2.0;
for (unsigned k = 1; k < 300; k++) {
term *= (half_x / (double)k);
double term_sq = term * term;
sum += term_sq;
if (term_sq < 1e-15 * sum) break;
}
API:
double MD_bessel_i0(double x)
Zeroth-order modified Bessel function of the first kind, .
Normalized sinc
The sinc function is the ideal lowpass interpolation kernel. The normalized form is:
\[\mathrm{sinc}(x) = \begin{cases}
1 & \text{if } |x| < 10^{-12} \\
\dfrac{\sin(\pi x)}{\pi x} & \text{otherwise}
\end{cases}
\]
Reading the formula in C:
double result;
if (fabs(x) < 1e-12) {
result = 1.0;
} else {
double px = M_PI * x;
result = sin(px) / px;
}
API:
double MD_sinc(double x)
Normalized sinc function: .
The polyphase sinc resampler
How it works
The resampler converts a signal from sample rate \(f_{\mathrm{in}}\) to \(f_{\mathrm{out}}\) by treating each output sample as a fractional-position lookup into the input signal, filtered through a windowed sinc kernel.
For each output sample \(n\):
- Compute the fractional input position: \(p = n \cdot f_{\mathrm{in}} / f_{\mathrm{out}}\)
- Split into integer index \(\lfloor p \rfloor\) and fractional offset
- Select two adjacent filter sub-phases from a precomputed table
- Linearly interpolate the sub-phase coefficients
- Dot product with surrounding input samples
The filter table contains 512 sub-phases, each with \(2 \times \mathrm{num\_zero\_crossings}\) taps of a Kaiser-windowed sinc. Anti-aliasing is handled automatically: for downsampling, the sinc cutoff is scaled to \(\min(f_{\mathrm{in}}, f_{\mathrm{out}})/2\).
Output buffer sizing
\[N_{\mathrm{out}} = \left\lceil N_{\mathrm{in}} \cdot \frac{f_{\mathrm{out}}}{f_{\mathrm{in}}} \right\rceil
\]
Reading the formula in C:
unsigned out_len = (unsigned)ceil((double)input_len * out_rate / in_rate);
API:
double in_rate, double out_rate);
unsigned MD_resample_output_len(unsigned input_len, double in_rate, double out_rate)
Compute the output buffer size needed for resampling.
Resampling a signal
API:
unsigned MD_resample(
const double *input,
unsigned input_len,
double *output, unsigned max_output_len,
double in_rate, double out_rate,
unsigned num_zero_crossings, double kaiser_beta);
unsigned MD_resample(const double *input, unsigned input_len, double *output, unsigned max_output_len, double in_rate, double out_rate, unsigned num_zero_crossings, double kaiser_beta)
Resample a signal from one sample rate to another using polyphase sinc interpolation.
Parameters:
- num_zero_crossings controls filter quality. Range: 8 (fast) to 64 (high quality). Default recommendation: 32.
- kaiser_beta controls stopband attenuation. Recommendation: 10.0 for ~100 dB.
- Returns the number of samples written to output.
Quick example:
const double out_rate = 48000.0;
double *output = malloc(N_out * sizeof(double));
unsigned n_written =
MD_resample(input, N_in, output, N_out,
in_rate, out_rate, 32, 10.0);
Common rate pairs
| Conversion | Ratio | Use case |
| 44100 to 48000 | 160/147 | CD audio to professional video/DAW |
| 48000 to 44100 | 147/160 | Professional to CD quality |
| 48000 to 16000 | 1/3 | Wideband to narrowband speech |
| 44100 to 22050 | 1/2 | Half-rate for reduced storage |
| 16000 to 8000 | 1/2 | Wideband to telephone bandwidth |
Further reading
API reference