Sample rate conversion between 44.1 kHz and 48 kHz

Hello. I am looking for opinions of experienced JUCE users about how to do
conversion between 44.1 kHz and 48 kHz.

My original thought was an exact solution to the problem, that is upsampling,
low-pass filtering, down-sampling. That is, sinc filtering. The 147 / 160
ratio is a pain of course. I faintly recall that polyphase filters are the way
to go to efficiently solve these problems.

I have not found any such solution in JUCE though, maybe I am missing
something? I’ve found the Lagrange and Catmull-Rom interpolator, but both of
these are just approximations, not the real thing, so I distrust them. Are
these acceptable in professional grade audio applications?

I also found https://github.com/justinfrankel/WDL/tree/master/WDL where
resample.h / resample.cpp contain implementations that offer sinc
interpolation. They also seem to be an approximation, because you can specify
the width of the sinc, whereas the upsample, filter, dowsample approach
corresponds to a theoretically infinite sinc. The code also seems suspiciously
linear, although the compiler may be able to efficiently vectorize it. Does
anybody have any experience with this?

There is also
https://github.com/avaneev/r8brain-free-src/blob/master/CDSPResampler.h with
lots of configurable parameters. Maybe this is the way to go.

What are you thinking? How would you do 44.1 kHz and 48 kHz sample rate
conversion?

You can check out how the juce::ResamplingAudioSource class does this.

1 Like

Used this a long time ago. Very good quality.

At the time it was GPL but now it’s BSD so much more permissive

3 Likes

Thank you for the suggestions.

libsamplerate is very promising. It was easy to set up, and it has a simple approachable API. The cmake project built without a problem on Windows. It does sinc interpolation. So far I prefer it to WDL or r8brain. On highest settings I’m getting 840,000 samples / second performance, which I hoped would be higher. But this is definetly something I can use.

I also took a look at juce::ResamplingAudioSource. It seems like it does some combination of linear interpolation and filtering with a single IIR biquad. So quality wise that is probably not what I’m looking for.

Check out https://src.infinitewave.ca
It will show you the quality of different resamplers.
As you mentioned ResamplingAudioSource does linear interpolation, so don’t be surprised by JUCE’s image :wink:

5 Likes

Being dissatisfied with the performance of libsamplerate 0.1.9 I gave r8brain 4.6 another chance. It’s waay faster.

I measured the 48 kHz --> 44.1 kHz conversion time of 382706 samples

libsamplerate: 0.452 s (SINC_BEST), 0.169 s (SINC_MEDIUM)
r8brain: 0.019 s
scipy.signal.resample_poly: 0.011 s

With Intel IPP enabled, it gets even faster.

r8brain: 0.014 s

Quality wise r8brain is closer to SINC_BEST.

2 Likes

I will go with r8brain for now.

But I will come back to figure out how Python’s scipy.signal.resample_poly can be even faster by 20%, even though I have preallocated mostly everything I could for r8brain, while the scipy code had to do its own allocations within the measured time. It also looks even higher quality.

Maybe it does something with larger filters that would make it unsuitable for realtime use.

1 Like

Hi! My suggestion is to try R8B_FASTTIMING 1 and R8B_EXTFFT 1 as well, and use a higher TransBand value. I’m quite confident r8brain free src is the fastest sample rate converter out there, given the constraints. I’m not aware of scipy.signal.resample_poly quality or the window function parameters you are using - there’s no magic bullet exists to speed-up conversion except using less steep low-pass filters (which TransBand adjusts). (and memory allocations are usually not that time consuming anyway)

1 Like

Hey @avaneev, thank you for your great work with r8brain!

Indeed, with the R8B_FASTTIMING and R8B_EXTFFT definitions scipy and
r8brain speeds are within the margin of error of my measurements.

One thing I noticed, is that I get some significant looking preringing at the
Nyquist-frequency when ReqTransBand is low, which is something that scipy
does not exhibit at all. The plot below is made with ReqTransBand = 0.5 and
ReqAtten = 180.15.

r8brain_0.5_180.15

If I relax the parameters to ReqTransBand = 7.0 and ReqAtten = 100.0 the
preringing is significantly reduced and the overall error compared to scipy
also decreases, which suggest that these parameters are closer to what scipy
uses anyway. At this point r8brain is consistently faster than scipy by about
10%
. It’s just interesting to see more preringing with a tighter
specification.

Hoping to also use r8brain for realtime processing I was experimenting with
all kinds of parameter values. I managed to go as low as 48 samples with
ReqTransBand = 30.0 and ReqAtten = 100.0, which will do. By just simply
relaxing the parameters even further, the latency started going up again. I
was hoping to decrease it further but using the fprMinPhase parameter, but
it only yielded a modest decrease to 37 samples, probably not worth
sacrificing linear phase for it. Do you have any low-latency tips using the
minimum-phase mode?

Pre-ringing is totally fine, it’s how steep linear-phase filters work. With minphase filters the pre-ringing is absent. Given scipy shows less ringing it means its filters are of lower steepness and do not provide good resolution, ReqAtten=100 is even lower than 16-bit resolution.

It’s not possible to reduce latency considerably with quality- and speed-oriented r8brain’s design.

1 Like