V0.997.2
version = "0.997.2" breaking = true applies_if = "cli_version_from < 0.997.2 && cli_version_to >= 0.997.2" summary = "OversamplerT now defaults to the elliptic polyphase IIR lane (better anti-aliasing, cheaper, about 1.7 base samples more delay), and Convolver now renders impulse responses longer than one block correctly"
What changed¶
pulp::signal::OversamplerT (Oversampler, Oversampler64) constructed
without set_kind() now filters with Kind::polyphase_iir instead of
Kind::fir_biquad. The polyphase lane is an elliptic half-band design
(design_halfband_iir()); Quality::standard is the default.
fir_biquad (old default) |
polyphase_iir standard (new default) |
|
|---|---|---|
| Alias / image rejection | about 7-9 dB | 94.8 dB from 0.58 Fs |
| Passband | -1.8 dB at 0.3 Fs | flat (< 1e-8 dB) to 0.42 Fs |
Cost per sample, x2 / x4, relative to fir_biquad |
1 / 1 | about 0.17 / 0.26 |
| Round-trip delay near DC, x2 / x4 | 0.97 / 1.46 base samples | 2.66 / 3.49 base samples |
| Round-trip delay at 0.4 Fs, x2 | 1.5 base samples | 7.6 base samples |
Both lanes are minimum phase. latency() still reports a non-constant
delay and latency_samples() still returns 0, so host latency
compensation is unchanged; the extra delay is uncompensated.
The half-band classes (HalfBandUpsampler2x, HalfBandDownsampler2x)
also default to the new design: three sections per path instead of six,
rejecting 94.8 dB from 0.29 of their own rate.
Who is affected¶
Only code that never calls set_kind(). You will hear a brighter top end
(the passband no longer droops) and less aliasing. Code that blends the
oversampled path with an unprocessed dry path, or aligns it against another
signal, sees the extra delay as comb filtering or a timing offset.
Migration steps¶
To keep the previous sound and timing exactly, select the biquad lane:
To keep the new lane and align a dry path, read its delay from the filter instead of hard-coding it:
group_delay_samples(f) is frequency dependent for both minimum-phase
lanes; use Kind::linear_phase_fir when a constant, host-compensated
latency is required.
Filter kinds, rejection, and delay curves are tabulated in Oversampling filters and the real-input FFT.
Convolver output for impulse responses longer than one block¶
pulp::signal::ConvolverT (Convolver, Convolver64) accumulated only the
most recent block's overlap. When the impulse response was longer than the
block size, every tail segment beyond the next block was dropped, so the
output was truncated rather than the full convolution. The overlap is now
accumulated across blocks and the output is the complete convolution.
The output is still delayed by exactly one block, as before: a block is transformed once it has filled and its result is read out while the next one fills. That delay is now stated in the header.
Who is affected¶
Code that loads an impulse response longer than its block_size into
Convolver. Its output gains the reverb or filter tail it was missing, so it
is louder and longer than before. An impulse response no longer than the block
size renders as it did, to rounding. PartitionedConvolver, FixedIrConvolver and
ZeroLatencyConvolver are separate classes and are not affected.
Migration steps¶
None for correctness. A test that pinned the old truncated output against a long impulse response needs its expectation regenerated; compare against a direct-form convolution of the same impulse response delayed by one block.