xuko_audio/
lib.rs

1//! Small wrapper around [`rodio`] for use with [xuko-ecs](https://crates.io/crates/xuko-ecs)
2
3use xuko_core::bytes::Bytes;
4
5pub use std::time::Duration;
6
7pub(crate) fn decode<B: AsRef<[u8]>>(buffer: B) -> Result<impl rodio::Source, AudioError> {
8    let cursor = std::io::BufReader::new(std::io::Cursor::new(buffer.as_ref().to_vec()));
9    let source = rodio::Decoder::try_from(cursor)?;
10    Ok(source)
11}
12
13/// An error that can occur when using [`AudioPlayer`] or [`AudioSink`]
14#[derive(Debug, thiserror::Error)]
15pub enum AudioError {
16    /// I/O Error.
17    #[error("IO Error: {0}")]
18    IOError(#[from] std::io::Error),
19
20    /// Error that occurs when opening a stream.
21    #[error("Stream error: {0}")]
22    StreamError(#[from] rodio::StreamError),
23
24    /// Error that occurs when decoding a sound buffer.
25    #[error("Decoder error: {0}")]
26    DecoderError(#[from] rodio::decoder::DecoderError),
27
28    /// Error that occurs when playing a sound.
29    #[error("Play error: {0}")]
30    PlayError(#[from] rodio::PlayError),
31
32    /// Error that occurs when setting the position of the sink.
33    #[error("Seek error: {0}")]
34    SeekError(#[from] rodio::source::SeekError),
35}
36
37/// Structure to construct [`AudioSink`]s
38pub struct AudioPlayer {
39    buffer: Bytes,
40}
41
42impl AudioPlayer {
43    /// Create an [`AudioPlayer`] from a buffer
44    pub fn new<B: AsRef<[u8]>>(buffer: B) -> Self {
45        Self {
46            buffer: Bytes::from(buffer),
47        }
48    }
49
50    /// Create the [`AudioSink`], this can be called multiple times to reuse the audio buffer
51    pub fn spawn(&self, settings: PlaybackSettings) -> Result<AudioSink, AudioError> {
52        AudioSink::new(&self.buffer, settings)
53    }
54}
55
56/// Audio stream
57pub struct AudioSink {
58    sink: rodio::Sink,
59    _stream_handle: rodio::OutputStream,
60}
61
62impl AudioSink {
63    pub(crate) fn new(buffer: &[u8], settings: PlaybackSettings) -> Result<Self, AudioError> {
64        let mut stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
65        stream_handle.log_on_drop(false);
66
67        let sink = rodio::Sink::connect_new(stream_handle.mixer());
68        sink.append(decode(buffer.to_vec())?);
69        sink.pause();
70        let _ = sink.try_seek(Duration::new(0, 0));
71
72        let s = Self {
73            sink,
74            _stream_handle: stream_handle,
75        };
76        settings.apply(&s);
77
78        Ok(s)
79    }
80
81    /// Append a stream to the [`AudioSink`]
82    pub fn add(&mut self, buffer: &[u8]) -> Result<(), AudioError> {
83        self.sink.append(decode(buffer.to_vec())?);
84        Ok(())
85    }
86
87    /// Seek to `pos` in the audio stream
88    pub fn seek(&self, pos: Duration) -> Result<(), AudioError> {
89        self.sink.try_seek(pos).map_err(AudioError::SeekError)
90    }
91
92    /// Get the position the audio stream is at
93    pub fn get_pos(&self) -> Duration {
94        self.sink.get_pos()
95    }
96
97    /// Plays the audio stream, no effect if it is already playing
98    pub fn play(&self) {
99        self.sink.play();
100    }
101
102    /// Pauses the audio stream, no effect if it is already paused
103    pub fn pause(&self) {
104        self.sink.pause();
105    }
106
107    /// Returns whether the audio stream is paused or not
108    pub fn is_paused(&self) -> bool {
109        self.sink.is_paused()
110    }
111
112    /// Toggle between playing and paused
113    pub fn pause_play(&self) {
114        if self.sink.is_paused() {
115            self.sink.play();
116        } else {
117            self.sink.pause();
118        }
119    }
120
121    /// Blocks the current thread until the sound finishes
122    pub fn sleep_until_end(&self) {
123        self.sink.sleep_until_end();
124    }
125
126    /// Get the volume of the audio stream, in decibels
127    pub fn volume(&self) -> f32 {
128        Volume::linear_to_decibels(self.sink.volume())
129    }
130
131    /// Set the volume of the audio stream to `value`, returns the previous volume
132    pub fn set_volume(&self, value: Volume) -> f32 {
133        let previous = self.volume();
134        self.sink.set_volume(value.to_linear());
135        previous
136    }
137
138    /// Get the speed of the audio stream
139    pub fn speed(&self) -> f32 {
140        self.sink.speed()
141    }
142
143    /// Set the speed of the audio stream to `value`, returns the previous speed
144    pub fn set_speed(&self, value: f32) -> f32 {
145        let previous = self.speed();
146        self.sink.set_speed(value);
147        previous
148    }
149
150    /// Stops all audio playback
151    pub fn stop(&self) {
152        self.sink.stop();
153    }
154}
155
156/// Volume for [`AudioSink`]
157#[derive(Default, Clone, Copy, PartialEq, PartialOrd)]
158pub enum Volume {
159    #[default]
160    /// Muted volume
161    Muted,
162    /// The volume in decibels
163    Decibels(f32),
164}
165
166impl Volume {
167    /// Convert linear volume to decibels volume
168    pub fn linear_to_decibels(x: f32) -> f32 {
169        20.0 * x.abs().log10()
170    }
171
172    /// Convert decibels volume to linear volume
173    pub fn decibels_to_linear(x: f32) -> f32 {
174        10.0f32.powf(x / 20.0)
175    }
176
177    /// Convert this [`Volume`] to linear
178    pub fn to_linear(&self) -> f32 {
179        match self {
180            Self::Muted => Self::decibels_to_linear(f32::NEG_INFINITY),
181            Self::Decibels(d) => Self::decibels_to_linear(*d),
182        }
183    }
184}
185
186/// Options for creating a [`AudioSink`]
187#[derive(Clone, Copy)]
188pub struct PlaybackSettings {
189    /// Volume of the [`AudioSink`]
190    pub volume: Volume,
191    /// Speed of the [`AudioSink`]
192    pub speed: f32,
193}
194
195impl PlaybackSettings {
196    /// Apply the [`PlaybackSettings`] on a [`AudioSink`]
197    pub fn apply(&self, sink: &AudioSink) {
198        sink.set_volume(self.volume);
199        sink.set_speed(self.speed);
200    }
201}
202
203impl Default for PlaybackSettings {
204    fn default() -> Self {
205        Self {
206            volume: Volume::Decibels(0.0),
207            speed: 1.0,
208        }
209    }
210}