Skip to main content

shuyi_sound_out/
convert_impl.rs

1//! From a mix to what a device takes: its sample format, its channels and its rate.
2//! Plain code on every target, so that its tests run with no device. Nothing here
3//! allocates after [`Boundary::new`], since it runs on the audio thread.
4
5/// The fractional bits of a boundary's place between two frames.
6const FRACTION_BITS: u32 = 16;
7/// The most frames of the mix one device frame passes: more than any two rates a
8/// device takes ask (192 kHz for 8 kHz is 24).
9const MOST_STEP_FRAMES: u64 = 64;
10
11/// A sample a device can take, from one of the mix's.
12pub trait DeviceSample: Copy {
13    /// `sample` as this format: the same fraction of full scale.
14    fn from_mix(sample: i16) -> Self;
15}
16
17impl DeviceSample for f32 {
18    fn from_mix(sample: i16) -> Self {
19        Self::from(sample) / 32_768.0
20    }
21}
22
23impl DeviceSample for f64 {
24    fn from_mix(sample: i16) -> Self {
25        Self::from(sample) / 32_768.0
26    }
27}
28
29impl DeviceSample for i16 {
30    fn from_mix(sample: i16) -> Self {
31        sample
32    }
33}
34
35impl DeviceSample for i32 {
36    fn from_mix(sample: i16) -> Self {
37        Self::from(sample) << 16
38    }
39}
40
41impl DeviceSample for u16 {
42    fn from_mix(sample: i16) -> Self {
43        sample.cast_unsigned() ^ 0x8000
44    }
45}
46
47impl DeviceSample for u8 {
48    fn from_mix(sample: i16) -> Self {
49        (sample.cast_unsigned() >> 8).to_le_bytes()[0] ^ 0x80
50    }
51}
52
53/// Fills a device's buffers from a mix of another format: its channels mapped, and
54/// its rate converted in a straight line between frames when the device's differs.
55/// When it doesn't, frames go through untouched.
56#[derive(Debug)]
57pub struct Boundary {
58    /// The mix's channels, and the device's.
59    from: usize,
60    to: usize,
61    /// One frame of the mix per this many device frames, in 16.16.
62    step: u64,
63    /// Where the device is between `previous` and the frame at `at`, in 16.16.
64    place: u64,
65    /// The last frame of the mix behind the device's place, and the one ahead.
66    previous: [i16; 2],
67    /// What came of the mix and hasn't been used.
68    held: Box<[i16]>,
69    at: usize,
70    filled: usize,
71}
72
73impl Boundary {
74    /// From a mix of `from` channels (one or two) at `from_rate`, asked for `frames`
75    /// at a time, to a device of `to` channels at `to_rate`.
76    pub fn new(from: usize, from_rate: u32, frames: usize, to: usize, to_rate: u32) -> Self {
77        Self {
78            from: from.clamp(1, 2),
79            to: to.max(1),
80            step: (u64::from(from_rate) << FRACTION_BITS) / u64::from(to_rate.max(1)),
81            place: 0,
82            previous: [0; 2],
83            held: vec![0; frames.max(1) * from.clamp(1, 2)].into_boxed_slice(),
84            at: 0,
85            filled: 0,
86        }
87    }
88
89    /// The next frame of the mix, left then right, asking `mix` for more when there
90    /// is none. A mono mix has its one sample in both.
91    fn next(&mut self, mix: &mut dyn FnMut(&mut [i16])) -> [i16; 2] {
92        if self.at >= self.filled {
93            mix(&mut self.held);
94            self.at = 0;
95            self.filled = self.held.len();
96        }
97        let frame = [self.held[self.at], self.held[self.at + self.from - 1]];
98        self.at += self.from;
99        frame
100    }
101
102    /// The frame at the device's place.
103    fn frame(&mut self, mix: &mut dyn FnMut(&mut [i16])) -> [i16; 2] {
104        let one = 1u64 << FRACTION_BITS;
105        if self.step == one {
106            return self.next(mix);
107        }
108        let mut ahead = self.next_held(mix);
109        // The frames of the mix the device's place has passed. A step is a few at most,
110        // and the bound means a bad one can't hold the audio thread.
111        for _ in 0..(self.place >> FRACTION_BITS).min(MOST_STEP_FRAMES) {
112            self.previous = ahead;
113            self.at += self.from;
114            ahead = self.next_held(mix);
115        }
116        self.place &= one - 1;
117        let between = |a: i16, b: i16| {
118            let (a, b) = (i64::from(a), i64::from(b));
119            let mixed = a + (b - a) * i64::try_from(self.place).unwrap_or(0) / (1 << FRACTION_BITS);
120            i16::try_from(mixed).unwrap_or(0)
121        };
122        let frame = [
123            between(self.previous[0], ahead[0]),
124            between(self.previous[1], ahead[1]),
125        ];
126        self.place += self.step;
127        frame
128    }
129
130    /// The frame ahead, left where it is.
131    fn next_held(&mut self, mix: &mut dyn FnMut(&mut [i16])) -> [i16; 2] {
132        if self.at >= self.filled {
133            mix(&mut self.held);
134            self.at = 0;
135            self.filled = self.held.len();
136        }
137        [self.held[self.at], self.held[self.at + self.from - 1]]
138    }
139
140    /// Fills `out`, whole frames of the device's channels, from `mix`. Past two
141    /// channels the rest are silence, and a mono device has the mean of the two.
142    pub fn fill<T: DeviceSample>(&mut self, out: &mut [T], mix: &mut dyn FnMut(&mut [i16])) {
143        let silence = T::from_mix(0);
144        for device in out.chunks_exact_mut(self.to) {
145            let [left, right] = self.frame(mix);
146            match device {
147                [only] => {
148                    *only = T::from_mix(i16::midpoint(left, right));
149                }
150                [first, second, rest @ ..] => {
151                    *first = T::from_mix(left);
152                    *second = T::from_mix(right);
153                    rest.fill(silence);
154                }
155                [] => {}
156            }
157        }
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    const CHUNK_FRAMES: usize = 1024;
166
167    /// A mix counting up on the left and down on the right, from 1.
168    fn counting() -> impl FnMut(&mut [i16]) {
169        let mut next = 1i16;
170        move |out: &mut [i16]| {
171            for frame in out.as_chunks_mut::<2>().0 {
172                frame[0] = next;
173                frame[1] = -next;
174                next = next.wrapping_add(1);
175            }
176        }
177    }
178
179    #[test]
180    fn a_float_device_gets_the_same_fraction_of_full_scale() {
181        assert!((f32::from_mix(i16::MIN) + 1.0).abs() < f32::EPSILON);
182        assert!(f32::from_mix(0).abs() < f32::EPSILON);
183        assert!((f32::from_mix(16_384) - 0.5).abs() < f32::EPSILON);
184        assert!((f32::from_mix(i16::MAX) - 32_767.0 / 32_768.0).abs() < f32::EPSILON);
185    }
186
187    #[test]
188    fn every_sixteen_bit_sample_survives_a_float_device_and_back() {
189        for sample in i16::MIN..=i16::MAX {
190            let back = f32::from_mix(sample) * 32_768.0;
191            assert!((back - f32::from(sample)).abs() < f32::EPSILON, "{sample}");
192        }
193    }
194
195    #[test]
196    fn integer_devices_keep_the_order_and_the_extremes() {
197        assert_eq!(i16::from_mix(-5), -5);
198        assert_eq!(i32::from_mix(i16::MIN), i32::MIN);
199        assert_eq!(i32::from_mix(1), 1 << 16);
200        assert_eq!(u16::from_mix(i16::MIN), 0);
201        assert_eq!(u16::from_mix(0), 0x8000);
202        assert_eq!(u16::from_mix(i16::MAX), u16::MAX);
203        assert_eq!(u8::from_mix(i16::MIN), 0);
204        assert_eq!(u8::from_mix(0), 0x80);
205        assert_eq!(u8::from_mix(i16::MAX), u8::MAX);
206        assert!((f64::from_mix(16_384) - 0.5).abs() < f64::EPSILON);
207    }
208
209    #[test]
210    fn at_the_same_rate_frames_go_through_untouched() {
211        let mut boundary = Boundary::new(2, 48_000, CHUNK_FRAMES, 2, 48_000);
212        let mut mix = counting();
213        // Past the chunk the mix is asked for, in sizes that line up with nothing.
214        let mut seen = Vec::new();
215        for size in [7usize, 1024, 1, 2000] {
216            let mut out = vec![0i16; size * 2];
217            boundary.fill(&mut out, &mut mix);
218            seen.extend(out);
219        }
220        for (index, frame) in seen.as_chunks::<2>().0.iter().enumerate() {
221            let want = i16::try_from(index + 1).unwrap_or(0);
222            assert_eq!(*frame, [want, -want]);
223        }
224    }
225
226    #[test]
227    fn a_mono_device_has_the_mean_and_a_surround_one_silence_past_two() {
228        let mut mix = |out: &mut [i16]| {
229            for frame in out.as_chunks_mut::<2>().0 {
230                *frame = [1000, 3000];
231            }
232        };
233        let mut mono = [0i16; 2];
234        Boundary::new(2, 8000, CHUNK_FRAMES, 1, 8000).fill(&mut mono, &mut mix);
235        assert_eq!(mono, [2000, 2000]);
236        let mut surround = [9i16; 8];
237        Boundary::new(2, 8000, CHUNK_FRAMES, 4, 8000).fill(&mut surround, &mut mix);
238        assert_eq!(surround, [1000, 3000, 0, 0, 1000, 3000, 0, 0]);
239    }
240
241    #[test]
242    fn a_mono_mix_sounds_on_both_channels() {
243        let mut mix = |out: &mut [i16]| out.fill(500);
244        let mut stereo = [0i16; 4];
245        Boundary::new(1, 8000, CHUNK_FRAMES, 2, 8000).fill(&mut stereo, &mut mix);
246        assert_eq!(stereo, [500; 4]);
247    }
248
249    #[test]
250    fn a_partial_frame_at_the_end_of_a_buffer_is_left_alone() {
251        let mut mix = counting();
252        let mut out = [9i16; 3];
253        Boundary::new(2, 8000, CHUNK_FRAMES, 2, 8000).fill(&mut out, &mut mix);
254        assert_eq!(out, [1, -1, 9]);
255    }
256
257    #[test]
258    fn another_rate_comes_out_in_a_straight_line_at_the_device_rate() {
259        // A ramp stays a ramp: one mix frame per two device frames.
260        let mut boundary = Boundary::new(2, 8000, CHUNK_FRAMES, 2, 16_000);
261        let mut mix = |out: &mut [i16]| {
262            for (index, frame) in out.as_chunks_mut::<2>().0.iter_mut().enumerate() {
263                let value = i16::try_from(index * 100).unwrap_or(0);
264                *frame = [value, value];
265            }
266        };
267        let mut out = [0i16; 16];
268        boundary.fill(&mut out, &mut mix);
269        let left: Vec<i16> = out.iter().step_by(2).copied().collect();
270        assert_eq!(left, [0, 0, 0, 50, 100, 150, 200, 250]);
271    }
272
273    #[test]
274    fn a_higher_mix_rate_uses_the_frames_up_at_its_rate() {
275        // Four seconds' worth of a 48 kHz mix for a 44.1 kHz device asks the mix for
276        // what the rates say, within a chunk.
277        let mut asked = 0usize;
278        let mut mix = |out: &mut [i16]| {
279            asked += out.len() / 2;
280            out.fill(7);
281        };
282        let mut boundary = Boundary::new(2, 48_000, CHUNK_FRAMES, 2, 44_100);
283        let mut out = vec![0i16; 44_100 * 2];
284        boundary.fill(&mut out, &mut mix);
285        assert!(
286            (48_000..48_000 + CHUNK_FRAMES * 2).contains(&asked),
287            "{asked}"
288        );
289        assert_eq!(out[100], 7);
290    }
291}