cog_task/resource/audio/
mod.rs

1use crate::server::Config;
2use eyre::{eyre, Result};
3use serde::{Deserialize, Serialize};
4use std::fmt::{Debug, Formatter};
5use std::path::Path;
6use std::time::Duration;
7
8#[cfg(feature = "rodio")]
9mod rodio;
10
11#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
12#[serde(rename_all = "snake_case")]
13pub enum AudioBackend {
14    None,
15    Inherit,
16    #[cfg(feature = "rodio")]
17    Rodio,
18}
19
20#[derive(Clone)]
21pub enum AudioBuffer {
22    None,
23    #[cfg(feature = "rodio")]
24    Rodio(rodio::Buffer),
25}
26
27pub enum AudioSink {
28    None,
29    #[cfg(feature = "rodio")]
30    Rodio(rodio::Sink),
31}
32
33pub enum AudioDevice {
34    None,
35    #[cfg(feature = "rodio")]
36    Rodio(rodio::Device),
37}
38
39impl AudioDevice {
40    pub fn try_clone(&self) -> Result<Self> {
41        match self {
42            AudioDevice::None => Ok(AudioDevice::None),
43            #[cfg(feature = "rodio")]
44            AudioDevice::Rodio(_) => rodio::Device::new().map(AudioDevice::Rodio),
45        }
46    }
47}
48
49impl Default for AudioBackend {
50    #[inline(always)]
51    fn default() -> Self {
52        AudioBackend::Inherit
53    }
54}
55
56impl AudioBackend {
57    pub fn or(&self, other: &Self) -> Self {
58        if let Self::Inherit = self {
59            *other
60        } else {
61            *self
62        }
63    }
64}
65
66#[derive(Copy, Clone, Deserialize, Serialize)]
67#[serde(untagged)]
68pub enum Volume {
69    Inherit,
70    Value(f32),
71}
72
73impl Default for Volume {
74    #[inline(always)]
75    fn default() -> Self {
76        Volume::Inherit
77    }
78}
79
80impl Debug for Volume {
81    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
82        if let Volume::Value(vol) = self {
83            write!(f, "{vol}")
84        } else {
85            write!(f, "inherit")
86        }
87    }
88}
89
90impl Volume {
91    pub fn and(&self, other: &Self) -> Self {
92        match (self, other) {
93            (&Self::Value(x), &Self::Value(y)) => Self::Value(x * y),
94            (Self::Value(_), _) => *self,
95            (Self::Inherit, _) => *other,
96        }
97    }
98
99    pub fn or(&self, other: &Self) -> Self {
100        if let Self::Inherit = self {
101            *other
102        } else {
103            *self
104        }
105    }
106
107    pub fn value(&self) -> f32 {
108        if let &Self::Value(x) = self {
109            x
110        } else {
111            1.0
112        }
113    }
114}
115
116impl From<f32> for Volume {
117    fn from(v: f32) -> Self {
118        Self::Value(v.max(0.0))
119    }
120}
121
122#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
123#[serde(rename_all = "snake_case")]
124pub enum UseTrigger {
125    Inherit,
126    Yes,
127    No,
128}
129
130impl Default for UseTrigger {
131    #[inline(always)]
132    fn default() -> Self {
133        UseTrigger::Inherit
134    }
135}
136
137impl UseTrigger {
138    pub fn or(&self, other: &Self) -> Self {
139        if let Self::Inherit = self {
140            *other
141        } else {
142            *self
143        }
144    }
145
146    pub fn value(&self) -> bool {
147        !matches!(self, UseTrigger::No)
148    }
149}
150
151#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
152#[serde(rename_all = "snake_case")]
153pub enum TimePrecision {
154    Inherit,
155    RespectIntervals,
156    RespectBoundaries,
157}
158
159impl Default for TimePrecision {
160    #[inline(always)]
161    fn default() -> Self {
162        TimePrecision::Inherit
163    }
164}
165
166impl TimePrecision {
167    pub fn or(&self, other: &Self) -> Self {
168        if let Self::Inherit = self {
169            *other
170        } else {
171            *self
172        }
173    }
174}
175
176#[allow(unused_variables)]
177pub fn audio_from_file(path: &Path, config: &Config) -> Result<AudioBuffer> {
178    match config.audio_backend() {
179        AudioBackend::None => Err(eyre!("Cannot load audio file with backend=None.")),
180        AudioBackend::Inherit => Err(eyre!("Cannot load audio file with backend=Inherit.")),
181        #[cfg(feature = "rodio")]
182        AudioBackend::Rodio => rodio::Buffer::new(path, config).map(AudioBuffer::Rodio),
183    }
184}
185
186impl AudioBuffer {
187    pub fn duration(&self) -> Duration {
188        match self {
189            AudioBuffer::None => Duration::default(),
190            #[cfg(feature = "rodio")]
191            AudioBuffer::Rodio(x) => x.duration(),
192        }
193    }
194
195    pub fn sample_rate(&self) -> u32 {
196        match self {
197            AudioBuffer::None => 0,
198            #[cfg(feature = "rodio")]
199            AudioBuffer::Rodio(x) => x.sample_rate(),
200        }
201    }
202
203    pub fn channels(&self) -> u16 {
204        match self {
205            AudioBuffer::None => 0,
206            #[cfg(feature = "rodio")]
207            AudioBuffer::Rodio(x) => x.channels(),
208        }
209    }
210
211    pub fn interlace(self, other: AudioBuffer) -> Result<AudioBuffer> {
212        match (self, other) {
213            #[cfg(feature = "rodio")]
214            (AudioBuffer::Rodio(x), AudioBuffer::Rodio(y)) => {
215                x.interlace(y).map(AudioBuffer::Rodio)
216            }
217            (_, _) => Err(eyre!("Cannot interlace audio buffers of different types.")),
218        }
219    }
220
221    pub fn drop_last(self) -> Result<AudioBuffer> {
222        match self {
223            AudioBuffer::None => Err(eyre!("Cannot interlace audio buffers of different types.")),
224            #[cfg(feature = "rodio")]
225            AudioBuffer::Rodio(x) => x.drop_last().map(AudioBuffer::Rodio),
226        }
227    }
228}
229
230impl AudioSink {
231    pub fn pause(&mut self) -> Result<()> {
232        match self {
233            AudioSink::None => Err(eyre!("Cannot pause audio sink with backend=None.")),
234            #[cfg(feature = "rodio")]
235            AudioSink::Rodio(sink) => {
236                sink.pause();
237                Ok(())
238            }
239        }
240    }
241
242    #[allow(unused_variables)]
243    pub fn set_volume(&mut self, volume: f32) -> Result<()> {
244        match self {
245            AudioSink::None => Err(eyre!("Cannot pause audio sink with backend=None.")),
246            #[cfg(feature = "rodio")]
247            AudioSink::Rodio(sink) => {
248                sink.set_volume(volume);
249                Ok(())
250            }
251        }
252    }
253
254    pub fn queue(&mut self, buffer: AudioBuffer) -> Result<()> {
255        match (self, buffer) {
256            (AudioSink::None, _) => Err(eyre!("Cannot queue audio on sink=None.")),
257            #[cfg(feature = "rodio")]
258            (AudioSink::Rodio(sink), AudioBuffer::Rodio(buffer)) => {
259                sink.queue(buffer);
260                Ok(())
261            }
262            #[allow(unreachable_patterns)]
263            (_, _) => Err(eyre!("Cannot queue audio on incompatible sink.")),
264        }
265    }
266
267    pub fn repeat(&mut self, buffer: AudioBuffer) -> Result<()> {
268        match (self, buffer) {
269            (AudioSink::None, _) => Err(eyre!("Cannot repeat audio on sink=None.")),
270            #[cfg(feature = "rodio")]
271            (AudioSink::Rodio(sink), AudioBuffer::Rodio(buffer)) => {
272                sink.repeat(buffer);
273                Ok(())
274            }
275            #[allow(unreachable_patterns)]
276            (_, _) => Err(eyre!("Cannot repeat audio on incompatible sink.")),
277        }
278    }
279
280    pub fn play(&mut self) -> Result<()> {
281        match self {
282            AudioSink::None => Err(eyre!("Cannot play audio sink with backend=None.")),
283            #[cfg(feature = "rodio")]
284            AudioSink::Rodio(sink) => {
285                sink.play();
286                Ok(())
287            }
288        }
289    }
290
291    pub fn stop(&mut self) -> Result<()> {
292        match self {
293            AudioSink::None => Err(eyre!("Cannot stop audio sink with backend=None.")),
294            #[cfg(feature = "rodio")]
295            AudioSink::Rodio(sink) => {
296                sink.stop();
297                Ok(())
298            }
299        }
300    }
301
302    pub fn empty(&self) -> Result<bool> {
303        match self {
304            AudioSink::None => Ok(true),
305            #[cfg(feature = "rodio")]
306            AudioSink::Rodio(sink) => Ok(sink.empty()),
307        }
308    }
309
310    pub fn detach(self) -> Result<()> {
311        match self {
312            AudioSink::None => Ok(()),
313            #[cfg(feature = "rodio")]
314            AudioSink::Rodio(sink) => {
315                sink.detach();
316                Ok(())
317            }
318        }
319    }
320}
321
322impl AudioDevice {
323    pub fn new(config: &Config) -> Result<Self> {
324        match config.audio_backend() {
325            AudioBackend::None => Err(eyre!("Cannot obtain audio device with backend=None.")),
326            AudioBackend::Inherit => Err(eyre!("Cannot obtain audio device with backend=None.")),
327            #[cfg(feature = "rodio")]
328            AudioBackend::Rodio => rodio::Device::new().map(Self::Rodio),
329        }
330    }
331
332    pub fn sink(&self) -> Result<AudioSink> {
333        match self {
334            AudioDevice::None => Err(eyre!("Cannot create audio sink with backend=None.")),
335            #[cfg(feature = "rodio")]
336            AudioDevice::Rodio(device) => device.sink().map(AudioSink::Rodio),
337        }
338    }
339}