bmcfee / resampy

Efficient sample rate conversion in python
https://resampy.readthedocs.io
ISC License
253 stars 36 forks source link

resampling to the same hz #76

Closed muschellij2 closed 2 years ago

muschellij2 commented 3 years ago

Resampling with the same hz should likely give the exact result back. It doesn't (likely up to some floating point value).

import librosa
import resampy

x, sr_orig = librosa.load(librosa.util.example_audio_file(), sr=None, mono=False)

y_same = resampy.resample(x, sr_orig, sr_orig)
r = y_same == x
r.all()
#> False

y_same = resampy.resample(x, sr_orig, sr_orig, axis=1)
r = y_same == x
r.all()
#> False

y_same = resampy.resample(x, sr_orig, sr_orig, axis=1, num_zeros=30)
r = y_same == x
r.all()
#> False

import matplotlib.pyplot as plt
import numpy as np

import resampy

a = np.arange(0, 50, .1, dtype=float)
b = resampy.resample(a, 1, 1, axis=0)
c = resampy.resample(a, 1, 1, axis=0, num_zeros=16)

plt.figure()
#> <Figure size 432x288 with 0 Axes>
plt.plot(a, label='Original')
#> [<matplotlib.lines.Line2D at 0x7f88b02069d0>]
plt.plot(b, label='Resampled')
#> [<matplotlib.lines.Line2D at 0x7f88e0e58490>]
plt.plot(c, label='Resampeld Num Zeros')
#> [<matplotlib.lines.Line2D at 0x7f88e0e817c0>]
plt.legend()
#> <matplotlib.legend.Legend at 0x7f88b01ba6d0>

plt.show()

Created on 2020-11-29 by the reprexpy package

bmcfee commented 3 years ago

Thanks for reporting this, and sorry it's taken me so long to come around to responding.

This is definitely more than a floating point precision issue. What you're seeing here is a boundary effect arising from a sharp discontinuity at the end of the signal, x[-1] = 49.9, transitioning implicitly to zero outside of the signal's domain. This discontinuity induces ringing in the filter response, which shows up as ripples near the end of the signal.

This is more or less expected behavior in general, but for the specific case where the input and output sampling rates are identical, I agree that it should be avoided by not doing any interpolation whatsoever. In fact, the librosa resampling interface (which can use resampy, among other backends) does perform this check:

https://github.com/librosa/librosa/blob/790690cc604eec6092ad9a603c5e99be2b3f923b/librosa/core/audio.py#L550-L551

which is probably why I didn't implement it here as well.

As an aside, the way you're checking for equivalence isn't stable:

r = y_same == x
r.all()

Instead, you should use np.allclose(y_same, x) which checks for equivalence down to floating point precision. For the specific case here, it shouldn't matter, but something to be aware of in general.