Skip to main content

libretro_core/
microphone.rs

1//! Microphone service-interface wrappers.
2//!
3//! `MicrophoneInterface` models optional frontend microphone support, while
4//! `Microphone` is an RAII handle for an opened frontend microphone stream.
5
6use std::marker::PhantomData;
7use std::ptr::NonNull;
8
9use crate::raw;
10
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
12pub struct MicrophoneRateHz(u32);
13
14impl MicrophoneRateHz {
15    pub const fn new(rate: u32) -> Self {
16        Self(rate)
17    }
18
19    pub const fn frontend_default() -> Self {
20        Self(0)
21    }
22
23    pub const fn get(self) -> u32 {
24        self.0
25    }
26}
27
28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
29pub struct MicrophoneParams {
30    pub rate: MicrophoneRateHz,
31}
32
33impl MicrophoneParams {
34    pub const fn new(rate: MicrophoneRateHz) -> Self {
35        Self { rate }
36    }
37
38    pub const fn frontend_default() -> Self {
39        Self {
40            rate: MicrophoneRateHz::frontend_default(),
41        }
42    }
43
44    pub(crate) const fn into_raw(self) -> raw::retro_microphone_params {
45        raw::retro_microphone_params {
46            rate: self.rate.get(),
47        }
48    }
49
50    pub(crate) const fn from_raw(raw: raw::retro_microphone_params) -> Self {
51        Self {
52            rate: MicrophoneRateHz::new(raw.rate),
53        }
54    }
55}
56
57#[derive(Clone, Copy, Debug, Default)]
58pub struct MicrophoneInterface {
59    raw: raw::retro_microphone_interface,
60}
61
62impl MicrophoneInterface {
63    pub(crate) const fn from_raw(raw: raw::retro_microphone_interface) -> Self {
64        Self { raw }
65    }
66
67    pub const fn version(self) -> u32 {
68        self.raw.interface_version
69    }
70
71    pub const fn is_available(self) -> bool {
72        self.raw.interface_version >= raw::RETRO_MICROPHONE_INTERFACE_VERSION
73            && self.raw.open_mic.is_some()
74            && self.raw.close_mic.is_some()
75            && self.raw.get_params.is_some()
76            && self.raw.set_mic_state.is_some()
77            && self.raw.get_mic_state.is_some()
78            && self.raw.read_mic.is_some()
79    }
80
81    pub fn open_default(self) -> Option<Microphone> {
82        self.open_raw(None)
83    }
84
85    pub fn open(self, params: MicrophoneParams) -> Option<Microphone> {
86        self.open_raw(Some(params.into_raw()))
87    }
88
89    fn open_raw(self, params: Option<raw::retro_microphone_params>) -> Option<Microphone> {
90        let open_mic = self.raw.open_mic?;
91        let handle = match params {
92            Some(params) => unsafe { open_mic(&params) },
93            None => unsafe { open_mic(std::ptr::null()) },
94        };
95        Some(Microphone {
96            handle: NonNull::new(handle)?,
97            interface: self,
98            _not_send_sync: PhantomData,
99        })
100    }
101}
102
103#[derive(Debug, PartialEq, Eq)]
104pub enum MicrophoneReadError {
105    Unavailable,
106    ReturnedTooManySamples { returned: usize, capacity: usize },
107}
108
109#[derive(Debug)]
110pub struct Microphone {
111    handle: NonNull<raw::retro_microphone>,
112    interface: MicrophoneInterface,
113    _not_send_sync: PhantomData<*mut ()>,
114}
115
116impl Microphone {
117    pub fn params(&self) -> Option<MicrophoneParams> {
118        let get_params = self.interface.raw.get_params?;
119        let mut params = raw::retro_microphone_params::default();
120        if unsafe { get_params(self.handle.as_ptr().cast_const(), &mut params) } {
121            Some(MicrophoneParams::from_raw(params))
122        } else {
123            None
124        }
125    }
126
127    pub fn set_enabled(&mut self, enabled: bool) -> bool {
128        self.interface
129            .raw
130            .set_mic_state
131            .map(|set_mic_state| unsafe { set_mic_state(self.handle.as_ptr(), enabled) })
132            .unwrap_or(false)
133    }
134
135    pub fn enabled(&self) -> bool {
136        self.interface
137            .raw
138            .get_mic_state
139            .map(|get_mic_state| unsafe { get_mic_state(self.handle.as_ptr().cast_const()) })
140            .unwrap_or(false)
141    }
142
143    pub fn read_samples(&mut self, samples: &mut [i16]) -> Result<usize, MicrophoneReadError> {
144        let Some(read_mic) = self.interface.raw.read_mic else {
145            return Err(MicrophoneReadError::Unavailable);
146        };
147        let read = unsafe { read_mic(self.handle.as_ptr(), samples.as_mut_ptr(), samples.len()) };
148        let Ok(read) = usize::try_from(read) else {
149            return Err(MicrophoneReadError::Unavailable);
150        };
151        if read > samples.len() {
152            return Err(MicrophoneReadError::ReturnedTooManySamples {
153                returned: read,
154                capacity: samples.len(),
155            });
156        }
157        Ok(read)
158    }
159}
160
161impl Drop for Microphone {
162    fn drop(&mut self) {
163        if let Some(close_mic) = self.interface.raw.close_mic {
164            unsafe { close_mic(self.handle.as_ptr()) };
165        }
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::{MicrophoneInterface, MicrophoneParams, MicrophoneRateHz, MicrophoneReadError};
172
173    #[test]
174    fn microphone_params_preserve_frontend_default_rate() {
175        assert_eq!(MicrophoneRateHz::new(16_000).get(), 16_000);
176        assert_eq!(MicrophoneRateHz::frontend_default().get(), 0);
177        assert_eq!(MicrophoneParams::frontend_default().rate.get(), 0);
178    }
179
180    #[test]
181    fn empty_microphone_interface_reports_unavailable() {
182        let interface = MicrophoneInterface::default();
183
184        assert_eq!(interface.version(), 0);
185        assert!(!interface.is_available());
186        assert!(interface.open_default().is_none());
187        assert!(
188            interface
189                .open(MicrophoneParams::frontend_default())
190                .is_none()
191        );
192    }
193
194    #[test]
195    fn microphone_read_error_preserves_oversized_frontend_result() {
196        assert_eq!(
197            MicrophoneReadError::ReturnedTooManySamples {
198                returned: 4,
199                capacity: 2,
200            },
201            MicrophoneReadError::ReturnedTooManySamples {
202                returned: 4,
203                capacity: 2,
204            }
205        );
206    }
207}