@pipedog/resampler-wasm 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -0
- package/NOTICE +9 -0
- package/README.md +183 -2
- package/THIRD_PARTY_LICENSES.md +640 -0
- package/dist/engine.d.ts +140 -0
- package/dist/engine.js +278 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +111 -0
- package/dist/state.d.ts +9 -0
- package/dist/state.js +24 -0
- package/dist/wasm_bytes.d.ts +5 -0
- package/dist/wasm_bytes.js +10 -0
- package/dist/workerd.d.ts +8 -0
- package/dist/workerd.js +27 -0
- package/package.json +47 -3
- package/rust/Cargo.lock +137 -0
- package/rust/Cargo.toml +27 -0
- package/rust/src/lib.rs +533 -0
- package/wasm/resampler.wasm +0 -0
package/rust/src/lib.rs
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
// Copyright (c) 2026, pipedog contributors
|
|
2
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
3
|
+
//
|
|
4
|
+
// A rubato sinc resampler behind a tiny C ABI, for wasm32-unknown-unknown.
|
|
5
|
+
//
|
|
6
|
+
// No wasm-bindgen: the JS side talks to these `extern "C"` functions through
|
|
7
|
+
// `WebAssembly.Instance.exports` and reads/writes PCM directly in linear memory.
|
|
8
|
+
//
|
|
9
|
+
// Model: every resampler is a *stream* (one-shot resampling is "process the
|
|
10
|
+
// whole clip, then flush"). A stream owns its staging buffers:
|
|
11
|
+
//
|
|
12
|
+
// ptr = rs_stream_input(h, n) // room for n int16 samples (interleaved)
|
|
13
|
+
// ... JS copies n samples to ptr ...
|
|
14
|
+
// m = rs_stream_process(h, n) // m int16 samples are now at rs_stream_output(h)
|
|
15
|
+
// m = rs_stream_flush(h) // drain the filter tail, then reset
|
|
16
|
+
//
|
|
17
|
+
// The `_f32` variants do the same with float samples in [-1, 1] (unclipped).
|
|
18
|
+
// Internally everything runs in f64, so the arithmetic noise floor stays
|
|
19
|
+
// below the VHQ preset's stopband (f32 accumulation sits near -125 dB).
|
|
20
|
+
//
|
|
21
|
+
// Streams accept any number of samples per call. rubato's `Async` resampler
|
|
22
|
+
// processes fixed-size input chunks, so a call is split into pieces of at most
|
|
23
|
+
// MAX_CHUNK frames and the chunk size is set to each piece's length
|
|
24
|
+
// (`Resizable::set_chunk_size` keeps the filter history), which means no
|
|
25
|
+
// input is held back beyond the filter's own delay. A sample count that is
|
|
26
|
+
// not a multiple of the channel count leaves the remainder buffered for the
|
|
27
|
+
// next call.
|
|
28
|
+
//
|
|
29
|
+
// Output is aligned with the input: the filter's start-up delay is trimmed
|
|
30
|
+
// from the front of the stream, and `flush` emits exactly
|
|
31
|
+
// round(total_input_frames * out_rate / in_rate) frames in total, like
|
|
32
|
+
// libsoxr's `ResampleStream` with `last=True`.
|
|
33
|
+
|
|
34
|
+
use audioadapter_buffers::direct::InterleavedSlice;
|
|
35
|
+
use rubato::{
|
|
36
|
+
Async, FixedAsync, Indexing, Resampler, Resizable, SincInterpolationParameters,
|
|
37
|
+
SincInterpolationType, WindowFunction,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/// Largest input chunk handed to rubato in one go, in frames.
|
|
41
|
+
const MAX_CHUNK: usize = 1024;
|
|
42
|
+
|
|
43
|
+
/// Quality presets, in the same order as libsoxr's (QQ=0 .. VHQ=4).
|
|
44
|
+
///
|
|
45
|
+
/// Designed after libsoxr's quality table: the stopband starts at the
|
|
46
|
+
/// Nyquist frequency of the *lower* of the two rates, whichever way the
|
|
47
|
+
/// conversion goes, and
|
|
48
|
+
///
|
|
49
|
+
/// | preset | libsoxr: passband end, rejection | here: flat (0.01 dB) to, stopband |
|
|
50
|
+
/// |--------|----------------------------------|-----------------------------|
|
|
51
|
+
/// | QQ | cubic interpolation, no filter | 0.665, >= 75 dB |
|
|
52
|
+
/// | LQ | 0.676, 96 dB | 0.74, >= 112 dB |
|
|
53
|
+
/// | MQ | 0.915, 96 dB | 0.91, >= 111 dB |
|
|
54
|
+
/// | HQ | 0.913, 120 dB | 0.905, >= 138 dB |
|
|
55
|
+
/// | VHQ | 0.911, 169 dB | 0.91, >= 157 dB |
|
|
56
|
+
///
|
|
57
|
+
/// Lengths and the cutoff are given at the lower rate. rubato measures
|
|
58
|
+
/// `sinc_len` in input samples and only scales the cutoff when downsampling,
|
|
59
|
+
/// so `sinc_params` stretches the filter by the downsampling factor to keep
|
|
60
|
+
/// the same response at every ratio.
|
|
61
|
+
#[derive(Clone, Copy)]
|
|
62
|
+
pub struct Preset {
|
|
63
|
+
/// Filter length in samples of the lower rate.
|
|
64
|
+
pub len: usize,
|
|
65
|
+
/// Sinc table points per sample of the lower rate.
|
|
66
|
+
pub oversampling: usize,
|
|
67
|
+
pub window: WindowFunction,
|
|
68
|
+
pub interpolation: SincInterpolationType,
|
|
69
|
+
/// -6 dB cutoff relative to the lower Nyquist frequency.
|
|
70
|
+
pub cutoff: f32,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
pub fn preset(quality: u32) -> Option<Preset> {
|
|
74
|
+
use SincInterpolationType::*;
|
|
75
|
+
use WindowFunction::*;
|
|
76
|
+
let p = |len, oversampling, window, interpolation, cutoff| Preset {
|
|
77
|
+
len,
|
|
78
|
+
oversampling,
|
|
79
|
+
window,
|
|
80
|
+
interpolation,
|
|
81
|
+
cutoff,
|
|
82
|
+
};
|
|
83
|
+
Some(match quality {
|
|
84
|
+
0 => p(32, 32, Blackman, Linear, 0.82), // QQ
|
|
85
|
+
1 => p(64, 64, BlackmanHarris, Cubic, 0.84), // LQ
|
|
86
|
+
2 => p(160, 128, BlackmanHarris, Cubic, 0.95), // MQ
|
|
87
|
+
3 => p(240, 128, BlackmanHarris2, Cubic, 0.945), // HQ
|
|
88
|
+
4 => p(272, 256, BlackmanHarris2, Cubic, 0.945), // VHQ
|
|
89
|
+
_ => return None,
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// The rubato parameters of a preset for a given ratio (out/in):
|
|
94
|
+
/// `(sinc_len, oversampling, f_cutoff)`.
|
|
95
|
+
fn sinc_params(p: &Preset, ratio: f64) -> (usize, usize, f32) {
|
|
96
|
+
let down = ratio.min(1.0);
|
|
97
|
+
let sinc_len = ((p.len as f64 / down).ceil() as usize).next_multiple_of(8);
|
|
98
|
+
// The stretched sinc varies `1/down` times more slowly per input sample,
|
|
99
|
+
// so the table needs proportionally fewer points per input sample.
|
|
100
|
+
let oversampling = ((p.oversampling as f64 * down).ceil() as usize).max(8);
|
|
101
|
+
(sinc_len, oversampling, p.cutoff)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
pub struct Stream {
|
|
105
|
+
resampler: Async<f64>,
|
|
106
|
+
channels: usize,
|
|
107
|
+
ratio: f64,
|
|
108
|
+
cutoff: f32,
|
|
109
|
+
/// Output frames to drop at the start of every stream (filter delay).
|
|
110
|
+
delay: usize,
|
|
111
|
+
/// Zero frames fed before the first input so that `delay` is a whole
|
|
112
|
+
/// number of output frames (see `alignment`).
|
|
113
|
+
prime: usize,
|
|
114
|
+
/// Whether the priming zeros still have to be fed.
|
|
115
|
+
needs_prime: bool,
|
|
116
|
+
/// Output frames still to drop at the start of this stream.
|
|
117
|
+
delay_left: usize,
|
|
118
|
+
/// Input frames consumed since the stream started (or was last reset).
|
|
119
|
+
total_in: u64,
|
|
120
|
+
/// Output frames emitted since the stream started (or was last reset).
|
|
121
|
+
total_out: u64,
|
|
122
|
+
/// Samples (< channels) left over from the previous call.
|
|
123
|
+
carry: Vec<f64>,
|
|
124
|
+
/// Staging areas JS writes input into.
|
|
125
|
+
input_i16: Vec<i16>,
|
|
126
|
+
input_f32: Vec<f32>,
|
|
127
|
+
/// Interleaved float input (carry + input).
|
|
128
|
+
in_f: Vec<f64>,
|
|
129
|
+
/// One rubato output chunk.
|
|
130
|
+
out_f: Vec<f64>,
|
|
131
|
+
/// The result of the last process/flush call.
|
|
132
|
+
res: Vec<f64>,
|
|
133
|
+
output_f32: Vec<f32>,
|
|
134
|
+
output_i16: Vec<i16>,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
impl Stream {
|
|
138
|
+
pub fn new(in_rate: u32, out_rate: u32, channels: u32, quality: u32) -> Option<Stream> {
|
|
139
|
+
if in_rate == 0 || out_rate == 0 || channels == 0 || channels > 8 {
|
|
140
|
+
return None;
|
|
141
|
+
}
|
|
142
|
+
let p = preset(quality)?;
|
|
143
|
+
let ratio = out_rate as f64 / in_rate as f64;
|
|
144
|
+
let (sinc_len, oversampling, f_cutoff) = sinc_params(&p, ratio);
|
|
145
|
+
let params = SincInterpolationParameters::new(sinc_len, p.window)
|
|
146
|
+
.oversampling_factor(oversampling)
|
|
147
|
+
.interpolation(p.interpolation)
|
|
148
|
+
.f_cutoff(f_cutoff);
|
|
149
|
+
let resampler = Async::<f64>::new_sinc(
|
|
150
|
+
ratio,
|
|
151
|
+
1.0,
|
|
152
|
+
¶ms,
|
|
153
|
+
MAX_CHUNK,
|
|
154
|
+
channels as usize,
|
|
155
|
+
FixedAsync::Input,
|
|
156
|
+
)
|
|
157
|
+
.ok()?;
|
|
158
|
+
let cutoff = resampler.cutoff().unwrap_or(0.0);
|
|
159
|
+
let out_cap = resampler.output_frames_max() * channels as usize;
|
|
160
|
+
let (prime, delay) = alignment(in_rate, out_rate, sinc_len);
|
|
161
|
+
Some(Stream {
|
|
162
|
+
resampler,
|
|
163
|
+
channels: channels as usize,
|
|
164
|
+
ratio,
|
|
165
|
+
cutoff,
|
|
166
|
+
delay,
|
|
167
|
+
prime,
|
|
168
|
+
needs_prime: true,
|
|
169
|
+
delay_left: delay,
|
|
170
|
+
total_in: 0,
|
|
171
|
+
total_out: 0,
|
|
172
|
+
carry: Vec::new(),
|
|
173
|
+
input_i16: Vec::new(),
|
|
174
|
+
input_f32: Vec::new(),
|
|
175
|
+
in_f: Vec::new(),
|
|
176
|
+
out_f: vec![0.0; out_cap],
|
|
177
|
+
res: Vec::new(),
|
|
178
|
+
output_i16: Vec::new(),
|
|
179
|
+
output_f32: Vec::new(),
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
pub fn reset(&mut self) {
|
|
184
|
+
self.resampler.reset();
|
|
185
|
+
self.needs_prime = true;
|
|
186
|
+
self.delay_left = self.delay;
|
|
187
|
+
self.total_in = 0;
|
|
188
|
+
self.total_out = 0;
|
|
189
|
+
self.carry.clear();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Run one rubato chunk of `frames` input frames starting at frame
|
|
193
|
+
/// `offset` of `in_f` (`silence` feeds a full chunk of zeros instead), and
|
|
194
|
+
/// append the delay-trimmed result to `res`.
|
|
195
|
+
fn run_chunk(&mut self, offset: usize, frames: usize, silence: bool) -> bool {
|
|
196
|
+
let ch = self.channels;
|
|
197
|
+
let chunk = if silence { MAX_CHUNK } else { frames };
|
|
198
|
+
if self.resampler.set_chunk_size(chunk).is_err() {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
let (src, src_frames, indexing) = if silence {
|
|
202
|
+
(&[][..], 0, Indexing::new().partial_len(0))
|
|
203
|
+
} else {
|
|
204
|
+
(
|
|
205
|
+
&self.in_f[..],
|
|
206
|
+
self.in_f.len() / ch,
|
|
207
|
+
Indexing::new().input_offset(offset),
|
|
208
|
+
)
|
|
209
|
+
};
|
|
210
|
+
let Ok(input) = InterleavedSlice::new(src, ch, src_frames) else {
|
|
211
|
+
return false;
|
|
212
|
+
};
|
|
213
|
+
let out_frames = self.out_f.len() / ch;
|
|
214
|
+
let Ok(mut output) = InterleavedSlice::new_mut(&mut self.out_f, ch, out_frames) else {
|
|
215
|
+
return false;
|
|
216
|
+
};
|
|
217
|
+
let Ok((_, produced)) =
|
|
218
|
+
self.resampler
|
|
219
|
+
.process_into_buffer(&input, &mut output, Some(&indexing))
|
|
220
|
+
else {
|
|
221
|
+
return false;
|
|
222
|
+
};
|
|
223
|
+
let skip = self.delay_left.min(produced);
|
|
224
|
+
self.delay_left -= skip;
|
|
225
|
+
self.total_out += (produced - skip) as u64;
|
|
226
|
+
self.res
|
|
227
|
+
.extend_from_slice(&self.out_f[skip * ch..produced * ch]);
|
|
228
|
+
true
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/// Resample `samples` (interleaved, float). The result is left in `res`.
|
|
232
|
+
fn process(&mut self, samples: impl Iterator<Item = f64>) -> bool {
|
|
233
|
+
let ch = self.channels;
|
|
234
|
+
self.res.clear();
|
|
235
|
+
self.in_f.clear();
|
|
236
|
+
if self.needs_prime {
|
|
237
|
+
self.needs_prime = false;
|
|
238
|
+
self.in_f.resize(self.prime * ch, 0.0);
|
|
239
|
+
}
|
|
240
|
+
let primed = self.in_f.len() / ch;
|
|
241
|
+
self.in_f.extend_from_slice(&self.carry);
|
|
242
|
+
self.in_f.extend(samples);
|
|
243
|
+
let frames = self.in_f.len() / ch;
|
|
244
|
+
self.carry.clear();
|
|
245
|
+
self.carry.extend_from_slice(&self.in_f[frames * ch..]);
|
|
246
|
+
self.in_f.truncate(frames * ch);
|
|
247
|
+
self.total_in += (frames - primed) as u64;
|
|
248
|
+
|
|
249
|
+
let mut offset = 0;
|
|
250
|
+
while offset < frames {
|
|
251
|
+
let piece = (frames - offset).min(MAX_CHUNK);
|
|
252
|
+
if !self.run_chunk(offset, piece, false) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
offset += piece;
|
|
256
|
+
}
|
|
257
|
+
true
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/// Emit the audio held in the filter, then reset. The result is left in `res`.
|
|
261
|
+
fn flush(&mut self) -> bool {
|
|
262
|
+
let ch = self.channels;
|
|
263
|
+
self.res.clear();
|
|
264
|
+
let expected = (self.total_in as f64 * self.ratio).round() as u64;
|
|
265
|
+
// A few silent chunks always cover the filter delay; bound the loop anyway.
|
|
266
|
+
let mut guard = 64;
|
|
267
|
+
while self.total_out < expected && guard > 0 {
|
|
268
|
+
if !self.run_chunk(0, 0, true) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
guard -= 1;
|
|
272
|
+
}
|
|
273
|
+
if self.total_out > expected {
|
|
274
|
+
let extra = (self.total_out - expected) as usize;
|
|
275
|
+
let keep = self.res.len().saturating_sub(extra * ch);
|
|
276
|
+
self.res.truncate(keep);
|
|
277
|
+
}
|
|
278
|
+
self.reset();
|
|
279
|
+
true
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
fn result_i16(&mut self, ok: bool) -> i32 {
|
|
283
|
+
self.output_i16.clear();
|
|
284
|
+
if !ok {
|
|
285
|
+
return -1;
|
|
286
|
+
}
|
|
287
|
+
self.output_i16.extend(self.res.iter().map(|&v| to_i16(v)));
|
|
288
|
+
self.output_i16.len() as i32
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
fn result_f32(&mut self, ok: bool) -> i32 {
|
|
292
|
+
self.output_f32.clear();
|
|
293
|
+
if !ok {
|
|
294
|
+
return -1;
|
|
295
|
+
}
|
|
296
|
+
self.output_f32.extend(self.res.iter().map(|&v| v as f32));
|
|
297
|
+
self.output_f32.len() as i32
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/// Choose how many zero frames to prime a stream with, and how many output
|
|
302
|
+
/// frames to trim, so the output is sample-aligned with the input.
|
|
303
|
+
///
|
|
304
|
+
/// rubato's first output frame sits at input position `1/ratio - sinc_len/2`,
|
|
305
|
+
/// i.e. the stream is delayed by `D = sinc_len*ratio/2 - 1` output frames.
|
|
306
|
+
/// `D` is fractional for many ratios (e.g. 48k->16k), and a fractional delay
|
|
307
|
+
/// cannot be trimmed. Priming with `k` zero input frames adds `k*ratio`
|
|
308
|
+
/// output frames of delay; with ratio = p/q in lowest terms some k < 2q makes
|
|
309
|
+
/// the total a whole number. Returns `(k, round(total delay))`.
|
|
310
|
+
fn alignment(in_rate: u32, out_rate: u32, sinc_len: usize) -> (usize, usize) {
|
|
311
|
+
let g = gcd(in_rate as u64, out_rate as u64);
|
|
312
|
+
let (p, q) = ((out_rate as u64 / g) as i128, (in_rate as u64 / g) as i128);
|
|
313
|
+
let l = sinc_len.div_ceil(8) as i128 * 8;
|
|
314
|
+
// total(k) = (l*p + 2*k*p - 2*q) / (2*q) output frames.
|
|
315
|
+
let den = 2 * q;
|
|
316
|
+
let mut best = (0i128, i128::MAX);
|
|
317
|
+
for k in 0..den.min(1 << 16) {
|
|
318
|
+
let num = l * p + 2 * k * p - 2 * q;
|
|
319
|
+
let rem = num.rem_euclid(den).min(den - num.rem_euclid(den));
|
|
320
|
+
if rem < best.1 {
|
|
321
|
+
best = (k, rem);
|
|
322
|
+
if rem == 0 {
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
let k = best.0;
|
|
328
|
+
let num = l * p + 2 * k * p - 2 * q;
|
|
329
|
+
let delay = ((num as f64) / (den as f64)).round().max(0.0) as usize;
|
|
330
|
+
(k as usize, delay)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
fn gcd(mut a: u64, mut b: u64) -> u64 {
|
|
334
|
+
while b != 0 {
|
|
335
|
+
(a, b) = (b, a % b);
|
|
336
|
+
}
|
|
337
|
+
a.max(1)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
#[inline]
|
|
341
|
+
fn to_i16(v: f64) -> i16 {
|
|
342
|
+
let s = (v * 32768.0).round();
|
|
343
|
+
if s >= 32767.0 {
|
|
344
|
+
32767
|
|
345
|
+
} else if s <= -32768.0 {
|
|
346
|
+
-32768
|
|
347
|
+
} else {
|
|
348
|
+
s as i16
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// C ABI
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
|
|
356
|
+
/// Allocate `len` bytes (8-byte aligned) in linear memory.
|
|
357
|
+
#[no_mangle]
|
|
358
|
+
pub extern "C" fn rs_alloc(len: usize) -> *mut u8 {
|
|
359
|
+
let words = len.div_ceil(8).max(1);
|
|
360
|
+
let mut v: Vec<u64> = Vec::with_capacity(words);
|
|
361
|
+
let ptr = v.as_mut_ptr() as *mut u8;
|
|
362
|
+
core::mem::forget(v);
|
|
363
|
+
ptr
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/// Free a block returned by `rs_alloc(len)`.
|
|
367
|
+
///
|
|
368
|
+
/// # Safety
|
|
369
|
+
/// `ptr` must come from `rs_alloc` with the same `len`.
|
|
370
|
+
#[no_mangle]
|
|
371
|
+
pub unsafe extern "C" fn rs_free(ptr: *mut u8, len: usize) {
|
|
372
|
+
if ptr.is_null() {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
let words = len.div_ceil(8).max(1);
|
|
376
|
+
drop(Vec::from_raw_parts(ptr as *mut u64, 0, words));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/// Create a stream. `quality`: 0=QQ, 1=LQ, 2=MQ, 3=HQ, 4=VHQ. Returns 0 on
|
|
380
|
+
/// invalid parameters (zero rate, channels outside 1..=8, unknown quality).
|
|
381
|
+
#[no_mangle]
|
|
382
|
+
pub extern "C" fn rs_stream_new(
|
|
383
|
+
in_rate: u32,
|
|
384
|
+
out_rate: u32,
|
|
385
|
+
channels: u32,
|
|
386
|
+
quality: u32,
|
|
387
|
+
) -> *mut Stream {
|
|
388
|
+
match Stream::new(in_rate, out_rate, channels, quality) {
|
|
389
|
+
Some(s) => Box::into_raw(Box::new(s)),
|
|
390
|
+
None => core::ptr::null_mut(),
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/// # Safety
|
|
395
|
+
/// `h` must be a live handle from `rs_stream_new`; it is invalid afterwards.
|
|
396
|
+
#[no_mangle]
|
|
397
|
+
pub unsafe extern "C" fn rs_stream_free(h: *mut Stream) {
|
|
398
|
+
if !h.is_null() {
|
|
399
|
+
drop(Box::from_raw(h));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/// Make room for `n` int16 input samples and return where to write them.
|
|
404
|
+
///
|
|
405
|
+
/// # Safety
|
|
406
|
+
/// `h` must be a live handle from `rs_stream_new`.
|
|
407
|
+
#[no_mangle]
|
|
408
|
+
pub unsafe extern "C" fn rs_stream_input(h: *mut Stream, n: usize) -> *mut i16 {
|
|
409
|
+
let s = &mut *h;
|
|
410
|
+
s.input_i16.resize(n, 0);
|
|
411
|
+
s.input_i16.as_mut_ptr()
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/// Resample the `n` int16 samples written at `rs_stream_input`. Returns the
|
|
415
|
+
/// number of int16 samples now at `rs_stream_output`, or -1 on error.
|
|
416
|
+
///
|
|
417
|
+
/// # Safety
|
|
418
|
+
/// `h` must be a live handle from `rs_stream_new`.
|
|
419
|
+
#[no_mangle]
|
|
420
|
+
pub unsafe extern "C" fn rs_stream_process(h: *mut Stream, n: usize) -> i32 {
|
|
421
|
+
let s = &mut *h;
|
|
422
|
+
let input = core::mem::take(&mut s.input_i16);
|
|
423
|
+
let n = n.min(input.len());
|
|
424
|
+
let ok = s.process(input[..n].iter().map(|&v| v as f64 / 32768.0));
|
|
425
|
+
s.input_i16 = input;
|
|
426
|
+
s.result_i16(ok)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/// Drain the filter tail and reset. Returns the number of int16 samples now
|
|
430
|
+
/// at `rs_stream_output`, or -1 on error.
|
|
431
|
+
///
|
|
432
|
+
/// # Safety
|
|
433
|
+
/// `h` must be a live handle from `rs_stream_new`.
|
|
434
|
+
#[no_mangle]
|
|
435
|
+
pub unsafe extern "C" fn rs_stream_flush(h: *mut Stream) -> i32 {
|
|
436
|
+
let s = &mut *h;
|
|
437
|
+
let ok = s.flush();
|
|
438
|
+
s.result_i16(ok)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/// Pointer to the int16 output of the last `rs_stream_process`/`rs_stream_flush`.
|
|
442
|
+
///
|
|
443
|
+
/// # Safety
|
|
444
|
+
/// `h` must be a live handle from `rs_stream_new`.
|
|
445
|
+
#[no_mangle]
|
|
446
|
+
pub unsafe extern "C" fn rs_stream_output(h: *mut Stream) -> *const i16 {
|
|
447
|
+
(*h).output_i16.as_ptr()
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/// Make room for `n` float input samples and return where to write them.
|
|
451
|
+
///
|
|
452
|
+
/// # Safety
|
|
453
|
+
/// `h` must be a live handle from `rs_stream_new`.
|
|
454
|
+
#[no_mangle]
|
|
455
|
+
pub unsafe extern "C" fn rs_stream_input_f32(h: *mut Stream, n: usize) -> *mut f32 {
|
|
456
|
+
let s = &mut *h;
|
|
457
|
+
s.input_f32.resize(n, 0.0);
|
|
458
|
+
s.input_f32.as_mut_ptr()
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/// Resample the `n` float samples written at `rs_stream_input_f32`. Returns
|
|
462
|
+
/// the number of float samples now at `rs_stream_output_f32`, or -1 on error.
|
|
463
|
+
///
|
|
464
|
+
/// # Safety
|
|
465
|
+
/// `h` must be a live handle from `rs_stream_new`.
|
|
466
|
+
#[no_mangle]
|
|
467
|
+
pub unsafe extern "C" fn rs_stream_process_f32(h: *mut Stream, n: usize) -> i32 {
|
|
468
|
+
let s = &mut *h;
|
|
469
|
+
let input = core::mem::take(&mut s.input_f32);
|
|
470
|
+
let n = n.min(input.len());
|
|
471
|
+
let ok = s.process(input[..n].iter().map(|&v| v as f64));
|
|
472
|
+
s.input_f32 = input;
|
|
473
|
+
s.result_f32(ok)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/// Float variant of `rs_stream_flush`; output at `rs_stream_output_f32`.
|
|
477
|
+
///
|
|
478
|
+
/// # Safety
|
|
479
|
+
/// `h` must be a live handle from `rs_stream_new`.
|
|
480
|
+
#[no_mangle]
|
|
481
|
+
pub unsafe extern "C" fn rs_stream_flush_f32(h: *mut Stream) -> i32 {
|
|
482
|
+
let s = &mut *h;
|
|
483
|
+
let ok = s.flush();
|
|
484
|
+
s.result_f32(ok)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/// Pointer to the float output of the last `_f32` process/flush call.
|
|
488
|
+
///
|
|
489
|
+
/// # Safety
|
|
490
|
+
/// `h` must be a live handle from `rs_stream_new`.
|
|
491
|
+
#[no_mangle]
|
|
492
|
+
pub unsafe extern "C" fn rs_stream_output_f32(h: *mut Stream) -> *const f32 {
|
|
493
|
+
(*h).output_f32.as_ptr()
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/// Discard all state (filter history, pending samples, counters).
|
|
497
|
+
///
|
|
498
|
+
/// # Safety
|
|
499
|
+
/// `h` must be a live handle from `rs_stream_new`.
|
|
500
|
+
#[no_mangle]
|
|
501
|
+
pub unsafe extern "C" fn rs_stream_reset(h: *mut Stream) {
|
|
502
|
+
(*h).reset()
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/// Filter delay in output frames (trimmed from the start of every stream).
|
|
506
|
+
///
|
|
507
|
+
/// # Safety
|
|
508
|
+
/// `h` must be a live handle from `rs_stream_new`.
|
|
509
|
+
#[no_mangle]
|
|
510
|
+
pub unsafe extern "C" fn rs_stream_delay(h: *mut Stream) -> u32 {
|
|
511
|
+
(*h).delay as u32
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/// Relative anti-aliasing cutoff (1.0 = input Nyquist; scaled down when downsampling).
|
|
515
|
+
///
|
|
516
|
+
/// # Safety
|
|
517
|
+
/// `h` must be a live handle from `rs_stream_new`.
|
|
518
|
+
#[no_mangle]
|
|
519
|
+
pub unsafe extern "C" fn rs_stream_cutoff(h: *mut Stream) -> f32 {
|
|
520
|
+
(*h).cutoff
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/// Sinc filter length of a quality preset, in samples of the lower rate (0 if unknown).
|
|
524
|
+
#[no_mangle]
|
|
525
|
+
pub extern "C" fn rs_quality_sinc_len(quality: u32) -> u32 {
|
|
526
|
+
preset(quality).map(|p| p.len as u32).unwrap_or(0)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/// Sinc table points per sample of the lower rate for a quality preset (0 if unknown).
|
|
530
|
+
#[no_mangle]
|
|
531
|
+
pub extern "C" fn rs_quality_oversampling(quality: u32) -> u32 {
|
|
532
|
+
preset(quality).map(|p| p.oversampling as u32).unwrap_or(0)
|
|
533
|
+
}
|
|
Binary file
|