libretro_core/
microphone.rs1use 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(¶ms) },
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}