Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-soxr for openSUSE:Factory checked in at 2026-05-20 15:25:02 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-soxr (Old) and /work/SRC/openSUSE:Factory/.python-soxr.new.1966 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-soxr" Wed May 20 15:25:02 2026 rev:5 rq:1354119 version:1.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-soxr/python-soxr.changes 2026-04-01 19:53:02.287135504 +0200 +++ /work/SRC/openSUSE:Factory/.python-soxr.new.1966/python-soxr.changes 2026-05-20 15:26:17.002495887 +0200 @@ -1,0 +2,17 @@ +Tue May 19 21:47:17 UTC 2026 - Dirk Müller <[email protected]> + +- update to 1.1.0: + * Added experimental **variable-rate resampling** and improved + **ResampleStream performance**. + * There are no breaking changes in this release. + * **Added (Experimental) Variable-rate Resampling** + * Now available via `ResampleStream(vr=True)` and + `set_io_ratio()`. + * Improved processing speed **≈5–10%** (benchmarked with + 1920-sample chunks). + * Optimized internal memory allocation logic within + `ResampleStream`. + * Added **randomized test cases** to enhance stability and + edge-case coverage. + +------------------------------------------------------------------- Old: ---- soxr-1.0.0.tar.gz New: ---- soxr-1.1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-soxr.spec ++++++ --- /var/tmp/diff_new_pack.2FZhMY/_old 2026-05-20 15:26:18.134542534 +0200 +++ /var/tmp/diff_new_pack.2FZhMY/_new 2026-05-20 15:26:18.138542699 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-soxr -Version: 1.0.0 +Version: 1.1.0 Release: 0 Summary: High quality, one-dimensional sample-rate conversion library License: LGPL-2.1-or-later ++++++ soxr-1.0.0.tar.gz -> soxr-1.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/soxr-1.0.0/CMakeLists.txt new/soxr-1.1.0/CMakeLists.txt --- old/soxr-1.0.0/CMakeLists.txt 2022-11-09 13:37:21.000000000 +0100 +++ new/soxr-1.1.0/CMakeLists.txt 2022-11-09 13:37:21.000000000 +0100 @@ -104,7 +104,6 @@ option(WITH_OPENMP "" OFF) # OpenMP seems not working (dunno why). Disable it for portability anyway. option(WITH_LSR_BINDINGS "" OFF) option(BUILD_SHARED_LIBS "" OFF) # make it shared someday? - option(WITH_VR32 "" OFF) set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_INSTALL_PREFIX ../install) add_subdirectory(libsoxr libsoxr) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/soxr-1.0.0/PKG-INFO new/soxr-1.1.0/PKG-INFO --- old/soxr-1.0.0/PKG-INFO 2022-11-09 13:37:21.000000000 +0100 +++ new/soxr-1.1.0/PKG-INFO 2022-11-09 13:37:21.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: soxr -Version: 1.0.0 +Version: 1.1.0 Summary: High quality, one-dimensional sample-rate conversion library Keywords: audio resampling,samplerate conversion,SRC,signal processing,resampler Author: KEUM Myungchul @@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta Project-URL: Homepage, https://github.com/dofuuz/python-soxr Project-URL: Documentation, https://python-soxr.readthedocs.io +Project-URL: Release Notes, https://github.com/dofuuz/python-soxr/releases Project-URL: Source, https://github.com/dofuuz/python-soxr Project-URL: Bug Tracker, https://github.com/dofuuz/python-soxr/issues Requires-Python: >=3.9 @@ -39,6 +40,7 @@ - Homepage: https://github.com/dofuuz/python-soxr - Documentation: https://python-soxr.readthedocs.io +- Release Notes: https://github.com/dofuuz/python-soxr/releases - PyPI: https://pypi.org/project/soxr/ Keywords: Resampler, Audio resampling, Samplerate conversion, DSP(Digital Signal Processing) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/soxr-1.0.0/README.md new/soxr-1.1.0/README.md --- old/soxr-1.0.0/README.md 2022-11-09 13:37:21.000000000 +0100 +++ new/soxr-1.1.0/README.md 2022-11-09 13:37:21.000000000 +0100 @@ -6,6 +6,7 @@ - Homepage: https://github.com/dofuuz/python-soxr - Documentation: https://python-soxr.readthedocs.io +- Release Notes: https://github.com/dofuuz/python-soxr/releases - PyPI: https://pypi.org/project/soxr/ Keywords: Resampler, Audio resampling, Samplerate conversion, DSP(Digital Signal Processing) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/soxr-1.0.0/pyproject.toml new/soxr-1.1.0/pyproject.toml --- old/soxr-1.0.0/pyproject.toml 2022-11-09 13:37:21.000000000 +0100 +++ new/soxr-1.1.0/pyproject.toml 2022-11-09 13:37:21.000000000 +0100 @@ -1,14 +1,12 @@ [build-system] requires = [ - "scikit-build-core >=0.10", - "nanobind >=2", + "scikit-build-core >=0.11", + "nanobind >=2, <3", "setuptools >=45", "setuptools_scm[toml] >=6.2", "typing-extensions; python_version < '3.11'", - - # No, it doesn't require NumPy on build-time. ] build-backend = "scikit_build_core.build" @@ -53,6 +51,7 @@ [project.urls] Homepage = "https://github.com/dofuuz/python-soxr" Documentation = "https://python-soxr.readthedocs.io" +"Release Notes" = "https://github.com/dofuuz/python-soxr/releases" Source = "https://github.com/dofuuz/python-soxr" "Bug Tracker" = "https://github.com/dofuuz/python-soxr/issues" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/soxr-1.0.0/src/soxr/__init__.py new/soxr-1.1.0/src/soxr/__init__.py --- old/soxr-1.0.0/src/soxr/__init__.py 2022-11-09 13:37:21.000000000 +0100 +++ new/soxr-1.1.0/src/soxr/__init__.py 2022-11-09 13:37:21.000000000 +0100 @@ -77,11 +77,14 @@ quality : int or str, optional Quality setting. One of `QQ`, `LQ`, `MQ`, `HQ`, `VHQ`. + vr : bool, optional + (Experimental) Enable variable-rate resampling. + The ratio of the given in_rate and out_rate must equate to the maximum I/O ratio that will be used. """ def __init__(self, in_rate: float, out_rate: float, num_channels: int, - dtype='float32', quality='HQ'): + dtype='float32', quality='HQ', vr=False): if in_rate <= 0 or out_rate <= 0: raise ValueError('Sample rate should be over 0') @@ -93,7 +96,7 @@ q = _quality_to_enum(quality) - self._csoxr = soxr_ext.CSoxr(in_rate, out_rate, num_channels, stype, q) + self._csoxr = soxr_ext.CSoxr(in_rate, out_rate, num_channels, stype, q, vr) self._process = getattr(self._csoxr, f'process_{self._type}') def resample_chunk(self, x: np.ndarray, last=False) -> np.ndarray: @@ -156,6 +159,25 @@ """ self._csoxr.clear() + def set_io_ratio(self, in_rate: float, out_rate: float, slew_len: int = 0) -> None: + """ (Experimental) Set new sample-rate ratio for next processing. + + `vr=True` must be set at constructor to use this function. + WARNING: It's an experimental feature and this API may change in future release. + + Parameters + ---------- + in_rate : float + New input sample-rate. + out_rate : float + New output sample-rate. + slew_len : int, optional + Length of smooth transition in input samples. (default: 0) + If slew_len > 0, the transition will be done smoothly over the given length. + If slew_len == 0, the transition will be done immediately. + """ + self._csoxr.set_io_ratio(in_rate / out_rate, slew_len) + def resample(x: ArrayLike, in_rate: float, out_rate: float, quality='HQ') -> np.ndarray: """ Resample signal diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/soxr-1.0.0/src/soxr/_version.py new/soxr-1.1.0/src/soxr/_version.py --- old/soxr-1.0.0/src/soxr/_version.py 2022-11-09 13:37:21.000000000 +0100 +++ new/soxr-1.1.0/src/soxr/_version.py 2022-11-09 13:37:21.000000000 +0100 @@ -1,5 +1,6 @@ -# file generated by setuptools-scm +# file generated by vcs-versioning # don't change, don't track in version control +from __future__ import annotations __all__ = [ "__version__", @@ -10,25 +11,14 @@ "commit_id", ] -TYPE_CHECKING = False -if TYPE_CHECKING: - from typing import Tuple - from typing import Union - - VERSION_TUPLE = Tuple[Union[int, str], ...] - COMMIT_ID = Union[str, None] -else: - VERSION_TUPLE = object - COMMIT_ID = object - version: str __version__: str -__version_tuple__: VERSION_TUPLE -version_tuple: VERSION_TUPLE -commit_id: COMMIT_ID -__commit_id__: COMMIT_ID +__version_tuple__: tuple[int | str, ...] +version_tuple: tuple[int | str, ...] +commit_id: str | None +__commit_id__: str | None -__version__ = version = '1.0.0' -__version_tuple__ = version_tuple = (1, 0, 0) +__version__ = version = '1.1.0' +__version_tuple__ = version_tuple = (1, 1, 0) -__commit_id__ = commit_id = 'gb419df031' +__commit_id__ = commit_id = 'g91479780e' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/soxr-1.0.0/src/soxr_ext.cpp new/soxr-1.1.0/src/soxr_ext.cpp --- old/soxr-1.0.0/src/soxr_ext.cpp 2022-11-09 13:37:21.000000000 +0100 +++ new/soxr-1.1.0/src/soxr_ext.cpp 2022-11-09 13:37:21.000000000 +0100 @@ -13,7 +13,6 @@ #include <algorithm> #include <cmath> #include <memory> -#include <typeinfo> #include <nanobind/nanobind.h> #include <nanobind/ndarray.h> @@ -23,7 +22,6 @@ #include "csoxr_version.h" -using std::type_info; using std::make_unique; namespace nb = nanobind; @@ -31,56 +29,49 @@ using nb::ndarray; -static soxr_datatype_t to_soxr_datatype(const type_info& ntype) { - if (ntype == typeid(float)) - return SOXR_FLOAT32_I; - else if (ntype == typeid(double)) - return SOXR_FLOAT64_I; - else if (ntype == typeid(int32_t)) - return SOXR_INT32_I; - else if (ntype == typeid(int16_t)) - return SOXR_INT16_I; - else - throw nb::type_error("Data type not support"); -} - -static soxr_datatype_t to_soxr_split_dtype(const type_info& ntype) { - if (ntype == typeid(float)) - return SOXR_FLOAT32_S; - else if (ntype == typeid(double)) - return SOXR_FLOAT64_S; - else if (ntype == typeid(int32_t)) - return SOXR_INT32_S; - else if (ntype == typeid(int16_t)) - return SOXR_INT16_S; - else - throw nb::type_error("Data type not support"); -} +template <typename T> constexpr soxr_datatype_t to_i_dtype = [] { + static_assert(sizeof(T) == 0, "Unsupported type for SOXR"); +}(); +template <> constexpr soxr_datatype_t to_i_dtype<float> = SOXR_FLOAT32_I; +template <> constexpr soxr_datatype_t to_i_dtype<double> = SOXR_FLOAT64_I; +template <> constexpr soxr_datatype_t to_i_dtype<int32_t> = SOXR_INT32_I; +template <> constexpr soxr_datatype_t to_i_dtype<int16_t> = SOXR_INT16_I; + +template <typename T> constexpr soxr_datatype_t to_s_dtype = [] { + static_assert(sizeof(T) == 0, "Unsupported type for SOXR"); +}(); +template <> constexpr soxr_datatype_t to_s_dtype<float> = SOXR_FLOAT32_S; +template <> constexpr soxr_datatype_t to_s_dtype<double> = SOXR_FLOAT64_S; +template <> constexpr soxr_datatype_t to_s_dtype<int32_t> = SOXR_INT32_S; +template <> constexpr soxr_datatype_t to_s_dtype<int16_t> = SOXR_INT16_S; class CSoxr { soxr_t _soxr = nullptr; - const double _oi_rate; + double _oi_ratio; // out_rate/in_rate + std::unique_ptr<uint8_t[]> _y_buf; + size_t _y_buf_bytes = 0; // _y_buf size in bytes + size_t _olen = 0; // _y_buf size in frames public: const double _in_rate; const double _out_rate; const soxr_datatype_t _ntype; const unsigned _channels; - const size_t _div_len; + const size_t _div_len; // length to divide long input (in frames) bool _ended = false; CSoxr(double in_rate, double out_rate, unsigned num_channels, - soxr_datatype_t ntype, unsigned long quality) : + soxr_datatype_t ntype, unsigned long quality, bool vr) : _in_rate(in_rate), _out_rate(out_rate), - _oi_rate(out_rate / in_rate), + _oi_ratio(out_rate / in_rate), _ntype(ntype), _channels(num_channels), _div_len(std::max(1000., 48000 * _in_rate / _out_rate)) { soxr_error_t err = NULL; soxr_io_spec_t io_spec = soxr_io_spec(ntype, ntype); - soxr_quality_spec_t quality_spec = soxr_quality_spec(quality, 0); + soxr_quality_spec_t quality_spec = soxr_quality_spec(quality, vr ? SOXR_VR : 0); _soxr = soxr_create( in_rate, out_rate, num_channels, @@ -96,6 +87,46 @@ } template <typename T> + T* _resize_ybuf(size_t req_size, bool copy) { + if (_y_buf && req_size < _y_buf_bytes) + return reinterpret_cast<T*>(_y_buf.get()); + + // Grow to next power of 2 + size_t new_size = 1024; + while (new_size < req_size) new_size <<= 1; + + auto new_buf = std::make_unique<uint8_t[]>(new_size); + if (copy && _y_buf) { + std::copy_n(_y_buf.get(), _y_buf_bytes, new_buf.get()); + } + _y_buf = std::move(new_buf); + _y_buf_bytes = new_size; + _olen = _y_buf_bytes / (sizeof(T) * _channels); + + return reinterpret_cast<T*>(_y_buf.get()); + } + + template <typename T> + T* _flush(soxr_in_t input, size_t& out_pos) { + // flush until no more output + T* y = reinterpret_cast<T*>(_y_buf.get()); + size_t odone = 0; + do { + if (_olen <= out_pos) { + y = _resize_ybuf<T>(_y_buf_bytes * 2, true); + } + soxr_error_t err = soxr_process( + _soxr, + input, 0, NULL, + &y[out_pos*_channels], _olen-out_pos, &odone); + out_pos += odone; + + if (err != NULL) throw std::runtime_error(err); + } while (0 < odone); + return y; + } + + template <typename T> auto process( ndarray<const T, nb::ndim<2>, nb::c_contig, nb::device::cpu> x, bool last=false) { @@ -107,7 +138,7 @@ if (channels != _channels) throw std::invalid_argument("Channel num mismatch"); - const soxr_datatype_t ntype = to_soxr_datatype(typeid(T)); + constexpr soxr_datatype_t ntype = to_i_dtype<T>; if (ntype != _ntype) throw nb::type_error("Data type mismatch"); @@ -121,12 +152,10 @@ const size_t ilen = x.shape(0); - // This is slower then allocating fixed `ilen * _oi_rate`. - // But it insures lowest output delay provided by libsoxr. - const size_t olen = soxr_delay(_soxr) + ilen * _oi_rate + 1; - - // alloc - y = new T[olen * channels] { 0 }; + // This is slower than returning fixed `ilen * _oi_ratio` buffers w/o copying. + // But it ensures the lowest output delay provided by libsoxr. + const size_t req_len = soxr_delay(_soxr) + ilen * _oi_ratio + 1; + y = _resize_ybuf<T>(sizeof(T) * req_len * channels, false); // divide long input and process size_t odone = 0; @@ -134,31 +163,28 @@ err = soxr_process( _soxr, &x.data()[idx*channels], std::min(_div_len, ilen-idx), NULL, - &y[out_pos*channels], olen-out_pos, &odone); + &y[out_pos*channels], _olen-out_pos, &odone); out_pos += odone; + + if (_olen <= out_pos) { + // for VR mode, output buffer may be full + y = _flush<T>(&x.data()[idx*channels], out_pos); + } } // flush if last input if (last) { _ended = true; - err = soxr_process( - _soxr, - NULL, 0, NULL, - &y[out_pos*channels], olen-out_pos, &odone); - out_pos += odone; + y = _flush<T>(NULL, out_pos); } } if (err) { - delete[] y; throw std::runtime_error(err); } - // Delete 'y' when the 'owner' capsule expires - nb::capsule owner(y, [](void *p) noexcept { - delete[] (T *) p; - }); - return ndarray<nb::numpy, T>(y, { out_pos, channels }, owner); + // Return a copy + return ndarray<nb::numpy, T>(y, { out_pos, channels }).cast(); } size_t num_clips() { return *soxr_num_clips(_soxr); } @@ -170,6 +196,12 @@ if (err != NULL) throw std::runtime_error(err); _ended = false; } + + void set_io_ratio(double io_ratio, size_t slew_len=0) { + soxr_error_t err = soxr_set_io_ratio(_soxr, io_ratio, slew_len); + if (err != NULL) throw std::runtime_error(err); + _oi_ratio = std::max(_oi_ratio, 1 / io_ratio); + } }; @@ -189,7 +221,7 @@ do { nb::gil_scoped_release release; - const soxr_datatype_t ntype = to_soxr_datatype(typeid(T)); + constexpr soxr_datatype_t ntype = to_i_dtype<T>; // init soxr const soxr_io_spec_t io_spec = soxr_io_spec(ntype, ntype); @@ -264,7 +296,7 @@ do { nb::gil_scoped_release release; - const soxr_datatype_t ntype = to_soxr_split_dtype(typeid(T)); + constexpr soxr_datatype_t ntype = to_s_dtype<T>; // init soxr const soxr_io_spec_t io_spec = soxr_io_spec(ntype, ntype); @@ -336,7 +368,7 @@ const size_t olen = ilen * out_rate / in_rate + 1; unsigned channels = x.shape(1); - const soxr_datatype_t ntype = to_soxr_datatype(typeid(T)); + constexpr soxr_datatype_t ntype = to_i_dtype<T>; // make soxr config soxr_error_t err = NULL; @@ -379,7 +411,7 @@ .def_ro("ntype", &CSoxr::_ntype) .def_ro("channels", &CSoxr::_channels) .def_ro("ended", &CSoxr::_ended) - .def(nb::init<double, double, unsigned, soxr_datatype_t, unsigned long>()) + .def(nb::init<double, double, unsigned, soxr_datatype_t, unsigned long, bool>()) .def("process_float32", &CSoxr::process<float>) .def("process_float64", &CSoxr::process<double>) .def("process_int32", &CSoxr::process<int32_t>) @@ -387,7 +419,8 @@ .def("num_clips", &CSoxr::num_clips) .def("delay", &CSoxr::delay) .def("engine", &CSoxr::engine) - .def("clear", &CSoxr::clear); + .def("clear", &CSoxr::clear) + .def("set_io_ratio", &CSoxr::set_io_ratio); m.def("csoxr_divide_proc_float32", csoxr_divide_proc<float>); m.def("csoxr_divide_proc_float64", csoxr_divide_proc<double>); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/soxr-1.0.0/tests/bench.py new/soxr-1.1.0/tests/bench.py --- old/soxr-1.0.0/tests/bench.py 2022-11-09 13:37:21.000000000 +0100 +++ new/soxr-1.1.0/tests/bench.py 2022-11-09 13:37:21.000000000 +0100 @@ -56,14 +56,15 @@ # soxr with clear() # It becomes faster then soxr.resample() when input length (=LEN) is short -rs = soxr.ResampleStream(P, Q, sig.shape[1], dtype=sig.dtype, quality=QUALITY) +if hasattr(soxr.ResampleStream, 'clear'): + rs = soxr.ResampleStream(P, Q, sig.shape[1], dtype=sig.dtype, quality=QUALITY) -def soxr_with_reset(): - rs.clear() - return rs.resample_chunk(sig, last=True) + def soxr_with_reset(): + rs.clear() + return rs.resample_chunk(sig, last=True) -t = timeit.timeit(soxr_with_reset, number=REPEAT) -print(f'soxr w/ clear(): {t:f} (sec)') + t = timeit.timeit(soxr_with_reset, number=REPEAT) + print(f'soxr w/ clear(): {t:f} (sec)') # soxr stream chunk processing diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/soxr-1.0.0/tests/test_random.py new/soxr-1.1.0/tests/test_random.py --- old/soxr-1.0.0/tests/test_random.py 1970-01-01 01:00:00.000000000 +0100 +++ new/soxr-1.1.0/tests/test_random.py 2022-11-09 13:37:21.000000000 +0100 @@ -0,0 +1,214 @@ +""" +Python-SoXR +https://github.com/dofuuz/python-soxr + +SPDX-FileCopyrightText: (c) 2021 Myungchul Keum +SPDX-License-Identifier: LGPL-2.1-or-later + +High quality, one-dimensional sample-rate conversion library for Python. +Python-SoXR is a Python wrapper of libsoxr. +""" + +import random +from concurrent.futures import ThreadPoolExecutor +from functools import partial + +import numpy as np +import pytest +import soxr + + +def get_random_sr_pairs(): + return [ + (random.randint(8000, 96000), random.randint(8000, 96000)), + (random.uniform(8000, 96000), random.uniform(8000, 96000)), + ] + + [email protected]('in_rate, out_rate', [ + (random.randint(0, 192000), 0), + (random.randint(0, 192000), random.randint(-192000, 0)), + (0, random.uniform(0, 192000)), + (random.uniform(-192000, 0), random.randint(0, 192000)), +]) +def test_bad_sr(in_rate, out_rate): + # test invalid samplerate + x = np.zeros(100) + with pytest.raises(ValueError): + soxr.resample(x, in_rate, out_rate) + + [email protected]('in_rate, out_rate', get_random_sr_pairs()) [email protected]('frames', [random.randint(1, 50000)]) [email protected]('dtype', [np.float32, np.float64]) +def test_divide_match(in_rate, out_rate, frames, dtype): + # test resample() with a randomized shape + x = np.random.randn(frames, 2).astype(dtype) + + y_oneshot = soxr._resample_oneshot(x, in_rate, out_rate) + y_divide = soxr.resample(x, in_rate, out_rate) + y_split = soxr.resample(np.asfortranarray(x), in_rate, out_rate) + + assert np.all(y_oneshot == y_divide) + assert np.all(y_oneshot == y_split) + + [email protected]('in_rate, out_rate', get_random_sr_pairs()) [email protected]('length', [0, 1] + [random.randint(2, 150000) for _ in range(6)]) [email protected]('arr_len', [random.randint(150000, 300000)]) +def test_length_match(in_rate, out_rate, length, arr_len): + # test sliced array with various length + x = np.random.randn(arr_len, 2).astype(np.float32) + + y_oneshot = soxr._resample_oneshot(x[:length], in_rate, out_rate) + y_divide = soxr.resample(x[:length], in_rate, out_rate) + y_split = soxr.resample(np.asfortranarray(x)[:length], in_rate, out_rate) + + assert np.all(y_oneshot == y_divide) + assert np.all(y_oneshot == y_split) + + [email protected]('frames', [random.randint(1, 10000)]) [email protected]('channels', [1] + random.sample(range(2, 64), 6)) +def test_channel_match(frames, channels): + # test sliced array with various channel number + x = np.random.randn(frames, 64).astype(np.float32) + + y_oneshot = soxr._resample_oneshot(x[:, :channels], 44100, 32000) + y_divide = soxr.resample(x[:, :channels], 44100, 32000) + y_split = soxr.resample(np.asfortranarray(x)[:, :channels], 44100, 32000) + + assert np.all(y_oneshot == y_divide) + assert np.all(y_oneshot == y_split) + + +def stream_resample(x, in_rate, out_rate, chunk_size, dtype): + channels = x.shape[1] + + rs_stream = soxr.ResampleStream(in_rate, out_rate, channels, dtype=dtype) + + y_list = [np.ndarray([0, channels], dtype=dtype)] + for idx in range(0, len(x), chunk_size): + end = idx + chunk_size + eof = False + if len(x) <= end: + eof = True + end = len(x) + y_chunk = rs_stream.resample_chunk(x[idx:end], last=eof) + y_list.append(y_chunk) + + return np.concatenate(y_list) + + [email protected]('in_rate, out_rate', get_random_sr_pairs()) [email protected]('chunk_size', [random.randint(5, 50000) for _ in range(2)]) [email protected]('length', [0] + [random.randint(2, 150000) for _ in range(4)]) [email protected]('dtype', ['float32', np.float64]) +def test_stream_length(in_rate, out_rate, chunk_size, length, dtype): + # test resample_chunk() with various length and chunk size + x = np.random.randn(length, 1).astype(dtype) # 1ch + + y_oneshot = soxr._resample_oneshot(x, in_rate, out_rate) + y_stream = stream_resample(x, in_rate, out_rate, chunk_size, dtype) + + assert np.all(y_oneshot == y_stream) + + [email protected]('in_rate, out_rate', get_random_sr_pairs()) [email protected]('chunk_size', [random.randint(5, 5000) for _ in range(3)]) [email protected]('length', [1] + [random.randint(2, 30000) for _ in range(4)]) [email protected]('dtype', ['int32', np.int16]) +def test_stream_int(in_rate, out_rate, chunk_size, length, dtype): + # test int resample_chunk() with various length and chunk size + x = (np.random.randn(length, 2) * 5000).astype(dtype) # 2ch + + y_oneshot = soxr._resample_oneshot(x, in_rate, out_rate) + y_stream = stream_resample(x, in_rate, out_rate, chunk_size, dtype) + + assert np.allclose(y_oneshot, y_stream, atol=2) + + +def make_tone(freq, sr, duration): + # make reference tone + length = int(sr * duration) + sig = np.sin(2 * np.pi * freq / sr * np.arange(length)) + sig = sig * np.hanning(length) + + return sig + + [email protected]('in_rate,out_rate', get_random_sr_pairs()) [email protected]('quality', [soxr.VHQ, 'HQ', 'SOXR_MQ', 'lq', 'soxr_qq']) +def test_quality_sine(in_rate, out_rate, quality): + # compare result with reference + FREQ = 32.0 + DURATION = 4.0 + + x = make_tone(FREQ, in_rate, DURATION) + y = make_tone(FREQ, out_rate, DURATION) + + y_pred = soxr.resample(x, in_rate, out_rate, quality=quality) + y_split = soxr.resample(np.asfortranarray(x), in_rate, out_rate, quality=quality) + + min_len = min(len(y), len(y_pred)) + + # some rate combination makes error bigger + assert np.allclose(y[:min_len], y_pred[:min_len], atol=2e-4) + assert np.allclose(y[:min_len], y_split[:min_len], atol=2e-4) + + [email protected]('in_rate,out_rate', get_random_sr_pairs()) [email protected]('dtype', [np.int32, np.int16]) +def test_int_sine(in_rate, out_rate, dtype): + # compare result with reference (int I/O) + FREQ = 32.0 + DURATION = 4.0 + + x = (make_tone(FREQ, in_rate, DURATION) * 16384).astype(dtype) + y = (make_tone(FREQ, out_rate, DURATION) * 16384).astype(dtype) + + y_pred = soxr.resample(x, in_rate, out_rate) + y_split = soxr.resample(np.asfortranarray(x), in_rate, out_rate) + y_oneshot = soxr._resample_oneshot(x, in_rate, out_rate) + + min_len = min(len(y), len(y_pred)) + + # some rate combination makes error bigger + assert np.allclose(y[:min_len], y_pred[:min_len], atol=5) + assert np.allclose(y[:min_len], y_split[:min_len], atol=5) + assert np.allclose(y_oneshot, y_split, atol=2) + + [email protected]('num_task', random.sample(range(2, 40), 6)) +def test_multithread(num_task): + # test multi-thread operation + x = np.random.randn(75999, 2).astype(np.float32) + + with ThreadPoolExecutor() as p: + results = p.map( + partial(soxr.resample, in_rate=44100, out_rate=32000), + [x] * num_task + ) + results = list(results) + + assert np.all(results[-2] == results[-1]) + + [email protected]('num_task', random.sample(range(2, 40), 6)) +def test_mt_dither(num_task): + # test dithering randomness and multi-thread operation + x = (np.random.randn(70001, 2) * 5000).astype(np.int16) + + with ThreadPoolExecutor() as p: + results = p.map( + partial(soxr.resample, in_rate=32000, out_rate=48000), + [x] * num_task + ) + results = list(results) + + assert np.allclose(results[0], results[1], atol=2) + + try: + assert np.all(results[-2] == results[-1]) + except AssertionError: + pytest.xfail("Random dithering seed used. May produce slightly different result when using int I/O.") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/soxr-1.0.0/tests/test_resample.py new/soxr-1.1.0/tests/test_resample.py --- old/soxr-1.0.0/tests/test_resample.py 2022-11-09 13:37:21.000000000 +0100 +++ new/soxr-1.1.0/tests/test_resample.py 2022-11-09 13:37:21.000000000 +0100 @@ -74,7 +74,7 @@ @pytest.mark.parametrize('channels', [1, 2, 3, 5, 7, 24, 49]) def test_channel_match(channels): # test sliced array with various channel number - x = np.random.randn(30011, 49).astype(np.float32) + x = np.random.randn(15013, 49).astype(np.float32) y_oneshot = soxr._resample_oneshot(x[:, :channels], 44100, 32000) y_divide = soxr.resample(x[:, :channels], 44100, 32000) @@ -103,8 +103,8 @@ @pytest.mark.parametrize('in_rate, out_rate', [(44100, 32000), (32000, 44100)]) [email protected]('chunk_size', [7, 509, 44100]) [email protected]('length', [0, 100, 31999, 44100, 266151]) [email protected]('chunk_size', [17, 509, 44100]) [email protected]('length', [0, 100, 31999, 44100, 166151]) @pytest.mark.parametrize('dtype', ['float32', np.float64]) def test_stream_length(in_rate, out_rate, chunk_size, length, dtype): # test resample_chunk() with various length and chunk size diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/soxr-1.0.0/tests/vr.py new/soxr-1.1.0/tests/vr.py --- old/soxr-1.0.0/tests/vr.py 1970-01-01 01:00:00.000000000 +0100 +++ new/soxr-1.1.0/tests/vr.py 2022-11-09 13:37:21.000000000 +0100 @@ -0,0 +1,119 @@ +import soxr +import numpy as np +import matplotlib.pyplot as plt + + +def make_tone(freq, sr, duration): + # make reference tone + length = int(sr * duration) + sig = np.sin(2 * np.pi * freq / sr * np.arange(length)) + sig = sig * np.hanning(length) + + return np.stack([sig, np.zeros_like(sig)], axis=-1) + +''' +rs = soxr.ResampleStream(1, 20, 1, vr=True) +print(rs.delay()) + +rs.set_io_ratio(10, 1) + +resampled = rs.resample_chunk(np.zeros(1000).astype(np.float32)) +print(resampled.shape) +print(rs.delay()) +cat = resampled.copy() + +resampled = rs.resample_chunk(np.ones(1000).astype(np.float32)) +print(resampled.shape) +print(rs.delay()) +cat = np.concatenate([cat, resampled]) + +rs.set_io_ratio(1, 1) +# rs.clear() + +resampled = rs.resample_chunk(np.zeros(1000).astype(np.float32)) +print(resampled.shape) +print(rs.delay()) +cat = np.concatenate([cat, resampled]) + +resampled = rs.resample_chunk(np.ones(1000).astype(np.float32)) +print(resampled.shape) +print(rs.delay()) +cat = np.concatenate([cat, resampled]) + +rs.set_io_ratio(10, 1) + +resampled = rs.resample_chunk(np.zeros(1000).astype(np.float32)) +print(resampled.shape) +print(rs.delay()) +cat = np.concatenate([cat, resampled]) + +resampled = rs.resample_chunk(np.ones(1000).astype(np.float32), last=True) +print(resampled.shape) +print(rs.delay()) +cat = np.concatenate([cat, resampled]) + + +print(cat.shape) +plt.plot(cat) +plt.show() +''' + + +rs = soxr.ResampleStream(10, 1, 2, vr=True) +print(rs.delay()) + +rs.set_io_ratio(5, 1) + +resampled = rs.resample_chunk(np.zeros((0, 2)).astype(np.float32)) +resampled = rs.resample_chunk(np.zeros((10, 2)).astype(np.float32)) +resampled = rs.resample_chunk(np.zeros((0, 2)).astype(np.float32)) + +resampled = rs.resample_chunk(make_tone(30, 5000, 0.5).astype(np.float32)) +print(resampled.shape) +print(rs.delay()) +cat = resampled.copy() + +resampled = rs.resample_chunk(make_tone(30, 5000, 0.5).astype(np.float32)) +print(resampled.shape) +print(rs.delay()) +cat = np.concatenate([cat, resampled]) + +rs.set_io_ratio(10, 1) +# rs.clear() + +resampled = rs.resample_chunk(make_tone(30, 5000, 0.5).astype(np.float32)) +print(resampled.shape) +print(rs.delay()) +cat = np.concatenate([cat, resampled]) + +resampled = rs.resample_chunk(make_tone(30, 5000, 0.5).astype(np.float32)) +print(resampled.shape) +print(rs.delay()) +cat = np.concatenate([cat, resampled]) + +rs.set_io_ratio(5, 1) + +resampled = rs.resample_chunk(make_tone(30, 5000, 0.5).astype(np.float32)) +print(resampled.shape) +print(rs.delay()) +cat = np.concatenate([cat, resampled]) + +resampled = rs.resample_chunk(make_tone(30, 5000, 0.5).astype(np.float32)) +print(resampled.shape) +print(rs.delay()) +cat = np.concatenate([cat, resampled]) + +resampled = rs.resample_chunk(make_tone(30, 5000, 0.5).astype(np.float32)) +print(resampled.shape) +print(rs.delay()) +cat = np.concatenate([cat, resampled]) + +resampled = rs.resample_chunk(make_tone(30, 5000, 0.5).astype(np.float32), last=True) +print(resampled.shape) +print(rs.delay()) +cat = np.concatenate([cat, resampled]) + + +print(f'{cat.shape = }') +plt.plot(cat) +plt.show()
