|
31 | 31 | #include <samplerate.h> |
32 | 32 |
|
33 | 33 | #include <cmath> |
| 34 | +#include <cstring> |
34 | 35 | #include <iostream> |
| 36 | +#include <sstream> |
35 | 37 | #include <string> |
36 | 38 | #include <typeinfo> |
37 | 39 | #include <vector> |
@@ -104,6 +106,114 @@ void error_handler(int errnum) { |
104 | 106 | } |
105 | 107 | } |
106 | 108 |
|
| 109 | +nb::ndarray<nb::numpy, float> resample( |
| 110 | + const nb::ndarray<nb::numpy, const float, nb::c_contig> &input, |
| 111 | + double sr_ratio, const nb::object &converter_type, bool verbose) { |
| 112 | + // input array has shape (n_samples, n_channels) |
| 113 | + int converter_type_int = get_converter_type(converter_type); |
| 114 | + |
| 115 | + // Get array dimensions |
| 116 | + size_t ndim = input.ndim(); |
| 117 | + size_t num_frames = input.shape(0); |
| 118 | + |
| 119 | + // set the number of channels |
| 120 | + int channels = 1; |
| 121 | + if (ndim == 2) { |
| 122 | + channels = input.shape(1); |
| 123 | + } else if (ndim > 2) { |
| 124 | + throw std::domain_error("Input array should have at most 2 dimensions"); |
| 125 | + } |
| 126 | + |
| 127 | + if (channels == 0) { |
| 128 | + throw std::domain_error("Invalid number of channels (0) in input data."); |
| 129 | + } |
| 130 | + |
| 131 | + // Add buffer space to match Resampler.process() behavior with end_of_input=True |
| 132 | + // src_simple internally behaves like end_of_input=True, so it may generate |
| 133 | + // extra samples from buffer flushing, especially for certain converters |
| 134 | + const auto new_size = |
| 135 | + static_cast<size_t>(std::ceil(num_frames * sr_ratio)) |
| 136 | + + END_OF_INPUT_EXTRA_OUTPUT_FRAMES; |
| 137 | + |
| 138 | + // Allocate output array |
| 139 | + size_t total_elements = new_size * channels; |
| 140 | + float* output_data = new float[total_elements]; |
| 141 | + |
| 142 | + // Create capsule for memory management |
| 143 | + nb::capsule owner(output_data, [](void* p) noexcept { |
| 144 | + delete[] static_cast<float*>(p); |
| 145 | + }); |
| 146 | + |
| 147 | + // libsamplerate struct |
| 148 | + SRC_DATA src_data = { |
| 149 | + const_cast<float *>(input.data()), // data_in |
| 150 | + output_data, // data_out |
| 151 | + static_cast<long>(num_frames), // input_frames |
| 152 | + long(new_size), // output_frames |
| 153 | + 0, // input_frames_used, filled by libsamplerate |
| 154 | + 0, // output_frames_gen, filled by libsamplerate |
| 155 | + 0, // end_of_input, not used by src_simple ? |
| 156 | + sr_ratio // src_ratio, sampling rate conversion ratio |
| 157 | + }; |
| 158 | + |
| 159 | + // Release GIL for the entire resampling operation |
| 160 | + int err_code; |
| 161 | + long output_frames_gen; |
| 162 | + long input_frames_used; |
| 163 | + { |
| 164 | + nb::gil_scoped_release release; |
| 165 | + err_code = src_simple(&src_data, converter_type_int, channels); |
| 166 | + output_frames_gen = src_data.output_frames_gen; |
| 167 | + input_frames_used = src_data.input_frames_used; |
| 168 | + } |
| 169 | + error_handler(err_code); |
| 170 | + |
| 171 | + // Handle unexpected output size |
| 172 | + if ((size_t)output_frames_gen > new_size) { |
| 173 | + // This means our fudge factor is too small. |
| 174 | + throw std::runtime_error("Generated more output samples than expected!"); |
| 175 | + } |
| 176 | + |
| 177 | + if (verbose) { |
| 178 | + nb::print("samplerate info:"); |
| 179 | + std::ostringstream oss1, oss2; |
| 180 | + oss1 << input_frames_used << " input frames used"; |
| 181 | + oss2 << output_frames_gen << " output frames generated"; |
| 182 | + nb::print(oss1.str().c_str()); |
| 183 | + nb::print(oss2.str().c_str()); |
| 184 | + } |
| 185 | + |
| 186 | + // Create output ndarray with proper shape and stride |
| 187 | + size_t output_shape[2]; |
| 188 | + int64_t output_stride[2]; |
| 189 | + |
| 190 | + if (ndim == 2) { |
| 191 | + output_shape[0] = output_frames_gen; |
| 192 | + output_shape[1] = channels; |
| 193 | + output_stride[0] = channels * sizeof(float); |
| 194 | + output_stride[1] = sizeof(float); |
| 195 | + |
| 196 | + return nb::ndarray<nb::numpy, float>( |
| 197 | + output_data, |
| 198 | + 2, |
| 199 | + output_shape, |
| 200 | + owner, |
| 201 | + output_stride |
| 202 | + ); |
| 203 | + } else { |
| 204 | + output_shape[0] = output_frames_gen; |
| 205 | + output_stride[0] = sizeof(float); |
| 206 | + |
| 207 | + return nb::ndarray<nb::numpy, float>( |
| 208 | + output_data, |
| 209 | + 1, |
| 210 | + output_shape, |
| 211 | + owner, |
| 212 | + output_stride |
| 213 | + ); |
| 214 | + } |
| 215 | +} |
| 216 | + |
107 | 217 | } // namespace samplerate |
108 | 218 |
|
109 | 219 | namespace sr = samplerate; |
@@ -153,7 +263,41 @@ NB_MODULE(samplerate, m) { |
153 | 263 | .value("linear", sr::ConverterType::linear) |
154 | 264 | .export_values(); |
155 | 265 |
|
| 266 | + m_converters.def("resample", &sr::resample, R"mydelimiter( |
| 267 | + Resample the signal in `input_data` at once. |
| 268 | +
|
| 269 | + Parameters |
| 270 | + ---------- |
| 271 | + input_data : ndarray |
| 272 | + Input data. |
| 273 | + Input data with one or more channels is represented as a 2D array of shape |
| 274 | + (`num_frames`, `num_channels`). |
| 275 | + A single channel can be provided as a 1D array of `num_frames` length. |
| 276 | + For use with `libsamplerate`, `input_data` |
| 277 | + is converted to 32-bit float and C (row-major) memory order. |
| 278 | + ratio : float |
| 279 | + Conversion ratio = output sample rate / input sample rate. |
| 280 | + converter_type : ConverterType, str, or int |
| 281 | + Sample rate converter (default: `sinc_best`). |
| 282 | + verbose : bool |
| 283 | + If `True`, print additional information about the conversion. |
| 284 | +
|
| 285 | + Returns |
| 286 | + ------- |
| 287 | + output_data : ndarray |
| 288 | + Resampled input data. |
| 289 | +
|
| 290 | + Note |
| 291 | + ---- |
| 292 | + If samples are to be processed in chunks, `Resampler` and |
| 293 | + `CallbackResampler` will provide better results and allow for variable |
| 294 | + conversion ratios. |
| 295 | + )mydelimiter", |
| 296 | + "input"_a, "ratio"_a, "converter_type"_a = "sinc_best", |
| 297 | + "verbose"_a = false); |
| 298 | + |
156 | 299 | // Convenience imports |
157 | 300 | m.attr("ResamplingError") = m_exceptions.attr("ResamplingError"); |
| 301 | + m.attr("resample") = m_converters.attr("resample"); |
158 | 302 | m.attr("ConverterType") = m_converters.attr("ConverterType"); |
159 | 303 | } |
0 commit comments