craydate/sound/
headphone.rs

1use alloc::boxed::Box;
2use core::ffi::c_void;
3
4use super::Sound;
5use crate::capi_state::CApiState;
6
7type ActiveMicrophoneInnerBox = Box<dyn Fn(&[i16]) -> MicrophoneCallbackOutput + Sync>;
8
9pub enum MicrophoneCallbackOutput {
10  StopRecording,
11  ContinueRecording,
12}
13
14pub struct ActiveMicrophoneCallback {
15  generation: usize,
16  // Holds the data alive while the callback is set. The pointer in this box is passed to the C
17  // function from Playdate.
18  _c_function_data: Box<ActiveMicrophoneInnerBox>,
19}
20impl ActiveMicrophoneCallback {
21  pub(crate) fn set_active_callback<F: Fn(&[i16]) -> MicrophoneCallbackOutput + Sync + 'static>(
22    closure: F,
23    force_device_microphone: bool,
24  ) -> Self {
25    let gen = CApiState::get().headphone_change_generation.get() + 1;
26    CApiState::get().headphone_change_generation.set(gen);
27
28    // A wide pointer.
29    let inner: ActiveMicrophoneInnerBox = Box::new(closure);
30    // Boxed a second time to get a narrow pointer, which we can give to C, and unwrapped.
31    let c_function_data: *mut ActiveMicrophoneInnerBox = Box::into_raw(Box::new(inner));
32    // Ownership of the `c_function_data`.
33    let boxed_c_function_data = unsafe { Box::from_raw(c_function_data) };
34
35    unsafe extern "C" fn c_func(c_data: *mut c_void, buf: *mut i16, len: i32) -> i32 {
36      let closure = c_data as *mut ActiveMicrophoneInnerBox;
37      let out = (*closure)(core::slice::from_raw_parts(buf, len as usize));
38      match out {
39        MicrophoneCallbackOutput::ContinueRecording => 1,
40        MicrophoneCallbackOutput::StopRecording => 0,
41      }
42    }
43    unsafe {
44      Sound::fns().setMicCallback.unwrap()(
45        Some(c_func),
46        c_function_data as *mut c_void,
47        force_device_microphone as i32,
48      )
49    };
50
51    ActiveMicrophoneCallback {
52      generation: gen,
53      _c_function_data: boxed_c_function_data,
54    }
55  }
56}
57
58impl Drop for ActiveMicrophoneCallback {
59  fn drop(&mut self) {
60    // Use a generation tag to avoid unsetting the headphone callback if another callback was set
61    // before this object was dropped.
62    if self.generation == CApiState::get().headphone_change_generation.get() {
63      unsafe { Sound::fns().setMicCallback.unwrap()(None, core::ptr::null_mut(), false as i32) }
64    }
65  }
66}