Filter<T, NB, NA> runs its DF2T recurrence entirely in T, narrowing and rounding every product back to T immediately instead of accumulating in something wider and rounding once per sample. A hand written Q15 biquad normally keeps a 32 or 40 bit accumulator across the whole recurrence for this reason. T is the type the filter calculation actually runs in at each sample.
Without a wide accumulator (which apparently is the canonical way of doing this), each multiply gets its own rounding instead of one rounding at the end of the sum, adding noise on top of whatever T's own resolution already costs. Intermediate partial sums can also hit T's saturation limit even when the final per sample result would not have, since there is no wide headroom to carry a temporarily out of range partial sum through to completion.
Realized while reviewing the fixed point support added in #60, and is at least related (if not solved) by #20.
Filter<T, NB, NA> runs its DF2T recurrence entirely in T, narrowing and rounding every product back to T immediately instead of accumulating in something wider and rounding once per sample. A hand written Q15 biquad normally keeps a 32 or 40 bit accumulator across the whole recurrence for this reason. T is the type the filter calculation actually runs in at each sample.
Without a wide accumulator (which apparently is the canonical way of doing this), each multiply gets its own rounding instead of one rounding at the end of the sum, adding noise on top of whatever T's own resolution already costs. Intermediate partial sums can also hit T's saturation limit even when the final per sample result would not have, since there is no wide headroom to carry a temporarily out of range partial sum through to completion.
Realized while reviewing the fixed point support added in #60, and is at least related (if not solved) by #20.