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}