Skip to content

Commit

Permalink
Add more error handling, Refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
dofuuz committed May 26, 2024
1 parent 2ecc37e commit 55872a8
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 63 deletions.
78 changes: 26 additions & 52 deletions src/soxr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# Python-SoXR
# High quality, one-dimensional sample-rate conversion library for Python.
# Python-SoXR is a Python wrapper of libsoxr.
# https://github.com/dofuuz/python-soxr
"""
Python-SoXR
https://github.com/dofuuz/python-soxr
import warnings
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 numpy as np

Expand All @@ -18,7 +22,7 @@
# Too much channels will cause memory error.
_CH_LIMIT = 65536

_DTYPE_UNMATCH_ERR_STR = 'Converting input ****to {}. Change ResampleStream/input dtype to match.'
_DTYPE_UNMATCH_ERR_STR = 'Input should be a `np.ndarray` with matching dtype for ResampleStream({}).'
_CH_EXEED_ERR_STR = 'Channel num({}) out of limit. Should be in [1, %d]' % _CH_LIMIT
_DTYPE_ERR_STR = 'Data type must be one of [float32, float64, int16, int32], not {}'
_QUALITY_ERR_STR = "Quality must be one of [QQ, LQ, MQ, HQ, VHQ]"
Expand Down Expand Up @@ -46,8 +50,7 @@ def _quality_to_enum(q):
raise ValueError(_QUALITY_ERR_STR)


# NumPy scalar type to soxr_io_spec_t
def to_soxr_datatype(ntype):
def _to_soxr_datatype(ntype):
if ntype == np.float32:
return soxr_ext.SOXR_FLOAT32_I
elif ntype == np.float64:
Expand All @@ -57,7 +60,7 @@ def to_soxr_datatype(ntype):
elif ntype == np.int16:
return soxr_ext.SOXR_INT16_I
else:
ValueError(_DTYPE_ERR_STR.format(ntype))
TypeError(_DTYPE_ERR_STR.format(ntype))


class ResampleStream:
Expand Down Expand Up @@ -91,11 +94,12 @@ def __init__(self,
raise ValueError(_CH_EXEED_ERR_STR.format(num_channels))

self._type = np.dtype(dtype)
stype = to_soxr_datatype(self._type)
stype = _to_soxr_datatype(self._type)

q = _quality_to_enum(quality)

self._cysoxr = soxr_ext.CySoxr(in_rate, out_rate, num_channels, stype, q)
self._process = getattr(self._cysoxr, f'process_{self._type}')

def resample_chunk(self, x: np.ndarray, last=False):
""" Resample chunk with streaming resampler
Expand All @@ -104,8 +108,7 @@ def resample_chunk(self, x: np.ndarray, last=False):
----------
x : np.ndarray
Input array. Input can be 1D(mono) or 2D(frames, channels).
If input is not `np.ndarray` or not dtype in constructor,
it will be converted to `np.ndarray` with dtype setting.
dtype should match with constructor.
last : bool, optional
Set True at end of input sequence.
Expand All @@ -118,27 +121,15 @@ def resample_chunk(self, x: np.ndarray, last=False):
"""
if type(x) != np.ndarray or x.dtype != self._type:
raise ValueError(_DTYPE_UNMATCH_ERR_STR.format(self._type))

if x.dtype.type == np.int16:
proc = self._cysoxr.process_int16

if x.dtype.type == np.int32:
proc = self._cysoxr.process_int32

if x.dtype.type == np.float32:
proc = self._cysoxr.process_float32

if x.dtype.type == np.float64:
proc = self._cysoxr.process_float64
raise TypeError(_DTYPE_UNMATCH_ERR_STR.format(self._type))

x = np.ascontiguousarray(x) # make array C-contiguous

if x.ndim == 1:
y = proc(x[:, np.newaxis], last)
y = self._process(x[:, np.newaxis], last)
return np.squeeze(y, axis=1)
elif x.ndim == 2:
return proc(x, last)
return self._process(x, last)
else:
raise ValueError('Input must be 1-D or 2-D array')

Expand Down Expand Up @@ -172,20 +163,10 @@ def resample(x, in_rate: float, out_rate: float, quality='HQ'):
if type(x) != np.ndarray:
x = np.asarray(x, dtype=np.float32)

if not x.dtype.type in (np.float32, np.float64, np.int16, np.int32):
raise ValueError(_DTYPE_ERR_STR.format(x.dtype.type))

if x.dtype.type == np.int16:
divide_proc = soxr_ext.cysoxr_divide_proc_int16

if x.dtype.type == np.int32:
divide_proc = soxr_ext.cysoxr_divide_proc_int32

if x.dtype.type == np.float32:
divide_proc = soxr_ext.cysoxr_divide_proc_float32

if x.dtype.type == np.float64:
divide_proc = soxr_ext.cysoxr_divide_proc_float64
try:
divide_proc = getattr(soxr_ext, f'cysoxr_divide_proc_{x.dtype}')
except AttributeError:
raise TypeError(_DTYPE_ERR_STR.format(x.dtype))

q = _quality_to_enum(quality)

Expand All @@ -210,17 +191,10 @@ def _resample_oneshot(x, in_rate: float, out_rate: float, quality='HQ'):
`soxr_oneshot()` becomes slow with long input.
This function exists for test purpose.
"""
if x.dtype.type == np.int16:
oneshot = soxr_ext.cysoxr_oneshot_int16

if x.dtype.type == np.int32:
oneshot = soxr_ext.cysoxr_oneshot_int32

if x.dtype.type == np.float32:
oneshot = soxr_ext.cysoxr_oneshot_float32

if x.dtype.type == np.float64:
oneshot = soxr_ext.cysoxr_oneshot_float64
try:
oneshot = getattr(soxr_ext, f'cysoxr_oneshot_{x.dtype}')
except AttributeError:
raise TypeError(_DTYPE_ERR_STR.format(x.dtype))

if x.ndim == 1:
y = oneshot(in_rate, out_rate, x[:, np.newaxis], _quality_to_enum(quality))
Expand Down
45 changes: 34 additions & 11 deletions src/soxr_ext.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
/*
Python-SoXR
https://github.com/dofuuz/python-soxr
SPDX-FileCopyrightText: (c) 2024 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.
*/

#include <stdint.h>
#include <algorithm>
#include <cmath>
Expand All @@ -13,13 +24,13 @@ using std::type_info;

namespace nb = nanobind;
using namespace nb::literals;
using nb::ndarray;


// C type to soxr_io_spec_t
static soxr_datatype_t to_soxr_datatype(const type_info& ntype) {
if (ntype == typeid(float))
return SOXR_FLOAT32_I;
else if (ntype ==typeid(double))
else if (ntype == typeid(double))
return SOXR_FLOAT64_I;
else if (ntype == typeid(int32_t))
return SOXR_INT32_I;
Expand All @@ -29,6 +40,7 @@ static soxr_datatype_t to_soxr_datatype(const type_info& ntype) {
throw nb::type_error("Data type not support");
}


class CySoxr {
soxr_t _soxr;
double _in_rate;
Expand All @@ -38,7 +50,8 @@ class CySoxr {
bool _ended;

public:
CySoxr(double in_rate, double out_rate, unsigned num_channels, soxr_datatype_t ntype, unsigned long quality) {
CySoxr(double in_rate, double out_rate, unsigned num_channels,
soxr_datatype_t ntype, unsigned long quality) {
_in_rate = in_rate;
_out_rate = out_rate;
_ntype = ntype;
Expand All @@ -63,7 +76,9 @@ class CySoxr {
}

template <typename T>
auto process(nb::ndarray<const T, nb::ndim<2>, nb::c_contig, nb::device::cpu> x, bool last=false) {
auto process(
ndarray<const T, nb::ndim<2>, nb::c_contig, nb::device::cpu> x,
bool last=false) {
size_t ilen = x.shape(0);
size_t olen = 2 * ilen * _out_rate / _in_rate + 1;
unsigned channels = x.shape(1);
Expand All @@ -88,6 +103,9 @@ class CySoxr {
x.data(), ilen, NULL,
y, olen, &odone);

if (err != NULL)
throw std::runtime_error(err);

// flush if last input
if (last) {
_ended = true;
Expand All @@ -104,24 +122,29 @@ class CySoxr {
NULL, 0, NULL,
&last_buf[odone*channels], delay, &ldone);

if (err != NULL)
throw std::runtime_error(err);

nb::capsule last_owner(last_buf, [](void *p) noexcept {
delete[] (T *) p;
});
return nb::ndarray<nb::numpy, T>(last_buf, { odone+ldone, channels }, last_owner);
return ndarray<nb::numpy, T>(
last_buf, { odone+ldone, channels }, last_owner);
}
}
// Delete 'y' when the 'owner' capsule expires
nb::capsule owner(y, [](void *p) noexcept {
delete[] (T *) p;
});
return nb::ndarray<nb::numpy, T>(y, { odone, channels }, owner);
return ndarray<nb::numpy, T>(y, { odone, channels }, owner);
}
};


template <typename T>
auto cysoxr_divide_proc(double in_rate, double out_rate,
nb::ndarray<T, nb::ndim<2>, nb::c_contig, nb::device::cpu> x,
auto cysoxr_divide_proc(
double in_rate, double out_rate,
ndarray<const T, nb::ndim<2>, nb::c_contig, nb::device::cpu> x,
unsigned long quality) {
size_t ilen = x.shape(0);
size_t olen = ilen * out_rate / in_rate + 1;
Expand Down Expand Up @@ -187,14 +210,14 @@ auto cysoxr_divide_proc(double in_rate, double out_rate,
nb::capsule owner(y, [](void *p) noexcept {
delete[] (T *) p;
});
return nb::ndarray<nb::numpy, T>(y, { out_pos, channels }, owner);
return ndarray<nb::numpy, T>(y, { out_pos, channels }, owner);
}


template <typename T>
auto cysoxr_oneshot(
double in_rate, double out_rate,
nb::ndarray<T, nb::ndim<2>, nb::c_contig, nb::device::cpu> x,
ndarray<const T, nb::ndim<2>, nb::c_contig, nb::device::cpu> x,
unsigned long quality) {
const size_t ilen = x.shape(0);
const size_t olen = ilen * out_rate / in_rate + 1;
Expand Down Expand Up @@ -224,7 +247,7 @@ auto cysoxr_oneshot(
nb::capsule owner(y, [](void *p) noexcept {
delete[] (T *) p;
});
return nb::ndarray<nb::numpy, T>(y, { odone, channels }, owner);
return ndarray<nb::numpy, T>(y, { odone, channels }, owner);
}


Expand Down

0 comments on commit 55872a8

Please sign in to comment.