lingon/
audio.rs

1use std::sync::{Arc, RwLock};
2
3use crate::asset::{self, audio::Samples};
4use crate::random::{self, Distribute};
5
6use luminance_sdl2::sdl2::Sdl;
7use luminance_sdl2::sdl2::audio::{AudioCallback, AudioDevice, AudioSpecDesired};
8
9pub const SAMPLE_RATE: i32 = 48000;
10
11macro_rules! impl_builder {
12    ( $( $field:ident : $type:ty ),* $(,)? ) => {
13        $(
14            pub fn $field(mut self, $field: $type) -> Self {
15                self.$field = $field;
16                self
17            }
18        )*
19    }
20}
21
22/// A sound that is playing or can be played.
23#[derive(Clone)]
24pub struct AudioSource {
25    /// Which specific sample we're currently on.
26    position: f32,
27    /// Whether we should loop when the sample is done.
28    looping: bool,
29    /// The actual samples.
30    samples: Arc<RwLock<Samples>>,
31
32    gain: f32,
33    gain_variance: f32,
34    pitch: f32,
35    pitch_variance: f32,
36
37    /// If we should remove this source when we get the opportunity.
38    ///
39    /// This gets set if
40    /// a) the audio is done playing and it doesn't loop,
41    /// b) it is requested by the user.
42    remove: bool,
43}
44
45impl AudioSource {
46    pub fn new(audio: &asset::Audio) -> Self {
47        Self {
48            position: 0.0,
49            looping: false,
50            samples: audio.samples(),
51            gain: 1.0,
52            gain_variance: 0.0,
53            pitch: 1.0,
54            pitch_variance: 0.0,
55            remove: false,
56        }
57    }
58
59    impl_builder!(
60        looping: bool,
61        gain: f32,
62        gain_variance: f32,
63        pitch: f32,
64        pitch_variance: f32,
65    );
66}
67
68/// The audio subsystem.
69pub struct Audio {
70    sources: Vec<AudioSource>,
71    gain: f32,
72}
73
74impl Audio {
75    pub fn init(sdl: &Sdl) -> AudioDevice<Self> {
76        let audio_subsystem = sdl.audio().unwrap();
77        let desired = AudioSpecDesired {
78            freq: Some(SAMPLE_RATE),
79            channels: Some(2),
80            samples: Some(1024),
81        };
82
83        audio_subsystem.open_playback(None, &desired, |spec| {
84            assert_eq!(spec.freq, SAMPLE_RATE); //TODO handle differing sample rates gracefully
85            Self {
86                sources: Vec::new(),
87                gain: 1.0,
88            }
89        }).unwrap()
90    }
91
92    /// Start playing a new source.
93    ///
94    /// The source can be created via [AudioSource::new] and modified by builders on [AudioSource]
95    /// (like [AudioSource::looping]).
96    ///
97    /// # Panics
98    ///
99    /// Panics if pitch <= 0.0 after applying pitch variance.
100    pub fn play(&mut self, mut source: AudioSource) {
101        if source.gain_variance != 0.0 {
102            source.gain += random::Uniform.between(-source.gain_variance, source.gain_variance);
103        }
104        if source.pitch_variance != 0.0 {
105            source.pitch += random::Uniform.between(-source.pitch_variance, source.pitch_variance);
106        }
107        assert!(source.pitch > 0.0);
108        self.sources.push(source);
109    }
110
111    pub fn gain(&self) -> f32 {
112        self.gain
113    }
114
115    pub fn gain_mut(&mut self) -> &mut f32 {
116        &mut self.gain
117    }
118}
119
120impl AudioCallback for Audio {
121    type Channel = f32;
122
123    fn callback(&mut self, out: &mut [Self::Channel]) {
124        // Clear the buffer.
125        for x in out.iter_mut() {
126            *x = 0.0;
127        }
128
129        'sources: for source in self.sources.iter_mut() {
130            let samples = source.samples.read().unwrap();
131            for x in out.iter_mut() {
132                // Move forward
133                source.position += source.pitch * samples.sample_rate() as f32 / SAMPLE_RATE as f32 ;
134                let position = source.position as usize; // Truncates
135                let data = samples.data();
136                let num_samples = data.len();
137
138                // Check if we're done
139                if num_samples <= position && !source.looping {
140                    source.remove = true;
141                    continue 'sources;
142                }
143
144                let (a, b) = if source.looping {
145                    // Keep the decimal on source.position
146                    source.position -= (source.position as usize - position) as f32;
147                    (&data[position % num_samples], &data[(position + 1) % num_samples])
148                } else {
149                    (data.get(position).unwrap_or(&0.0), data.get(position + 1).unwrap_or(&0.0))
150                };
151
152                // Write data
153                let fade = source.position.rem_euclid(1.0);
154                *x += (1.0 - fade) * a * source.gain * self.gain;
155                *x +=       (fade) * b * source.gain * self.gain;
156            }
157        }
158
159        // Remove sources that have finished.
160        let mut i = 0;
161        while i != self.sources.len() {
162            if self.sources[i].remove {
163                self.sources.remove(i);
164            } else {
165                i += 1;
166            }
167        }
168    }
169}