rodio/source/
mix.rs

1use std::cmp;
2use std::time::Duration;
3
4use crate::source::uniform::UniformSourceIterator;
5use crate::source::SeekError;
6use crate::{Sample, Source};
7use cpal::{FromSample, Sample as CpalSample};
8
9/// Internal function that builds a `Mix` object.
10pub fn mix<I1, I2>(input1: I1, input2: I2) -> Mix<I1, I2>
11where
12    I1: Source,
13    I1::Item: FromSample<I2::Item> + Sample,
14    I2: Source,
15    I2::Item: Sample,
16{
17    let channels = input1.channels();
18    let rate = input1.sample_rate();
19
20    Mix {
21        input1: UniformSourceIterator::new(input1, channels, rate),
22        input2: UniformSourceIterator::new(input2, channels, rate),
23    }
24}
25
26/// Filter that modifies each sample by a given value.
27#[derive(Clone)]
28pub struct Mix<I1, I2>
29where
30    I1: Source,
31    I1::Item: FromSample<I2::Item> + Sample,
32    I2: Source,
33    I2::Item: Sample,
34{
35    input1: UniformSourceIterator<I1, I1::Item>,
36    input2: UniformSourceIterator<I2, I2::Item>,
37}
38
39impl<I1, I2> Iterator for Mix<I1, I2>
40where
41    I1: Source,
42    I1::Item: FromSample<I2::Item> + Sample,
43    I2: Source,
44    I2::Item: Sample,
45{
46    type Item = I1::Item;
47
48    #[inline]
49    fn next(&mut self) -> Option<I1::Item> {
50        let s1 = self.input1.next();
51        let s2 = self.input2.next();
52
53        match (s1, s2) {
54            (Some(s1), Some(s2)) => Some(s1.saturating_add(CpalSample::from_sample(s2))),
55            (Some(s1), None) => Some(s1),
56            (None, Some(s2)) => Some(CpalSample::from_sample(s2)),
57            (None, None) => None,
58        }
59    }
60
61    #[inline]
62    fn size_hint(&self) -> (usize, Option<usize>) {
63        let s1 = self.input1.size_hint();
64        let s2 = self.input2.size_hint();
65
66        let min = cmp::max(s1.0, s2.0);
67        let max = match (s1.1, s2.1) {
68            (Some(s1), Some(s2)) => Some(cmp::max(s1, s2)),
69            _ => None,
70        };
71
72        (min, max)
73    }
74}
75
76impl<I1, I2> ExactSizeIterator for Mix<I1, I2>
77where
78    I1: Source + ExactSizeIterator,
79    I1::Item: FromSample<I2::Item> + Sample,
80    I2: Source + ExactSizeIterator,
81    I2::Item: Sample,
82{
83}
84
85impl<I1, I2> Source for Mix<I1, I2>
86where
87    I1: Source,
88    I1::Item: FromSample<I2::Item> + Sample,
89    I2: Source,
90    I2::Item: Sample,
91{
92    #[inline]
93    fn current_frame_len(&self) -> Option<usize> {
94        let f1 = self.input1.current_frame_len();
95        let f2 = self.input2.current_frame_len();
96
97        match (f1, f2) {
98            (Some(f1), Some(f2)) => Some(cmp::min(f1, f2)),
99            _ => None,
100        }
101    }
102
103    #[inline]
104    fn channels(&self) -> u16 {
105        self.input1.channels()
106    }
107
108    #[inline]
109    fn sample_rate(&self) -> u32 {
110        self.input1.sample_rate()
111    }
112
113    #[inline]
114    fn total_duration(&self) -> Option<Duration> {
115        let f1 = self.input1.total_duration();
116        let f2 = self.input2.total_duration();
117
118        match (f1, f2) {
119            (Some(f1), Some(f2)) => Some(cmp::max(f1, f2)),
120            _ => None,
121        }
122    }
123
124    /// Will only attempt a seek if both underlying sources support seek.
125    #[inline]
126    fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
127        Err(SeekError::NotSupported {
128            underlying_source: std::any::type_name::<Self>(),
129        })
130
131        // uncomment when #510 is implemented (query position of playback)
132        // TODO use source_intact to check if rollback makes sense
133
134        // let org_pos = self.input1.playback_pos();
135        // self.input1.try_seek(pos)?;
136        //
137        // let res = self.input2.try_seek(pos);
138        // if res.is_err() { // rollback seek in input1
139        //     self.input1.try_seek(org_pos)?;
140        // }
141        //
142        // res
143    }
144}