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()

Reply via email to