Skip to content

Commit 7ef5915

Browse files
committed
Add Variable-rate resampling (experimental)
1 parent b87b75c commit 7ef5915

File tree

2 files changed

+98
-28
lines changed

2 files changed

+98
-28
lines changed

src/soxr/__init__.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,14 @@ class ResampleStream:
7777
quality : int or str, optional
7878
Quality setting.
7979
One of `QQ`, `LQ`, `MQ`, `HQ`, `VHQ`.
80+
vr : bool, optional
81+
(Experimental) Enable variable-rate resampling.
82+
The ratio of the given in_rate and out_rate must equate to the maximum I/O ratio that will be used.
8083
"""
8184

8285
def __init__(self,
8386
in_rate: float, out_rate: float, num_channels: int,
84-
dtype='float32', quality='HQ'):
87+
dtype='float32', quality='HQ', vr=False):
8588
if in_rate <= 0 or out_rate <= 0:
8689
raise ValueError('Sample rate should be over 0')
8790

@@ -93,7 +96,7 @@ def __init__(self,
9396

9497
q = _quality_to_enum(quality)
9598

96-
self._csoxr = soxr_ext.CSoxr(in_rate, out_rate, num_channels, stype, q)
99+
self._csoxr = soxr_ext.CSoxr(in_rate, out_rate, num_channels, stype, q, vr)
97100
self._process = getattr(self._csoxr, f'process_{self._type}')
98101

99102
def resample_chunk(self, x: np.ndarray, last=False) -> np.ndarray:
@@ -156,6 +159,25 @@ def clear(self) -> None:
156159
"""
157160
self._csoxr.clear()
158161

162+
def set_io_ratio(self, in_rate: float, out_rate: float, slew_len: int = 0) -> None:
163+
""" (Experimental) Set new sample-rate ratio for next processing.
164+
165+
`vr=True` must be set at constructor to use this function.
166+
WARNING: It's an experimental feature and this API may change in future release.
167+
168+
Parameters
169+
----------
170+
in_rate : float
171+
New input sample-rate.
172+
out_rate : float
173+
New output sample-rate.
174+
slew_len : int, optional
175+
Length of smooth transition in input samples. (default: 0)
176+
If slew_len > 0, the transition will be done smoothly over the given length.
177+
If slew_len == 0, the transition will be done immediately.
178+
"""
179+
self._csoxr.set_io_ratio(in_rate / out_rate, slew_len)
180+
159181

160182
def resample(x: ArrayLike, in_rate: float, out_rate: float, quality='HQ') -> np.ndarray:
161183
""" Resample signal

src/soxr_ext.cpp

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ static soxr_datatype_t to_soxr_split_dtype(const type_info& ntype) {
6060

6161
class CSoxr {
6262
soxr_t _soxr = nullptr;
63-
const double _oi_rate;
63+
double _oi_ratio;
6464
std::unique_ptr<uint8_t[]> _y_buf;
65-
size_t _y_buf_size = 0;
65+
size_t _y_buf_bytes = 0;
66+
size_t _olen = 0;
6667

6768
public:
6869
const double _in_rate;
@@ -73,16 +74,16 @@ class CSoxr {
7374
bool _ended = false;
7475

7576
CSoxr(double in_rate, double out_rate, unsigned num_channels,
76-
soxr_datatype_t ntype, unsigned long quality) :
77+
soxr_datatype_t ntype, unsigned long quality, bool vr) :
7778
_in_rate(in_rate),
7879
_out_rate(out_rate),
79-
_oi_rate(out_rate / in_rate),
80+
_oi_ratio(out_rate / in_rate),
8081
_ntype(ntype),
8182
_channels(num_channels),
8283
_div_len(std::max(1000., 48000 * _in_rate / _out_rate)) {
8384
soxr_error_t err = NULL;
8485
soxr_io_spec_t io_spec = soxr_io_spec(ntype, ntype);
85-
soxr_quality_spec_t quality_spec = soxr_quality_spec(quality, 0);
86+
soxr_quality_spec_t quality_spec = soxr_quality_spec(quality, vr ? SOXR_VR : 0);
8687

8788
_soxr = soxr_create(
8889
in_rate, out_rate, num_channels,
@@ -97,6 +98,51 @@ class CSoxr {
9798
soxr_delete(_soxr);
9899
}
99100

101+
template <typename T>
102+
T* _resize_ybuf(size_t req_size, bool copy) {
103+
if (_y_buf && req_size < _y_buf_bytes)
104+
return reinterpret_cast<T*>(_y_buf.get());
105+
106+
// Grow to next power of 2
107+
size_t new_size = 1024;
108+
while (new_size < req_size) new_size <<= 1;
109+
// size_t new_size = req_size;
110+
111+
auto new_buf = std::make_unique<uint8_t[]>(new_size);
112+
if (copy && _y_buf) {
113+
std::copy_n(_y_buf.get(), _y_buf_bytes, new_buf.get());
114+
}
115+
_y_buf = std::move(new_buf);
116+
_y_buf_bytes = new_size;
117+
_olen = _y_buf_bytes / (sizeof(T) * _channels);
118+
119+
// printf("Realloc output buffer: %zu bytes\n", new_size);
120+
// fflush(stdout);
121+
return reinterpret_cast<T*>(_y_buf.get());
122+
}
123+
124+
template <typename T>
125+
T* _flush(soxr_in_t input, size_t& out_pos) {
126+
// flush until no more output
127+
T* y = reinterpret_cast<T*>(_y_buf.get());
128+
size_t odone = 0;
129+
// int cnt = 0;
130+
do {
131+
if (_olen <= out_pos) {
132+
// printf("_flush Realloc cnt = %d\n", ++cnt);
133+
y = _resize_ybuf<T>(_y_buf_bytes * 2, true);
134+
}
135+
soxr_error_t err = soxr_process(
136+
_soxr,
137+
input, 0, NULL,
138+
&y[out_pos*_channels], _olen-out_pos, &odone);
139+
out_pos += odone;
140+
141+
if (err != NULL) throw std::runtime_error(err);
142+
} while (0 < odone);
143+
return y;
144+
}
145+
100146
template <typename T>
101147
auto process(
102148
ndarray<const T, nb::ndim<2>, nb::c_contig, nb::device::cpu> x,
@@ -123,40 +169,35 @@ class CSoxr {
123169

124170
const size_t ilen = x.shape(0);
125171

126-
// This is slower than returning fixed `ilen * _oi_rate` buffers w/o copying.
172+
// This is slower than returning fixed `ilen * _oi_ratio` buffers w/o copying.
127173
// But it ensures the lowest output delay provided by libsoxr.
128-
const size_t olen = soxr_delay(_soxr) + ilen * _oi_rate + 1;
129-
130-
// Reuse output buffer if possible, else reallocate
131-
size_t req_size = sizeof(T) * olen * channels;
132-
if (!_y_buf || _y_buf_size < req_size) {
133-
// Grow to next power of 2
134-
size_t new_size = 1;
135-
while (new_size < req_size) new_size <<= 1;
136-
_y_buf = std::make_unique<uint8_t[]>(new_size);
137-
_y_buf_size = new_size;
138-
}
139-
y = reinterpret_cast<T*>(_y_buf.get());
174+
const size_t req_len = soxr_delay(_soxr) + ilen * _oi_ratio + 1;
175+
y = _resize_ybuf<T>(sizeof(T) * req_len * channels, false);
140176

141177
// divide long input and process
142178
size_t odone = 0;
143179
for (size_t idx = 0; idx < ilen; idx += _div_len) {
144180
err = soxr_process(
145181
_soxr,
146182
&x.data()[idx*channels], std::min(_div_len, ilen-idx), NULL,
147-
&y[out_pos*channels], olen-out_pos, &odone);
183+
&y[out_pos*channels], _olen-out_pos, &odone);
148184
out_pos += odone;
185+
186+
if (_olen <= out_pos) {
187+
// for VR mode, output buffer may be full
188+
y = _flush<T>(&x.data()[idx*channels], out_pos);
189+
}
149190
}
150191

151192
// flush if last input
152193
if (last) {
153194
_ended = true;
154-
err = soxr_process(
155-
_soxr,
156-
NULL, 0, NULL,
157-
&y[out_pos*channels], olen-out_pos, &odone);
158-
out_pos += odone;
195+
y = _flush<T>(NULL, out_pos);
159196
}
197+
198+
// if (_olen <= out_pos) {
199+
// printf("Warning: output buffer overflow %zu <= %zu\n", _olen, out_pos);
200+
// }
160201
}
161202

162203
if (err) {
@@ -176,6 +217,12 @@ class CSoxr {
176217
if (err != NULL) throw std::runtime_error(err);
177218
_ended = false;
178219
}
220+
221+
void set_io_ratio(double io_ratio, size_t slew_len=0) {
222+
soxr_error_t err = soxr_set_io_ratio(_soxr, io_ratio, slew_len);
223+
if (err != NULL) throw std::runtime_error(err);
224+
_oi_ratio = std::max(_oi_ratio, 1 / io_ratio);
225+
}
179226
};
180227

181228

@@ -385,15 +432,16 @@ NB_MODULE(soxr_ext, m) {
385432
.def_ro("ntype", &CSoxr::_ntype)
386433
.def_ro("channels", &CSoxr::_channels)
387434
.def_ro("ended", &CSoxr::_ended)
388-
.def(nb::init<double, double, unsigned, soxr_datatype_t, unsigned long>())
435+
.def(nb::init<double, double, unsigned, soxr_datatype_t, unsigned long, bool>())
389436
.def("process_float32", &CSoxr::process<float>)
390437
.def("process_float64", &CSoxr::process<double>)
391438
.def("process_int32", &CSoxr::process<int32_t>)
392439
.def("process_int16", &CSoxr::process<int16_t>)
393440
.def("num_clips", &CSoxr::num_clips)
394441
.def("delay", &CSoxr::delay)
395442
.def("engine", &CSoxr::engine)
396-
.def("clear", &CSoxr::clear);
443+
.def("clear", &CSoxr::clear)
444+
.def("set_io_ratio", &CSoxr::set_io_ratio);
397445

398446
m.def("csoxr_divide_proc_float32", csoxr_divide_proc<float>);
399447
m.def("csoxr_divide_proc_float64", csoxr_divide_proc<double>);

0 commit comments

Comments
 (0)