rodio/
spatial_sink.rs

1use std::f32;
2use std::sync::{Arc, Mutex};
3use std::time::Duration;
4
5use dasp_sample::FromSample;
6
7use crate::mixer::Mixer;
8use crate::source::{SeekError, Spatial};
9use crate::{Sink, Source};
10
11/// A sink that allows changing the position of the source and the listeners
12/// ears while playing. The sources played are then transformed to give a simple
13/// spatial effect. See [`Spatial`] for details.
14pub struct SpatialSink {
15    sink: Sink,
16    positions: Arc<Mutex<SoundPositions>>,
17}
18
19struct SoundPositions {
20    emitter_position: [f32; 3],
21    left_ear: [f32; 3],
22    right_ear: [f32; 3],
23}
24
25impl SpatialSink {
26    /// Builds a new `SpatialSink`.
27    pub fn connect_new(
28        mixer: &Mixer,
29        emitter_position: [f32; 3],
30        left_ear: [f32; 3],
31        right_ear: [f32; 3],
32    ) -> SpatialSink {
33        SpatialSink {
34            sink: Sink::connect_new(mixer),
35            positions: Arc::new(Mutex::new(SoundPositions {
36                emitter_position,
37                left_ear,
38                right_ear,
39            })),
40        }
41    }
42
43    /// Sets the position of the sound emitter in 3 dimensional space.
44    pub fn set_emitter_position(&self, pos: [f32; 3]) {
45        self.positions.lock().unwrap().emitter_position = pos;
46    }
47
48    /// Sets the position of the left ear in 3 dimensional space.
49    pub fn set_left_ear_position(&self, pos: [f32; 3]) {
50        self.positions.lock().unwrap().left_ear = pos;
51    }
52
53    /// Sets the position of the right ear in 3 dimensional space.
54    pub fn set_right_ear_position(&self, pos: [f32; 3]) {
55        self.positions.lock().unwrap().right_ear = pos;
56    }
57
58    /// Appends a sound to the queue of sounds to play.
59    #[inline]
60    pub fn append<S>(&self, source: S)
61    where
62        S: Source + Send + 'static,
63        f32: FromSample<S::Item>,
64    {
65        let positions = self.positions.clone();
66        let pos_lock = self.positions.lock().unwrap();
67        let source = Spatial::new(
68            source,
69            pos_lock.emitter_position,
70            pos_lock.left_ear,
71            pos_lock.right_ear,
72        )
73        .periodic_access(Duration::from_millis(10), move |i| {
74            let pos = positions.lock().unwrap();
75            i.set_positions(pos.emitter_position, pos.left_ear, pos.right_ear);
76        });
77        self.sink.append(source);
78    }
79
80    // Gets the volume of the sound.
81    ///
82    /// The value `1.0` is the "normal" volume (unfiltered input). Any value other than 1.0 will
83    /// multiply each sample by this value.
84    #[inline]
85    pub fn volume(&self) -> f32 {
86        self.sink.volume()
87    }
88
89    /// Changes the volume of the sound.
90    ///
91    /// The value `1.0` is the "normal" volume (unfiltered input). Any value other than 1.0 will
92    /// multiply each sample by this value.
93    #[inline]
94    pub fn set_volume(&self, value: f32) {
95        self.sink.set_volume(value);
96    }
97
98    /// Changes the play speed of the sound. Does not adjust the samples, only the playback speed.
99    ///
100    /// # Note:
101    /// 1. **Increasing the speed will increase the pitch by the same factor**
102    /// - If you set the speed to 0.5 this will halve the frequency of the sound
103    ///   lowering its pitch.
104    /// - If you set the speed to 2 the frequency will double raising the
105    ///   pitch of the sound.
106    /// 2. **Change in the speed affect the total duration inversely**
107    /// - If you set the speed to 0.5, the total duration will be twice as long.
108    /// - If you set the speed to 2 the total duration will be halve of what it
109    ///   was.
110    ///
111    /// See [`Speed`](crate::source::Speed) for details
112    #[inline]
113    pub fn speed(&self) -> f32 {
114        self.sink.speed()
115    }
116
117    /// Changes the speed of the sound.
118    ///
119    /// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0` will
120    /// change the play speed of the sound.
121    #[inline]
122    pub fn set_speed(&self, value: f32) {
123        self.sink.set_speed(value)
124    }
125
126    /// Resumes playback of a paused sound.
127    ///
128    /// No effect if not paused.
129    #[inline]
130    pub fn play(&self) {
131        self.sink.play();
132    }
133
134    /// Pauses playback of this sink.
135    ///
136    /// No effect if already paused.
137    ///
138    /// A paused sound can be resumed with `play()`.
139    pub fn pause(&self) {
140        self.sink.pause();
141    }
142
143    /// Gets if a sound is paused
144    ///
145    /// Sounds can be paused and resumed using pause() and play(). This gets if a sound is paused.
146    pub fn is_paused(&self) -> bool {
147        self.sink.is_paused()
148    }
149
150    /// Removes all currently loaded `Source`s from the `SpatialSink` and pauses it.
151    ///
152    /// See `pause()` for information about pausing a `Sink`.
153    #[inline]
154    pub fn clear(&self) {
155        self.sink.clear();
156    }
157
158    /// Stops the sink by emptying the queue.
159    #[inline]
160    pub fn stop(&self) {
161        self.sink.stop()
162    }
163
164    /// Destroys the sink without stopping the sounds that are still playing.
165    #[inline]
166    pub fn detach(self) {
167        self.sink.detach();
168    }
169
170    /// Sleeps the current thread until the sound ends.
171    #[inline]
172    pub fn sleep_until_end(&self) {
173        self.sink.sleep_until_end();
174    }
175
176    /// Returns true if this sink has no more sounds to play.
177    #[inline]
178    pub fn empty(&self) -> bool {
179        self.sink.empty()
180    }
181
182    /// Returns the number of sounds currently in the queue.
183    #[allow(clippy::len_without_is_empty)]
184    #[inline]
185    pub fn len(&self) -> usize {
186        self.sink.len()
187    }
188
189    /// Attempts to seek to a given position in the current source.
190    ///
191    /// This blocks between 0 and ~5 milliseconds.
192    ///
193    /// As long as the duration of the source is known seek is guaranteed to saturate
194    /// at the end of the source. For example given a source that reports a total duration
195    /// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to
196    /// 42 seconds.
197    ///
198    /// # Errors
199    /// This function will return [`SeekError::NotSupported`] if one of the underlying
200    /// sources does not support seeking.  
201    ///
202    /// It will return an error if an implementation ran
203    /// into one during the seek.  
204    ///
205    /// When seeking beyond the end of a source this
206    /// function might return an error if the duration of the source is not known.
207    pub fn try_seek(&self, pos: Duration) -> Result<(), SeekError> {
208        self.sink.try_seek(pos)
209    }
210
211    /// Returns the position of the sound that's being played.
212    ///
213    /// This takes into account any speedup or delay applied.
214    ///
215    /// Example: if you apply a speedup of *2* to an mp3 decoder source and
216    /// [`get_pos()`](Sink::get_pos) returns *5s* then the position in the mp3
217    /// recording is *10s* from its start.
218    #[inline]
219    pub fn get_pos(&self) -> Duration {
220        self.sink.get_pos()
221    }
222}