Python bindings for libsamplerate (Secret Rabbit Code), Erik de Castro Lopo's high-quality sample rate converter, built with pybind11 and NumPy.
This is the LedFx team's maintained fork of tuxu/python-samplerate. LedFx depends on it, and upstream releases rarely, so the fork provides:
- Prebuilt wheels for CPython 3.11–3.15 on Linux (x86_64, aarch64), macOS (Intel, Apple Silicon) and Windows (x64).
- Optional GIL release during resampling, for multi-threaded use.
- Fixes for crashes and memory errors in the bindings and in libsamplerate itself (see CHANGELOG.md).
All credit for python-samplerate goes to its original authors.
pip install samplerate-ledfxThe module is imported as samplerate, the same name as upstream's
samplerate package, so install one or
the other: pip uninstall samplerate first if you are switching.
Building from source (where no wheel fits) needs a C++14 compiler and network access: CMake fetches libsamplerate and pybind11 at build time.
The three libsamplerate APIs are all available:
- Simple:
resample()converts a whole signal in one call. - Full:
Resampler.process()converts a stream chunk by chunk, and the ratio can change between chunks. - Callback:
CallbackResampler.read()pulls input from a function you provide.
import numpy as np
import samplerate
# Synthesize data
fs = 1000.0
t = np.arange(fs * 2) / fs
input_data = np.sin(2 * np.pi * 5 * t)
# Simple API
ratio = 1.5
converter = "sinc_best" # or "sinc_medium", "sinc_fastest", "zero_order_hold", "linear"
output_data_simple = samplerate.resample(input_data, ratio, converter)
# Full API
resampler = samplerate.Resampler(converter, channels=1)
output_data_full = resampler.process(input_data, ratio, end_of_input=True)
# The result is the same for both APIs.
assert np.allclose(output_data_simple, output_data_full)
# Callback API: the callback returns the next chunk of input, or None at the
# end of the stream. It is called again after that, so keep returning None.
def producer():
for _ in range(10):
yield np.random.uniform(-1, 1, 1024).astype(np.float32)
data_iter = producer()
resampler = samplerate.CallbackResampler(lambda: next(data_iter, None), ratio, converter)
output_chunks = []
while True:
chunk = resampler.read(512) # may return fewer frames than asked for
if chunk.shape[0] == 0:
break
output_chunks.append(chunk)examples/play_modulation.py uses the callback
API to play a frequency-modulated tone. Type hints ship with the package.
- Multi-channel data is a 2-D array of shape
(frames, channels); a single channel can also be a 1-D array. - Data is converted to 32-bit float. Integer samples are cast, not scaled: divide 16-bit PCM by 32768 yourself to get the usual -1.0 to 1.0 range.
- The ratio (output rate / input rate) must be between 1/256 and 256;
anything else raises
samplerate.ResamplingError. - The sinc converters handle at most 128 channels per
ResamplerorCallbackResampler.resample()takes any number: it converts wider input in groups of channels.
- Pass
np.float32. libsamplerate works on 32-bit floats;float64(NumPy's default) or integer input is copied and cast first. - Pass C-contiguous arrays. Non-contiguous input, such as a column slice, is copied too.
- Tune the GIL threshold if you process many small chunks from several threads (see below).
data = np.zeros(1000, dtype=np.float32) # no copy
samplerate.resample(data, 1.5)
data = np.zeros(1000) # float64: copied and cast first
samplerate.resample(data, 1.5)resample(), Resampler.process() and CallbackResampler.read() take a
release_gil argument:
# Default ("auto", or None): release the GIL only for inputs of at least
# 1000 frames, where the ~1-5 µs release/re-acquire cost is negligible.
output = samplerate.resample(input_data, ratio)
# Always release it: other Python threads run while this one resamples.
output = samplerate.resample(input_data, ratio, release_gil=True)
# Never release it: lowest overhead for single-threaded, small inputs.
output = samplerate.resample(input_data, ratio, release_gil=False)
# Change the "auto" threshold (in frames).
samplerate.set_gil_release_threshold(100)Separate Resampler and CallbackResampler objects can run in parallel
threads. A single object holds stream state, so using it from two threads at
once raises RuntimeError instead of corrupting that state. While the GIL is
released the input array is read without it: don't modify it from another
thread during the call.
samplerate.get_build_info() reports the versions, compiler and settings the
module was built with, for bug reports.
uv sync --group test --group dev # builds the extension
uv run pytest -m "not perf" # what CI runs; drop -m for timing benchmarks
uv run prek run --all-files # lint: ruff, actionlint, zizmor, ...PR titles follow Conventional Commits; releases are cut by release-please. See MAINTAINING.md for releases, dependency updates, the libsamplerate patches and upstream syncs.
- scikits.samplerate implements
only the Simple API, with Cython. Positional calls to its
resample(resample(input, ratio, "sinc_best")) work unchanged here, but its keyword names differ (randtyperather thanratioandconverter_type). - resampy: sample rate conversion in Python and Cython.
This project is licensed under the MIT license. libsamplerate is licensed under the 2-clause BSD license.