wavers/
conversion.rs

1/// Module containing the functionality for converting between the supported audio sample types
2use std::fmt::Debug;
3
4use bytemuck::Pod;
5use i24::i24;
6use num_traits::Num;
7
8use crate::core::alloc_sample_buffer;
9
10/// Trait used to indicate that a type is an audio sample and can be treated as such.
11pub trait AudioSample:
12    Copy
13    + Pod
14    + Num
15    + ConvertTo<i16>
16    + ConvertTo<i32>
17    + ConvertTo<i24>
18    + ConvertTo<f32>
19    + ConvertTo<f64>
20    + Sync
21    + Send
22    + Debug
23{
24}
25
26impl AudioSample for i16 {}
27impl AudioSample for i24 {}
28impl AudioSample for i32 {}
29impl AudioSample for f32 {}
30impl AudioSample for f64 {}
31
32/// Trait for converting between audio sample types
33/// The type ``T`` must implement the ``AudioSample`` trait
34pub trait ConvertTo<T: AudioSample> {
35    fn convert_to(&self) -> T
36    where
37        Self: Sized + AudioSample;
38}
39
40/// Trait for converting between audio sample types in a slice
41/// The type ``T`` must implement the ``AudioSample`` trait
42pub trait ConvertSlice<T: AudioSample> {
43    fn convert_slice(self) -> Box<[T]>;
44}
45
46impl<T: AudioSample, F> ConvertSlice<T> for Box<[F]>
47where
48    F: AudioSample + ConvertTo<T>,
49{
50    fn convert_slice(self) -> Box<[T]> {
51        let mut out: Box<[T]> = alloc_sample_buffer(self.len());
52        for i in 0..self.len() {
53            out[i] = self[i].convert_to();
54        }
55        out
56    }
57}
58
59// i16 //
60impl ConvertTo<i16> for i16 {
61    #[inline(always)]
62    fn convert_to(&self) -> i16 {
63        *self
64    }
65}
66
67impl ConvertTo<i24> for i16 {
68    #[inline(always)]
69    fn convert_to(&self) -> i24 {
70        i24::from_i32((*self as i32) << 8)
71    }
72}
73
74impl ConvertTo<i32> for i16 {
75    #[inline(always)]
76    fn convert_to(&self) -> i32 {
77        (*self as i32) << 16
78    }
79}
80
81impl ConvertTo<f32> for i16 {
82    #[inline(always)]
83    fn convert_to(&self) -> f32 {
84        ((*self as f32) / (i16::MAX as f32)).clamp(-1.0, 1.0)
85    }
86}
87
88impl ConvertTo<f64> for i16 {
89    #[inline(always)]
90    fn convert_to(&self) -> f64 {
91        ((*self as f64) / (i16::MAX as f64)).clamp(-1.0, 1.0)
92    }
93}
94
95// i24 //
96
97impl ConvertTo<i16> for i24 {
98    #[inline(always)]
99    fn convert_to(&self) -> i16 {
100        (self.to_i32() >> 8) as i16
101    }
102}
103
104impl ConvertTo<i24> for i24 {
105    #[inline(always)]
106    fn convert_to(&self) -> i24 {
107        *self
108    }
109}
110
111impl ConvertTo<i32> for i24 {
112    #[inline(always)]
113    fn convert_to(&self) -> i32 {
114        self.to_i32() << 8
115    }
116}
117
118impl ConvertTo<f32> for i24 {
119    #[inline(always)]
120    fn convert_to(&self) -> f32 {
121        (self.to_i32() as f32) / (i32::MAX as f32)
122    }
123}
124
125impl ConvertTo<f64> for i24 {
126    #[inline(always)]
127    fn convert_to(&self) -> f64 {
128        (self.to_i32() as f64) / (i32::MAX as f64)
129    }
130}
131
132// i32 //
133impl ConvertTo<i16> for i32 {
134    #[inline(always)]
135    fn convert_to(&self) -> i16 {
136        (*self >> 16) as i16
137    }
138}
139
140impl ConvertTo<i24> for i32 {
141    #[inline(always)]
142    fn convert_to(&self) -> i24 {
143        i24::from_i32(*self >> 8)
144    }
145}
146
147impl ConvertTo<i32> for i32 {
148    #[inline(always)]
149    fn convert_to(&self) -> i32 {
150        *self
151    }
152}
153
154impl ConvertTo<f32> for i32 {
155    #[inline(always)]
156    fn convert_to(&self) -> f32 {
157        ((*self as f32) / (i32::MAX as f32)).clamp(-1.0, 1.0)
158    }
159}
160
161impl ConvertTo<f64> for i32 {
162    #[inline(always)]
163    fn convert_to(&self) -> f64 {
164        ((*self as f64) / (i32::MAX as f64)).clamp(-1.0, 1.0)
165    }
166}
167
168// f32 //
169impl ConvertTo<i16> for f32 {
170    #[inline(always)]
171    fn convert_to(&self) -> i16 {
172        ((*self * (i16::MAX as f32)).clamp(i16::MIN as f32, i16::MAX as f32)).round() as i16
173    }
174}
175
176impl ConvertTo<i24> for f32 {
177    #[inline(always)]
178    fn convert_to(&self) -> i24 {
179        i24::from_i32(
180            ((*self * (i32::MAX as f32)).clamp(i32::MIN as f32, i32::MAX as f32)).round() as i32,
181        )
182    }
183}
184
185impl ConvertTo<i32> for f32 {
186    #[inline(always)]
187    fn convert_to(&self) -> i32 {
188        ((*self * (i32::MAX as f32)).clamp(i32::MIN as f32, i32::MAX as f32)).round() as i32
189    }
190}
191
192impl ConvertTo<f32> for f32 {
193    #[inline(always)]
194    fn convert_to(&self) -> f32 {
195        *self
196    }
197}
198
199impl ConvertTo<f64> for f32 {
200    #[inline(always)]
201    fn convert_to(&self) -> f64 {
202        *self as f64
203    }
204}
205
206// f64 //
207impl ConvertTo<i16> for f64 {
208    #[inline(always)]
209    fn convert_to(&self) -> i16 {
210        ((*self * (i16::MAX as f64)).clamp(i16::MIN as f64, i16::MAX as f64)).round() as i16
211    }
212}
213
214impl ConvertTo<i24> for f64 {
215    #[inline(always)]
216    fn convert_to(&self) -> i24 {
217        i24::from_i32(
218            ((*self * (i32::MAX as f64)).clamp(i32::MIN as f64, i32::MAX as f64)).round() as i32,
219        )
220    }
221}
222
223impl ConvertTo<i32> for f64 {
224    #[inline(always)]
225    fn convert_to(&self) -> i32 {
226        ((*self * (i32::MAX as f64)).clamp(i32::MIN as f64, i32::MAX as f64)).round() as i32
227    }
228}
229
230impl ConvertTo<f32> for f64 {
231    #[inline(always)]
232    fn convert_to(&self) -> f32 {
233        *self as f32
234    }
235}
236
237impl ConvertTo<f64> for f64 {
238    #[inline(always)]
239    fn convert_to(&self) -> f64 {
240        *self
241    }
242}
243
244#[cfg(test)]
245mod conversion_tests {
246
247    use super::*;
248    use std::fs::File;
249    use std::io::BufRead;
250    use std::path::Path;
251    use std::str::FromStr;
252
253    use approx_eq::assert_approx_eq;
254
255    #[test]
256    fn i16_to_f32() {
257        let i16_samples: Vec<i16> =
258            read_text_to_vec(Path::new("./test_resources/one_channel_i16.txt")).unwrap();
259        let i16_samples: &[i16] = &i16_samples;
260
261        let f32_samples: Vec<f32> =
262            read_text_to_vec(Path::new("./test_resources/one_channel_f32.txt")).unwrap();
263        let f32_samples: &[f32] = &f32_samples;
264        for (expected_sample, actual_sample) in f32_samples.iter().zip(i16_samples) {
265            let actual_sample: f32 = actual_sample.convert_to();
266            assert_approx_eq!(*expected_sample as f64, actual_sample as f64, 1e-4);
267        }
268    }
269
270    #[test]
271    fn i16_to_f32_slice() {
272        let i16_samples: Vec<i16> =
273            read_text_to_vec(Path::new("./test_resources/one_channel_i16.txt")).unwrap();
274        let i16_samples: Box<[i16]> = i16_samples.into_boxed_slice();
275        let f32_samples: Vec<f32> =
276            read_text_to_vec(Path::new("./test_resources/one_channel_f32.txt")).unwrap();
277
278        let f32_samples: &[f32] = &f32_samples;
279        let converted_i16_samples: Box<[f32]> = i16_samples.convert_slice();
280
281        for (_, (expected_sample, actual_sample)) in
282            converted_i16_samples.iter().zip(f32_samples).enumerate()
283        {
284            assert_approx_eq!(*expected_sample as f64, *actual_sample as f64, 1e-4);
285        }
286    }
287
288    #[test]
289    fn f32_to_i16() {
290        let i16_samples: Vec<i16> =
291            read_text_to_vec(Path::new("./test_resources/one_channel_i16.txt")).unwrap();
292        let i16_samples: &[i16] = &i16_samples;
293
294        let f32_samples: Vec<f32> =
295            read_text_to_vec(Path::new("./test_resources/one_channel_f32.txt")).unwrap();
296
297        let f32_samples: &[f32] = &f32_samples;
298        for (expected_sample, actual_sample) in i16_samples.iter().zip(f32_samples) {
299            let converted_sample: i16 = actual_sample.convert_to();
300            assert_eq!(
301                *expected_sample, converted_sample,
302                "Failed to convert sample {} to i16",
303                actual_sample
304            );
305        }
306    }
307
308    #[cfg(test)]
309    fn read_lines<P>(filename: P) -> std::io::Result<std::io::Lines<std::io::BufReader<File>>>
310    where
311        P: AsRef<Path>,
312    {
313        let file = File::open(filename)?;
314        Ok(std::io::BufReader::new(file).lines())
315    }
316
317    #[cfg(test)]
318    fn read_text_to_vec<T: FromStr>(fp: &Path) -> Result<Vec<T>, Box<dyn std::error::Error>>
319    where
320        <T as FromStr>::Err: std::error::Error + 'static,
321    {
322        let mut data = Vec::new();
323        let lines = read_lines(fp)?;
324        for line in lines {
325            let line = line?;
326            for sample in line.split(" ") {
327                let parsed_sample: T = match sample.trim().parse::<T>() {
328                    Ok(num) => num,
329                    Err(err) => {
330                        eprintln!("Failed to parse {}", sample);
331                        panic!("{}", err)
332                    }
333                };
334                data.push(parsed_sample);
335            }
336        }
337        Ok(data)
338    }
339}
340
341#[cfg(feature = "ndarray")]
342pub trait IntoNdarray {
343    type Target: AudioSample;
344    fn into_ndarray(self) -> crate::WaversResult<(ndarray::Array2<Self::Target>, i32)>;
345}
346
347#[cfg(feature = "ndarray")]
348pub trait AsNdarray {
349    type Target: AudioSample;
350    fn as_ndarray(&mut self) -> crate::WaversResult<(ndarray::Array2<Self::Target>, i32)>;
351}