From 15b32d6cd3c957480edec38e1c61029afd01637f Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Thu, 28 Sep 2023 03:11:11 +0400 Subject: [PATCH 1/6] Add cached api end points (part 3) (following #130, #131), refactoring sound api (closes #42). --- Cargo.lock | 18 +- Cargo.toml | 2 +- api/playdate/README.md | 2 +- api/sound/Cargo.toml | 8 +- api/sound/examples/fp-simple.rs | 10 +- api/sound/examples/sp-simple.rs | 12 +- api/sound/src/error.rs | 12 +- api/sound/src/lib.rs | 3 +- api/sound/src/player/fp/api.rs | 219 ++++++++++++------ api/sound/src/player/fp/cached.rs | 197 ---------------- api/sound/src/player/fp/mod.rs | 300 ++++++++++++------------- api/sound/src/player/mod.rs | 8 +- api/sound/src/player/sp/api.rs | 189 +++++++++++----- api/sound/src/player/sp/cached.rs | 174 -------------- api/sound/src/player/sp/mod.rs | 212 +++++++++--------- api/sound/src/sample.rs | 361 ++++++++++++++++++++++++++++++ api/sound/src/sample/mod.rs | 72 ------ 17 files changed, 920 insertions(+), 879 deletions(-) delete mode 100644 api/sound/src/player/fp/cached.rs delete mode 100644 api/sound/src/player/sp/cached.rs create mode 100644 api/sound/src/sample.rs delete mode 100644 api/sound/src/sample/mod.rs diff --git a/Cargo.lock b/Cargo.lock index afa9a8bf..bfe91cdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1869,9 +1869,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "ad227c3af19d4914570ad36d30409928b75967c298feb9ea1969db3a610bb14e" dependencies = [ "equivalent", "hashbrown 0.14.0", @@ -2609,7 +2609,7 @@ dependencies = [ [[package]] name = "playdate-sound" -version = "0.1.6" +version = "0.2.0" dependencies = [ "playdate-fs", "playdate-sys", @@ -3306,18 +3306,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", @@ -3406,7 +3406,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.0.1", "serde", "serde_spanned", "toml_datetime", @@ -3419,7 +3419,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca676d9ba1a322c1b64eb8045a5ec5c0cfb0c9d08e15e9ff622589ad5221c8fe" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.0.1", "serde", "serde_spanned", "toml_datetime", diff --git a/Cargo.toml b/Cargo.toml index 4b998ffa..f915d608 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ display = { version = "0.3", path = "api/display", package = "playdate-display", fs = { version = "0.2", path = "api/fs", package = "playdate-fs", default-features = false } gfx = { version = "0.3", path = "api/gfx", package = "playdate-graphics", default-features = false } menu = { version = "0.1", path = "api/menu", package = "playdate-menu", default-features = false } -sound = { version = "0.1", path = "api/sound", package = "playdate-sound", default-features = false } +sound = { version = "0.2", path = "api/sound", package = "playdate-sound", default-features = false } sprite = { version = "0.1", path = "api/sprite", package = "playdate-sprite", default-features = false } system = { version = "0.3", path = "api/system", package = "playdate-system", default-features = false } sys = { version = "0.1", path = "api/sys", package = "playdate-sys", default-features = false } diff --git a/api/playdate/README.md b/api/playdate/README.md index 776c4777..112be926 100644 --- a/api/playdate/README.md +++ b/api/playdate/README.md @@ -100,6 +100,6 @@ Just run `cargo new ` and add do following: - - - -Made with ❤️‍🔥 by [my](https://a.koz.world). +Made with ❤️‍🔥 by [me](https://a.koz.world). This software is not sponsored or supported by Panic. diff --git a/api/sound/Cargo.toml b/api/sound/Cargo.toml index 42473d6f..d09d12a9 100644 --- a/api/sound/Cargo.toml +++ b/api/sound/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "playdate-sound" -version = "0.1.6" +version = "0.2.0" readme = "README.md" description = "High-level sound API built on-top of Playdate API" keywords = ["playdate", "sdk", "api", "gamedev"] @@ -44,12 +44,6 @@ name = "sp-simple" crate-type = ["dylib", "staticlib"] path = "examples/sp-simple.rs" -# [[example]] -# name = "sp-cached" -# crate-type = ["dylib", "staticlib"] -# path = "examples/sp-cached.rs" -# required-features = ["bindings-derive-cache"] - [[example]] name = "fp-simple" crate-type = ["dylib", "staticlib"] diff --git a/api/sound/examples/fp-simple.rs b/api/sound/examples/fp-simple.rs index e2b172da..73a79784 100644 --- a/api/sound/examples/fp-simple.rs +++ b/api/sound/examples/fp-simple.rs @@ -34,8 +34,8 @@ impl State { /// Updates the state fn update(&mut self) -> Option<()> { let text: Cow = if let Some(player) = self.player.as_ref() { - let offset = player.try_get_offset().ok()?; - let length = player.try_get_length().ok()?; + let offset = player.get_offset(); + let length = player.get_length(); format!("{:.2} / {:.2}", offset, length).into() } else { "no player".into() @@ -76,15 +76,15 @@ impl State { (*(*sys::API).display).setRefreshRate?(60.0); // create player - self.player = Player::try_new().ok()?.into(); + self.player = Player::new().ok()?.into(); let player = self.player.as_ref()?; // load sound const SOUND_PATH: &Path = "sfx/main_theme.pda"; - player.try_load_into_player(SOUND_PATH).ok()?; + player.load_into_player(SOUND_PATH).ok()?; // start playback - player.try_play(Repeat::LoopsEndlessly).ok()?; + player.play(Repeat::LoopsEndlessly); }, _ => {}, } diff --git a/api/sound/examples/sp-simple.rs b/api/sound/examples/sp-simple.rs index d39658d0..fdc23d35 100644 --- a/api/sound/examples/sp-simple.rs +++ b/api/sound/examples/sp-simple.rs @@ -35,8 +35,8 @@ impl State { /// Updates the state fn update(&mut self) -> Option<()> { let text: Cow = if let Some(player) = self.player.as_ref() { - let offset = player.try_get_offset().ok()?; - let length = player.try_get_length().ok()?; + let offset = player.get_offset(); + let length = player.get_length(); format!("{:.2} / {:.2}", offset, length).into() } else { "no player".into() @@ -77,16 +77,16 @@ impl State { (*(*sys::API).display).setRefreshRate?(60.0); // create player - self.player = Player::try_new().ok()?.into(); + self.player = Player::new().ok()?.into(); let player = self.player.as_ref()?; // load sound const SOUND_PATH: &Path = "sfx/main_theme.pda"; - let sample = Sample::new_from_file(SOUND_PATH); - player.try_set_sample(&sample).ok()?; + let sample = Sample::new_from_file(SOUND_PATH).ok()?; + player.set_sample(&sample); // start playback - player.try_play(Repeat::LoopsEndlessly, 1.0).ok()?; + player.play(Repeat::LoopsEndlessly, 1.0); }, _ => {}, } diff --git a/api/sound/src/error.rs b/api/sound/src/error.rs index 538dee51..03c84187 100644 --- a/api/sound/src/error.rs +++ b/api/sound/src/error.rs @@ -6,16 +6,22 @@ pub type ApiError = sys::error::Error; #[derive(Debug)] pub enum Error { + /// The file does not exist. FileNotExist, + /// Causes when allocation failed and/or null-ptr returned. Alloc, + + /// Error caused by the file system. + Fs(fs::error::Error), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self { - Error::FileNotExist => write!(f, "Snd: File doesn't exist"), Error::Alloc => write!(f, "Snd: Allocation failed"), + Error::FileNotExist => write!(f, "Snd: File doesn't exist"), + Error::Fs(err) => err.fmt(f), } } } @@ -25,5 +31,9 @@ impl Into for Error { fn into(self) -> ApiError { ApiError::Api(self) } } +impl From for Error { + fn from(err: fs::error::Error) -> Self { Self::Fs(err) } +} + impl core::error::Error for Error {} diff --git a/api/sound/src/lib.rs b/api/sound/src/lib.rs index b62435c9..094e7e23 100644 --- a/api/sound/src/lib.rs +++ b/api/sound/src/lib.rs @@ -2,7 +2,6 @@ #![feature(error_in_core)] #![feature(const_trait_impl)] -#[macro_use] extern crate sys; extern crate alloc; @@ -10,6 +9,8 @@ pub mod error; pub mod player; pub mod sample; +// TODO: sound: channel, synth, sequence, effect, lfo, envelope, source!, etc.. + pub mod prelude { pub use crate::error::ApiError as SndApiError; diff --git a/api/sound/src/player/fp/api.rs b/api/sound/src/player/fp/api.rs index b5f817b6..d291d6e0 100644 --- a/api/sound/src/player/fp/api.rs +++ b/api/sound/src/player/fp/api.rs @@ -1,97 +1,170 @@ -#![cfg(not(feature = "bindings-derive-cache"))] - use core::ffi::c_char; use core::ffi::c_float; use core::ffi::c_int; use core::ffi::c_void; +use core::ptr::NonNull; use sys::ffi::FilePlayer; use sys::ffi::sndCallbackProc; -use sys::error::NullPtrError as Error; -use sys::error::OkOrNullFnErr; -use super::Endpoint; - - -/// Default cached sample-player api. -pub type CachedEndpoint = Ref<'static>; - - -pub trait FilePlayerApi { - type Error: core::error::Error; - - fn try_new_player(&self) -> Result<&FnNewPlayer, Self::Error>; - fn try_free_player(&self) -> Result<&FnFreePlayer, Self::Error>; - fn try_load_into_player(&self) -> Result<&FnLoadIntoPlayer, Self::Error>; - fn try_set_buffer_length(&self) -> Result<&FnSetBufferLength, Self::Error>; - fn try_play(&self) -> Result<&FnPlay, Self::Error>; - fn try_is_playing(&self) -> Result<&FnIsPlaying, Self::Error>; - fn try_stop(&self) -> Result<&FnStop, Self::Error>; - fn try_set_volume(&self) -> Result<&FnSetVolume, Self::Error>; - fn try_get_volume(&self) -> Result<&FnGetVolume, Self::Error>; - fn try_get_length(&self) -> Result<&FnGetLength, Self::Error>; - fn try_set_offset(&self) -> Result<&FnSetOffset, Self::Error>; - fn try_set_rate(&self) -> Result<&FnSetRate, Self::Error>; - fn try_set_loop_range(&self) -> Result<&FnSetLoopRange, Self::Error>; - fn try_did_underrun(&self) -> Result<&FnDidUnderrun, Self::Error>; - fn try_set_stop_on_underrun(&self) -> Result<&FnSetStopOnUnderrun, Self::Error>; - fn try_set_finish_callback(&self) -> Result<&FnSetFinishCallback, Self::Error>; - fn try_set_loop_callback(&self) -> Result<&FnSetLoopCallback, Self::Error>; - fn try_get_offset(&self) -> Result<&FnGetOffset, Self::Error>; - fn try_get_rate(&self) -> Result<&FnGetRate, Self::Error>; - fn try_fade_volume(&self) -> Result<&FnFadeVolume, Self::Error>; - fn try_set_mp3_stream_source(&self) -> Result<&FnSetMP3StreamSource, Self::Error>; -} +use sys::ffi::playdate_sound_fileplayer; + +/// Default file player api end-point, ZST. +/// +/// All calls approximately costs ~4 derefs. +#[derive(Debug, Clone, Copy, core::default::Default)] +pub struct Default; +impl Api for Default {} + +/// Cached file player api end-point. +/// +/// Stores one reference, so size on stack is eq `usize`. +/// +/// All calls approximately costs ~1 deref. #[derive(Clone, Copy)] #[cfg_attr(feature = "bindings-derive-debug", derive(Debug))] -pub struct Ref<'t: 'static>(&'t Endpoint); +pub struct Cache(&'static playdate_sound_fileplayer); +impl core::default::Default for Cache { + fn default() -> Self { Self(sys::api!(sound.fileplayer)) } +} -impl<'t> From<&'t Endpoint> for Ref<'t> { - fn from(api: &'t Endpoint) -> Self { Self(api) } +impl From<*const playdate_sound_fileplayer> for Cache { + #[inline(always)] + fn from(ptr: *const playdate_sound_fileplayer) -> Self { Self(unsafe { ptr.as_ref() }.expect("sp")) } } +impl From<&'static playdate_sound_fileplayer> for Cache { + #[inline(always)] + fn from(r: &'static playdate_sound_fileplayer) -> Self { Self(r) } +} -impl<'t> FilePlayerApi for Ref<'t> { - type Error = self::Error; +impl From> for Cache { + #[inline(always)] + fn from(ptr: NonNull) -> Self { Self(unsafe { ptr.as_ref() }) } +} - fn try_new_player(&self) -> Result<&FnNewPlayer, Error> { self.0.newPlayer.as_ref().ok_or_null() } - fn try_free_player(&self) -> Result<&FnFreePlayer, Error> { self.0.freePlayer.as_ref().ok_or_null() } - fn try_load_into_player(&self) -> Result<&FnLoadIntoPlayer, Error> { - self.0.loadIntoPlayer.as_ref().ok_or_null() - } - fn try_set_buffer_length(&self) -> Result<&FnSetBufferLength, Error> { - self.0.setBufferLength.as_ref().ok_or_null() - } - fn try_play(&self) -> Result<&FnPlay, Error> { self.0.play.as_ref().ok_or_null() } - fn try_is_playing(&self) -> Result<&FnIsPlaying, Error> { self.0.isPlaying.as_ref().ok_or_null() } - fn try_stop(&self) -> Result<&FnStop, Error> { self.0.stop.as_ref().ok_or_null() } - fn try_set_volume(&self) -> Result<&FnSetVolume, Error> { self.0.setVolume.as_ref().ok_or_null() } - fn try_get_volume(&self) -> Result<&FnGetVolume, Error> { self.0.getVolume.as_ref().ok_or_null() } - fn try_get_length(&self) -> Result<&FnGetLength, Error> { self.0.getLength.as_ref().ok_or_null() } - fn try_set_offset(&self) -> Result<&FnSetOffset, Error> { self.0.setOffset.as_ref().ok_or_null() } - fn try_set_rate(&self) -> Result<&FnSetRate, Error> { self.0.setRate.as_ref().ok_or_null() } - fn try_set_loop_range(&self) -> Result<&FnSetLoopRange, Error> { self.0.setLoopRange.as_ref().ok_or_null() } - fn try_did_underrun(&self) -> Result<&FnDidUnderrun, Error> { self.0.didUnderrun.as_ref().ok_or_null() } - fn try_set_stop_on_underrun(&self) -> Result<&FnSetStopOnUnderrun, Error> { - self.0.setStopOnUnderrun.as_ref().ok_or_null() - } - fn try_set_finish_callback(&self) -> Result<&FnSetFinishCallback, Error> { - self.0.setFinishCallback.as_ref().ok_or_null() - } - fn try_set_loop_callback(&self) -> Result<&FnSetLoopCallback, Error> { - self.0.setLoopCallback.as_ref().ok_or_null() - } - fn try_get_offset(&self) -> Result<&FnGetOffset, Error> { self.0.getOffset.as_ref().ok_or_null() } - fn try_get_rate(&self) -> Result<&FnGetRate, Error> { self.0.getRate.as_ref().ok_or_null() } - fn try_fade_volume(&self) -> Result<&FnFadeVolume, Error> { self.0.fadeVolume.as_ref().ok_or_null() } - fn try_set_mp3_stream_source(&self) -> Result<&FnSetMP3StreamSource, Error> { - self.0.setMP3StreamSource.as_ref().ok_or_null() +impl From<&'_ NonNull> for Cache { + #[inline(always)] + fn from(ptr: &NonNull) -> Self { Self(unsafe { ptr.as_ref() }) } +} + + +impl Api for Cache { + fn new_player(&self) -> FnNewPlayer { self.0.newPlayer.expect("newPlayer") } + fn free_player(&self) -> FnFreePlayer { self.0.freePlayer.expect("freePlayer") } + fn load_into_player(&self) -> FnLoadIntoPlayer { self.0.loadIntoPlayer.expect("loadIntoPlayer") } + fn set_buffer_length(&self) -> FnSetBufferLength { self.0.setBufferLength.expect("setBufferLength") } + fn play(&self) -> FnPlay { self.0.play.expect("play") } + fn is_playing(&self) -> FnIsPlaying { self.0.isPlaying.expect("isPlaying") } + fn stop(&self) -> FnStop { self.0.stop.expect("stop") } + fn set_volume(&self) -> FnSetVolume { self.0.setVolume.expect("setVolume") } + fn get_volume(&self) -> FnGetVolume { self.0.getVolume.expect("getVolume") } + fn get_length(&self) -> FnGetLength { self.0.getLength.expect("getLength") } + fn set_offset(&self) -> FnSetOffset { self.0.setOffset.expect("setOffset") } + fn set_rate(&self) -> FnSetRate { self.0.setRate.expect("setRate") } + fn set_loop_range(&self) -> FnSetLoopRange { self.0.setLoopRange.expect("setLoopRange") } + fn did_underrun(&self) -> FnDidUnderrun { self.0.didUnderrun.expect("didUnderrun") } + fn set_stop_on_underrun(&self) -> FnSetStopOnUnderrun { self.0.setStopOnUnderrun.expect("setStopOnUnderrun") } + fn set_finish_callback(&self) -> FnSetFinishCallback { self.0.setFinishCallback.expect("setFinishCallback") } + fn set_loop_callback(&self) -> FnSetLoopCallback { self.0.setLoopCallback.expect("setLoopCallback") } + fn get_offset(&self) -> FnGetOffset { self.0.getOffset.expect("getOffset") } + fn get_rate(&self) -> FnGetRate { self.0.getRate.expect("getRate") } + fn fade_volume(&self) -> FnFadeVolume { self.0.fadeVolume.expect("fadeVolume") } + fn set_mp3_stream_source(&self) -> FnSetMP3StreamSource { + self.0.setMP3StreamSource.expect("setMP3StreamSource") } } +pub trait Api { + /// Returns [`sys::ffi::playdate_sound_fileplayer::newPlayer`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::newPlayer")] + fn new_player(&self) -> FnNewPlayer { *sys::api!(sound.fileplayer.newPlayer) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::freePlayer`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::freePlayer")] + fn free_player(&self) -> FnFreePlayer { *sys::api!(sound.fileplayer.freePlayer) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::loadIntoPlayer`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::loadIntoPlayer")] + fn load_into_player(&self) -> FnLoadIntoPlayer { *sys::api!(sound.fileplayer.loadIntoPlayer) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::setBufferLength`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setBufferLength")] + fn set_buffer_length(&self) -> FnSetBufferLength { *sys::api!(sound.fileplayer.setBufferLength) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::play`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::play")] + fn play(&self) -> FnPlay { *sys::api!(sound.fileplayer.play) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::isPlaying`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::isPlaying")] + fn is_playing(&self) -> FnIsPlaying { *sys::api!(sound.fileplayer.isPlaying) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::stop`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::stop")] + fn stop(&self) -> FnStop { *sys::api!(sound.fileplayer.stop) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::setVolume`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setVolume")] + fn set_volume(&self) -> FnSetVolume { *sys::api!(sound.fileplayer.setVolume) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::getVolume`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::getVolume")] + fn get_volume(&self) -> FnGetVolume { *sys::api!(sound.fileplayer.getVolume) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::getLength`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::getLength")] + fn get_length(&self) -> FnGetLength { *sys::api!(sound.fileplayer.getLength) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::setOffset`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setOffset")] + fn set_offset(&self) -> FnSetOffset { *sys::api!(sound.fileplayer.setOffset) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::setRate`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setRate")] + fn set_rate(&self) -> FnSetRate { *sys::api!(sound.fileplayer.setRate) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::setLoopRange`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setLoopRange")] + fn set_loop_range(&self) -> FnSetLoopRange { *sys::api!(sound.fileplayer.setLoopRange) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::didUnderrun`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::didUnderrun")] + fn did_underrun(&self) -> FnDidUnderrun { *sys::api!(sound.fileplayer.didUnderrun) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::setStopOnUnderrun`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setStopOnUnderrun")] + fn set_stop_on_underrun(&self) -> FnSetStopOnUnderrun { *sys::api!(sound.fileplayer.setStopOnUnderrun) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::setFinishCallback`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setFinishCallback")] + fn set_finish_callback(&self) -> FnSetFinishCallback { *sys::api!(sound.fileplayer.setFinishCallback) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::setLoopCallback`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setLoopCallback")] + fn set_loop_callback(&self) -> FnSetLoopCallback { *sys::api!(sound.fileplayer.setLoopCallback) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::getOffset`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::getOffset")] + fn get_offset(&self) -> FnGetOffset { *sys::api!(sound.fileplayer.getOffset) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::getRate`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::getRate")] + fn get_rate(&self) -> FnGetRate { *sys::api!(sound.fileplayer.getRate) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::fadeVolume`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::fadeVolume")] + fn fade_volume(&self) -> FnFadeVolume { *sys::api!(sound.fileplayer.fadeVolume) } + + /// Returns [`sys::ffi::playdate_sound_fileplayer::setMP3StreamSource`] + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setMP3StreamSource")] + fn set_mp3_stream_source(&self) -> FnSetMP3StreamSource { *sys::api!(sound.fileplayer.setMP3StreamSource) } +} + + type FnNewPlayer = unsafe extern "C" fn() -> *mut FilePlayer; type FnFreePlayer = unsafe extern "C" fn(player: *mut FilePlayer); diff --git a/api/sound/src/player/fp/cached.rs b/api/sound/src/player/fp/cached.rs deleted file mode 100644 index 58da543d..00000000 --- a/api/sound/src/player/fp/cached.rs +++ /dev/null @@ -1,197 +0,0 @@ -#![cfg(feature = "bindings-derive-cache")] - -use core::ffi::c_void; -use core::ffi::c_float; -use core::ffi::c_int; - -use sys::ffi::sndCallbackProc; -use sys::ffi::PlaydateSoundFileplayer; -pub use sys::ffi::PlaydateSoundFileplayerTry as FilePlayerApi; - -use super::Repeat; -use super::Player; -use super::Endpoint; - - -/// Default cached file-player api. -/// -/// See also [`sys::ffi::PlaydateSoundFileplayerCache`]. -pub type CachedEndpoint = sys::cache::Ref<'static, Endpoint>; - - -// ctor // - -impl Player where Api: PlaydateSoundFileplayer { - pub fn new() -> Player - where Api: From<&'static Endpoint> { - let api = Api::from(sys::apifn!(sound.fileplayer)); - Self::new_with(api) - } - - - pub fn new_with(api: Api) -> Player { - let new_player = api.new_player(); - let player = unsafe { new_player() }; - if player.is_null() { - panic!("new player is null"); - } - Player(player, api) - } -} - - -// default impl // - -impl Player where Api: PlaydateSoundFileplayer { - /// Sets the buffer length of player to bufferLen seconds. - pub fn set_buffer_length(&self, len: c_float) { - let f = self.api().set_buffer_length(); - unsafe { f(self.0, len) } - } - - - /// Returns `true` if player is playing a sample, `false` if not. - /// - /// See also [`isPlaying`](playdate_sys::ffi::playdate_sound_fileplayer::isPlaying). - pub fn is_playing(&self) -> bool { - let f = self.api().is_playing(); - unsafe { f(self.0) == 1 } - } - - - /// Starts playing the sample in player. - /// Sets the playback rate for the sample. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. - /// - /// See also [`play`](playdate_sys::ffi::playdate_sound_fileplayer::play). - pub fn play(&self, repeat: Repeat) -> c_int { - let f = self.api().play(); - unsafe { f(self.0, repeat.into()) } - } - - /// Stops playing the sample. - /// - /// See also [`stop`](playdate_sys::ffi::playdate_sound_fileplayer::stop). - pub fn stop(&self) { - let f = self.api().stop(); - unsafe { f(self.0) } - } - - - /// Gets the current left and right channel volume of the player. - /// - /// See also [`getVolume`](playdate_sys::ffi::playdate_sound_fileplayer::getVolume). - pub fn get_volume(&self) -> (c_float, c_float) { - let (mut left, mut right) = (0.0, 0.0); - let f = self.api().get_volume(); - unsafe { f(self.0, &mut left, &mut right) }; - (left, right) - } - - /// Sets the playback volume for left and right channels. - /// - /// See also [`setVolume`](playdate_sys::ffi::playdate_sound_fileplayer::setVolume). - pub fn set_volume(&self, left: c_float, right: c_float) { - let f = self.api().set_volume(); - unsafe { f(self.0, left, right) } - } - - /// Returns the length, in seconds, of the sample assigned to player. - /// - /// See also [`getLength`](playdate_sys::ffi::playdate_sound_fileplayer::getLength). - pub fn get_length(&self) -> c_float { - let f = self.api().get_length(); - unsafe { f(self.0) } - } - - /// Gets the current offset in seconds for player. - /// - /// See also [`getOffset`](playdate_sys::ffi::playdate_sound_fileplayer::getOffset). - pub fn get_offset(&self) -> c_float { - let f = self.api().get_offset(); - unsafe { f(self.0) } - } - - /// Sets the current offset of the player, in seconds. - /// - /// See also [`setOffset`](playdate_sys::ffi::playdate_sound_fileplayer::setOffset). - pub fn set_offset(&self, offset: c_float) { - let f = self.api().set_offset(); - unsafe { f(self.0, offset) } - } - - /// Gets the playback rate for player. - /// - /// See also [`getRate`](playdate_sys::ffi::playdate_sound_fileplayer::getRate). - pub fn get_rate(&self) -> c_float { - let f = self.api().get_rate(); - unsafe { f(self.0) } - } - - /// Sets the playback rate for the player. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. A negative rate produces backwards playback for PCM files, but does not work for ADPCM-encoded files. - /// - /// See also [`setRate`](playdate_sys::ffi::playdate_sound_fileplayer::setRate). - pub fn set_rate(&self, rate: c_float) { - let f = self.api().set_rate(); - unsafe { f(self.0, rate) } - } - - /// When used with a [`Repeat::PingPong`], does ping-pong looping, with a `start` and `end` position in frames. - /// - /// See also [`setPlayRange`](playdate_sys::ffi::playdate_sound_fileplayer::setPlayRange). - pub fn set_loop_range(&self, start: c_float, end: c_float) { - let f = self.api().set_loop_range(); - unsafe { f(self.0, start, end) } - } - - /// Returns `true` if player has underrun, `false` if not. - pub fn did_underrun(&self) -> bool { - let f = self.api().did_underrun(); - unsafe { f(self.0) == 1 } - } - - /// If value is `true`, the player will restart playback (after an audible stutter) as soon as data is available. - pub fn set_stop_on_underrun(&self, value: bool) { - let f = self.api().set_stop_on_underrun(); - unsafe { f(self.0, value as _) } - } - - - // callbacks // - - /// Sets a function to be called when playback has completed. See sndCallbackProc. - /// - /// See also [`setFinishCallback`](playdate_sys::ffi::playdate_sound_fileplayer::setFinishCallback). - // TODO: rustify this function - // pub fn set_finish_callback(&self, callback: Option) - pub fn set_finish_callback(&self, callback: sndCallbackProc) { - let f = self.api().set_finish_callback(); - unsafe { f(self.0, callback) } - } - - // TODO: rustify this function - /// See also [`setLoopCallback`](playdate_sys::ffi::playdate_sound_fileplayer::setLoopCallback). - pub fn set_loop_callback(&self, callback: sndCallbackProc) { - let f = self.api().set_loop_callback(); - unsafe { f(self.0, callback) } - } - - // TODO: rustify this function - pub fn fade_volume(&self, left: c_float, right: c_float, len: i32, finish_callback: sndCallbackProc) { - let f = self.api().fade_volume(); - unsafe { f(self.0, left, right, len, finish_callback) } - } - - // TODO: rustify this function - pub fn et_mp3_stream_source(&self, - source: Option c_int>, - userdata: *mut c_void, - buffer_len: c_float) { - let f = self.api().set_mp3_stream_source(); - unsafe { f(self.0, source, userdata, buffer_len) } - } - - // TODO: try_set_mp3_stream_source -} diff --git a/api/sound/src/player/fp/mod.rs b/api/sound/src/player/fp/mod.rs index 9424dc84..3d24aabf 100644 --- a/api/sound/src/player/fp/mod.rs +++ b/api/sound/src/player/fp/mod.rs @@ -6,66 +6,47 @@ use core::ffi::c_void; use sys::ffi::CString; use sys::ffi::FilePlayer; use sys::ffi::sndCallbackProc; -use sys::ffi::playdate_sound_fileplayer as Endpoint; use fs::Path; use super::Repeat; -use crate::error::Error; use crate::error::ApiError; +use crate::error::Error; -mod cached; mod api; -#[cfg(feature = "bindings-derive-cache")] -use cached as api; -use api::*; - #[cfg_attr(feature = "bindings-derive-debug", derive(Debug))] -pub struct Player(*mut FilePlayer, Api); +pub struct Player(*mut FilePlayer, Api); // ctor // -impl Player - where Api: FilePlayerApi, - ApiError: From<::Error> -{ - pub fn try_new() -> Result, ApiError> - where Api: TryFrom<&'static Endpoint>, - ApiError: From<>::Error> { - let api = Api::try_from(sys::api_ok!(sound.fileplayer)?)?; - let new_player = api.try_new_player()?; - let player = unsafe { new_player() }; - if player.is_null() { - panic!("new player is null"); - } - Ok(Player(player, api)) +impl Player where Api: api::Api { + pub fn new() -> Result, Error> + where Api: Default { + let api = Api::default(); + Self::new_with(api) } - - pub fn try_new_with(api: Api) -> Result, ApiError> { - let new_player = api.try_new_player()?; - let player = unsafe { new_player() }; + pub fn new_with(api: Api) -> Result, Error> { + let f = api.new_player(); + let player = unsafe { f() }; if player.is_null() { - panic!("new player is null"); + Err(Error::Alloc) + } else { + Ok(Player(player, api)) } - Ok(Player(player, api)) } } -impl Drop for Player { +impl Drop for Player { fn drop(&mut self) { if !self.0.is_null() { - match self.api().try_free_player() { - Ok(f) => { - unsafe { f(self.0) } - self.0 = core::ptr::null_mut(); - }, - Err(err) => println!("SP on drop: {err}"), - } + let f = self.api().free_player(); + unsafe { f(self.0) } + self.0 = core::ptr::null_mut(); } } } @@ -73,16 +54,8 @@ impl Drop for Player { // utils // -impl Default for Player { - fn default() -> Self { - let api = Api::default(); - let player = unsafe { api.try_new_player().expect("try_new_player")() }; - Self(player, api) - } -} - -impl Player { +impl Player { #[inline(always)] pub fn api(&self) -> &Api { &self.1 } } @@ -90,18 +63,16 @@ impl Player { // impl // -impl Player - where Api: FilePlayerApi, - ApiError: From<::Error> -{ - /// Prepares player to stream the file at path. Returns 1 if the file exists, otherwise 0. +impl Player where Api: api::Api { + /// Prepares player to stream the file at path. /// - /// See also [`loadIntoPlayer`](playdate_sys::ffi::playdate_sound_fileplayer::loadIntoPlayer). - pub fn try_load_into_player>(&self, path: P) -> Result<(), ApiError> { + /// Equivalent to [loadIntoPlayer](sys::ffi::playdate_sound_fileplayer::loadIntoPlayer) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::loadIntoPlayer")] + pub fn load_into_player>(&self, path: P) -> Result<(), ApiError> { let path_cs = CString::new(path.as_ref())?; let path_ptr = path_cs.as_ptr() as *mut c_char; - let f = self.api().try_load_into_player()?; + let f = self.api().load_into_player(); let code = unsafe { f(self.0, path_ptr) }; if code == 1 { Ok(()) @@ -110,163 +81,188 @@ impl Player } } - /// Sets the buffer length of player to bufferLen seconds. + /// Sets the buffer length of player to `len` seconds. /// - /// See also [`setBufferLength`](playdate_sys::ffi::playdate_sound_fileplayer::setBufferLength). - pub fn try_set_buffer_length(&self, len: c_float) -> Result<(), ApiError> { - let f = self.api().try_set_buffer_length()?; - Ok(unsafe { f(self.0, len) }) + /// Equivalent to [setBufferLength](sys::ffi::playdate_sound_fileplayer::setBufferLength) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setBufferLength")] + pub fn set_buffer_length(&self, len: c_float) { + let f = self.api().set_buffer_length(); + unsafe { f(self.0, len) } } - /// Returns `true` if player is playing a sample, `false` if not. + /// Returns `true` if player is playing. /// - /// See also [`isPlaying`](playdate_sys::ffi::playdate_sound_fileplayer::isPlaying). - pub fn try_is_playing(&self) -> Result { - let f = self.api().try_is_playing()?; - Ok(unsafe { f(self.0) == 1 }) + /// Equivalent to [isPlaying](sys::ffi::playdate_sound_fileplayer::isPlaying) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::isPlaying")] + pub fn is_playing(&self) -> bool { + let f = self.api().is_playing(); + unsafe { f(self.0) == 1 } } - /// Starts playing the sample in player. - /// Sets the playback rate for the sample. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. + /// Starts playing the file player. /// - /// See also [`play`](playdate_sys::ffi::playdate_sound_fileplayer::play). - pub fn try_play(&self, repeat: Repeat) -> Result { - let f = self.api().try_play()?; - Ok(unsafe { f(self.0, repeat.into()) }) + /// Equivalent to [play](sys::ffi::playdate_sound_fileplayer::play) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::play")] + pub fn play(&self, repeat: Repeat) -> c_int { + let f = self.api().play(); + unsafe { f(self.0, repeat.into()) } } - /// Stops playing the sample. + /// Stops playing the file. /// - /// See also [`stop`](playdate_sys::ffi::playdate_sound_fileplayer::stop). - pub fn try_stop(&self) -> Result<(), ApiError> { - let f = self.api().try_stop()?; - Ok(unsafe { f(self.0) }) + /// Equivalent to [stop](sys::ffi::playdate_sound_fileplayer::stop) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::stop")] + pub fn stop(&self) { + let f = self.api().stop(); + unsafe { f(self.0) } } - /// Gets the current left and right channel volume of the player. + /// Gets the left and right channel playback volume for player. /// - /// See also [`getVolume`](playdate_sys::ffi::playdate_sound_fileplayer::getVolume). - pub fn try_get_volume(&self) -> Result<(c_float, c_float), ApiError> { + /// Equivalent to [getVolume](sys::ffi::playdate_sound_fileplayer::getVolume) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::getVolume")] + pub fn get_volume(&self) -> (c_float, c_float) { let (mut left, mut right) = (0.0, 0.0); - let f = self.api().try_get_volume()?; + let f = self.api().get_volume(); unsafe { f(self.0, &mut left, &mut right) }; - Ok((left, right)) + (left, right) } - /// Sets the playback volume for left and right channels. + /// Sets the playback volume for left and right channels of player. /// - /// See also [`setVolume`](playdate_sys::ffi::playdate_sound_fileplayer::setVolume). - pub fn try_set_volume(&self, left: c_float, right: c_float) -> Result<(), ApiError> { - let f = self.api().try_set_volume()?; - Ok(unsafe { f(self.0, left, right) }) + /// Equivalent to [setVolume](sys::ffi::playdate_sound_fileplayer::setVolume) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setVolume")] + pub fn set_volume(&self, left: c_float, right: c_float) { + let f = self.api().set_volume(); + unsafe { f(self.0, left, right) } } - /// Returns the length, in seconds, of the sample assigned to player. + /// Returns the length, in seconds, of the file loaded into player. /// - /// See also [`getLength`](playdate_sys::ffi::playdate_sound_fileplayer::getLength). - pub fn try_get_length(&self) -> Result { - let f = self.api().try_get_length()?; - Ok(unsafe { f(self.0) }) + /// Equivalent to [getLength](sys::ffi::playdate_sound_fileplayer::getLength) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::getLength")] + pub fn get_length(&self) -> c_float { + let f = self.api().get_length(); + unsafe { f(self.0) } } /// Gets the current offset in seconds for player. /// - /// See also [`getOffset`](playdate_sys::ffi::playdate_sound_fileplayer::getOffset). - pub fn try_get_offset(&self) -> Result { - let f = self.api().try_get_offset()?; - Ok(unsafe { f(self.0) }) + /// Equivalent to [getOffset](sys::ffi::playdate_sound_fileplayer::getOffset) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::getOffset")] + pub fn get_offset(&self) -> c_float { + let f = self.api().get_offset(); + unsafe { f(self.0) } } - /// Sets the current offset of the player, in seconds. + /// Sets the current offset in seconds. /// - /// See also [`setOffset`](playdate_sys::ffi::playdate_sound_fileplayer::setOffset). - pub fn try_set_offset(&self, offset: c_float) -> Result<(), ApiError> { - let f = self.api().try_set_offset()?; - Ok(unsafe { f(self.0, offset) }) + /// Equivalent to [setOffset](sys::ffi::playdate_sound_fileplayer::setOffset) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setOffset")] + pub fn set_offset(&self, offset: c_float) { + let f = self.api().set_offset(); + unsafe { f(self.0, offset) } } /// Gets the playback rate for player. /// - /// See also [`getRate`](playdate_sys::ffi::playdate_sound_fileplayer::getRate). - pub fn try_get_rate(&self) -> Result { - let f = self.api().try_get_rate()?; - Ok(unsafe { f(self.0) }) + /// Equivalent to [getRate](sys::ffi::playdate_sound_fileplayer::getRate) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::getRate")] + pub fn get_rate(&self) -> c_float { + let f = self.api().get_rate(); + unsafe { f(self.0) } } - /// Sets the playback rate for the player. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. A negative rate produces backwards playback for PCM files, but does not work for ADPCM-encoded files. + /// Sets the playback rate for the player. + /// + /// `1.0` is normal speed, `0.5` is down an octave, `2.0` is up an octave, etc. + /// + /// Unlike [`SamplePlayer`](crate::player::SamplePlayer), + /// [`FilePlayer`](crate::player::FilePlayer)s can’t play in reverse (i.e., rate < 0). /// - /// See also [`setRate`](playdate_sys::ffi::playdate_sound_fileplayer::setRate). - pub fn try_set_rate(&self, rate: c_float) -> Result<(), ApiError> { - let f = self.api().try_set_rate()?; - Ok(unsafe { f(self.0, rate) }) + /// Equivalent to [setRate](sys::ffi::playdate_sound_fileplayer::setRate) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setRate")] + pub fn set_rate(&self, rate: c_float) { + let f = self.api().set_rate(); + unsafe { f(self.0, rate) } } - /// When used with a [`Repeat::PingPong`], does ping-pong looping, with a `start` and `end` position in frames. + /// Sets the `start` and `end` of the loop region for playback, in seconds. /// - /// See also [`setLoopRange`](playdate_sys::ffi::playdate_sound_fileplayer::setLoopRange). - pub fn try_set_loop_range(&self, start: c_float, end: c_float) -> Result<(), ApiError> { - let f = self.api().try_set_loop_range()?; - Ok(unsafe { f(self.0, start, end) }) + /// If end is omitted, the end of the file is used. + /// + /// Equivalent to [setLoopRange](sys::ffi::playdate_sound_fileplayer::setLoopRange) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setLoopRange")] + pub fn set_loop_range(&self, start: c_float, end: c_float) { + let f = self.api().set_loop_range(); + unsafe { f(self.0, start, end) } } /// Returns `true` if player has underrun, `false` if not. - pub fn try_did_underrun(&self) -> Result { - let f = self.api().try_did_underrun()?; - Ok(unsafe { f(self.0) } == 1) + /// + /// Equivalent to [didUnderrun](sys::ffi::playdate_sound_fileplayer::didUnderrun) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::didUnderrun")] + pub fn did_underrun(&self) -> bool { + let f = self.api().did_underrun(); + unsafe { f(self.0) == 1 } } /// If value is `true`, the player will restart playback (after an audible stutter) as soon as data is available. - pub fn try_set_stop_on_underrun(&self, value: bool) -> Result<(), ApiError> { - let f = self.api().try_set_stop_on_underrun()?; - Ok(unsafe { f(self.0, value as _) }) + /// + /// Equivalent to [setStopOnUnderrun](sys::ffi::playdate_sound_fileplayer::setStopOnUnderrun) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setStopOnUnderrun")] + pub fn set_stop_on_underrun(&self, value: bool) { + let f = self.api().set_stop_on_underrun(); + unsafe { f(self.0, value as _) } } // callbacks // - /// Sets a function to be called when playback has completed. See sndCallbackProc. + // TODO: rustify this functions + + /// Sets a function to be called when playback has completed. + /// + /// This is an alias for [`sys::ffi::playdate_sound_source::setFinishCallback`]. /// - /// See also [`setFinishCallback`](playdate_sys::ffi::playdate_sound_fileplayer::setFinishCallback). - // TODO: rustify this function - pub fn try_set_finish_callback(&self, callback: sndCallbackProc) -> Result<(), ApiError> { - let f = self.api().try_set_finish_callback()?; - Ok(unsafe { f(self.0, callback) }) + /// Equivalent to [setFinishCallback](sys::ffi::playdate_sound_fileplayer::setFinishCallback) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setFinishCallback")] + pub fn set_finish_callback(&self, callback: sndCallbackProc) { + let f = self.api().set_finish_callback(); + unsafe { f(self.0, callback) } } - // TODO: rustify this function - /// See also [`setLoopCallback`](playdate_sys::ffi::playdate_sound_fileplayer::setLoopCallback). - pub fn try_set_loop_callback(&self, callback: sndCallbackProc) -> Result<(), ApiError> { - let f = self.api().try_set_loop_callback()?; - Ok(unsafe { f(self.0, callback) }) + /// Equivalent to [setLoopCallback](sys::ffi::playdate_sound_fileplayer::setLoopCallback) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setLoopCallback")] + pub fn set_loop_callback(&self, callback: sndCallbackProc) { + let f = self.api().set_loop_callback(); + unsafe { f(self.0, callback) } } - // TODO: rustify this function - pub fn try_fade_volume(&self, - left: c_float, - right: c_float, - len: i32, - finish_callback: sndCallbackProc) - -> Result<(), ApiError> { - let f = self.api().try_fade_volume()?; - Ok(unsafe { f(self.0, left, right, len, finish_callback) }) + /// Changes the volume of the [`Player`] to `left` and `right` over a length of `len` sample frames, + /// then calls the provided `callback` (if set). + /// + /// Equivalent to [fadeVolume](sys::ffi::playdate_sound_fileplayer::fadeVolume) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::fadeVolume")] + // Probably here we can use just FnOnce, because it will dropped after call by proxy. + pub fn fade_volume(&self, left: c_float, right: c_float, len: i32, finish_callback: sndCallbackProc) { + let f = self.api().fade_volume(); + unsafe { f(self.0, left, right, len, finish_callback) } } - // TODO: rustify this function - pub fn try_set_mp3_stream_source(&self, - source: Option c_int>, - userdata: *mut c_void, - buffer_len: c_float) - -> Result<(), ApiError> { - let f = self.api().try_set_mp3_stream_source()?; - Ok(unsafe { f(self.0, source, userdata, buffer_len) }) + /// Equivalent to [setMP3StreamSource](sys::ffi::playdate_sound_fileplayer::setMP3StreamSource) + #[doc(alias = "sys::ffi::playdate_sound_fileplayer::setMP3StreamSource")] + pub fn set_mp3_stream_source(&self, + source: Option c_int>, + userdata: *mut c_void, + buffer_len: c_float) { + let f = self.api().set_mp3_stream_source(); + unsafe { f(self.0, source, userdata, buffer_len) } } - - - // TODO: try_set_mp3_stream_source } diff --git a/api/sound/src/player/mod.rs b/api/sound/src/player/mod.rs index b262aa3a..e13de002 100644 --- a/api/sound/src/player/mod.rs +++ b/api/sound/src/player/mod.rs @@ -12,10 +12,9 @@ pub use sp::Player as SamplePlayer; pub enum Repeat { /// Player loops the given number of times. Loops(c_int), - /// Player loops endlessly until it is stopped with [`Player::stop()`] or [`Player::try_stop()`]. + /// Player loops endlessly until it is stopped with [`SamplePlayer::stop`] or [`FilePlayer::stop`]. LoopsEndlessly, /// Player does ping-pong looping. - // XXX: Strange doc, "If negative one, it does ping-pong looping." - test and probably remove int and set to `-1`. PingPong, } @@ -28,8 +27,3 @@ impl Into for Repeat { } } } - - -// pub trait AnyPlayer { -// pub fn api(&self) -> &Api -// } diff --git a/api/sound/src/player/sp/api.rs b/api/sound/src/player/sp/api.rs index 57bc777a..f449517e 100644 --- a/api/sound/src/player/sp/api.rs +++ b/api/sound/src/player/sp/api.rs @@ -1,79 +1,144 @@ -#![cfg(not(feature = "bindings-derive-cache"))] - use core::ffi::c_float; use core::ffi::c_int; +use core::ptr::NonNull; +use sys::ffi::AudioSample; use sys::ffi::SamplePlayer; use sys::ffi::sndCallbackProc; -use sys::ffi::AudioSample; -use sys::error::NullPtrError; -use sys::error::OkOrNullFnErr; -use super::Endpoint; - - -/// Default cached sample-player api. -pub type CachedEndpoint = Ref<'static>; - - -pub trait SampleplayerApi { - type Error: core::error::Error; - - fn try_new_player(&self) -> Result<&FnNewPlayer, Self::Error>; - fn try_free_player(&self) -> Result<&FnFreePlayer, Self::Error>; - fn try_set_sample(&self) -> Result<&FnSetSample, Self::Error>; - fn try_play(&self) -> Result<&FnPlay, Self::Error>; - fn try_is_playing(&self) -> Result<&FnIsPlaying, Self::Error>; - fn try_stop(&self) -> Result<&FnStop, Self::Error>; - fn try_set_volume(&self) -> Result<&FnSetVolume, Self::Error>; - fn try_get_volume(&self) -> Result<&FnGetVolume, Self::Error>; - fn try_get_length(&self) -> Result<&FnGetLength, Self::Error>; - fn try_set_offset(&self) -> Result<&FnSetOffset, Self::Error>; - fn try_set_rate(&self) -> Result<&FnSetRate, Self::Error>; - fn try_set_play_range(&self) -> Result<&FnSetPlayRange, Self::Error>; - fn try_set_finish_callback(&self) -> Result<&FnSetFinishCallback, Self::Error>; - fn try_set_loop_callback(&self) -> Result<&FnSetLoopCallback, Self::Error>; - fn try_get_offset(&self) -> Result<&FnGetOffset, Self::Error>; - fn try_get_rate(&self) -> Result<&FnGetRate, Self::Error>; - fn try_set_paused(&self) -> Result<&FnSetPaused, Self::Error>; -} +use sys::ffi::playdate_sound_sampleplayer; +/// Default sample player api end-point, ZST. +/// +/// All calls approximately costs ~4 derefs. +#[derive(Debug, Clone, Copy, core::default::Default)] +pub struct Default; +impl Api for Default {} + + +/// Cached sample player api end-point. +/// +/// Stores one reference, so size on stack is eq `usize`. +/// +/// All calls approximately costs ~1 deref. #[derive(Clone, Copy)] #[cfg_attr(feature = "bindings-derive-debug", derive(Debug))] -pub struct Ref<'t: 'static>(&'t Endpoint); +pub struct Cache(&'static playdate_sound_sampleplayer); + +impl core::default::Default for Cache { + fn default() -> Self { Self(sys::api!(sound.sampleplayer)) } +} + +impl From<*const playdate_sound_sampleplayer> for Cache { + #[inline(always)] + fn from(ptr: *const playdate_sound_sampleplayer) -> Self { Self(unsafe { ptr.as_ref() }.expect("sp")) } +} + +impl From<&'static playdate_sound_sampleplayer> for Cache { + #[inline(always)] + fn from(r: &'static playdate_sound_sampleplayer) -> Self { Self(r) } +} + +impl From> for Cache { + #[inline(always)] + fn from(ptr: NonNull) -> Self { Self(unsafe { ptr.as_ref() }) } +} + +impl From<&'_ NonNull> for Cache { + #[inline(always)] + fn from(ptr: &NonNull) -> Self { Self(unsafe { ptr.as_ref() }) } +} -impl<'t> From<&'t Endpoint> for Ref<'t> { - fn from(api: &'t Endpoint) -> Self { Self(api) } +impl Api for Cache { + fn new_player(&self) -> FnNewPlayer { self.0.newPlayer.expect("newPlayer") } + fn free_player(&self) -> FnFreePlayer { self.0.freePlayer.expect("freePlayer") } + fn set_sample(&self) -> FnSetSample { self.0.setSample.expect("setSample") } + fn play(&self) -> FnPlay { self.0.play.expect("play") } + fn is_playing(&self) -> FnIsPlaying { self.0.isPlaying.expect("isPlaying") } + fn stop(&self) -> FnStop { self.0.stop.expect("stop") } + fn set_volume(&self) -> FnSetVolume { self.0.setVolume.expect("setVolume") } + fn get_volume(&self) -> FnGetVolume { self.0.getVolume.expect("getVolume") } + fn get_length(&self) -> FnGetLength { self.0.getLength.expect("getLength") } + fn set_offset(&self) -> FnSetOffset { self.0.setOffset.expect("setOffset") } + fn set_rate(&self) -> FnSetRate { self.0.setRate.expect("setRate") } + fn set_play_range(&self) -> FnSetPlayRange { self.0.setPlayRange.expect("setPlayRange") } + fn set_finish_callback(&self) -> FnSetFinishCallback { self.0.setFinishCallback.expect("setFinishCallback") } + fn set_loop_callback(&self) -> FnSetLoopCallback { self.0.setLoopCallback.expect("setLoopCallback") } + fn get_offset(&self) -> FnGetOffset { self.0.getOffset.expect("getOffset") } + fn get_rate(&self) -> FnGetRate { self.0.getRate.expect("getRate") } + fn set_paused(&self) -> FnSetPaused { self.0.setPaused.expect("setPaused") } } -impl<'t> SampleplayerApi for Ref<'t> { - type Error = NullPtrError; - - fn try_new_player(&self) -> Result<&FnNewPlayer, NullPtrError> { self.0.newPlayer.as_ref().ok_or_null() } - fn try_free_player(&self) -> Result<&FnFreePlayer, NullPtrError> { self.0.freePlayer.as_ref().ok_or_null() } - fn try_set_sample(&self) -> Result<&FnSetSample, NullPtrError> { self.0.setSample.as_ref().ok_or_null() } - fn try_play(&self) -> Result<&FnPlay, NullPtrError> { self.0.play.as_ref().ok_or_null() } - fn try_is_playing(&self) -> Result<&FnIsPlaying, NullPtrError> { self.0.isPlaying.as_ref().ok_or_null() } - fn try_stop(&self) -> Result<&FnStop, NullPtrError> { self.0.stop.as_ref().ok_or_null() } - fn try_set_volume(&self) -> Result<&FnSetVolume, NullPtrError> { self.0.setVolume.as_ref().ok_or_null() } - fn try_get_volume(&self) -> Result<&FnGetVolume, NullPtrError> { self.0.getVolume.as_ref().ok_or_null() } - fn try_get_length(&self) -> Result<&FnGetLength, NullPtrError> { self.0.getLength.as_ref().ok_or_null() } - fn try_set_offset(&self) -> Result<&FnSetOffset, NullPtrError> { self.0.setOffset.as_ref().ok_or_null() } - fn try_set_rate(&self) -> Result<&FnSetRate, NullPtrError> { self.0.setRate.as_ref().ok_or_null() } - fn try_set_play_range(&self) -> Result<&FnSetPlayRange, NullPtrError> { - self.0.setPlayRange.as_ref().ok_or_null() - } - fn try_set_finish_callback(&self) -> Result<&FnSetFinishCallback, NullPtrError> { - self.0.setFinishCallback.as_ref().ok_or_null() - } - fn try_set_loop_callback(&self) -> Result<&FnSetLoopCallback, NullPtrError> { - self.0.setLoopCallback.as_ref().ok_or_null() - } - fn try_get_offset(&self) -> Result<&FnGetOffset, NullPtrError> { self.0.getOffset.as_ref().ok_or_null() } - fn try_get_rate(&self) -> Result<&FnGetRate, NullPtrError> { self.0.getRate.as_ref().ok_or_null() } - fn try_set_paused(&self) -> Result<&FnSetPaused, NullPtrError> { self.0.setPaused.as_ref().ok_or_null() } +pub trait Api { + /// Returns [`sys::ffi::playdate_sound_sampleplayer::newPlayer`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::newPlayer")] + fn new_player(&self) -> FnNewPlayer { *sys::api!(sound.sampleplayer.newPlayer) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::freePlayer`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::freePlayer")] + fn free_player(&self) -> FnFreePlayer { *sys::api!(sound.sampleplayer.freePlayer) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::setSample`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setSample")] + fn set_sample(&self) -> FnSetSample { *sys::api!(sound.sampleplayer.setSample) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::play`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::play")] + fn play(&self) -> FnPlay { *sys::api!(sound.sampleplayer.play) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::isPlaying`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::isPlaying")] + fn is_playing(&self) -> FnIsPlaying { *sys::api!(sound.sampleplayer.isPlaying) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::stop`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::stop")] + fn stop(&self) -> FnStop { *sys::api!(sound.sampleplayer.stop) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::setVolume`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setVolume")] + fn set_volume(&self) -> FnSetVolume { *sys::api!(sound.sampleplayer.setVolume) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::getVolume`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::getVolume")] + fn get_volume(&self) -> FnGetVolume { *sys::api!(sound.sampleplayer.getVolume) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::getLength`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::getLength")] + fn get_length(&self) -> FnGetLength { *sys::api!(sound.sampleplayer.getLength) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::setOffset`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setOffset")] + fn set_offset(&self) -> FnSetOffset { *sys::api!(sound.sampleplayer.setOffset) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::setRate`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setRate")] + fn set_rate(&self) -> FnSetRate { *sys::api!(sound.sampleplayer.setRate) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::setPlayRange`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setPlayRange")] + fn set_play_range(&self) -> FnSetPlayRange { *sys::api!(sound.sampleplayer.setPlayRange) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::setFinishCallback`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setFinishCallback")] + fn set_finish_callback(&self) -> FnSetFinishCallback { *sys::api!(sound.sampleplayer.setFinishCallback) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::setLoopCallback`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setLoopCallback")] + fn set_loop_callback(&self) -> FnSetLoopCallback { *sys::api!(sound.sampleplayer.setLoopCallback) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::getOffset`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::getOffset")] + fn get_offset(&self) -> FnGetOffset { *sys::api!(sound.sampleplayer.getOffset) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::getRate`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::getRate")] + fn get_rate(&self) -> FnGetRate { *sys::api!(sound.sampleplayer.getRate) } + + /// Returns [`sys::ffi::playdate_sound_sampleplayer::setPaused`] + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setPaused")] + fn set_paused(&self) -> FnSetPaused { *sys::api!(sound.sampleplayer.setPaused) } } diff --git a/api/sound/src/player/sp/cached.rs b/api/sound/src/player/sp/cached.rs deleted file mode 100644 index 8080e7dd..00000000 --- a/api/sound/src/player/sp/cached.rs +++ /dev/null @@ -1,174 +0,0 @@ -#![cfg(feature = "bindings-derive-cache")] - -use core::ffi::c_float; -use core::ffi::c_int; - -use sys::ffi::PlaydateSoundSampleplayer; -use sys::ffi::sndCallbackProc; -pub use sys::ffi::PlaydateSoundSampleplayerTry as SampleplayerApi; - -use super::Repeat; -use super::Player; -use super::Endpoint; - - -/// Default cached sample-player api. -/// -/// See also [`sys::ffi::PlaydateSoundSampleplayerCache`]. -pub type CachedEndpoint = sys::cache::Ref<'static, Endpoint>; - - -// ctor // - -impl Player where Api: PlaydateSoundSampleplayer { - pub fn new() -> Player - where Api: From<&'static Endpoint> { - let api = Api::from(sys::apifn!(sound.sampleplayer)); - Self::new_with(api) - } - - - pub fn new_with(api: Api) -> Player { - let new_player = api.new_player(); - let player = unsafe { new_player() }; - if player.is_null() { - panic!("new player is null"); - } - Player(player, api) - } -} - - -// default impl // - -impl Player where Api: PlaydateSoundSampleplayer { - /// Assigns sample to player. - /// - /// See also [`playdate_sound_sampleplayer::setSample`](playdate_sys::ffi::playdate_sound_sampleplayer::setSample). - pub fn set_sample(&self, sample: &crate::sample::Sample) { - let f = self.api().set_sample(); - unsafe { f(self.0, sample.0) } - } - - - /// Returns `true` if player is playing a sample, `false` if not. - /// - /// See also [`playdate_sound_sampleplayer::isPlaying`](playdate_sys::ffi::playdate_sound_sampleplayer::isPlaying). - pub fn is_playing(&self) -> bool { - let f = self.api().is_playing(); - unsafe { f(self.0) == 1 } - } - - - /// Starts playing the sample in player. - /// Sets the playback rate for the sample. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. - /// - /// See also [`playdate_sound_sampleplayer::play`](playdate_sys::ffi::playdate_sound_sampleplayer::play). - pub fn play(&self, repeat: Repeat, rate: c_float) -> c_int { - let f = self.api().play(); - unsafe { f(self.0, repeat.into(), rate) } - } - - /// Stops playing the sample. - /// - /// See also [`playdate_sound_sampleplayer::stop`](playdate_sys::ffi::playdate_sound_sampleplayer::stop). - pub fn stop(&self) { - let f = self.api().stop(); - unsafe { f(self.0) } - } - - - /// Gets the current left and right channel volume of the player. - /// - /// See also [`playdate_sound_sampleplayer::getVolume`](playdate_sys::ffi::playdate_sound_sampleplayer::getVolume). - pub fn get_volume(&self) -> (c_float, c_float) { - let (mut left, mut right) = (0.0, 0.0); - let f = self.api().get_volume(); - unsafe { f(self.0, &mut left, &mut right) }; - (left, right) - } - - /// Sets the playback volume for left and right channels. - /// - /// See also [`playdate_sound_sampleplayer::setVolume`](playdate_sys::ffi::playdate_sound_sampleplayer::setVolume). - pub fn set_volume(&self, left: c_float, right: c_float) { - let f = self.api().set_volume(); - unsafe { f(self.0, left, right) } - } - - /// Returns the length, in seconds, of the sample assigned to player. - /// - /// See also [`playdate_sound_sampleplayer::getLength`](playdate_sys::ffi::playdate_sound_sampleplayer::getLength). - pub fn get_length(&self) -> c_float { - let f = self.api().get_length(); - unsafe { f(self.0) } - } - - /// Gets the current offset in seconds for player. - /// - /// See also [`playdate_sound_sampleplayer::getOffset`](playdate_sys::ffi::playdate_sound_sampleplayer::getOffset). - pub fn get_offset(&self) -> c_float { - let f = self.api().get_offset(); - unsafe { f(self.0) } - } - - /// Sets the current offset of the player, in seconds. - /// - /// See also [`playdate_sound_sampleplayer::setOffset`](playdate_sys::ffi::playdate_sound_sampleplayer::setOffset). - pub fn set_offset(&self, offset: c_float) { - let f = self.api().set_offset(); - unsafe { f(self.0, offset) } - } - - /// Gets the playback rate for player. - /// - /// See also [`playdate_sound_sampleplayer::getRate`](playdate_sys::ffi::playdate_sound_sampleplayer::getRate). - pub fn get_rate(&self) -> c_float { - let f = self.api().get_rate(); - unsafe { f(self.0) } - } - - /// Sets the playback rate for the player. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. A negative rate produces backwards playback for PCM files, but does not work for ADPCM-encoded files. - /// - /// See also [`playdate_sound_sampleplayer::setRate`](playdate_sys::ffi::playdate_sound_sampleplayer::setRate). - pub fn set_rate(&self, rate: c_float) { - let f = self.api().set_rate(); - unsafe { f(self.0, rate) } - } - - /// When used with a [`Repeat::PingPong`], does ping-pong looping, with a `start` and `end` position in frames. - /// - /// See also [`playdate_sound_sampleplayer::setPlayRange`](playdate_sys::ffi::playdate_sound_sampleplayer::setPlayRange). - pub fn set_play_range(&self, start: c_int, end: c_int) { - let f = self.api().set_play_range(); - unsafe { f(self.0, start, end) } - } - - /// Pauses or resumes playback. - /// - /// See also [`playdate_sound_sampleplayer::setPaused`](playdate_sys::ffi::playdate_sound_sampleplayer::setPaused). - pub fn set_paused(&self, value: bool) { - let f = self.api().set_paused(); - unsafe { f(self.0, value as _) } - } - - - // callbacks // - - /// Sets a function to be called when playback has completed. See sndCallbackProc. - /// - /// See also [`playdate_sound_sampleplayer::setFinishCallback`](playdate_sys::ffi::playdate_sound_sampleplayer::setFinishCallback). - // TODO: rustify this function - // pub fn set_finish_callback(&self, callback: Option) { - pub fn set_finish_callback(&self, callback: sndCallbackProc) { - let f = self.api().set_finish_callback(); - unsafe { f(self.0, callback) } - } - - // TODO: rustify this function - /// See also [`playdate_sound_sampleplayer::setLoopCallback`](playdate_sys::ffi::playdate_sound_sampleplayer::setLoopCallback). - pub fn set_loop_callback(&self, callback: sndCallbackProc) { - let f = self.api().set_loop_callback(); - unsafe { f(self.0, callback) } - } -} diff --git a/api/sound/src/player/sp/mod.rs b/api/sound/src/player/sp/mod.rs index 39ed407b..599e1992 100644 --- a/api/sound/src/player/sp/mod.rs +++ b/api/sound/src/player/sp/mod.rs @@ -3,49 +3,40 @@ use core::ffi::c_int; use sys::ffi::SamplePlayer; use sys::ffi::sndCallbackProc; -use sys::ffi::playdate_sound_sampleplayer as Endpoint; -use crate::error::ApiError as Error; +use crate::error::Error; use super::Repeat; -mod cached; mod api; -#[cfg(feature = "bindings-derive-cache")] -use cached as api; -use api::*; - #[cfg_attr(feature = "bindings-derive-debug", derive(Debug))] -pub struct Player(*mut SamplePlayer, Api); +pub struct Player(*mut SamplePlayer, Api); // ctor // -impl Player - where Api: SampleplayerApi, - Error: From<::Error> -{ - pub fn try_new() -> Result, Error> - where Api: TryFrom<&'static Endpoint>, - Error: From<>::Error> { - let api = Api::try_from(sys::api_ok!(sound.sampleplayer)?)?; - let new_player = api.try_new_player()?; - let player = unsafe { new_player() }; - if player.is_null() { - Err(crate::error::Error::Alloc.into()) - } else { - Ok(Player(player, api)) - } +impl Player where Api: api::Api { + /// Allocates and returns a new sample player. + /// + /// Equivalent to [newPlayer](sys::ffi::playdate_sound_sampleplayer::newPlayer) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::newPlayer")] + pub fn new() -> Result, Error> + where Api: Default { + let api = Api::default(); + Self::new_with(api) } - - pub fn try_new_with(api: Api) -> Result, Error> { - let new_player = api.try_new_player()?; - let player = unsafe { new_player() }; + /// Allocates and returns a new sample player with given `api`. + /// + /// Equivalent to [newPlayer](sys::ffi::playdate_sound_sampleplayer::newPlayer) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::newPlayer")] + pub fn new_with(api: Api) -> Result, Error> { + let f = api.new_player(); + let player = unsafe { f() }; if player.is_null() { - Err(crate::error::Error::Alloc.into()) + Err(Error::Alloc) } else { Ok(Player(player, api)) } @@ -53,16 +44,12 @@ impl Player } -impl Drop for Player { +impl Drop for Player { fn drop(&mut self) { if !self.0.is_null() { - match self.api().try_free_player() { - Ok(f) => { - unsafe { f(self.0) } - self.0 = core::ptr::null_mut(); - }, - Err(err) => println!("SP on drop: {err}"), - } + let f = self.api().free_player(); + unsafe { f(self.0) } + self.0 = core::ptr::null_mut(); } } } @@ -70,16 +57,8 @@ impl Drop for Player { // utils // -impl Default for Player { - fn default() -> Self { - let api = Api::default(); - let player = unsafe { api.try_new_player().expect("try_new_player")() }; - Self(player, api) - } -} - -impl Player { +impl Player { #[inline(always)] pub fn api(&self) -> &Api { &self.1 } } @@ -87,142 +66,153 @@ impl Player { // impl // -impl Player - where Api: SampleplayerApi, - Error: From<::Error> -{ - /// Assigns sample to player. +impl Player where Api: api::Api { + /// Assigns `sample` to player. /// - /// See also [`setSample`](playdate_sys::ffi::playdate_sound_sampleplayer::setSample). - pub fn try_set_sample(&self, sample: &crate::sample::Sample) -> Result<(), Error> { - let f = self.api().try_set_sample()?; - Ok(unsafe { f(self.0, sample.0) }) + /// Equivalent to [setSample](sys::ffi::playdate_sound_sampleplayer::setSample) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setSample")] + pub fn set_sample(&self, sample: &crate::sample::Sample) { + let f = self.api().set_sample(); + unsafe { f(self.0, sample.0) } } /// Returns `true` if player is playing a sample, `false` if not. /// - /// See also [`isPlaying`](playdate_sys::ffi::playdate_sound_sampleplayer::isPlaying). - pub fn try_is_playing(&self) -> Result { - let f = self.api().try_is_playing()?; - Ok(unsafe { f(self.0) == 1 }) + /// Equivalent to [isPlaying](sys::ffi::playdate_sound_sampleplayer::isPlaying) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::isPlaying")] + pub fn is_playing(&self) -> bool { + let f = self.api().is_playing(); + unsafe { f(self.0) == 1 } } /// Starts playing the sample in player. /// Sets the playback rate for the sample. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. /// - /// See also [`play`](playdate_sys::ffi::playdate_sound_sampleplayer::play). - pub fn try_play(&self, repeat: Repeat, rate: c_float) -> Result { - let f = self.api().try_play()?; - Ok(unsafe { f(self.0, repeat.into(), rate) }) + /// Equivalent to [play](sys::ffi::playdate_sound_sampleplayer::play) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::play")] + pub fn play(&self, repeat: Repeat, rate: c_float) -> c_int { + let f = self.api().play(); + unsafe { f(self.0, repeat.into(), rate) } } /// Stops playing the sample. /// - /// See also [`stop`](playdate_sys::ffi::playdate_sound_sampleplayer::stop). - pub fn try_stop(&self) -> Result<(), Error> { - let f = self.api().try_stop()?; - Ok(unsafe { f(self.0) }) + /// Equivalent to [stop](sys::ffi::playdate_sound_sampleplayer::stop) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::stop")] + pub fn stop(&self) { + let f = self.api().stop(); + unsafe { f(self.0) } } /// Gets the current left and right channel volume of the player. /// - /// See also [`getVolume`](playdate_sys::ffi::playdate_sound_sampleplayer::getVolume). - pub fn try_get_volume(&self) -> Result<(c_float, c_float), Error> { + /// Equivalent to [getVolume](sys::ffi::playdate_sound_sampleplayer::getVolume) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::getVolume")] + pub fn get_volume(&self) -> (c_float, c_float) { let (mut left, mut right) = (0.0, 0.0); - let f = self.api().try_get_volume()?; + let f = self.api().get_volume(); unsafe { f(self.0, &mut left, &mut right) }; - Ok((left, right)) + (left, right) } /// Sets the playback volume for left and right channels. /// - /// See also [`setVolume`](playdate_sys::ffi::playdate_sound_sampleplayer::setVolume). - pub fn try_set_volume(&self, left: c_float, right: c_float) -> Result<(), Error> { - let f = self.api().try_set_volume()?; - Ok(unsafe { f(self.0, left, right) }) + /// Equivalent to [setVolume](sys::ffi::playdate_sound_sampleplayer::setVolume) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setVolume")] + pub fn set_volume(&self, left: c_float, right: c_float) { + let f = self.api().set_volume(); + unsafe { f(self.0, left, right) } } /// Returns the length, in seconds, of the sample assigned to player. /// - /// See also [`getLength`](playdate_sys::ffi::playdate_sound_sampleplayer::getLength). - pub fn try_get_length(&self) -> Result { - let f = self.api().try_get_length()?; - Ok(unsafe { f(self.0) }) + /// Equivalent to [getLength](sys::ffi::playdate_sound_sampleplayer::getLength) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::getLength")] + pub fn get_length(&self) -> c_float { + let f = self.api().get_length(); + unsafe { f(self.0) } } /// Gets the current offset in seconds for player. /// - /// See also [`getOffset`](playdate_sys::ffi::playdate_sound_sampleplayer::getOffset). - pub fn try_get_offset(&self) -> Result { - let f = self.api().try_get_offset()?; - Ok(unsafe { f(self.0) }) + /// Equivalent to [getOffset](sys::ffi::playdate_sound_sampleplayer::getOffset) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::getOffset")] + pub fn get_offset(&self) -> c_float { + let f = self.api().get_offset(); + unsafe { f(self.0) } } /// Sets the current offset of the player, in seconds. /// - /// See also [`setOffset`](playdate_sys::ffi::playdate_sound_sampleplayer::setOffset). - pub fn try_set_offset(&self, offset: c_float) -> Result<(), Error> { - let f = self.api().try_set_offset()?; - Ok(unsafe { f(self.0, offset) }) + /// Equivalent to [setOffset](sys::ffi::playdate_sound_sampleplayer::setOffset) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setOffset")] + pub fn set_offset(&self, offset: c_float) { + let f = self.api().set_offset(); + unsafe { f(self.0, offset) } } /// Gets the playback rate for player. /// - /// See also [`getRate`](playdate_sys::ffi::playdate_sound_sampleplayer::getRate). - pub fn try_get_rate(&self) -> Result { - let f = self.api().try_get_rate()?; - Ok(unsafe { f(self.0) }) + /// Equivalent to [getRate](sys::ffi::playdate_sound_sampleplayer::getRate) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::getRate")] + pub fn get_rate(&self) -> c_float { + let f = self.api().get_rate(); + unsafe { f(self.0) } } /// Sets the playback rate for the player. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. A negative rate produces backwards playback for PCM files, but does not work for ADPCM-encoded files. /// - /// See also [`setRate`](playdate_sys::ffi::playdate_sound_sampleplayer::setRate). - pub fn try_set_rate(&self, rate: c_float) -> Result<(), Error> { - let f = self.api().try_set_rate()?; - Ok(unsafe { f(self.0, rate) }) + /// Equivalent to [setRate](sys::ffi::playdate_sound_sampleplayer::setRate) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setRate")] + pub fn set_rate(&self, rate: c_float) { + let f = self.api().set_rate(); + unsafe { f(self.0, rate) } } /// When used with a [`Repeat::PingPong`], does ping-pong looping, with a `start` and `end` position in frames. /// - /// See also [`setPlayRange`](playdate_sys::ffi::playdate_sound_sampleplayer::setPlayRange). - pub fn try_set_play_range(&self, start: c_int, end: c_int) -> Result<(), Error> { - let f = self.api().try_set_play_range()?; - Ok(unsafe { f(self.0, start, end) }) + /// Equivalent to [setPlayRange](sys::ffi::playdate_sound_sampleplayer::setPlayRange) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setPlayRange")] + pub fn set_play_range(&self, start: c_int, end: c_int) { + let f = self.api().set_play_range(); + unsafe { f(self.0, start, end) } } /// Pauses or resumes playback. /// - /// See also [`setPaused`](playdate_sys::ffi::playdate_sound_sampleplayer::setPaused). - pub fn try_set_paused(&self, value: bool) -> Result<(), Error> { - let f = self.api().try_set_paused()?; - Ok(unsafe { f(self.0, value as _) }) + /// Equivalent to [setPaused](sys::ffi::playdate_sound_sampleplayer::setPaused) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setPaused")] + pub fn set_paused(&self, value: bool) { + let f = self.api().set_paused(); + unsafe { f(self.0, value as _) } } // callbacks // - /// Sets a function to be called when playback has completed. See sndCallbackProc. + /// Sets a function to be called when playback has completed. /// - /// See also [`setFinishCallback`](playdate_sys::ffi::playdate_sound_sampleplayer::setFinishCallback). - // TODO: rustify this function + /// Equivalent to [setFinishCallback](sys::ffi::playdate_sound_sampleplayer::setFinishCallback) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setFinishCallback")] + // TODO: Rustify this function, maybe impl like it done for sprites. // My idea is to store user-callback into self, // and here use proxy "extern C" function. // Maybe move self into the wrapper with user-callback? // We're need so store user-callback somewhere like StackedMap(stacked_type_map) or ErasedSet by type. // Type of `F: FnMut(*mut SoundSource)` is unique, so we can do it. // But with cost of memory - one static for each `F`*`Self`, so so much. - pub fn try_set_finish_callback(&self, callback: sndCallbackProc) -> Result<(), Error> { - let f = self.api().try_set_finish_callback()?; + pub fn set_finish_callback_raw(&self, callback: sndCallbackProc) -> Result<(), Error> { + let f = self.api().set_finish_callback(); Ok(unsafe { f(self.0, callback) }) } - // TODO: rustify this function - /// See also [`setLoopCallback`](playdate_sys::ffi::playdate_sound_sampleplayer::setLoopCallback). - pub fn try_set_loop_callback(&self, callback: sndCallbackProc) -> Result<(), Error> { - let f = self.api().try_set_loop_callback()?; + /// Equivalent to [setLoopCallback](sys::ffi::playdate_sound_sampleplayer::setLoopCallback) + #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::setLoopCallback")] + pub fn set_loop_callback_raw(&self, callback: sndCallbackProc) -> Result<(), Error> { + let f = self.api().set_loop_callback(); Ok(unsafe { f(self.0, callback) }) } } diff --git a/api/sound/src/sample.rs b/api/sound/src/sample.rs new file mode 100644 index 00000000..0c470119 --- /dev/null +++ b/api/sound/src/sample.rs @@ -0,0 +1,361 @@ +//! Playdate sound sample API + +use alloc::boxed::Box; +use core::ffi::c_char; +use core::ffi::c_float; +use core::ffi::c_int; +use core::ops::Deref; +use sys::ffi::CString; +use sys::ffi::AudioSample; +use fs::Path; +use sys::ffi::SoundFormat; + +use crate::error::ApiError; +use crate::error::Error; + + +#[cfg_attr(feature = "bindings-derive-debug", derive(Debug))] +pub struct Sample(pub(super) *mut AudioSample, Api); + + +impl Drop for Sample { + fn drop(&mut self) { + if !self.0.is_null() { + // TODO: use inner api instead + let f = self.1.free_sample(); + unsafe { f(self.0) }; + self.0 = core::ptr::null_mut(); + } + } +} + + +impl Sample { + /// Allocates and returns a new [`Sample`] with a buffer large enough to load a file of length `bytes`. + /// + /// Equivalent to [`sys::ffi::playdate_sound_sample::newSampleBuffer`] + #[doc(alias = "sys::ffi::playdate_sound_sample::newSampleBuffer")] + #[inline(always)] + pub fn new_with_size(bytes: c_int) -> Result { + let api: Api = Default::default(); + Self::new_with_size_with(api, bytes) + } + + /// Retrieves `size` of file and allocate with that size. + /// + /// __Does not load a file.__ + /// + /// Uses [`sys::ffi::playdate_sound_sample::newSampleBuffer`] + #[inline(always)] + pub fn new_for_file>(path: P) -> Result { + let api: Api = Default::default(); + Self::new_for_file_with(api, path).map_err(Into::into) + } + + /// Loads the file into the self. + /// + /// Equivalent to [`sys::ffi::playdate_sound_sample::load`] + #[doc(alias = "sys::ffi::playdate_sound_sample::load")] + #[inline(always)] + pub fn new_from_file>(path: P) -> Result { + let api: Api = Default::default(); + Self::new_from_file_with(api, path) + } + + + /// Returns a new [`Sample`] referencing the given audio data. + /// + /// The sample keeps a reference to the `data` instead of copying it, + /// so the data must remain valid while the sample is active. + /// + /// Equivalent to [`sys::ffi::playdate_sound_sample::newSampleFromData`] + #[doc(alias = "sys::ffi::playdate_sound_sample::newSampleFromData")] + pub fn new_from_data<'t>(data: &'t mut [u8], + format: SoundFormat, + sample_rate: u32) + -> Result, Error> { + let api: Api = Default::default(); + Self::new_from_data_with(api, data, format, sample_rate) + } +} + + +impl Sample { + /// Allocates and returns a new [`Sample`] with a buffer large enough to load a file of length `bytes`. + /// + /// Equivalent to [`sys::ffi::playdate_sound_sample::newSampleBuffer`] + #[doc(alias = "sys::ffi::playdate_sound_sample::newSampleBuffer")] + pub fn new_with_size_with(api: Api, bytes: c_int) -> Result { + let f = api.new_sample_buffer(); + let ptr = unsafe { f(bytes) }; + if ptr.is_null() { + Err(Error::Alloc) + } else { + Ok(Self(ptr, api)) + } + } + + /// Retrieves `size` of file and allocate with that size. + /// + /// __Does not load a file.__ + /// + /// Uses [`sys::ffi::playdate_sound_sample::newSampleBuffer`] + pub fn new_for_file_with>(api: Api, path: P) -> Result { + let size = match fs::metadata(path) { + Ok(stats) => stats.size, + Err(err) => return Err(ApiError::from_err(err)), + }; + + Self::new_with_size_with(api, size as _).map_err(Into::into) + } + + /// Loads the file into the self. + /// + /// Equivalent to [`sys::ffi::playdate_sound_sample::load`] + #[doc(alias = "sys::ffi::playdate_sound_sample::load")] + pub fn new_from_file_with>(api: Api, path: P) -> Result { + let path_cs = CString::new(path.as_ref())?; + let path_ptr = path_cs.as_ptr() as *mut c_char; + + let f = api.load(); + + let ptr = unsafe { f(path_ptr) }; + if ptr.is_null() { + Err(crate::error::Error::Alloc.into()) + } else { + Ok(Self(ptr, api)) + } + } + + + /// Returns a new [`Sample`] referencing the given audio data. + /// + /// The sample keeps a reference to the `data` instead of copying it, + /// so the `data` __must remain valid while the sample is active__. + /// + /// Equivalent to [`sys::ffi::playdate_sound_sample::newSampleFromData`] + #[doc(alias = "sys::ffi::playdate_sound_sample::newSampleFromData")] + pub fn new_from_data_with<'t>(api: Api, + data: &'t mut [u8], + format: SoundFormat, + sample_rate: u32) + -> Result, Error> { + let f = api.new_sample_from_data(); + let ptr = unsafe { f(data.as_mut_ptr(), format, sample_rate, data.len() as _) }; + + if ptr.is_null() { + Err(Error::Alloc) + } else { + Ok(SampleWithData(Self(ptr, api), data)) + } + } +} + + +impl Sample { + /// Returns the length, in seconds. + /// + /// Equivalent to [`sys::ffi::playdate_sound_sample::getLength`] + #[doc(alias = "sys::ffi::playdate_sound_sample::getLength")] + pub fn length(&self) -> c_float { + if self.0.is_null() { + 0.0 + } else { + let f = self.1.get_length(); + unsafe { f(self.0) } + } + } + + /// Equivalent to [`sys::ffi::playdate_sound_sample::getData`] + #[doc(alias = "sys::ffi::playdate_sound_sample::getData")] + pub fn get_data<'t>(&'t self) -> SampleData<'t> { + let mut format: SoundFormat = SoundFormat::kSound8bitMono; + let mut sample_rate: u32 = 0; + let mut byte_length: u32 = 0; + + let mut boxed_data = Box::new(core::ptr::null_mut()); + let data = Box::into_raw(boxed_data); + + let f = self.1.get_data(); + unsafe { f(self.0, data, &mut format, &mut sample_rate, &mut byte_length) }; + + boxed_data = unsafe { Box::from_raw(data) }; + let data = unsafe { core::slice::from_raw_parts_mut::(*boxed_data, byte_length as usize) }; + + SampleData { data, sample_rate } + } +} + + +/// Sample over borrowed audio data. +#[cfg_attr(feature = "bindings-derive-debug", derive(Debug))] +pub struct SampleWithData<'t, Api: api::Api>(Sample, &'t mut [u8]); + +impl Deref for SampleWithData<'_, Api> { + type Target = Sample; + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl AsRef> for SampleWithData<'_, Api> { + fn as_ref(&self) -> &Sample { &self.0 } +} + + +pub struct SampleData<'t> { + pub sample_rate: u32, + pub data: &'t mut [u8], +} + + +pub mod api { + use core::ffi::c_int; + use core::ffi::c_char; + use core::ffi::c_float; + use core::ptr::NonNull; + use sys::ffi::AudioSample; + use sys::ffi::SoundFormat; + use sys::ffi::playdate_sound_sample; + + + /// Default sound sample api end-point, ZST. + /// + /// All calls approximately costs ~4 derefs. + #[derive(Debug, Clone, Copy, core::default::Default)] + pub struct Default; + impl Api for Default {} + + + /// Cached sound sample api end-point. + /// + /// Stores one reference, so size on stack is eq `usize`. + /// + /// All calls approximately costs ~1 deref. + #[derive(Clone, Copy)] + #[cfg_attr(feature = "bindings-derive-debug", derive(Debug))] + pub struct Cache(&'static playdate_sound_sample); + + impl core::default::Default for Cache { + fn default() -> Self { Self(sys::api!(sound.sample)) } + } + + impl From<*const playdate_sound_sample> for Cache { + #[inline(always)] + fn from(ptr: *const playdate_sound_sample) -> Self { Self(unsafe { ptr.as_ref() }.expect("sample")) } + } + + impl From<&'static playdate_sound_sample> for Cache { + #[inline(always)] + fn from(r: &'static playdate_sound_sample) -> Self { Self(r) } + } + + impl From> for Cache { + #[inline(always)] + fn from(ptr: NonNull) -> Self { Self(unsafe { ptr.as_ref() }) } + } + + impl From<&'_ NonNull> for Cache { + #[inline(always)] + fn from(ptr: &NonNull) -> Self { Self(unsafe { ptr.as_ref() }) } + } + + + impl Api for Cache { + fn new_sample_buffer(&self) -> unsafe extern "C" fn(byteCount: c_int) -> *mut AudioSample { + self.0.newSampleBuffer.expect("newSampleBuffer") + } + + fn load_into_sample(&self) -> unsafe extern "C" fn(sample: *mut AudioSample, path: *const c_char) -> c_int { + self.0.loadIntoSample.expect("loadIntoSample") + } + + fn load(&self) -> unsafe extern "C" fn(path: *const c_char) -> *mut AudioSample { + self.0.load.expect("load") + } + + fn new_sample_from_data( + &self) + -> unsafe extern "C" fn(data: *mut u8, + format: SoundFormat, + sampleRate: u32, + byteCount: c_int) -> *mut AudioSample { + self.0.newSampleFromData.expect("newSampleFromData") + } + + fn get_data( + &self) + -> unsafe extern "C" fn(sample: *mut AudioSample, + data: *mut *mut u8, + format: *mut SoundFormat, + sampleRate: *mut u32, + bytelength: *mut u32) { + self.0.getData.expect("getData") + } + + fn free_sample(&self) -> unsafe extern "C" fn(sample: *mut AudioSample) { + self.0.freeSample.expect("freeSample") + } + + fn get_length(&self) -> unsafe extern "C" fn(sample: *mut AudioSample) -> c_float { + self.0.getLength.expect("getLength") + } + } + + + pub trait Api { + /// Returns [`sys::ffi::playdate_sound_sample::newSampleBuffer`] + #[doc(alias = "sys::ffi::playdate_sound_sample::newSampleBuffer")] + fn new_sample_buffer(&self) -> unsafe extern "C" fn(byteCount: c_int) -> *mut AudioSample { + *sys::api!(sound.sample.newSampleBuffer) + } + + + /// Returns [`sys::ffi::playdate_sound_sample::loadIntoSample`] + #[doc(alias = "sys::ffi::playdate_sound_sample::loadIntoSample")] + fn load_into_sample(&self) -> unsafe extern "C" fn(sample: *mut AudioSample, path: *const c_char) -> c_int { + *sys::api!(sound.sample.loadIntoSample) + } + + + /// Returns [`sys::ffi::playdate_sound_sample::load`] + #[doc(alias = "sys::ffi::playdate_sound_sample::load")] + fn load(&self) -> unsafe extern "C" fn(path: *const c_char) -> *mut AudioSample { + *sys::api!(sound.sample.load) + } + + + /// Returns [`sys::ffi::playdate_sound_sample::newSampleFromData`] + #[doc(alias = "sys::ffi::playdate_sound_sample::newSampleFromData")] + fn new_sample_from_data( + &self) + -> unsafe extern "C" fn(data: *mut u8, + format: SoundFormat, + sampleRate: u32, + byteCount: c_int) -> *mut AudioSample { + *sys::api!(sound.sample.newSampleFromData) + } + + /// Returns [`sys::ffi::playdate_sound_sample::getData`] + #[doc(alias = "sys::ffi::playdate_sound_sample::getData")] + fn get_data( + &self) + -> unsafe extern "C" fn(sample: *mut AudioSample, + data: *mut *mut u8, + format: *mut SoundFormat, + sampleRate: *mut u32, + bytelength: *mut u32) { + *sys::api!(sound.sample.getData) + } + + /// Returns [`sys::ffi::playdate_sound_sample::freeSample`] + #[doc(alias = "sys::ffi::playdate_sound_sample::freeSample")] + fn free_sample(&self) -> unsafe extern "C" fn(sample: *mut AudioSample) { + *sys::api!(sound.sample.freeSample) + } + + /// Returns [`sys::ffi::playdate_sound_sample::getLength`] + #[doc(alias = "sys::ffi::playdate_sound_sample::getLength")] + fn get_length(&self) -> unsafe extern "C" fn(sample: *mut AudioSample) -> c_float { + *sys::api!(sound.sample.getLength) + } + } +} diff --git a/api/sound/src/sample/mod.rs b/api/sound/src/sample/mod.rs deleted file mode 100644 index 182095f0..00000000 --- a/api/sound/src/sample/mod.rs +++ /dev/null @@ -1,72 +0,0 @@ -// TODO: support cache api with try - -use core::ffi::c_char; -use core::ffi::c_float; -use core::ffi::c_int; -use fs::Path; -use sys::ffi::CString; -use sys::ffi::AudioSample; - - -#[cfg_attr(feature = "bindings-derive-debug", derive(Debug))] -pub struct Sample(pub(super) *mut AudioSample); - - -impl Drop for Sample { - fn drop(&mut self) { - if !self.0.is_null() { - // TODO: use inner api instead - let f = sys::api!(sound.sample.freeSample); - unsafe { f(self.0) }; - self.0 = core::ptr::null_mut(); - } - } -} - - -impl Sample { - pub fn new_with_size(bytes: c_int) -> Self { - let f = sys::api!(sound.sample.newSampleBuffer); - let sample = unsafe { f(bytes) }; - if sample.is_null() { - panic!("failed sample allocation"); - } - Self(sample) - } - - /// Takes `stats.size` for file and allocate with that size. - /// - /// __Does not loads a file.__ - pub fn new_for_file>(path: P) -> Self { - let size = { fs::metadata(path).expect("fs metadata").size }; - - Self::new_with_size(size as _) - } - - /// Loads the file into the self. - pub fn new_from_file>(path: P) -> Self { - let path_cs = CString::new(path.as_ref()).unwrap(); - let path_ptr = path_cs.as_ptr() as *mut c_char; - - let f = sys::api!(sound.sample.load); - - let ptr = unsafe { f(path_ptr) }; - assert!(!ptr.is_null(), "failed sample allocation/loading"); - - Self(ptr) - } - - - // TODO: newSampleFromData - // TODO: getData - - - pub fn get_length(&self) -> c_float { - if self.0.is_null() { - 0.0 - } else { - let f = sys::api!(sound.sample.getLength); - unsafe { f(self.0) } - } - } -} From e2257637144ebab1aa8f8280d3dd9995b8e33d25 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Thu, 28 Sep 2023 03:15:42 +0400 Subject: [PATCH 2/6] bump pd version --- Cargo.lock | 2 +- api/playdate/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bfe91cdf..08f4fb57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2507,7 +2507,7 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "playdate" -version = "0.1.3" +version = "0.1.4" dependencies = [ "playdate-controls", "playdate-display", diff --git a/api/playdate/Cargo.toml b/api/playdate/Cargo.toml index a65c456c..01cdfc47 100644 --- a/api/playdate/Cargo.toml +++ b/api/playdate/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "playdate" -version = "0.1.3" +version = "0.1.4" readme = "README.md" description = "High-level Playdate API" keywords = ["playdate", "sdk", "api", "gamedev"] From 564cb626ca6c6b8b934ca524d5fa7a7af5d44a6a Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Thu, 28 Sep 2023 16:02:29 +0400 Subject: [PATCH 3/6] =?UTF-8?q?gfx:=20improve=20docs=20=F0=9F=A4=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/gfx/Cargo.toml | 2 +- api/gfx/src/video.rs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/api/gfx/Cargo.toml b/api/gfx/Cargo.toml index 3afcc40f..271b261d 100644 --- a/api/gfx/Cargo.toml +++ b/api/gfx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "playdate-graphics" -version = "0.3.1" +version = "0.3.2" readme = "README.md" description = "High-level graphics API built on-top of Playdate API" keywords = ["playdate", "sdk", "api", "gamedev"] diff --git a/api/gfx/src/video.rs b/api/gfx/src/video.rs index 6ac5cd96..4e9dcf7c 100644 --- a/api/gfx/src/video.rs +++ b/api/gfx/src/video.rs @@ -213,6 +213,16 @@ impl VideoPlayer { /// Retrieves information about the video, by passing optional mutable references. /// + /// Example: + /// ```no_run + /// let mut frame_count = Some(0); + /// let mut current_frame = Some(0); + /// player.info_raw(None, None, None, + /// frame_count.as_mut(), + /// current_frame.as_mut() + /// ); + /// println!( "{}/{}", current_frame.unwrap(), frame_count.unwrap()); + /// ``` /// Calls [`sys::ffi::playdate_video::renderFrame`]. #[doc(alias = "sys::ffi::playdate_video::renderFrame")] pub fn info_raw(&self, From e1011c5ded0a6d1bc80715b661652068e0b4fe25 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Thu, 28 Sep 2023 16:10:05 +0400 Subject: [PATCH 4/6] sound: add source, various little improvements --- Cargo.lock | 2 +- api/sound/examples/fp-simple.rs | 4 +- api/sound/examples/sp-simple.rs | 4 +- api/sound/src/lib.rs | 406 +++++++++++++++++++++++++++++++- api/sound/src/player/fp/mod.rs | 8 +- api/sound/src/player/sp/mod.rs | 8 +- api/sound/src/source.rs | 167 +++++++++++++ 7 files changed, 585 insertions(+), 14 deletions(-) create mode 100644 api/sound/src/source.rs diff --git a/Cargo.lock b/Cargo.lock index 08f4fb57..15fb5caa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2593,7 +2593,7 @@ dependencies = [ [[package]] name = "playdate-graphics" -version = "0.3.1" +version = "0.3.2" dependencies = [ "playdate-color", "playdate-fs", diff --git a/api/sound/examples/fp-simple.rs b/api/sound/examples/fp-simple.rs index 73a79784..8585b779 100644 --- a/api/sound/examples/fp-simple.rs +++ b/api/sound/examples/fp-simple.rs @@ -34,8 +34,8 @@ impl State { /// Updates the state fn update(&mut self) -> Option<()> { let text: Cow = if let Some(player) = self.player.as_ref() { - let offset = player.get_offset(); - let length = player.get_length(); + let offset = player.offset(); + let length = player.length(); format!("{:.2} / {:.2}", offset, length).into() } else { "no player".into() diff --git a/api/sound/examples/sp-simple.rs b/api/sound/examples/sp-simple.rs index fdc23d35..dea9c881 100644 --- a/api/sound/examples/sp-simple.rs +++ b/api/sound/examples/sp-simple.rs @@ -35,8 +35,8 @@ impl State { /// Updates the state fn update(&mut self) -> Option<()> { let text: Cow = if let Some(player) = self.player.as_ref() { - let offset = player.get_offset(); - let length = player.get_length(); + let offset = player.offset(); + let length = player.length(); format!("{:.2} / {:.2}", offset, length).into() } else { "no player".into() diff --git a/api/sound/src/lib.rs b/api/sound/src/lib.rs index 094e7e23..31e254a9 100644 --- a/api/sound/src/lib.rs +++ b/api/sound/src/lib.rs @@ -2,14 +2,21 @@ #![feature(error_in_core)] #![feature(const_trait_impl)] +use core::ffi::c_int; +use core::ffi::c_void; + +use sys::ffi::AudioSourceFunction; +use sys::traits::AsRaw; + extern crate sys; extern crate alloc; pub mod error; pub mod player; pub mod sample; +pub mod source; -// TODO: sound: channel, synth, sequence, effect, lfo, envelope, source!, etc.. +// TODO: Sound api: channel, synth, sequence, effect, lfo, envelope, callbacks, etc.. pub mod prelude { @@ -19,3 +26,400 @@ pub mod prelude { pub use crate::player; pub use crate::sample; } + + +#[derive(Debug, Clone, Copy)] +pub struct Sound(Api); + +impl Sound { + /// Creates default [`Sound`] without type parameter requirement. + /// + /// Uses ZST [`api::Default`]. + #[allow(non_snake_case)] + pub fn Default() -> Self { Self(Default::default()) } +} + +impl Sound { + /// Creates [`Sound`] without type parameter requirement. + /// + /// Uses [`api::Cache`]. + #[allow(non_snake_case)] + pub fn Cached() -> Self { Self(Default::default()) } +} + +impl Default for Sound { + fn default() -> Self { Self(Default::default()) } +} + +impl Sound { + pub fn new() -> Self { Self(Default::default()) } +} + +impl Sound { + pub fn new_with(api: Api) -> Self { Self(api) } +} + +impl Sound { + /// Returns the sound engine’s current time value, in units of sample frames, `44100` per second. + /// + /// Equivalent to [`sys::ffi::playdate_sound::getCurrentTime`] + #[doc(alias = "sys::ffi::playdate_sound::getCurrentTime")] + pub fn current_time(&self) -> u32 { + let f = self.0.get_current_time(); + unsafe { f() } + } + + + /// If `headphone` is `Some`, the value is set to 1 if headphones are currently plugged in. + /// + /// Likewise, `mic` is set if the headphones include a microphone. + /// + /// Example: + /// ```no_run + /// let mut headphone = Some(0); + /// let mut mic = Some(0); + /// sound.headphone_state(headphone.as_mut(), mic.as_mut()); + /// println!( "{}/{}", current_frame.unwrap(), mic.unwrap()); + /// ``` + /// See also [`Sound::set_headphone_state_change_callback`]. + #[doc(alias = "sys::ffi::playdate_sound::getHeadphoneState")] + #[inline(always)] + pub fn headphone_state(&self, headphone: Option<&mut c_int>, mic: Option<&mut c_int>) { + self.set_headphone_state_change_callback(headphone, mic, None) + } + + /// If `headphone` is `Some`, the value is set to 1 if headphones are currently plugged in. + /// + /// Likewise, `mic` is set if the headphones include a microphone. + /// + /// If `change_callback` is provided, it will be called when the headset or mic status changes, + /// and audio output will not automatically switch from speaker to headphones when headphones are plugged in (and vice versa). + /// + /// In this case, the callback should use [`Sound::set_outputs_active`] to change the output if needed. + /// + /// Equivalent to [`sys::ffi::playdate_sound::getHeadphoneState`] + #[doc(alias = "sys::ffi::playdate_sound::getHeadphoneState")] + pub fn set_headphone_state_change_callback(&self, + headphone: Option<&mut c_int>, + mic: Option<&mut c_int>, + change_callback: Option) { + use core::ptr::null_mut; + + let f = self.0.get_headphone_state(); + unsafe { + f( + headphone.map_or(null_mut() as _, |v| v as *mut _), + mic.map_or(null_mut() as _, |v| v as *mut _), + change_callback, + ) + } + } + + /// Force audio output to the given outputs, regardless of headphone status. + /// + /// Equivalent to [`sys::ffi::playdate_sound::setOutputsActive`] + #[doc(alias = "sys::ffi::playdate_sound::setOutputsActive")] + pub fn set_outputs_active(&self, headphone: bool, speaker: bool) { + let f = self.0.set_outputs_active(); + unsafe { f(headphone.into(), speaker.into()) } + } + + /// The callback function you pass in will be called every audio render cycle. + /// + /// ```no_run + /// // AudioSourceFunction: + /// unsafe extern "C" fn(context: *mut c_void, left: *mut i16, right: *mut i16, len: c_int) -> c_int + /// ``` + /// This function should fill the passed-in `left` buffer (and `right` if it’s a stereo source) + /// with `len` samples each and return 1, + /// or return 0 if the source is silent through the cycle. + /// + /// Equivalent to [`sys::ffi::playdate_sound::addSource`] + #[doc(alias = "sys::ffi::playdate_sound::addSource")] + pub fn add_source_raw(&self, + callback: AudioSourceFunction, + context: *mut c_void, + stereo: bool) + -> source::SoundSource { + let f = self.0.add_source(); + unsafe { f(callback, context, stereo.into()) }.into() + } + + /// Removes the given [`SoundSource`](source::SoundSource) object from its channel, + /// whether it’s in the default channel or a channel created with [`Sound::add_channel`]. + /// + /// Returns `true` if a source was removed, `false` if the source wasn’t in a channel. + /// + /// Equivalent to [`sys::ffi::playdate_sound::removeSource`] + #[doc(alias = "sys::ffi::playdate_sound::removeSource")] + pub fn remove_source(&self, source: &source::SoundSource) -> bool { + let f = self.0.remove_source(); + unsafe { f(source.as_raw()) == 1 } + } + + + // /// Returns the default channel, where sound sources play + // /// if they haven’t been explicitly assigned to a different channel. + // /// + // /// Equivalent to [`sys::ffi::playdate_sound::getDefaultChannel`] + // #[doc(alias = "sys::ffi::playdate_sound::getDefaultChannel")] + // pub fn default_channel(&self) -> *mut SoundChannel { + // let f = self.0.get_default_channel(); + // unsafe { f() } + // } + + // /// Adds the given channel to the sound engine. + // /// + // /// Returns 1 if the channel was added, 0 if it was already in the engine. + // /// + // /// Equivalent to [`sys::ffi::playdate_sound::addChannel`] + // #[doc(alias = "sys::ffi::playdate_sound::addChannel")] + // pub fn add_channel(&self, channel: *mut SoundChannel) -> c_int { + // let f = self.0.add_channel(); + // unsafe { f(channel) } + // } + + // /// Removes the given channel from the sound engine. + // /// + // /// Returns 1 if the channel was successfully removed, 0 if the channel is the default channel or hadn’t been previously added. + // /// + // /// Equivalent to [`sys::ffi::playdate_sound::removeChannel`] + // #[doc(alias = "sys::ffi::playdate_sound::removeChannel")] + // pub fn remove_channel(&self, channel: *mut SoundChannel) -> c_int { + // let f = self.0.remove_channel(); + // unsafe { f(channel) } + // } + + // /// The callback you pass in will be called every audio cycle. + // /// + // /// Equivalent to [`sys::ffi::playdate_sound::setMicCallback`] + // #[doc(alias = "sys::ffi::playdate_sound::setMicCallback")] + // pub fn set_mic_callback(&self, callback: RecordCallback, context: *mut c_void, force_internal: c_int) { + // let f = self.0.set_mic_callback(); + // unsafe { f(callback, context, force_internal) } + // } +} + + +pub mod api { + use core::ffi::c_int; + use core::ffi::c_void; + use core::ptr::NonNull; + use sys::ffi::*; + + + /// Default sound api end-point, ZST. + /// + /// All calls approximately costs ~3 derefs. + #[derive(Debug, Clone, Copy, core::default::Default)] + pub struct Default; + impl Api for Default {} + + + /// Cached sound api end-point. + /// + /// Stores one reference, so size on stack is eq `usize`. + /// + /// All calls approximately costs ~1 deref. + #[derive(Clone, Copy)] + #[cfg_attr(feature = "bindings-derive-debug", derive(Debug))] + pub struct Cache(&'static playdate_sound); + + impl core::default::Default for Cache { + fn default() -> Self { Self(sys::api!(sound)) } + } + + impl From<*const playdate_sound> for Cache { + #[inline(always)] + fn from(ptr: *const playdate_sound) -> Self { Self(unsafe { ptr.as_ref() }.expect("sp")) } + } + + impl From<&'static playdate_sound> for Cache { + #[inline(always)] + fn from(r: &'static playdate_sound) -> Self { Self(r) } + } + + impl From> for Cache { + #[inline(always)] + fn from(ptr: NonNull) -> Self { Self(unsafe { ptr.as_ref() }) } + } + + impl From<&'_ NonNull> for Cache { + #[inline(always)] + fn from(ptr: &NonNull) -> Self { Self(unsafe { ptr.as_ref() }) } + } + + + impl Api for Cache { + #[inline(always)] + fn channel(&self) -> &'static playdate_sound_channel { + unsafe { self.0.channel.as_ref() }.expect("channel") + } + #[inline(always)] + fn fileplayer(&self) -> &'static playdate_sound_fileplayer { + unsafe { self.0.fileplayer.as_ref() }.expect("fileplayer") + } + #[inline(always)] + fn sample(&self) -> &'static playdate_sound_sample { unsafe { self.0.sample.as_ref() }.expect("sample") } + #[inline(always)] + fn sampleplayer(&self) -> &'static playdate_sound_sampleplayer { + unsafe { self.0.sampleplayer.as_ref() }.expect("sampleplayer") + } + #[inline(always)] + fn synth(&self) -> &'static playdate_sound_synth { unsafe { self.0.synth.as_ref() }.expect("synth") } + #[inline(always)] + fn sequence(&self) -> &'static playdate_sound_sequence { + unsafe { self.0.sequence.as_ref() }.expect("sequence") + } + #[inline(always)] + fn effect(&self) -> &'static playdate_sound_effect { unsafe { self.0.effect.as_ref() }.expect("effect") } + #[inline(always)] + fn lfo(&self) -> &'static playdate_sound_lfo { unsafe { self.0.lfo.as_ref() }.expect("lfo") } + #[inline(always)] + fn envelope(&self) -> &'static playdate_sound_envelope { + unsafe { self.0.envelope.as_ref() }.expect("envelope") + } + #[inline(always)] + fn source(&self) -> &'static playdate_sound_source { unsafe { self.0.source.as_ref() }.expect("source") } + #[inline(always)] + fn control_signal(&self) -> &'static playdate_control_signal { + unsafe { self.0.controlsignal.as_ref() }.expect("controlsignal") + } + #[inline(always)] + fn track(&self) -> &'static playdate_sound_track { unsafe { self.0.track.as_ref() }.expect("track") } + #[inline(always)] + fn instrument(&self) -> &'static playdate_sound_instrument { + unsafe { self.0.instrument.as_ref() }.expect("instrument") + } + #[inline(always)] + fn signal(&self) -> &'static playdate_sound_signal { unsafe { self.0.signal.as_ref() }.expect("signal") } + + + fn get_current_time(&self) -> unsafe extern "C" fn() -> u32 { + self.0.getCurrentTime.expect("getCurrentTime") + } + + fn add_source( + &self) + -> unsafe extern "C" fn(callback: AudioSourceFunction, + context: *mut c_void, + stereo: c_int) -> *mut SoundSource { + self.0.addSource.expect("addSource") + } + + fn get_default_channel(&self) -> unsafe extern "C" fn() -> *mut SoundChannel { + self.0.getDefaultChannel.expect("getDefaultChannel") + } + + fn add_channel(&self) -> unsafe extern "C" fn(channel: *mut SoundChannel) -> c_int { + self.0.addChannel.expect("addChannel") + } + + fn remove_channel(&self) -> unsafe extern "C" fn(channel: *mut SoundChannel) -> c_int { + self.0.removeChannel.expect("removeChannel") + } + + fn set_mic_callback( + &self) + -> unsafe extern "C" fn(callback: RecordCallback, context: *mut c_void, forceInternal: c_int) { + self.0.setMicCallback.expect("setMicCallback") + } + + fn get_headphone_state( + &self) + -> unsafe extern "C" fn(headphone: *mut c_int, + headsetmic: *mut c_int, + changeCallback: Option) { + self.0.getHeadphoneState.expect("getHeadphoneState") + } + + fn set_outputs_active(&self) -> unsafe extern "C" fn(headphone: c_int, speaker: c_int) { + self.0.setOutputsActive.expect("setOutputsActive") + } + + fn remove_source(&self) -> unsafe extern "C" fn(source: *mut SoundSource) -> c_int { + self.0.removeSource.expect("removeSource") + } + } + + + pub trait Api { + fn channel(&self) -> &'static playdate_sound_channel { sys::api!(sound.channel) } + fn fileplayer(&self) -> &'static playdate_sound_fileplayer { sys::api!(sound.fileplayer) } + fn sample(&self) -> &'static playdate_sound_sample { sys::api!(sound.sample) } + fn sampleplayer(&self) -> &'static playdate_sound_sampleplayer { sys::api!(sound.sampleplayer) } + fn synth(&self) -> &'static playdate_sound_synth { sys::api!(sound.synth) } + fn sequence(&self) -> &'static playdate_sound_sequence { sys::api!(sound.sequence) } + fn effect(&self) -> &'static playdate_sound_effect { sys::api!(sound.effect) } + fn lfo(&self) -> &'static playdate_sound_lfo { sys::api!(sound.lfo) } + fn envelope(&self) -> &'static playdate_sound_envelope { sys::api!(sound.envelope) } + fn source(&self) -> &'static playdate_sound_source { sys::api!(sound.source) } + fn control_signal(&self) -> &'static playdate_control_signal { sys::api!(sound.controlsignal) } + fn track(&self) -> &'static playdate_sound_track { sys::api!(sound.track) } + fn instrument(&self) -> &'static playdate_sound_instrument { sys::api!(sound.instrument) } + fn signal(&self) -> &'static playdate_sound_signal { sys::api!(sound.signal) } + + /// Returns [`sys::ffi::playdate_sound::getCurrentTime`] + #[doc(alias = "sys::ffi::playdate_sound::getCurrentTime")] + fn get_current_time(&self) -> unsafe extern "C" fn() -> u32 { *sys::api!(sound.getCurrentTime) } + + /// Returns [`sys::ffi::playdate_sound::addSource`] + #[doc(alias = "sys::ffi::playdate_sound::addSource")] + fn add_source( + &self) + -> unsafe extern "C" fn(callback: AudioSourceFunction, + context: *mut c_void, + stereo: c_int) -> *mut SoundSource { + *sys::api!(sound.addSource) + } + + /// Returns [`sys::ffi::playdate_sound::getDefaultChannel`] + #[doc(alias = "sys::ffi::playdate_sound::getDefaultChannel")] + fn get_default_channel(&self) -> unsafe extern "C" fn() -> *mut SoundChannel { + *sys::api!(sound.getDefaultChannel) + } + + /// Returns [`sys::ffi::playdate_sound::addChannel`] + #[doc(alias = "sys::ffi::playdate_sound::addChannel")] + fn add_channel(&self) -> unsafe extern "C" fn(channel: *mut SoundChannel) -> c_int { + *sys::api!(sound.addChannel) + } + + /// Returns [`sys::ffi::playdate_sound::removeChannel`] + #[doc(alias = "sys::ffi::playdate_sound::removeChannel")] + fn remove_channel(&self) -> unsafe extern "C" fn(channel: *mut SoundChannel) -> c_int { + *sys::api!(sound.removeChannel) + } + + /// Returns [`sys::ffi::playdate_sound::setMicCallback`] + #[doc(alias = "sys::ffi::playdate_sound::setMicCallback")] + fn set_mic_callback( + &self) + -> unsafe extern "C" fn(callback: RecordCallback, context: *mut c_void, forceInternal: c_int) { + *sys::api!(sound.setMicCallback) + } + + /// Returns [`sys::ffi::playdate_sound::getHeadphoneState`] + #[doc(alias = "sys::ffi::playdate_sound::getHeadphoneState")] + fn get_headphone_state( + &self) + -> unsafe extern "C" fn(headphone: *mut c_int, + headsetmic: *mut c_int, + changeCallback: Option) { + *sys::api!(sound.getHeadphoneState) + } + + /// Returns [`sys::ffi::playdate_sound::setOutputsActive`] + #[doc(alias = "sys::ffi::playdate_sound::setOutputsActive")] + fn set_outputs_active(&self) -> unsafe extern "C" fn(headphone: c_int, speaker: c_int) { + *sys::api!(sound.setOutputsActive) + } + + /// Returns [`sys::ffi::playdate_sound::removeSource`] + #[doc(alias = "sys::ffi::playdate_sound::removeSource")] + fn remove_source(&self) -> unsafe extern "C" fn(source: *mut SoundSource) -> c_int { + *sys::api!(sound.removeSource) + } + } +} diff --git a/api/sound/src/player/fp/mod.rs b/api/sound/src/player/fp/mod.rs index 3d24aabf..33dd33f7 100644 --- a/api/sound/src/player/fp/mod.rs +++ b/api/sound/src/player/fp/mod.rs @@ -124,7 +124,7 @@ impl Player where Api: api::Api { /// /// Equivalent to [getVolume](sys::ffi::playdate_sound_fileplayer::getVolume) #[doc(alias = "sys::ffi::playdate_sound_fileplayer::getVolume")] - pub fn get_volume(&self) -> (c_float, c_float) { + pub fn volume(&self) -> (c_float, c_float) { let (mut left, mut right) = (0.0, 0.0); let f = self.api().get_volume(); unsafe { f(self.0, &mut left, &mut right) }; @@ -144,7 +144,7 @@ impl Player where Api: api::Api { /// /// Equivalent to [getLength](sys::ffi::playdate_sound_fileplayer::getLength) #[doc(alias = "sys::ffi::playdate_sound_fileplayer::getLength")] - pub fn get_length(&self) -> c_float { + pub fn length(&self) -> c_float { let f = self.api().get_length(); unsafe { f(self.0) } } @@ -153,7 +153,7 @@ impl Player where Api: api::Api { /// /// Equivalent to [getOffset](sys::ffi::playdate_sound_fileplayer::getOffset) #[doc(alias = "sys::ffi::playdate_sound_fileplayer::getOffset")] - pub fn get_offset(&self) -> c_float { + pub fn offset(&self) -> c_float { let f = self.api().get_offset(); unsafe { f(self.0) } } @@ -171,7 +171,7 @@ impl Player where Api: api::Api { /// /// Equivalent to [getRate](sys::ffi::playdate_sound_fileplayer::getRate) #[doc(alias = "sys::ffi::playdate_sound_fileplayer::getRate")] - pub fn get_rate(&self) -> c_float { + pub fn rate(&self) -> c_float { let f = self.api().get_rate(); unsafe { f(self.0) } } diff --git a/api/sound/src/player/sp/mod.rs b/api/sound/src/player/sp/mod.rs index 599e1992..27b8ac08 100644 --- a/api/sound/src/player/sp/mod.rs +++ b/api/sound/src/player/sp/mod.rs @@ -111,7 +111,7 @@ impl Player where Api: api::Api { /// /// Equivalent to [getVolume](sys::ffi::playdate_sound_sampleplayer::getVolume) #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::getVolume")] - pub fn get_volume(&self) -> (c_float, c_float) { + pub fn volume(&self) -> (c_float, c_float) { let (mut left, mut right) = (0.0, 0.0); let f = self.api().get_volume(); unsafe { f(self.0, &mut left, &mut right) }; @@ -131,7 +131,7 @@ impl Player where Api: api::Api { /// /// Equivalent to [getLength](sys::ffi::playdate_sound_sampleplayer::getLength) #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::getLength")] - pub fn get_length(&self) -> c_float { + pub fn length(&self) -> c_float { let f = self.api().get_length(); unsafe { f(self.0) } } @@ -140,7 +140,7 @@ impl Player where Api: api::Api { /// /// Equivalent to [getOffset](sys::ffi::playdate_sound_sampleplayer::getOffset) #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::getOffset")] - pub fn get_offset(&self) -> c_float { + pub fn offset(&self) -> c_float { let f = self.api().get_offset(); unsafe { f(self.0) } } @@ -158,7 +158,7 @@ impl Player where Api: api::Api { /// /// Equivalent to [getRate](sys::ffi::playdate_sound_sampleplayer::getRate) #[doc(alias = "sys::ffi::playdate_sound_sampleplayer::getRate")] - pub fn get_rate(&self) -> c_float { + pub fn rate(&self) -> c_float { let f = self.api().get_rate(); unsafe { f(self.0) } } diff --git a/api/sound/src/source.rs b/api/sound/src/source.rs new file mode 100644 index 00000000..4d4263fc --- /dev/null +++ b/api/sound/src/source.rs @@ -0,0 +1,167 @@ +//! Playdate sound-source API + +use core::ffi::c_float; + +use sys::ffi::sndCallbackProc; +use sys::ffi::SoundSource as OpaqueSoundSource; +use sys::traits::AsRaw; + + +#[cfg_attr(feature = "bindings-derive-debug", derive(Debug))] +pub struct SoundSource(*mut OpaqueSoundSource, Api); + +impl AsRaw for SoundSource { + type Type = OpaqueSoundSource; + unsafe fn as_raw(&self) -> *mut Self::Type { self.0 } +} + +impl From<*mut OpaqueSoundSource> for SoundSource { + fn from(ptr: *mut OpaqueSoundSource) -> Self { Self(ptr, Default::default()) } +} + +impl SoundSource { + pub fn from_with(api: Api, ptr: *mut OpaqueSoundSource) -> Self { Self(ptr, api) } + + /// Returns `true` if the source is currently playing. + /// + /// Equivalent to [`sys::ffi::playdate_sound_source::isPlaying`] + #[doc(alias = "sys::ffi::playdate_sound_source::isPlaying")] + pub fn is_playing(&self) -> bool { + let f = self.1.is_playing(); + unsafe { f(self.0) == 1 } + } + + /// Gets the playback volume (`0.0` - `1.0`) for `left` and `right` channels of the source. + /// + /// Equivalent to [`sys::ffi::playdate_sound_source::getVolume`] + #[doc(alias = "sys::ffi::playdate_sound_source::getVolume")] + pub fn get_volume(&self) -> (c_float, c_float) { + let mut l = 0.; + let mut r = 0.; + let f = self.1.get_volume(); + unsafe { f(self.0, &mut l, &mut r) }; + (l, r) + } + + /// Sets the playback volume (`0.0` - `1.0`) for `left` and `right` channels of the source. + /// + /// Equivalent to [`sys::ffi::playdate_sound_source::setVolume`] + #[doc(alias = "sys::ffi::playdate_sound_source::setVolume")] + pub fn set_volume(&self, left: c_float, right: c_float) { + let f = self.1.set_volume(); + unsafe { f(self.0, left, right) } + } + + + /// Equivalent to [`sys::ffi::playdate_sound_source::setFinishCallback`] + #[doc(alias = "sys::ffi::playdate_sound_source::setFinishCallback")] + pub fn set_finish_callback_raw(&self, callback: sndCallbackProc) { + let f = self.1.set_finish_callback(); + unsafe { f(self.0, callback) } + } +} + + +pub mod api { + use core::ffi::c_float; + use core::ptr::NonNull; + + use sys::ffi::SoundSource; + use sys::ffi::sndCallbackProc; + use sys::ffi::playdate_sound_source; + + /// Default sound source api end-point, ZST. + /// + /// All calls approximately costs ~4 derefs. + #[derive(Debug, Clone, Copy, core::default::Default)] + pub struct Default; + impl Api for Default {} + + + /// Cached sound source api end-point. + /// + /// Stores one reference, so size on stack is eq `usize`. + /// + /// All calls approximately costs ~1 deref. + #[derive(Clone, Copy)] + #[cfg_attr(feature = "bindings-derive-debug", derive(Debug))] + pub struct Cache(&'static playdate_sound_source); + + impl core::default::Default for Cache { + fn default() -> Self { Self(sys::api!(sound.source)) } + } + + impl From<*const playdate_sound_source> for Cache { + #[inline(always)] + fn from(ptr: *const playdate_sound_source) -> Self { Self(unsafe { ptr.as_ref() }.expect("snd.src")) } + } + + impl From<&'static playdate_sound_source> for Cache { + #[inline(always)] + fn from(r: &'static playdate_sound_source) -> Self { Self(r) } + } + + impl From> for Cache { + #[inline(always)] + fn from(ptr: NonNull) -> Self { Self(unsafe { ptr.as_ref() }) } + } + + impl From<&'_ NonNull> for Cache { + #[inline(always)] + fn from(ptr: &NonNull) -> Self { Self(unsafe { ptr.as_ref() }) } + } + + + impl Api for Cache { + #[inline(always)] + fn set_volume(&self) -> unsafe extern "C" fn(c: *mut SoundSource, lvol: c_float, rvol: c_float) { + self.0.setVolume.expect("setVolume") + } + + #[inline(always)] + fn get_volume(&self) -> unsafe extern "C" fn(c: *mut SoundSource, outl: *mut c_float, outr: *mut c_float) { + self.0.getVolume.expect("getVolume") + } + + #[inline(always)] + fn is_playing(&self) -> unsafe extern "C" fn(c: *mut SoundSource) -> core::ffi::c_int { + self.0.isPlaying.expect("isPlaying") + } + + #[inline(always)] + fn set_finish_callback(&self) -> unsafe extern "C" fn(c: *mut SoundSource, callback: sndCallbackProc) { + self.0.setFinishCallback.expect("setFinishCallback") + } + } + + + pub trait Api { + /// Returns [`sys::ffi::playdate_sound_source::setVolume`] + #[doc(alias = "sys::ffi::playdate_sound_source::setVolume")] + #[inline(always)] + fn set_volume(&self) -> unsafe extern "C" fn(c: *mut SoundSource, lvol: c_float, rvol: c_float) { + *sys::api!(sound.source.setVolume) + } + + /// Returns [`sys::ffi::playdate_sound_source::getVolume`] + #[doc(alias = "sys::ffi::playdate_sound_source::getVolume")] + #[inline(always)] + fn get_volume(&self) -> unsafe extern "C" fn(c: *mut SoundSource, outl: *mut c_float, outr: *mut c_float) { + *sys::api!(sound.source.getVolume) + } + + /// Returns [`sys::ffi::playdate_sound_source::isPlaying`] + #[doc(alias = "sys::ffi::playdate_sound_source::isPlaying")] + #[inline(always)] + fn is_playing(&self) -> unsafe extern "C" fn(c: *mut SoundSource) -> core::ffi::c_int { + *sys::api!(sound.source.isPlaying) + } + + /// Returns [`sys::ffi::playdate_sound_source::setFinishCallback`] + #[doc(alias = "sys::ffi::playdate_sound_source::setFinishCallback")] + #[inline(always)] + fn set_finish_callback(&self) -> unsafe extern "C" fn(c: *mut SoundSource, callback: sndCallbackProc) { + *sys::api!(sound.source.setFinishCallback) + } + } +} From 57c61d0416593b88833520dae7e2811b2989b756 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Thu, 28 Sep 2023 16:11:17 +0400 Subject: [PATCH 5/6] core: add sound end-point for easy-access from event-handler, improve video example --- api/playdate/examples/hello-world.rs | 8 +++--- api/playdate/examples/video.rs | 41 ++++++++++++++-------------- api/playdate/src/lib.rs | 12 +++++++- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/api/playdate/examples/hello-world.rs b/api/playdate/examples/hello-world.rs index 8854b2e8..8c24acb1 100644 --- a/api/playdate/examples/hello-world.rs +++ b/api/playdate/examples/hello-world.rs @@ -104,14 +104,14 @@ impl State { // Background music // Create player - let player = SamplePlayer::try_new().unwrap(); + let player = SamplePlayer::new().unwrap(); // load sound - let sample = Sample::new_from_file(SOUND_PATH); - player.try_set_sample(&sample).unwrap(); + let sample = Sample::new_from_file(SOUND_PATH).unwrap(); + player.set_sample(&sample); // start playback - player.try_play(Repeat::LoopsEndlessly, 1.0).unwrap(); + player.play(Repeat::LoopsEndlessly, 1.0); // Finally store it all in the state Self { location: Point::new(CENTER_X as _, CENTER_Y as _), diff --git a/api/playdate/examples/video.rs b/api/playdate/examples/video.rs index a4a2c0fb..0072ca7e 100644 --- a/api/playdate/examples/video.rs +++ b/api/playdate/examples/video.rs @@ -46,26 +46,27 @@ fn event_handler(api: NonNull, event: SystemEvent, _sim_key_code: u player.use_screen_context(); // Register update handler - api.system().set_update_callback_boxed( - |state| { - // Draw current frame of the player - state.player.render_frame(state.current).unwrap(); - - // Advance to the next frame - state.current += 1; - if state.current >= state.length { - state.current = 0; - } - - // Draw FPS on-top of the player's render - System::Default().draw_fps(0, 0); - - // Continue - true - }, - State { length: player.info().frame_count, - current: 0, - player, }, + let system = api.system(); + system.set_update_callback_boxed( + move |state| { + // Draw current frame of the player + state.player.render_frame(state.current).unwrap(); + + // Advance to the next frame + state.current += 1; + if state.current >= state.length { + state.current = 0; + } + + // Draw FPS on-top of the player's render + system.draw_fps(0, 0); + + // Continue + true + }, + State { length: player.info().frame_count, + current: 0, + player, }, ); true diff --git a/api/playdate/src/lib.rs b/api/playdate/src/lib.rs index 2eec511b..3a6b3a35 100644 --- a/api/playdate/src/lib.rs +++ b/api/playdate/src/lib.rs @@ -55,7 +55,9 @@ pub mod ext { /// Playdate Display API. fn display(&self) -> display::Display; - // fn sound() -> sound::Sound; + /// Playdate Sound API. + fn sound(&self) -> sound::Sound; + // fn lua() -> lua::Lua; // fn json() -> json::Json; // fn scoreboards() -> scoreboards::Scoreboards; @@ -78,6 +80,10 @@ pub mod ext { fn display(&self) -> display::Display { display::Display::new_with(display::api::Cache::from(unsafe { self.as_ref() }.display)) } + + fn sound(&self) -> sound::Sound { + sound::Sound::new_with(sound::api::Cache::from(unsafe { self.as_ref() }.sound)) + } } impl PlaydateAPIExt for *const sys::ffi::PlaydateAPI { @@ -96,5 +102,9 @@ pub mod ext { fn display(&self) -> display::Display { display::Display::new_with(display::api::Cache::from(unsafe { self.as_ref() }.expect("api").display)) } + + fn sound(&self) -> sound::Sound { + sound::Sound::new_with(sound::api::Cache::from(unsafe { self.as_ref() }.expect("api").sound)) + } } } From 88fa7d6c5a894f8e1ba41dc2ef6223a9d1ad1176 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Thu, 28 Sep 2023 16:25:14 +0400 Subject: [PATCH 6/6] Add new and changed parts to the readme. --- api/sound/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/api/sound/README.md b/api/sound/README.md index 88bdc836..fd28abce 100644 --- a/api/sound/README.md +++ b/api/sound/README.md @@ -3,13 +3,15 @@ High-level sound API built on-top of [playdate-sys][]. Covered components: -- FilePlayer -- SamplePlayer +- File Player +- Sample Player - Sample +- Sound Source +- Headphones and microphone (incomplete) 🤏 ⚠️ __Incomplete__, WiP. -🚨 Before the version `0.3` API is highly unstable and subject to change. After that old versions will yanked. +Before the version `0.3` API is unstable and can be changed. ## Prerequisites