1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use std::sync::{Arc, Mutex};

use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};

use super::{Mixer, Sound, SoundSource};
use crate::converter::{ChannelConverter, SampleRateConverter};

/// The main struct of the crate.
///
/// This hold all existing `SoundSource`s and `cpal::platform::Stream`.
pub struct AudioEngine {
    mixer: Arc<Mutex<Mixer>>,
    channels: u16,
    sample_rate: u32,
    _stream: cpal::platform::Stream,
}
impl AudioEngine {
    /// Tries to create a new AudioEngine.
    ///
    /// `cpal` will spawn a new thread where the sound samples will be sampled, mixed, and outputed
    /// to the output stream.
    pub fn new() -> Result<Self, &'static str> {
        let host = cpal::default_host();
        let device = host
            .default_output_device()
            .ok_or("no output device available")?;
        let mut supported_configs_range = device
            .supported_output_configs()
            .map_err(|_| "error while querying formats")?;
        let config = supported_configs_range
            .next()
            .ok_or("no supported format?!")?
            .with_max_sample_rate()
            .config();

        let mixer = Arc::new(Mutex::new(Mixer::new(
            config.channels,
            super::SampleRate(config.sample_rate.0),
        )));

        let stream = {
            let mixer = mixer.clone();
            device
                .build_output_stream(
                    &config,
                    move |buffer, _| {
                        let mut buf = vec![0; buffer.len()];
                        mixer.lock().unwrap().write_samples(&mut buf);
                        for i in 0..buffer.len() {
                            buffer[i] = buf[i] as f32 / i16::max_value() as f32;
                        }
                    },
                    move |err| match err {
                        cpal::StreamError::DeviceNotAvailable => {
                            todo!("handle device disconnection")
                        }
                        cpal::StreamError::BackendSpecific { err } => panic!("{}", err),
                    },
                )
                .unwrap()
        };
        stream.play().unwrap();

        let m = mixer.lock().unwrap();
        let channels = m.channels;
        let sample_rate = m.sample_rate;
        drop(m);
        Ok(Self {
            mixer,
            channels,
            sample_rate,
            _stream: stream,
        })
    }

    /// The sample rate that is currently being output to the device.
    pub fn sample_rate(&self) -> u32 {
        self.sample_rate
    }

    /// Create a new Sound.
    ///
    /// Return a `Err` if the number of channels doesn't match the output number of channels. If
    /// the ouput number of channels is 1, or the number of channels of `source` is 1, `source`
    /// will be automatic wrapped in a [`ChannelConverter`].
    ///
    /// If the `sample_rate` of `source` mismatch the output `sample_rate`, `source` will be
    /// wrapped in a [`SampleRateConverter`].
    pub fn new_sound<T: SoundSource + Send + 'static>(
        &self,
        source: T,
    ) -> Result<Sound, &'static str> {
        let sound: Box<dyn SoundSource + Send> = if source.sample_rate() != self.sample_rate {
            if source.channels() == self.channels {
                Box::new(SampleRateConverter::new(source, self.sample_rate))
            } else if source.channels() == 1 {
                Box::new(ChannelConverter::new(
                    SampleRateConverter::new(source, self.sample_rate),
                    self.channels,
                ))
            } else {
                return Err("Number of channels do not match the output, and is not 1");
            }
        } else if source.channels() == self.channels {
            Box::new(source)
        } else if source.channels() == 1 {
            Box::new(ChannelConverter::new(source, self.channels))
        } else {
            return Err("Number of channels do not match the output, and is not 1");
        };

        let id = self.mixer.lock().unwrap().add_sound(sound);
        Ok(Sound {
            mixer: self.mixer.clone(),
            id,
        })
    }
}