1use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
9
10use kira::sound::{Sound, SoundData};
11use kira::{Frame, info::Info};
12
13pub trait AudioStream: Send + 'static {
18 fn fill(&mut self, out: &mut [f32]);
20}
21
22impl<F: FnMut(&mut [f32]) + Send + 'static> AudioStream for F {
23 fn fill(&mut self, out: &mut [f32]) {
24 self(out);
25 }
26}
27
28const CHUNK: usize = 512;
30
31#[derive(Debug)]
33struct Shared {
34 volume: AtomicU32,
36 paused: AtomicBool,
37 stopped: AtomicBool,
38 pending: AtomicBool,
40}
41
42#[derive(Clone, Debug)]
48pub struct StreamHandle {
49 shared: Arc<Shared>,
50}
51
52impl StreamHandle {
53 fn new(volume: f32, pending: bool) -> Self {
54 Self {
55 shared: Arc::new(Shared {
56 volume: AtomicU32::new(volume.max(0.0).to_bits()),
59 paused: AtomicBool::new(false),
60 stopped: AtomicBool::new(false),
61 pending: AtomicBool::new(pending),
62 }),
63 }
64 }
65
66 pub fn set_volume(&self, volume: f32) {
68 self.shared
69 .volume
70 .store(volume.max(0.0).to_bits(), Ordering::Relaxed);
71 }
72
73 pub fn volume(&self) -> f32 {
75 f32::from_bits(self.shared.volume.load(Ordering::Relaxed))
76 }
77
78 pub fn pause(&self) {
81 self.shared.paused.store(true, Ordering::Relaxed);
82 }
83
84 pub fn resume(&self) {
86 self.shared.paused.store(false, Ordering::Relaxed);
87 }
88
89 pub fn stop(&self) {
92 self.shared.stopped.store(true, Ordering::Relaxed);
93 }
94
95 pub fn is_playing(&self) -> bool {
97 !self.is_pending() && !self.is_paused() && !self.is_finished()
98 }
99
100 pub fn is_paused(&self) -> bool {
102 self.shared.paused.load(Ordering::Relaxed)
103 }
104
105 pub fn is_pending(&self) -> bool {
107 self.shared.pending.load(Ordering::Relaxed)
108 }
109
110 pub fn is_finished(&self) -> bool {
112 self.shared.stopped.load(Ordering::Relaxed)
113 }
114
115 pub(crate) fn mark_started(&self) {
116 self.shared.pending.store(false, Ordering::Relaxed);
117 }
118}
119
120pub(crate) struct StreamData {
122 stream: Box<dyn AudioStream>,
123 sample_rate: u32,
124 handle: StreamHandle,
125}
126
127impl StreamData {
128 pub(crate) fn new(
129 stream: Box<dyn AudioStream>,
130 sample_rate: u32,
131 volume: f32,
132 pending: bool,
133 ) -> (Self, StreamHandle) {
134 let handle = StreamHandle::new(volume, pending);
135 let data = Self {
136 stream,
137 sample_rate: sample_rate.max(1),
138 handle: handle.clone(),
139 };
140 (data, handle)
141 }
142
143 pub(crate) fn handle(&self) -> &StreamHandle {
144 &self.handle
145 }
146}
147
148impl SoundData for StreamData {
149 type Error = std::convert::Infallible;
150 type Handle = ();
151
152 fn into_sound(self) -> Result<(Box<dyn Sound>, ()), Self::Error> {
153 let sound = StreamSound::new(self.stream, self.sample_rate, self.handle.shared);
154 Ok((Box::new(sound), ()))
155 }
156}
157
158struct StreamSound {
161 stream: Box<dyn AudioStream>,
162 sample_rate: f64,
163 shared: Arc<Shared>,
164 chunk: Vec<f32>,
165 next_in_chunk: usize,
166 previous: f32,
168 current: f32,
169 phase: f64,
170 primed: bool,
172 applied_volume: f32,
174}
175
176impl StreamSound {
177 fn new(stream: Box<dyn AudioStream>, sample_rate: u32, shared: Arc<Shared>) -> Self {
178 let applied_volume = if shared.paused.load(Ordering::Relaxed) {
180 0.0
181 } else {
182 f32::from_bits(shared.volume.load(Ordering::Relaxed))
183 };
184 Self {
185 stream,
186 sample_rate: f64::from(sample_rate),
187 shared,
188 chunk: vec![0.0; CHUNK],
189 next_in_chunk: CHUNK,
190 previous: 0.0,
191 current: 0.0,
192 phase: 0.0,
193 primed: false,
194 applied_volume,
195 }
196 }
197
198 fn next_sample(&mut self) -> f32 {
199 if self.next_in_chunk == self.chunk.len() {
200 self.stream.fill(&mut self.chunk);
201 self.next_in_chunk = 0;
202 }
203 let s = self.chunk[self.next_in_chunk];
204 self.next_in_chunk += 1;
205 s
206 }
207
208 fn render(&mut self, out: &mut [Frame], dt: f64) {
210 let silenced = self.shared.paused.load(Ordering::Relaxed)
211 || self.shared.stopped.load(Ordering::Relaxed);
212 let target = if silenced {
213 0.0
214 } else {
215 f32::from_bits(self.shared.volume.load(Ordering::Relaxed))
216 };
217 if silenced && self.applied_volume == 0.0 {
218 out.fill(Frame::ZERO);
219 return;
220 }
221 if !self.primed {
222 self.previous = self.next_sample();
223 self.current = self.next_sample();
224 self.primed = true;
225 }
226 let from = self.applied_volume;
227 let len = out.len() as f32;
228 let step = self.sample_rate * dt;
229 for (i, frame) in out.iter_mut().enumerate() {
230 let volume = from + (target - from) * ((i + 1) as f32 / len);
232 let s = self.previous + (self.current - self.previous) * self.phase as f32;
233 *frame = Frame::from_mono(s * volume);
234 self.phase += step;
235 while self.phase >= 1.0 {
236 self.phase -= 1.0;
237 self.previous = self.current;
238 self.current = self.next_sample();
239 }
240 }
241 if !out.is_empty() {
242 self.applied_volume = target;
243 }
244 }
245}
246
247impl Sound for StreamSound {
248 fn process(&mut self, out: &mut [Frame], dt: f64, _info: &Info<'_>) {
249 self.render(out, dt);
250 }
251
252 fn finished(&self) -> bool {
253 self.shared.stopped.load(Ordering::Relaxed) && self.applied_volume == 0.0
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 fn sound(stream: impl AudioStream, rate: u32) -> (StreamSound, StreamHandle) {
263 sound_at(stream, rate, 1.0)
264 }
265
266 fn sound_at(stream: impl AudioStream, rate: u32, volume: f32) -> (StreamSound, StreamHandle) {
267 let (data, handle) = StreamData::new(Box::new(stream), rate, volume, false);
268 let sound = StreamSound::new(data.stream, data.sample_rate, handle.shared.clone());
269 (sound, handle)
270 }
271
272 fn ramp() -> impl AudioStream {
273 let mut n = 0u32;
274 move |out: &mut [f32]| {
275 for s in out {
276 *s = n as f32;
277 n += 1;
278 }
279 }
280 }
281
282 #[test]
283 fn same_rate_passes_samples_through() {
284 let (mut s, _) = sound(ramp(), 100);
285 let mut out = [Frame::ZERO; 4];
286 s.render(&mut out, 1.0 / 100.0);
287 let left: Vec<f32> = out.iter().map(|f| f.left).collect();
288 assert_eq!(left, [0.0, 1.0, 2.0, 3.0]);
289 }
290
291 #[test]
292 fn half_rate_interpolates() {
293 let (mut s, _) = sound(ramp(), 50);
294 let mut out = [Frame::ZERO; 6];
295 s.render(&mut out, 1.0 / 100.0);
296 let left: Vec<f32> = out.iter().map(|f| f.left).collect();
297 assert_eq!(left, [0.0, 0.5, 1.0, 1.5, 2.0, 2.5]);
298 }
299
300 #[test]
301 fn paused_and_stopped_streams_fade_out_then_go_silent() {
302 let (mut s, handle) = sound(|out: &mut [f32]| out.fill(1.0), 100);
303 let mut out = [Frame::ZERO; 4];
304 handle.pause();
305 s.render(&mut out, 0.01);
306 let left: Vec<f32> = out.iter().map(|f| f.left).collect();
307 assert_eq!(left, [0.75, 0.5, 0.25, 0.0]);
308 s.render(&mut out, 0.01);
309 assert!(out.iter().all(|f| *f == Frame::ZERO));
310
311 handle.resume();
312 s.render(&mut out, 0.01);
313 let left: Vec<f32> = out.iter().map(|f| f.left).collect();
314 assert_eq!(left, [0.25, 0.5, 0.75, 1.0]);
315
316 handle.stop();
317 assert!(handle.is_finished() && !handle.is_playing());
318 assert!(!s.finished(), "dropped before the fade-out block");
319 s.render(&mut out, 0.01);
320 assert_eq!(out[3].left, 0.0);
321 assert!(s.finished());
322 }
323
324 #[test]
325 fn a_paused_stream_is_not_asked_for_samples() {
326 use std::sync::atomic::AtomicUsize;
327
328 let pulled = Arc::new(AtomicUsize::new(0));
329 let counter = Arc::clone(&pulled);
330 let (mut s, handle) = sound(
331 move |out: &mut [f32]| {
332 counter.fetch_add(1, Ordering::Relaxed);
333 out.fill(1.0);
334 },
335 100,
336 );
337 let mut out = [Frame::ZERO; 4];
338 handle.pause();
339 s.render(&mut out, 0.01); let after_fade = pulled.load(Ordering::Relaxed);
341 s.render(&mut out, 0.01);
342 s.render(&mut out, 0.01);
343 assert_eq!(pulled.load(Ordering::Relaxed), after_fade);
344 }
345
346 #[test]
347 fn the_mixer_pulls_samples_from_a_playing_stream() {
348 use std::sync::atomic::AtomicUsize;
349
350 use kira::backend::mock::{MockBackend, MockBackendSettings};
351 use kira::{AudioManager, AudioManagerSettings};
352
353 let pulled = Arc::new(AtomicUsize::new(0));
354 let counter = Arc::clone(&pulled);
355 let stream = move |out: &mut [f32]| {
356 counter.fetch_add(out.len(), Ordering::Relaxed);
357 out.fill(0.5);
358 };
359 let mut manager = AudioManager::<MockBackend>::new(AudioManagerSettings {
360 backend_settings: MockBackendSettings { sample_rate: 100 },
361 ..AudioManagerSettings::default()
362 })
363 .unwrap();
364 let (data, handle) = StreamData::new(Box::new(stream), 100, 1.0, false);
365 manager.play(data).unwrap();
366 handle.mark_started();
367 manager.backend_mut().on_start_processing();
368 manager.backend_mut().process();
369 assert!(
370 pulled.load(Ordering::Relaxed) > 0,
371 "stream was never asked for samples"
372 );
373 assert!(handle.is_playing());
374
375 handle.stop();
376 manager.backend_mut().on_start_processing();
377 manager.backend_mut().process();
378 assert!(handle.is_finished());
379 }
380
381 #[test]
382 fn negative_start_volume_is_silence() {
383 let (mut s, handle) = sound_at(|out: &mut [f32]| out.fill(1.0), 100, -0.5);
384 assert_eq!(handle.volume(), 0.0);
385 let mut out = [Frame::ZERO; 2];
386 s.render(&mut out, 0.01);
387 assert!(out.iter().all(|f| f.left == 0.0 && f.right == 0.0));
388 }
389
390 #[test]
391 fn volume_changes_ramp_over_one_block() {
392 let (mut s, handle) = sound(|out: &mut [f32]| out.fill(1.0), 100);
393 handle.set_volume(0.25);
394 let mut out = [Frame::ZERO; 3];
395 s.render(&mut out, 0.01);
396 let left: Vec<f32> = out.iter().map(|f| f.left).collect();
397 assert_eq!(left, [0.75, 0.5, 0.25]);
398 s.render(&mut out, 0.01);
399 assert!(out.iter().all(|f| f.left == 0.25));
400 }
401
402 #[test]
403 fn a_stream_starts_at_its_volume_without_fading_in() {
404 let (mut s, _) = sound_at(|out: &mut [f32]| out.fill(1.0), 100, 0.5);
405 let mut out = [Frame::ZERO; 3];
406 s.render(&mut out, 0.01);
407 assert!(out.iter().all(|f| f.left == 0.5));
408 }
409}