Resampling#
The resampling stack is built on precomputed windowed-sinc interpolation tables with per-channel history buffers, so it operates seamlessly across audio blocks (real-time safe). It covers integer-factor oversampling, async sample-rate conversion, and the two building blocks: a compile-time circular buffer and the sinc lookup table.
Building blocks#
CircularBuffer#
CircularBuffer<SampleType, BufferSize> is a fixed-size compile-time ring
buffer for O(1) push plus random-access sample history — the per-channel
history primitive used by the resamplers:
yup::CircularBuffer<float, 512> history;
history.push (sample);
float oldest = history[0]; // logical index: 0 = oldest, BufferSize-1 = newest
history.clear();
BufferSize is enforced > 0 at compile time; the constructor can prefill
every entry with a value (explicit CircularBuffer (SampleType initValue)).
SincTable#
SincTable<CoeffType, OversampleFactor, SincRadius> precomputes the positive
half of a symmetric windowed-sinc kernel; entry (tap, delta) holds the sinc
at t = tap + delta / OversampleFactor. It stores (SincRadius + 1) × OversampleFactor entries.
yup::SincTable<double, 256, 8> table;
table.configureWithCutoff (20000.0, 44100.0); // explicit cutoff (downsampling)
table.configure (44100.0); // or cutoff = sampleRate/2 (upsampling)
table.applyKaiserWindow (5.0); // optional Kaiser windowing, beta = 5
double v = table (tap, delta); // fractional-phase access; negative taps mirrored
configure sets the cutoff to sampleRate/2 (correct for integer-factor
upsampling); configureWithCutoff takes an explicit cutoff in
(0, sampleRate/2] (correct for downsampling, where the anti-aliasing cutoff
is the target Nyquist). applyKaiserWindow multiplies the stored half-kernel
by the second half of a Kaiser window without touching the center coefficient.
Oversampler#
Oversampler<SampleType, OversampleFactor, SincRadius, CoeffType> provides
multi-channel integer-factor oversampling (typically 2×/4×/8×) for
processing chains that need headroom — distortion, nonlinear filters, etc.
Compile-time constraints: OversampleFactor >= 2, SincRadius >= 1.
yup::Oversampler<float, 4, 8> os; // 4x oversampling, sinc radius 8
os.prepare (44100.0, 2, 512);
// audio thread:
os.upsample (inPtrs, numChannels, numSamples);
os.processOversampledBlock ([] (auto& buffer) { applyDistortion (buffer); });
os.downsample (outPtrs, numChannels, numSamples);
preparebuilds the interpolation table (Kaiser β = 5), the decimation table (cutoff at0.45 × input Nyquist, leaving transition bandwidth), and allocates the per-channel history and staging buffers. Not realtime-safe.upsamplewritesnumSamples × OversampleFactorbandlimited samples per channel into an internal buffer; exact phase multiples pass through directly, fractional phases use the2·SincRadius + 1-tap sinc.processOversampledBlock (callback)hands the internal oversampledAudioBufferto your callback for the nonlinear processing.downsampleapplies the anti-aliasing FIR and decimates back; it must be called after the oversampled block was processed, with matching channel and sample counts.getLatencyInSamples()returns2 × SincRadius(input-rate samples).reset()clears history without re-preparing.
Convenience aliases: Oversampler2xFloat, Oversampler4xFloat,
Oversampler8xFloat and the Double variants (all radius 8).
Resampler#
Resampler<SampleType, SincRadius, Resolution, CoeffType> is an async
resampler for arbitrary (including non-integer) sample-rate conversion
using a polyphase windowed-sinc filter with high-resolution phase lookup.
Phase state persists across blocks, so streams stay gapless.
yup::Resampler<float, 8> r; // radius 8, default 256 phases
r.prepare (44100.0, 48000.0, 2, 512);
int produced = r.resample (inPtrs, outPtrs, numChannels, numSamples);
preparebuilds a sinc table with cutoffmin (source, target) / 2(Kaiser β = 5) and computes the ratiotarget / source.resampleconvertsnumSamplesper channel and returns the number of output samples written per channel. Output buffers must hold at leastceil (numSamples × target / source) + 1. When downsampling, the gain is auto-scaled by the ratio; exact phase multiples pass through directly.getLatencyInSamples()returnsSincRadius(input-rate samples).reset()resets the phase accumulator and clears history — use it after a transport discontinuity.
Aliases: ResamplerFloat = Resampler<float, 8>,
ResamplerDouble = Resampler<double, 8>.