spotify / pedalboard

🎛 🔊 A Python library for audio.
https://spotify.github.io/pedalboard
GNU General Public License v3.0
4.98k stars 250 forks source link

WindowedSinc resampling introduces distortion #142

Open iCorv opened 1 year ago

iCorv commented 1 year ago

Resample: WindowedSinc resampling introduces distortion when setting a target sampling rate ≤ 8 kHz

Expected behaviour

Since WindowedSinc is a relatively high-quality resampling scheme, I would expect a band-limited signal without distortion.

Actual behaviour

For resampling targets of 8 kHz or 4 kHz, I observe noticeable distortion in the signal, compared to the resampling library resampy which also allows using sinc_window resampling, which doesn't have this issue. Since the other resampling algorithms in pedalboard are prone to introduce aliasing and other artifacts, I ask myself is this a bug or a feature? :D

Steps to reproduce the behaviour

The distortion is especially prominent in speech audio when there are sibilants. Therefore, I recommend using an speech example from librosa for testing:

import pedalboard as pb
import numpy as np
from pedalboard import Resample
import librosa
import soundfile as sf
import resampy

original_sample_rate = 24000
target_sample_rate = 8000

# needs librosa >= 0.9.2
audio, sr = librosa.load(librosa.example('libri3'), sr=original_sample_rate)

# resample with pedalboard
audio_resampled_pb = Resample(
    target_sample_rate,
    quality=pb.Resample.Quality.WindowedSinc,
    )(audio, original_sample_rate)

sf.write(
    "./libri3_pb.wav",
    audio_resampled_pb,
    samplerate=original_sample_rate)

# resample with resampy
audio_resampled_res = resampy.resample(
    audio,
    original_sample_rate,
    target_sample_rate,
    filter="sinc_window",
)
audio_resampled_res = resampy.resample(
    audio_resampled_res,
    target_sample_rate,
    original_sample_rate,
    filter="sinc_window",
)

sf.write(
    "./libri3_resampy.wav",
    audio_resampled_res,
    samplerate=original_sample_rate
    )
psobot commented 1 year ago

Hi @iCorv! Thanks for the bug report. Pedalboard's resamplers are just thin wrappers around JUCE's Interpolators package; the WindowedSinc algorithm just uses the WindowedSincInterpolator, which uses a 10,000-point static lookup table.

Resampy's sinc_window filter seems to construct a different window filter, which might be of higher quality. I'll look into a way to compare the two, as it would be possible to write a custom interpolator to avoid distortion.

iCorv commented 1 year ago

Hi @psobot, yes, there seem to be more differences. Resampy also provides pre-computed window filters as kaiser_best and kaiser_fast, maybe that could be a starting point. resampy + kaiser_best also being the default resampling librosa uses, from experience I can say it is very slow compared to kaiser_fast. It surprises me that JUCE hasn't got any better resampling plugin, perhaps it is easier to use another library for high-quality resampling? Here is a list of open-source solutions. In general, the resampy algorithm is described here in more detail.

iCorv commented 1 year ago

Revisiting this issue, I solved this in my application by using a low pass filter set to target_sampling_rate / 2 before and after resampling. I guess this is common practice when resampling signals, but often this is taken care of by the library already. In case of pedalboard I think it would be helpful to either mention this in the docs or make the LP part of the resampling.

And I am still not sure with the windowed-sinc-interpolation, isn't the point of this resampling technique that it already is a type of bandlimited interpolation which makes LP filter redundant?