playdate_sound/
lib.rs

1#![cfg_attr(not(test), no_std)]
2#![feature(const_trait_impl)]
3
4use core::ffi::c_int;
5use core::ffi::c_void;
6
7use sys::ffi::AudioSourceFunction;
8use sys::traits::AsRaw;
9
10extern crate sys;
11extern crate alloc;
12
13pub mod error;
14pub mod player;
15pub mod sample;
16pub mod source;
17
18// TODO: Sound api: channel, synth, sequence, effect, lfo, envelope, callbacks, etc..
19
20
21pub mod prelude {
22	pub use crate::error::ApiError as SndApiError;
23	pub use crate::error::Error as SndError;
24
25	pub use crate::player;
26	pub use crate::sample;
27}
28
29
30#[derive(Debug, Clone, Copy)]
31pub struct Sound<Api = api::Default>(Api);
32
33impl Sound<api::Default> {
34	/// Creates default [`Sound`] without type parameter requirement.
35	///
36	/// Uses ZST [`api::Default`].
37	#[allow(non_snake_case)]
38	pub fn Default() -> Self { Self(Default::default()) }
39}
40
41impl Sound<api::Cache> {
42	/// Creates [`Sound`] without type parameter requirement.
43	///
44	/// Uses [`api::Cache`].
45	#[allow(non_snake_case)]
46	pub fn Cached() -> Self { Self(Default::default()) }
47}
48
49impl<Api: Default + api::Api> Default for Sound<Api> {
50	fn default() -> Self { Self(Default::default()) }
51}
52
53impl<Api: Default + api::Api> Sound<Api> {
54	pub fn new() -> Self { Self(Default::default()) }
55}
56
57impl<Api: api::Api> Sound<Api> {
58	pub fn new_with(api: Api) -> Self { Self(api) }
59}
60
61impl<Api: api::Api> Sound<Api> {
62	/// Returns the sound engine’s current time value, in units of sample frames, `44100` per second.
63	///
64	/// Equivalent to [`sys::ffi::playdate_sound::getCurrentTime`]
65	#[doc(alias = "sys::ffi::playdate_sound::getCurrentTime")]
66	pub fn current_time(&self) -> u32 {
67		let f = self.0.get_current_time();
68		unsafe { f() }
69	}
70
71
72	/// If `headphone` is `Some`, the value is set to 1 if headphones are currently plugged in.
73	///
74	/// Likewise, `mic` is set if the headphones include a microphone.
75	///
76	/// Example:
77	/// ```no_run
78	/// let mut headphone = Some(0);
79	/// let mut mic = Some(0);
80	/// sound.headphone_state(headphone.as_mut(), mic.as_mut());
81	/// println!( "{}/{}", current_frame.unwrap(), mic.unwrap());
82	/// ```
83	/// See also [`Sound::set_headphone_state_change_callback`].
84	#[doc(alias = "sys::ffi::playdate_sound::getHeadphoneState")]
85	#[inline(always)]
86	pub fn headphone_state(&self, headphone: Option<&mut c_int>, mic: Option<&mut c_int>) {
87		self.set_headphone_state_change_callback(headphone, mic, None)
88	}
89
90	/// If `headphone` is `Some`, the value is set to 1 if headphones are currently plugged in.
91	///
92	/// Likewise, `mic` is set if the headphones include a microphone.
93	///
94	/// If `change_callback` is provided, it will be called when the headset or mic status changes,
95	/// and audio output will not automatically switch from speaker to headphones when headphones are plugged in (and vice versa).
96	///
97	/// In this case, the callback should use [`Sound::set_outputs_active`] to change the output if needed.
98	///
99	/// Equivalent to [`sys::ffi::playdate_sound::getHeadphoneState`]
100	#[doc(alias = "sys::ffi::playdate_sound::getHeadphoneState")]
101	pub fn set_headphone_state_change_callback(&self,
102	                                           headphone: Option<&mut c_int>,
103	                                           mic: Option<&mut c_int>,
104	                                           change_callback: Option<unsafe extern "C" fn(headphone: c_int,
105	                                                                       mic: c_int)>) {
106		use core::ptr::null_mut;
107
108		let f = self.0.get_headphone_state();
109		unsafe {
110			f(
111			  headphone.map_or(null_mut() as _, |v| v as *mut _),
112			  mic.map_or(null_mut() as _, |v| v as *mut _),
113			  change_callback,
114			)
115		}
116	}
117
118	/// Force audio output to the given outputs, regardless of headphone status.
119	///
120	/// Equivalent to [`sys::ffi::playdate_sound::setOutputsActive`]
121	#[doc(alias = "sys::ffi::playdate_sound::setOutputsActive")]
122	pub fn set_outputs_active(&self, headphone: bool, speaker: bool) {
123		let f = self.0.set_outputs_active();
124		unsafe { f(headphone.into(), speaker.into()) }
125	}
126
127	/// The callback function you pass in will be called every audio render cycle.
128	///
129	/// ```no_run
130	/// // AudioSourceFunction:
131	/// unsafe extern "C" fn(context: *mut c_void, left: *mut i16, right: *mut i16, len: c_int) -> c_int
132	/// ```
133	/// This function should fill the passed-in `left` buffer (and `right` if it’s a stereo source)
134	/// with `len` samples each and return 1,
135	/// or return 0 if the source is silent through the cycle.
136	///
137	/// Equivalent to [`sys::ffi::playdate_sound::addSource`]
138	#[doc(alias = "sys::ffi::playdate_sound::addSource")]
139	pub fn add_source_raw(&self,
140	                      callback: AudioSourceFunction,
141	                      context: *mut c_void,
142	                      stereo: bool)
143	                      -> source::SoundSource {
144		let f = self.0.add_source();
145		unsafe { f(callback, context, stereo.into()) }.into()
146	}
147
148	/// Removes the given [`SoundSource`](source::SoundSource) object from its channel,
149	/// whether it’s in the default channel or a channel created with [`Sound::add_channel`].
150	///
151	/// Returns `true` if a source was removed, `false` if the source wasn’t in a channel.
152	///
153	/// Equivalent to [`sys::ffi::playdate_sound::removeSource`]
154	#[doc(alias = "sys::ffi::playdate_sound::removeSource")]
155	pub fn remove_source(&self, source: &source::SoundSource) -> bool {
156		let f = self.0.remove_source();
157		unsafe { f(source.as_raw()) == 1 }
158	}
159
160
161	// /// Returns the default channel, where sound sources play
162	// /// if they haven’t been explicitly assigned to a different channel.
163	// ///
164	// /// Equivalent to [`sys::ffi::playdate_sound::getDefaultChannel`]
165	// #[doc(alias = "sys::ffi::playdate_sound::getDefaultChannel")]
166	// pub fn default_channel(&self) -> *mut SoundChannel {
167	// 	let f = self.0.get_default_channel();
168	// 	unsafe { f() }
169	// }
170
171	// /// Adds the given channel to the sound engine.
172	// ///
173	// /// Returns 1 if the channel was added, 0 if it was already in the engine.
174	// ///
175	// /// Equivalent to [`sys::ffi::playdate_sound::addChannel`]
176	// #[doc(alias = "sys::ffi::playdate_sound::addChannel")]
177	// pub fn add_channel(&self, channel: *mut SoundChannel) -> c_int {
178	// 	let f = self.0.add_channel();
179	// 	unsafe { f(channel) }
180	// }
181
182	// /// Removes the given channel from the sound engine.
183	// ///
184	// /// Returns 1 if the channel was successfully removed, 0 if the channel is the default channel or hadn’t been previously added.
185	// ///
186	// /// Equivalent to [`sys::ffi::playdate_sound::removeChannel`]
187	// #[doc(alias = "sys::ffi::playdate_sound::removeChannel")]
188	// pub fn remove_channel(&self, channel: *mut SoundChannel) -> c_int {
189	// 	let f = self.0.remove_channel();
190	// 	unsafe { f(channel) }
191	// }
192
193	// /// The callback you pass in will be called every audio cycle.
194	// ///
195	// /// Equivalent to [`sys::ffi::playdate_sound::setMicCallback`]
196	// #[doc(alias = "sys::ffi::playdate_sound::setMicCallback")]
197	// pub fn set_mic_callback(&self, callback: RecordCallback, context: *mut c_void, force_internal: c_int) {
198	// 	let f = self.0.set_mic_callback();
199	// 	unsafe { f(callback, context, force_internal) }
200	// }
201}
202
203
204pub mod api {
205	use core::ffi::c_int;
206	use core::ffi::c_void;
207	use core::ptr::NonNull;
208	use sys::ffi::*;
209
210
211	/// Default sound api end-point, ZST.
212	///
213	/// All calls approximately costs ~3 derefs.
214	#[derive(Debug, Clone, Copy, core::default::Default)]
215	pub struct Default;
216	impl Api for Default {}
217
218
219	/// Cached sound api end-point.
220	///
221	/// Stores one reference, so size on stack is eq `usize`.
222	///
223	/// All calls approximately costs ~1 deref.
224	#[derive(Clone, Copy)]
225	#[cfg_attr(feature = "bindings-derive-debug", derive(Debug))]
226	pub struct Cache(&'static playdate_sound);
227
228	impl core::default::Default for Cache {
229		fn default() -> Self { Self(sys::api!(sound)) }
230	}
231
232	impl From<*const playdate_sound> for Cache {
233		#[inline(always)]
234		fn from(ptr: *const playdate_sound) -> Self { Self(unsafe { ptr.as_ref() }.expect("sp")) }
235	}
236
237	impl From<&'static playdate_sound> for Cache {
238		#[inline(always)]
239		fn from(r: &'static playdate_sound) -> Self { Self(r) }
240	}
241
242	impl From<NonNull<playdate_sound>> for Cache {
243		#[inline(always)]
244		fn from(ptr: NonNull<playdate_sound>) -> Self { Self(unsafe { ptr.as_ref() }) }
245	}
246
247	impl From<&'_ NonNull<playdate_sound>> for Cache {
248		#[inline(always)]
249		fn from(ptr: &NonNull<playdate_sound>) -> Self { Self(unsafe { ptr.as_ref() }) }
250	}
251
252
253	impl Api for Cache {
254		#[inline(always)]
255		fn channel(&self) -> &'static playdate_sound_channel {
256			unsafe { self.0.channel.as_ref() }.expect("channel")
257		}
258		#[inline(always)]
259		fn fileplayer(&self) -> &'static playdate_sound_fileplayer {
260			unsafe { self.0.fileplayer.as_ref() }.expect("fileplayer")
261		}
262		#[inline(always)]
263		fn sample(&self) -> &'static playdate_sound_sample { unsafe { self.0.sample.as_ref() }.expect("sample") }
264		#[inline(always)]
265		fn sampleplayer(&self) -> &'static playdate_sound_sampleplayer {
266			unsafe { self.0.sampleplayer.as_ref() }.expect("sampleplayer")
267		}
268		#[inline(always)]
269		fn synth(&self) -> &'static playdate_sound_synth { unsafe { self.0.synth.as_ref() }.expect("synth") }
270		#[inline(always)]
271		fn sequence(&self) -> &'static playdate_sound_sequence {
272			unsafe { self.0.sequence.as_ref() }.expect("sequence")
273		}
274		#[inline(always)]
275		fn effect(&self) -> &'static playdate_sound_effect { unsafe { self.0.effect.as_ref() }.expect("effect") }
276		#[inline(always)]
277		fn lfo(&self) -> &'static playdate_sound_lfo { unsafe { self.0.lfo.as_ref() }.expect("lfo") }
278		#[inline(always)]
279		fn envelope(&self) -> &'static playdate_sound_envelope {
280			unsafe { self.0.envelope.as_ref() }.expect("envelope")
281		}
282		#[inline(always)]
283		fn source(&self) -> &'static playdate_sound_source { unsafe { self.0.source.as_ref() }.expect("source") }
284		#[inline(always)]
285		fn control_signal(&self) -> &'static playdate_control_signal {
286			unsafe { self.0.controlsignal.as_ref() }.expect("controlsignal")
287		}
288		#[inline(always)]
289		fn track(&self) -> &'static playdate_sound_track { unsafe { self.0.track.as_ref() }.expect("track") }
290		#[inline(always)]
291		fn instrument(&self) -> &'static playdate_sound_instrument {
292			unsafe { self.0.instrument.as_ref() }.expect("instrument")
293		}
294		#[inline(always)]
295		fn signal(&self) -> &'static playdate_sound_signal { unsafe { self.0.signal.as_ref() }.expect("signal") }
296
297
298		fn get_current_time(&self) -> unsafe extern "C" fn() -> u32 {
299			self.0.getCurrentTime.expect("getCurrentTime")
300		}
301
302		fn add_source(
303			&self)
304			-> unsafe extern "C" fn(callback: AudioSourceFunction,
305			                        context: *mut c_void,
306			                        stereo: c_int) -> *mut SoundSource {
307			self.0.addSource.expect("addSource")
308		}
309
310		fn get_default_channel(&self) -> unsafe extern "C" fn() -> *mut SoundChannel {
311			self.0.getDefaultChannel.expect("getDefaultChannel")
312		}
313
314		fn add_channel(&self) -> unsafe extern "C" fn(channel: *mut SoundChannel) -> c_int {
315			self.0.addChannel.expect("addChannel")
316		}
317
318		fn remove_channel(&self) -> unsafe extern "C" fn(channel: *mut SoundChannel) -> c_int {
319			self.0.removeChannel.expect("removeChannel")
320		}
321
322		fn set_mic_callback(
323			&self)
324			-> unsafe extern "C" fn(callback: RecordCallback, context: *mut c_void, source: MicSource) -> c_int {
325			self.0.setMicCallback.expect("setMicCallback")
326		}
327
328		fn get_headphone_state(
329			&self)
330			-> unsafe extern "C" fn(headphone: *mut c_int,
331			                        headsetmic: *mut c_int,
332			                        changeCallback: Option<unsafe extern "C" fn(headphone: c_int, mic: c_int)>) {
333			self.0.getHeadphoneState.expect("getHeadphoneState")
334		}
335
336		fn set_outputs_active(&self) -> unsafe extern "C" fn(headphone: c_int, speaker: c_int) {
337			self.0.setOutputsActive.expect("setOutputsActive")
338		}
339
340		fn remove_source(&self) -> unsafe extern "C" fn(source: *mut SoundSource) -> c_int {
341			self.0.removeSource.expect("removeSource")
342		}
343	}
344
345
346	pub trait Api {
347		fn channel(&self) -> &'static playdate_sound_channel { sys::api!(sound.channel) }
348		fn fileplayer(&self) -> &'static playdate_sound_fileplayer { sys::api!(sound.fileplayer) }
349		fn sample(&self) -> &'static playdate_sound_sample { sys::api!(sound.sample) }
350		fn sampleplayer(&self) -> &'static playdate_sound_sampleplayer { sys::api!(sound.sampleplayer) }
351		fn synth(&self) -> &'static playdate_sound_synth { sys::api!(sound.synth) }
352		fn sequence(&self) -> &'static playdate_sound_sequence { sys::api!(sound.sequence) }
353		fn effect(&self) -> &'static playdate_sound_effect { sys::api!(sound.effect) }
354		fn lfo(&self) -> &'static playdate_sound_lfo { sys::api!(sound.lfo) }
355		fn envelope(&self) -> &'static playdate_sound_envelope { sys::api!(sound.envelope) }
356		fn source(&self) -> &'static playdate_sound_source { sys::api!(sound.source) }
357		fn control_signal(&self) -> &'static playdate_control_signal { sys::api!(sound.controlsignal) }
358		fn track(&self) -> &'static playdate_sound_track { sys::api!(sound.track) }
359		fn instrument(&self) -> &'static playdate_sound_instrument { sys::api!(sound.instrument) }
360		fn signal(&self) -> &'static playdate_sound_signal { sys::api!(sound.signal) }
361
362		/// Returns [`sys::ffi::playdate_sound::getCurrentTime`]
363		#[doc(alias = "sys::ffi::playdate_sound::getCurrentTime")]
364		fn get_current_time(&self) -> unsafe extern "C" fn() -> u32 { *sys::api!(sound.getCurrentTime) }
365
366		/// Returns [`sys::ffi::playdate_sound::addSource`]
367		#[doc(alias = "sys::ffi::playdate_sound::addSource")]
368		fn add_source(
369			&self)
370			-> unsafe extern "C" fn(callback: AudioSourceFunction,
371			                        context: *mut c_void,
372			                        stereo: c_int) -> *mut SoundSource {
373			*sys::api!(sound.addSource)
374		}
375
376		/// Returns [`sys::ffi::playdate_sound::getDefaultChannel`]
377		#[doc(alias = "sys::ffi::playdate_sound::getDefaultChannel")]
378		fn get_default_channel(&self) -> unsafe extern "C" fn() -> *mut SoundChannel {
379			*sys::api!(sound.getDefaultChannel)
380		}
381
382		/// Returns [`sys::ffi::playdate_sound::addChannel`]
383		#[doc(alias = "sys::ffi::playdate_sound::addChannel")]
384		fn add_channel(&self) -> unsafe extern "C" fn(channel: *mut SoundChannel) -> c_int {
385			*sys::api!(sound.addChannel)
386		}
387
388		/// Returns [`sys::ffi::playdate_sound::removeChannel`]
389		#[doc(alias = "sys::ffi::playdate_sound::removeChannel")]
390		fn remove_channel(&self) -> unsafe extern "C" fn(channel: *mut SoundChannel) -> c_int {
391			*sys::api!(sound.removeChannel)
392		}
393
394		/// Returns [`sys::ffi::playdate_sound::setMicCallback`]
395		#[doc(alias = "sys::ffi::playdate_sound::setMicCallback")]
396		fn set_mic_callback(
397			&self)
398			-> unsafe extern "C" fn(callback: RecordCallback, context: *mut c_void, source: MicSource) -> c_int {
399			*sys::api!(sound.setMicCallback)
400		}
401
402		/// Returns [`sys::ffi::playdate_sound::getHeadphoneState`]
403		#[doc(alias = "sys::ffi::playdate_sound::getHeadphoneState")]
404		fn get_headphone_state(
405			&self)
406			-> unsafe extern "C" fn(headphone: *mut c_int,
407			                        headsetmic: *mut c_int,
408			                        changeCallback: Option<unsafe extern "C" fn(headphone: c_int, mic: c_int)>) {
409			*sys::api!(sound.getHeadphoneState)
410		}
411
412		/// Returns [`sys::ffi::playdate_sound::setOutputsActive`]
413		#[doc(alias = "sys::ffi::playdate_sound::setOutputsActive")]
414		fn set_outputs_active(&self) -> unsafe extern "C" fn(headphone: c_int, speaker: c_int) {
415			*sys::api!(sound.setOutputsActive)
416		}
417
418		/// Returns [`sys::ffi::playdate_sound::removeSource`]
419		#[doc(alias = "sys::ffi::playdate_sound::removeSource")]
420		fn remove_source(&self) -> unsafe extern "C" fn(source: *mut SoundSource) -> c_int {
421			*sys::api!(sound.removeSource)
422		}
423	}
424}