@equipoise i feel like i must have also been insufficiently clear. i tried to answer the original questions. but on re-reading them i think i failed. JOS is an excellent theorist, and Tom Erbe is a great person, developer, and mentor; and all the reference materials linkd so far are right on point. but sometimes it’s nice to start from the beginning to make sure everyone is on the same page.
(and look, i know this is basic, but to be clear to anyone following who doesn’t want to click through JOS)
first off, what is a digital allpass filter?
well, functionally it is a filter with a flat magnitude response. which is, eh, quite vague. algorithmically, usually means a filter graph with an equal number of poles (feedback terms) and zeroes (feedforward terms), where the pole coefficients and the zero coefficients are complex conjugates. but since we’re talking about audio, these are real numbers.
its very common to encounter first-order allpasses, (or let’s say APs), and from here on we’ll assume that’s what we’re talking about first-order, meaning one pole and one zero.
the graph looks like this (sorry for bad ascii art):
x(n)--+---> z_1 ----+---> y(n)
and difference equation:
y(n) = a*x(b) + x(n-1) + b*y(n-1)
and transfer function:
(z) = (a + z_1) / (1 + b*z_1)
a is the pole coefficient and
b is the zero. for the allpass condition, a = b. so there is only one coefficent, which i’ll just call
c. super easy!
I’m also interested in how to get delay times (if I understand correctly, there’s a delay time as a key parameter of an allpass filter, right?) that are fractions of a sample.
if you think of an allpass as a delay element, then the delay time is always less than one sample for a first-order AP, or i think it’s fair to say less than N samples for an Nth-order AP.
so in this context, think of an allpass as a “tuning” element in series with an integral delay line.
Can anyone point me to any references that explain how to design and then program efficient allpass filters in C, preferably “from scratch"
this was done a bit already, but to be really specific and without a lot of baggage:
D = (1-c) / (1+c) , where
D is the delay time in samples, in [0,1].
it follows that
c = (1-D) / (1+D).
and that is literally the only design parameter you get with 1st-order allpass.
with higher order allpasses, you can get a much wider range of effects. for example, if you combine 2x 1st order APs with opposite pole locations, you get a notch filter, and this is a common way to implement phaser-style effects in the digital domain.
OK, but you are mainly interested in these things as building blocks in reverbs, and particularly in classic Feedback Delay Network - style reverbs. here, the allpass has two nice qualities:
if used as a delay tuning component, since it has flat magnitude response, you can accurately compute the loss function as the eigenvalue of the feedback matrix. this means you can have a fully-lossless reverb network (“freeze” effect) without blowing up, as you might if you tried to use a different kind of sub-sample delay tuning method (e.g. polynomial interpolation, which as you’ve observed has a mag response resembling a comb filter - in fact an aliased sinc function.)
as a phase-decorrelation element. (if i recall correctly,) Schroeder’s reverb uses some matrix algebra to refactor a full NxN tuned delay network into an integral delay network, a summing term, and a bank of parallel APs for phase decorrelation. once you do this, you can actually choose different stuff for the phase decorrelation part, including filters with non-flat mag response (for coloration) and even nonlinear elements…
(TODO: demonstrate Schroeder reverb and variants in Faust, using different decorrelation and coloring filters / saturators!)
resonators: karplus-strong, scattering junctions
similarly, the flat response of the AP makes it useful for other algorithms where lossless tuned feedback is a potential configuration. there are tradeoffs: it’s not as straightforward to modulate the delay time without weird artifacts. and since the “delay time” is not frequency-dependent, it doesn’t preserve high-bandwidth transients and stuff in the excitation signal.