rill_core_dsp/generators/
resampler.rs1use crate::generators::InterpolatedReader;
19use rill_core::traits::algorithm::{Algorithm, AlgorithmCategory, AlgorithmMetadata};
20use rill_core::traits::ProcessResult;
21use rill_core::Transcendental;
22
23pub struct Resampler<T: Transcendental> {
43 reader: InterpolatedReader<T>,
44 source_rate: f64,
45 target_rate: f64,
46}
47
48impl<T: Transcendental> Resampler<T> {
49 pub fn new(buffer: Vec<T>, source_rate: f64) -> Self {
55 let mut reader = InterpolatedReader::new(buffer);
56 reader.set_rate(1.0);
57 Self {
58 reader,
59 source_rate,
60 target_rate: source_rate,
61 }
62 }
63
64 pub fn from_boxed(buffer: Box<[T]>, source_rate: f64) -> Self {
66 let mut reader = InterpolatedReader::from_boxed(buffer);
67 reader.set_rate(1.0);
68 Self {
69 reader,
70 source_rate,
71 target_rate: source_rate,
72 }
73 }
74
75 #[inline(always)]
77 pub fn len(&self) -> usize {
78 self.reader.len()
79 }
80
81 #[inline(always)]
83 pub fn is_empty(&self) -> bool {
84 self.reader.is_empty()
85 }
86
87 #[inline(always)]
89 pub fn source_rate(&self) -> f64 {
90 self.source_rate
91 }
92
93 #[inline(always)]
95 pub fn set_source_rate(&mut self, hz: f64) {
96 self.source_rate = hz;
97 self.update_ratio();
98 }
99
100 #[inline(always)]
102 pub fn target_rate(&self) -> f64 {
103 self.target_rate
104 }
105
106 #[inline(always)]
108 pub fn set_target_rate(&mut self, hz: f64) {
109 self.target_rate = hz;
110 self.update_ratio();
111 }
112
113 #[inline(always)]
118 pub fn set_cubic(&mut self, cubic: bool) {
119 self.reader.set_cubic(cubic);
120 }
121
122 #[inline(always)]
124 pub fn is_cubic(&self) -> bool {
125 self.reader.is_cubic()
126 }
127
128 #[inline(always)]
130 pub fn position(&self) -> f64 {
131 self.reader.position()
132 }
133
134 #[inline(always)]
136 pub fn set_position(&mut self, pos: f64) {
137 self.reader.set_position(pos);
138 }
139
140 pub fn set_buffer(&mut self, buffer: Vec<T>) {
144 self.reader.set_buffer(buffer);
145 }
146
147 #[inline(always)]
149 pub fn as_slice(&self) -> &[T] {
150 self.reader.as_slice()
151 }
152
153 #[inline(always)]
158 pub fn ratio(&self) -> f64 {
159 self.reader.rate()
160 }
161
162 fn update_ratio(&mut self) {
164 let ratio = if self.target_rate > 0.0 {
165 self.source_rate / self.target_rate
166 } else {
167 1.0
168 };
169 self.reader.set_rate(ratio);
170 }
171}
172
173impl<T: Transcendental> Algorithm<T> for Resampler<T> {
174 fn init(&mut self, sample_rate: f32) {
175 self.set_target_rate(sample_rate as f64);
176 }
177
178 fn reset(&mut self) {
179 self.reader.set_position(0.0);
180 }
181
182 fn process(&mut self, _input: Option<&[T]>, output: &mut [T]) -> ProcessResult<()> {
183 self.reader.render_block(output);
184 Ok(())
185 }
186
187 fn metadata(&self) -> AlgorithmMetadata {
188 AlgorithmMetadata {
189 name: "Resampler",
190 category: AlgorithmCategory::Utility,
191 description: "Sample-rate converter using linear or cubic interpolation",
192 author: "Rill",
193 version: env!("CARGO_PKG_VERSION"),
194 }
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 fn process(rs: &mut Resampler<f64>, out: &mut [f64]) {
203 rs.process(None, out).unwrap();
204 }
205
206 #[test]
207 fn test_passthrough() {
208 let buf = vec![1.0f64, 2.0, 3.0, 4.0];
209 let mut rs = Resampler::new(buf, 44100.0);
210 rs.init(44100.0);
211
212 let mut out = [0.0f64; 4];
213 process(&mut rs, &mut out);
214 assert_eq!(out, [1.0, 2.0, 3.0, 4.0]);
215 }
216
217 #[test]
218 fn test_upsample_2x() {
219 let buf = vec![0.0f64, 10.0];
220 let mut rs = Resampler::new(buf, 22050.0);
221 rs.init(44100.0);
222
223 let mut out = [0.0f64; 4];
225 process(&mut rs, &mut out);
226 assert!((out[0] - 0.0).abs() < 1e-10, "pos 0.0, got {}", out[0]);
228 assert!((out[1] - 5.0).abs() < 1e-10, "pos 0.5, got {}", out[1]);
229 assert!((out[2] - 10.0).abs() < 1e-10, "pos 1.0, got {}", out[2]);
230 assert!(
231 (out[3] - 10.0).abs() < 1e-10,
232 "pos 1.5 clamped, got {}",
233 out[3]
234 );
235 }
236
237 #[test]
238 fn test_downsample_2x() {
239 let buf: Vec<f64> = (0..8).map(|i| i as f64 * 100.0).collect();
240 let mut rs = Resampler::new(buf, 88200.0);
241 rs.init(44100.0);
242
243 let mut out = [0.0f64; 4];
245 process(&mut rs, &mut out);
246 assert!((out[0] - 0.0).abs() < 1e-10);
247 assert!((out[1] - 200.0).abs() < 1e-10);
248 assert!((out[2] - 400.0).abs() < 1e-10);
249 assert!((out[3] - 600.0).abs() < 1e-10);
250 }
251
252 #[test]
253 fn test_44k1_to_48k_non_integer_ratio() {
254 let buf: Vec<f64> = (0..441).map(|i| i as f64 * 0.01).collect();
255 let mut rs = Resampler::new(buf, 44100.0);
256 rs.init(48000.0);
257 rs.set_cubic(true);
258
259 let abs_diff = (rs.ratio() - 44100.0 / 48000.0).abs();
261 assert!(abs_diff < 1e-10, "ratio mismatch: {}", rs.ratio());
262
263 let mut out = [0.0f64; 480];
264 process(&mut rs, &mut out);
265 let expected_pos = 480.0 * 44100.0 / 48000.0;
267 let pos_diff = (rs.position() - expected_pos).abs();
268 assert!(pos_diff < 1e-9, "position mismatch: {}", rs.position());
269 }
270
271 #[test]
272 fn test_empty_buffer() {
273 let buf: Vec<f64> = vec![];
274 let mut rs = Resampler::new(buf, 44100.0);
275 rs.init(48000.0);
276 let mut out = [1.0f64; 4];
277 process(&mut rs, &mut out);
278 assert_eq!(out, [0.0; 4]);
279 }
280
281 #[test]
282 fn test_set_source_rate_dynamic() {
283 let buf = vec![0.0f64, 10.0];
284 let mut rs = Resampler::new(buf, 44100.0);
285 rs.init(44100.0);
286 assert!((rs.ratio() - 1.0).abs() < 1e-10);
287
288 rs.set_source_rate(22050.0);
289 assert!((rs.ratio() - 0.5).abs() < 1e-10);
290 }
291
292 #[test]
293 fn test_reset() {
294 let buf: Vec<f64> = (0..10).map(|i| i as f64).collect();
295 let mut rs = Resampler::new(buf, 44100.0);
296 rs.init(44100.0);
297
298 let mut out = [0.0f64; 3];
299 process(&mut rs, &mut out);
300 assert!((rs.position() - 3.0).abs() < 1e-10);
301
302 rs.reset();
303 assert!((rs.position() - 0.0).abs() < 1e-10);
304 }
305}