Low latency sample rate conversion

There seems to be a hard trade-off when considering speed, quality, and latency. I looked into r8brain for its nice API, its speed and quality – but there are thousands of samples of transport delay making it unsuitable for my purposes.

The libsamplerate FAQ also mentions transport delay when filling up the output buffer. Is this unavoidable for high quality SRC?

I believe this is all dependent on the filter used during resampling. Linear phase FIR filters with a close to ideal response are going to have much greater latency than minimum phase IIR filters that cut into the passband.

Are you asking for fractional resampling or integer (power of two) resampling algorithms?

I was asking just generally. I didn’t realize at the time of my post that all resampling requires filters. Even power of two resampling algorithms like the IIR version of juce::dsp::Oversampling induce at least a few samples of delay.

I have used a lot of plugins with oversampling and I don’t recall any of them reporting latency, so I was surprised to learn these basic facts.

I have used a lot of plugins oversampling processing which don’t report latency too, and generally a quick test with some dry signal in parallel shows very easily that this is a mistake :smile:

Yes, for lowest-latency sampling-rate conversion, minimum-phase filters make the most sense. I am using simple Butterworth lowpass filters myself (from the Faust Libraries).

The IIR version of juce::dsp::Oversampling (which I just learned about here) looks very nice, but my question is then why do the polyphase branch filters have to be allpass? I downloaded the cited paper “Digital Signal Processing Schemes for efficient interpolation and decimation” by Pavel Valenzuela and Constantinides, and they merely state this requirement without any proof or citation. It’s possible that their “References 2, 3” explain this. Does anyone here know?

A disadvantage of this polyphase method is the heavily constrained filter-design problem. That, for example, is presumably why only one specific half-band filter is available. (More options can be found in “Multirate Systems and Filter Banks” by P.P. Vaidyanathan, and I can probably find the answer to my allpass question there.)

While my simple Butterworth solution may burn more CPU, it is very flexible and easy to work with, so I’m sticking with it for now, but I’ll also offer the min-phase juce::dsp::Oversampling method for comparison purposes. (I similarly ruled out r8brain and libresample since they appear to have no minimum-phase offerings.)

1 Like

IIUC, the reason for using allpasses is stated in the paper:

“It is required, however, that the output baseband spectrum be identical to the input spectrum (except for the higher sampling rate). This has the consequence that the transfer functions of the individual branches should be of the all-pass type”

I’ve not used the filters in juce::dsp, but I’ve solved my own for a factor of 2. They’re power-complementary elliptics, so -3 dB at SR/2 and almost flat on the passband. It’s also possible to get quasi-linear-phase by setting one branch to be a pure delay.

Yes, I read that quote, and I don’t think it’s correct. If it is approximately correct, then a derivation is needed. When you add allpasses you do not get an allpass, and it’s the phase that determines where things reinforce and cancel. Furthermore, the final filter is not allpass, so the baseband spectrum will NOT be identical to the input spectrum over the original band, so setting the branch filters to allpass does not provide this. This sounds like something ChatGPT would say. I guess I have to find Vaidyanathan’s book.

We know the output of the system is lowpass, so I read that as referring to each branch. Now that I think about it, it does sound like bs. In any case, the idea is simple enough to picture:

phase

You have two allpasses of the same order, with the second one delayed by one sample. You find a set of coefficients for which they’re most closely in phase below pi/2, and most closely out of phase above. For a quasi-linear-phase response, you constrain the first branch to be a pure delay, which of course requires higher orders to get comparable reductions. Setting the stopband to start at 2/3 SR, I get these amplitude responses

and these group delays in samples

using the amount of 1-pole allpass stages stated in the pictures. As the filters are (the LP of a) power-complementary (pair), the passband ripple is very small: 1.6E-6 dB for the worst one of these. I don’t know how this scales for arbitrary factors, but at least for this case, getting a 7th order elliptic response from three 1-pole allpasses seems pretty good.

2 Likes

Nice description and illustrations! Your example looks Chebyshev in the stopband only, but maybe it’s elliptic and the passband ripple is too small to see. Yes, the allpass sum is a numerically good way to make an elliptic filter, and Vaidyanathan covers that, but there are other more general polyphase solutions as well. The allpass-decomposition insight apparently goes back to Fettweis, who probably discovered it in the Wave Digital Filter context. If you need the spectral carving power of an elliptic function filter, and are working at high enough sampling rates that you’ll never hear it ringing at its corner frequency, then this is definitely a good way to go. Is that what juce::dsp::Oversampling implements? I’m looking at JUCE/modules/juce_dsp/filter_design/juce_FilterDesign.cpp:613 right now, and I see no comments saying so, nor do I find it in the .h file. It’s nice how the filter order is internally calculated from transition bandwidth and stopband gain. I suppose I’ll recognize its type when I impulse it and look at the spectrum.

1 Like

maybe it’s elliptic and the passband ripple is too small to see

That’s the case. Because passband^2 + reverse_stopband^2 == 1, the passband ripple gets very small.

The allpass-decomposition insight apparently goes back to Fettweis, who probably discovered it in the Wave Digital Filter context.

I’ve been very intrigued about that whole thing since I discovered this, but it’s a bit over my head :sweat_smile:

Is that what juce::dsp::Oversampling implements?

It is indeed. It’s the “analytic design method” in section 4.2 of the Valenzuela / Constantinides paper:

I went the numerical way because it seemed simpler, and I wanted to fix the stopband corner instead of the transition width.

1 Like

What order Butterworth filter(s) are you using? I’m building integer-ratio up- and down- samplers with juce::dsp::FilterDesign<T>::designIIRLowpassHighOrderButterworthMethod and results seem decent so far (same frequency response as juce::dsp::Oversampling).

In a Butterworth filter, everything above the corner frequency is “transition band”, so, you just transition (roll off) down to a low enough gain for the situation. We get ~20*log10(2) ~ 6 dB per octave per pole of roll-off above the corner frequency, for large sampling rates. Due to the bilinear transform, which maps the entire infinite frequency axis to the sampling rate, the rolloff accelerates rapidly toward the Nyquist limit, so results are better than in the analog case. In my application, I estimated order 9 would be needed from -6dB/octave rolloff per pole, but looking at it in Octave showed that order 5 was sufficient for 2x oversampling with ~120 dB of spectral image suppression. (My application upsamples the output of a guitar amp cabinet that I’m willing to limit to 7 kHz, so I get a lot of roll-off room at normal 44.1 kHz sampling rates and such.) I think Butterworth filters have the same polyphase allpass decomposition as elliptic function filters, so this would be a nice new case to add to juce::dsp::Oversampling. How are you showing “same frequency response” ? It should actually look very different in the stop band (ripple versus no ripple), and the elliptic corner is very sharp, while the Butterworth corner is “maximally smooth”, which is why its latency is lower and it is safer to use in the audio range without ringing artifacts.