diff --git a/Cargo.toml b/Cargo.toml index e921201..f0b2190 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,7 @@ repository = "https://github.com/alisomay/libpd-rs" documentation = "https://docs.rs/libpd-rs/latest/libpd_rs/#" keywords = ["puredata", "libpd", "audio", "midi", "bindings"] categories = ["multimedia"] -exclude = [ - "tests/*", - "assets/favicon/*", - "assets/logo_*" -] +exclude = ["tests/*", "assets/favicon/*", "assets/logo_*"] [lib] name = "libpd_rs" @@ -35,11 +31,12 @@ tempfile = "3.3.0" embed-doc-image = "0.1.4" [dev-dependencies] -cpal = "0.15.2" +cpal = "0.15.3" sys-info = "0.9.1" nannou = "0.19" nannou_audio = "0.19" rand = "0.8.5" +serial_test = "3" # For local development, # [patch.crates-io] @@ -48,7 +45,3 @@ rand = "0.8.5" # For local development, # [patch."https://github.com/alisomay/libpd-sys"] # libpd-sys = { path = "../libpd-sys" } - - - - diff --git a/examples/simple.rs b/examples/simple.rs index 0e1af11..d83c2f6 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -20,6 +20,7 @@ fn main() -> Result<(), Box> { // Initialize libpd with that configuration, // with no input channels since we're not going to use them. let mut pd = Pd::init_and_configure(0, output_channels, sample_rate)?; + let mut ctx = pd.audio_context(); // Let's evaluate a pd patch. // We could have opened a `.pd` file also. @@ -49,7 +50,7 @@ fn main() -> Result<(), Box> { // Here if we had an input buffer we could have modified it to do pre-processing. // Process audio, advance internal scheduler. - libpd_rs::functions::process::process_float(ticks, &[], data); + ctx.process_float(ticks, &[], data); // Here we could have done post processing after pd processed our output buffer in place. }, diff --git a/examples/with_nannou/bubble.rs b/examples/with_nannou/bubble.rs index 4aa61cf..18e59bf 100644 --- a/examples/with_nannou/bubble.rs +++ b/examples/with_nannou/bubble.rs @@ -140,7 +140,7 @@ impl Bubble { } /// Transforms the voice message of the bubble to send to pure data. - pub fn pack_message(&self) -> Vec { + pub fn pack_message(&self) -> Vec { self.state .message .into_iter() @@ -212,6 +212,7 @@ impl Bubble { // Collision with the floor! if distance_to_floor < self.properties.r * 2.0 { + model.pd.set_as_current(); // On collision we tell the right voice to play with the right parameters in pd. libpd_rs::functions::send::send_list_to("bubble_collision", &self.pack_message()) .unwrap(); diff --git a/examples/with_nannou/main.rs b/examples/with_nannou/main.rs index f4f333a..a326a0f 100644 --- a/examples/with_nannou/main.rs +++ b/examples/with_nannou/main.rs @@ -2,6 +2,7 @@ mod bubble; use bubble::Bubble; +use libpd_rs::PdAudioContext; use nannou::prelude::*; use nannou_audio as audio; use nannou_audio::Buffer; @@ -15,7 +16,7 @@ fn main() { // This data structure will be shared across nannou functions. pub struct Model { pd: libpd_rs::Pd, - output_stream: audio::Stream<()>, + output_stream: audio::Stream, gravity: f32, bubbles: RefCell>, bubble_count: usize, @@ -50,9 +51,13 @@ fn model(app: &App) -> Model { // .into_iter() // .find(|d| d.name().unwrap() == "BlackHole 16ch"); + let pd = libpd_rs::Pd::init_and_configure(0, channels as i32, sample_rate as i32).unwrap(); + pd.set_as_current(); + let audio_ctx = pd.audio_context(); + // Start the stream registering our audio callback. let output_stream = audio_host - .new_output_stream(()) + .new_output_stream(audio_ctx) // Uncomment to pick another audio device. // .device(device.unwrap()) .channels(channels) @@ -69,7 +74,7 @@ fn model(app: &App) -> Model { // This data structure will be shared across nannou functions. let mut model = Model { // Initialize pd - pd: libpd_rs::Pd::init_and_configure(0, channels as i32, sample_rate as i32).unwrap(), + pd, output_stream, gravity: 0.8, bubbles: RefCell::new(vec![]), @@ -138,15 +143,16 @@ impl Model { // This is where we process audio. // We hand over all tasks to our pd patch! -fn audio_callback(_: &mut (), buffer: &mut Buffer) { +fn audio_callback(pd_audio_context: &mut PdAudioContext, buffer: &mut Buffer) { let ticks = libpd_rs::functions::util::calculate_ticks(buffer.channels() as i32, buffer.len() as i32); - libpd_rs::functions::process::process_float(ticks, &[], buffer); + pd_audio_context.process_float(ticks, &[], buffer); } // This is where we draw repeatedly! fn view(app: &App, model: &Model, frame: Frame) { // Let's poll pd messages here, for every frame. + model.pd.set_as_current(); libpd_rs::functions::receive::receive_messages_from_pd(); let background_color = nannou::color::srgb8(238, 108, 77); diff --git a/src/atom.rs b/src/atom.rs new file mode 100644 index 0000000..a0b2c45 --- /dev/null +++ b/src/atom.rs @@ -0,0 +1,381 @@ +use std::ffi::{CStr, CString}; +use std::fmt::{self, Display}; +use std::ptr; + +use crate::error::{InstanceError, PdError, StringConversionError}; + +/// A type to represent a pd Atom type in Rust side. +/// +/// Pd has floating point numbers and symbols as primitive types. +/// This enum maps those to their Rust counterparts. +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub enum Atom { + /// A floating point number from pd. + Float(f64), + /// A symbol from pd. Symbols are interned in pd, but it can be treated as Strings in Rust. + Symbol(String), +} + +impl Atom { + /// Converts a Rust `Atom` to a C `t_atom`. + /// + /// For symbols, this function requires a current libpd instance to be set. + /// + /// # Errors + /// + /// A list of errors that can occur: + /// - [`NoCurrentInstanceSet`](crate::error::InstanceError::NoCurrentInstanceSet) + /// - [`StringConversion`](crate::error::PdError::StringConversion) + pub fn to_t_atom(&self) -> Result { + match self { + Self::Float(value) => { + let mut t_atom = libpd_sys::t_atom { + a_type: libpd_sys::t_atomtype_A_FLOAT, + a_w: libpd_sys::word { w_float: 0.0 }, + }; + let p = &mut t_atom as *mut libpd_sys::t_atom; + unsafe { + libpd_sys::libpd_set_double(p, *value); + } + Ok(t_atom) + } + Self::Symbol(s) => { + if unsafe { libpd_sys::libpd_this_instance().is_null() } { + return Err(InstanceError::NoCurrentInstanceSet.into()); + } + let c_str = CString::new(s.as_str()).map_err(StringConversionError::from)?; + let sym_ptr = unsafe { libpd_sys::gensym(c_str.as_ptr()) }; + let t_atom = libpd_sys::t_atom { + a_type: libpd_sys::t_atomtype_A_SYMBOL, + a_w: libpd_sys::word { w_symbol: sym_ptr }, + }; + Ok(t_atom) + } + } + } + + /// Converts a C `t_atom` to a Rust `Atom`. + pub fn from_t_atom(t_atom: &libpd_sys::t_atom) -> Option { + match t_atom.a_type { + libpd_sys::t_atomtype_A_FLOAT => { + let p = ptr::from_ref::(t_atom).cast_mut(); + let value = unsafe { libpd_sys::libpd_get_double(p) }; + Some(Self::Float(value)) + } + libpd_sys::t_atomtype_A_SYMBOL => { + let p = ptr::from_ref::(t_atom).cast_mut(); + let sym_ptr = unsafe { libpd_sys::libpd_get_symbol(p) }; + if sym_ptr.is_null() { + None + } else { + // We trust the symbols we receive from the pd patch. + // If this proves that this assumption is not true this can panic. + let c_str = unsafe { CStr::from_ptr(sym_ptr) }; + c_str.to_str().ok().map(|s| Self::Symbol(s.to_owned())) + } + } + _ => None, + } + } +} + +impl Display for Atom { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Float(value) => write!(f, "{value}"), + Self::Symbol(s) => write!(f, "{s}"), + } + } +} + +impl From for Atom { + fn from(s: String) -> Self { + Self::Symbol(s) + } +} +impl From<&String> for Atom { + fn from(s: &String) -> Self { + Self::Symbol(s.clone()) + } +} +impl From<&str> for Atom { + fn from(s: &str) -> Self { + Self::Symbol(s.to_owned()) + } +} +impl From for Atom { + fn from(c: char) -> Self { + Self::Symbol(c.to_string()) + } +} +impl From<&char> for Atom { + fn from(c: &char) -> Self { + Self::Symbol(c.to_string()) + } +} + +macro_rules! atom_from_number_type { + ($type:ty) => { + impl From<$type> for Atom { + fn from(value: $type) -> Self { + Self::Float(value.into()) + } + } + }; +} + +macro_rules! atom_from_reference_number_type { + ($type:ty) => { + impl From<$type> for Atom { + fn from(value: $type) -> Self { + Self::Float((*value).into()) + } + } + }; +} + +atom_from_number_type!(i8); +atom_from_number_type!(i16); +atom_from_number_type!(i32); +atom_from_number_type!(u8); +atom_from_number_type!(u16); +atom_from_number_type!(u32); +atom_from_number_type!(f32); +atom_from_number_type!(f64); +atom_from_reference_number_type!(&i8); +atom_from_reference_number_type!(&i16); +atom_from_reference_number_type!(&i32); +atom_from_reference_number_type!(&u8); +atom_from_reference_number_type!(&u16); +atom_from_reference_number_type!(&u32); +atom_from_reference_number_type!(&f32); +atom_from_reference_number_type!(&f64); + +/// Convenience function to convert a list of `Atom`s to a list of `t_atom`s. +/// +/// # Errors +/// +/// A list of errors that can occur: +/// - [`NoCurrentInstanceSet`](crate::error::InstanceError::NoCurrentInstanceSet) +/// - [`StringConversion`](crate::error::PdError::StringConversion) +pub fn make_t_atom_list_from_atom_list(atoms: &[Atom]) -> Result, PdError> { + atoms.iter().map(Atom::to_t_atom).collect() +} + +/// Convenience function to convert a list of `t_atom`s to a list of `Atom`s. +/// +/// This function will ignore any `t_atom`s that cannot be converted to `Atom`. +pub fn make_atom_list_from_t_atom_list(t_atoms: &[libpd_sys::t_atom]) -> Vec { + t_atoms.iter().filter_map(Atom::from_t_atom).collect() +} + +#[cfg(test)] +mod tests { + #![allow(clippy::restriction, clippy::nursery, clippy::all, clippy::pedantic)] + use crate::instance::PdInstance; + + use super::*; + use serial_test::serial; + + #[test] + #[serial] + fn test_float_conversion() { + // Test converting a float Atom to t_atom and back + let original_atom = Atom::Float(3.14); + let t_atom = original_atom + .to_t_atom() + .expect("Conversion to t_atom failed"); + let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed"); + assert_eq!(original_atom, converted_atom); + } + + #[test] + #[serial] + fn test_symbol_conversion() { + // Initialize libpd before using symbols + let main_instance = PdInstance::new().expect("Failed to create Pd instance"); + main_instance.set_as_current(); + + // Test converting a symbol Atom to t_atom and back + let original_atom = Atom::Symbol("test_symbol".to_string()); + let t_atom = original_atom + .to_t_atom() + .expect("Conversion to t_atom failed"); + let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed"); + assert_eq!(original_atom, converted_atom); + } + + #[test] + #[serial] + fn test_atom_list_conversion() { + // Initialize libpd before using symbols + let main_instance = PdInstance::new().expect("Failed to create Pd instance"); + main_instance.set_as_current(); + + // Create a list of Atoms + let original_atoms = vec![ + Atom::Float(1.23), + Atom::Symbol("hello".to_string()), + Atom::Float(4.56), + Atom::Symbol("world".to_string()), + ]; + + // Convert the list of Atoms to a list of t_atoms + let t_atoms = make_t_atom_list_from_atom_list(&original_atoms) + .expect("Conversion to t_atom list failed"); + + // Convert the list of t_atoms back to a list of Atoms + let converted_atoms = make_atom_list_from_t_atom_list(&t_atoms); + + // Assert that the original and converted lists are equal + assert_eq!(original_atoms, converted_atoms); + } + + #[test] + #[serial] + fn test_empty_atom_list_conversion() { + // Test converting an empty list of Atoms + let original_atoms: Vec = Vec::new(); + let t_atoms = make_t_atom_list_from_atom_list(&original_atoms) + .expect("Conversion to t_atom list failed"); + let converted_atoms = make_atom_list_from_t_atom_list(&t_atoms); + assert_eq!(original_atoms, converted_atoms); + } + + #[test] + #[serial] + fn test_float_reference_conversion() { + // Test converting a reference to a float to Atom + let value = 2.718; + let atom = Atom::from(&value); + assert_eq!(atom, Atom::Float(2.718)); + + // Convert to t_atom and back + let t_atom = atom.to_t_atom().expect("Conversion to t_atom failed"); + let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed"); + assert_eq!(atom, converted_atom); + } + + #[test] + #[serial] + fn test_symbol_reference_conversion() { + // Initialize libpd before using symbols + let main_instance = PdInstance::new().expect("Failed to create Pd instance"); + main_instance.set_as_current(); + + // Test converting a &str to Atom + let value = "reference_symbol"; + let atom = Atom::from(value); + assert_eq!(atom, Atom::Symbol("reference_symbol".to_string())); + + // Convert to t_atom and back + let t_atom = atom.to_t_atom().expect("Conversion to t_atom failed"); + let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed"); + assert_eq!(atom, converted_atom); + } + + #[test] + #[serial] + fn test_char_conversion() { + // Initialize libpd before using symbols + let main_instance = PdInstance::new().expect("Failed to create Pd instance"); + main_instance.set_as_current(); + + // Test converting a char to Atom + let value = 'a'; + let atom = Atom::from(value); + assert_eq!(atom, Atom::Symbol("a".to_string())); + + // Convert to t_atom and back + let t_atom = atom.to_t_atom().expect("Conversion to t_atom failed"); + let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed"); + assert_eq!(atom, converted_atom); + } + + #[test] + #[serial] + fn test_number_type_conversions() { + // Test various number types + let int_value: i32 = -42; + let atom = Atom::from(int_value); + assert_eq!(atom, Atom::Float(-42.0)); + + let uint_value: u32 = 42; + let atom = Atom::from(uint_value); + assert_eq!(atom, Atom::Float(42.0)); + + let float_value: f32 = 3.14; + let atom = Atom::from(float_value); + if let Atom::Float(value) = atom { + assert!( + (value - 3.14).abs() < 1e-6, + "Expected value close to 3.14, got {}", + value + ); + } else { + panic!("Expected Atom::Float"); + } + + let double_value: f64 = 2.71828; + let atom = Atom::from(double_value); + if let Atom::Float(value) = atom { + assert!( + (value - 2.71828).abs() < 1e-10, + "Expected value close to 2.71828, got {}", + value + ); + } else { + panic!("Expected Atom::Float"); + } + } + + #[test] + #[serial] + fn test_symbol_with_null_byte() { + // Initialize libpd before using symbols + let main_instance = PdInstance::new().expect("Failed to create Pd instance"); + main_instance.set_as_current(); + + // Test creating a symbol with a null byte (should fail) + let symbol_with_null = "null\0byte"; + let atom_result = Atom::from(symbol_with_null).to_t_atom(); + + assert!(atom_result.is_err()); + } + + #[test] + #[serial] + fn test_large_float_conversion() { + let value = 1e308; // Large f64 value + let atom = Atom::Float(value); + let t_atom = atom.to_t_atom().expect("Conversion to t_atom failed"); + let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed"); + assert_eq!(atom, converted_atom); + } + + #[test] + #[serial] + fn test_unicode_symbol_conversion() { + let main_instance = PdInstance::new().expect("Failed to create Pd instance"); + main_instance.set_as_current(); + + let symbol = "こんにちは"; // "Hello" in Japanese + let atom = Atom::Symbol(symbol.to_string()); + let t_atom = atom.to_t_atom().expect("Conversion to t_atom failed"); + let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed"); + assert_eq!(atom, converted_atom); + } + + #[test] + #[serial] + fn test_empty_symbol_conversion() { + let main_instance = PdInstance::new().expect("Failed to create Pd instance"); + main_instance.set_as_current(); + + let atom = Atom::Symbol(String::new()); + let t_atom = atom.to_t_atom().expect("Conversion to t_atom failed"); + let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed"); + assert_eq!(atom, converted_atom); + } +} diff --git a/src/error.rs b/src/error.rs index 73536d1..5a3d543 100644 --- a/src/error.rs +++ b/src/error.rs @@ -45,7 +45,7 @@ pub enum PdError { /// /// `CString` or `CStr` conversion error. #[error(transparent)] - StringConversionError(#[from] StringConversionError), + StringConversion(#[from] StringConversionError), } /// Errors related to initialization. @@ -206,6 +206,9 @@ pub enum InstanceError { /// The instance which is being tried to be accessed does not exist. #[error("The instance failed to create. Error: {0}")] InstanceFailedToCreate(String), + /// There is no pd instance set for the current thread. + #[error("There is no pd instance set for the current thread.")] + NoCurrentInstanceSet, } /// Errors related to string conversion. diff --git a/src/functions/receive.rs b/src/functions/receive.rs index cbb23fd..424548e 100644 --- a/src/functions/receive.rs +++ b/src/functions/receive.rs @@ -6,9 +6,9 @@ )] use crate::{ + atom::{make_atom_list_from_t_atom_list, Atom}, error::{StringConversionError, SubscriptionError, C_STR_FAILURE}, - helpers::make_atom_list_from_t_atom_list, - types::{Atom, ReceiverHandle}, + types::ReceiverHandle, }; use libffi::high::{ @@ -352,7 +352,7 @@ pub fn on_symbol(mut user_provided /// # Example /// ```rust /// use libpd_rs::functions::receive::{on_list, start_listening_from}; -/// use libpd_rs::types::Atom; +/// use libpd_rs::Atom; /// use libpd_rs::instance::PdInstance; /// /// let _main_instance = PdInstance::new().unwrap(); @@ -403,7 +403,7 @@ pub fn on_list(mut user_provide reason = "We're trusting Pd to not send a negative list length. I think this is sane enough." )] let atom_list = unsafe { slice::from_raw_parts(atom_list, list_length as usize) }; - let atoms = make_atom_list_from_t_atom_list!(atom_list); + let atoms = make_atom_list_from_t_atom_list(atom_list); user_provided_closure(source, &atoms); }, )); @@ -436,7 +436,7 @@ pub fn on_list(mut user_provide /// # Example /// ```rust /// use libpd_rs::functions::receive::{on_message, start_listening_from}; -/// use libpd_rs::types::Atom; +/// use libpd_rs::Atom; /// use libpd_rs::instance::PdInstance; /// /// let _main_instance = PdInstance::new().unwrap(); @@ -478,7 +478,7 @@ pub fn on_message( reason = "We're trusting Pd to not send a negative list length. I think this is sane enough." )] let atom_list = unsafe { slice::from_raw_parts(atom_list, list_length as usize) }; - let atoms = make_atom_list_from_t_atom_list!(atom_list); + let atoms = make_atom_list_from_t_atom_list(atom_list); user_provided_closure(source, message, &atoms); }, )); diff --git a/src/functions/send.rs b/src/functions/send.rs index d2da7ff..60c4b26 100644 --- a/src/functions/send.rs +++ b/src/functions/send.rs @@ -1,7 +1,6 @@ use crate::{ - error::{SendError, SizeError, StringConversionError}, - helpers::make_t_atom_list_from_atom_list, - types::Atom, + atom::{make_t_atom_list_from_atom_list, Atom}, + error::{PdError, SendError, SizeError, StringConversionError}, }; use std::ffi::CString; @@ -351,7 +350,7 @@ pub fn finish_message_as_typed_message_and_send_to, S: AsRef> /// # Example /// ```rust /// use libpd_rs::functions::send::{send_list_to}; -/// use libpd_rs::types::Atom; +/// use libpd_rs::Atom; /// use libpd_rs::instance::PdInstance; /// /// let _main_instance = PdInstance::new().unwrap(); @@ -369,11 +368,13 @@ pub fn finish_message_as_typed_message_and_send_to, S: AsRef> /// /// A list of errors that can occur: /// - [`MissingDestination`](crate::error::SendError::MissingDestination) -/// - [`StringConversion`](crate::error::SendError::StringConversion) -pub fn send_list_to>(receiver: T, list: &[Atom]) -> Result<(), SendError> { +/// - [`NoCurrentInstanceSet`](crate::error::InstanceError::NoCurrentInstanceSet) +/// - [`SendError::StringConversion`](crate::error::SendError::StringConversion) +/// - [`PdError::StringConversion`](crate::error::PdError::StringConversion) +pub fn send_list_to>(receiver: T, list: &[Atom]) -> Result<(), PdError> { let recv = CString::new(receiver.as_ref()).map_err(StringConversionError::from)?; - let mut atom_list: Vec = make_t_atom_list_from_atom_list!(list); + let mut atom_list: Vec = make_t_atom_list_from_atom_list(list)?; let atom_list_slice = atom_list.as_mut_slice(); unsafe { @@ -388,7 +389,7 @@ pub fn send_list_to>(receiver: T, list: &[Atom]) -> Result<(), Sen atom_list_slice.as_mut_ptr(), ) { 0 => Ok(()), - _ => Err(SendError::MissingDestination(receiver.as_ref().to_owned())), + _ => Err(SendError::MissingDestination(receiver.as_ref().to_owned()).into()), } } } @@ -402,7 +403,7 @@ pub fn send_list_to>(receiver: T, list: &[Atom]) -> Result<(), Sen /// # Example /// ```rust /// use libpd_rs::functions::send::{send_message_to}; -/// use libpd_rs::types::Atom; +/// use libpd_rs::Atom; /// use libpd_rs::instance::PdInstance; /// /// let _main_instance = PdInstance::new().unwrap(); @@ -420,16 +421,18 @@ pub fn send_list_to>(receiver: T, list: &[Atom]) -> Result<(), Sen /// /// A list of errors that can occur: /// - [`MissingDestination`](crate::error::SendError::MissingDestination) -/// - [`StringConversion`](crate::error::SendError::StringConversion) +/// - [`NoCurrentInstanceSet`](crate::error::InstanceError::NoCurrentInstanceSet) +/// - [`SendError::StringConversion`](crate::error::SendError::StringConversion) +/// - [`PdError::StringConversion`](crate::error::PdError::StringConversion) pub fn send_message_to>( receiver: T, message: T, list: &[Atom], -) -> Result<(), SendError> { +) -> Result<(), PdError> { let recv = CString::new(receiver.as_ref()).map_err(StringConversionError::from)?; let msg = CString::new(message.as_ref()).map_err(StringConversionError::from)?; - let mut atom_list: Vec = make_t_atom_list_from_atom_list!(list); + let mut atom_list: Vec = make_t_atom_list_from_atom_list(list)?; let atom_list_slice = atom_list.as_mut_slice(); unsafe { @@ -446,7 +449,7 @@ pub fn send_message_to>( atom_list_slice.as_mut_ptr(), ) { 0 => Ok(()), - _ => Err(SendError::MissingDestination(receiver.as_ref().to_owned())), + _ => Err(SendError::MissingDestination(receiver.as_ref().to_owned()).into()), } } } diff --git a/src/helpers.rs b/src/helpers.rs deleted file mode 100644 index 77fbcfc..0000000 --- a/src/helpers.rs +++ /dev/null @@ -1,66 +0,0 @@ -#![allow(clippy::redundant_pub_crate)] - -/// Transforms an iterable of type `Atom` to a `Vec`. -macro_rules! make_t_atom_list_from_atom_list { - ($list: expr) => { - $list - .into_iter() - .map(|atom_variant| match atom_variant { - Atom::Float(value) => { - let t_atom = libpd_sys::t_atom { - a_type: libpd_sys::t_atomtype_A_FLOAT, - a_w: libpd_sys::word { w_float: *value }, - }; - let p = std::ptr::from_ref::(&t_atom).cast_mut(); - // Using a setter us crucial or else float values become 0s when sending a list. - unsafe { - libpd_sys::libpd_set_double(p, *value); - } - t_atom - } - - // If there will be a bug related to this later, - // Try using libpd_sys::libpd_set_symbol instead of manually setting the value. - Atom::Symbol(value) => libpd_sys::t_atom { - a_type: libpd_sys::t_atomtype_A_SYMBOL, - a_w: libpd_sys::word { - w_symbol: unsafe { - let sym = CString::new(value.to_owned()).unwrap(); - libpd_sys::gensym(sym.as_ptr()) - }, - }, - }, - // TODO: See if there are more cases to be covered. - }) - .collect::>() - }; -} - -/// Transforms an iterable of type `t_atom` to a `Vec`. -macro_rules! make_atom_list_from_t_atom_list { - ($list: expr) => { - $list - .into_iter() - .map(|atom_type| match atom_type.a_type { - libpd_sys::t_atomtype_A_FLOAT => { - let ptr_to_inner = - std::ptr::from_ref::(atom_type).cast_mut(); - let f: f64 = unsafe { libpd_sys::libpd_get_double(ptr_to_inner) }; - Atom::Float(f) - } - libpd_sys::t_atomtype_A_SYMBOL => { - let ptr_to_inner = - std::ptr::from_ref::(atom_type).cast_mut(); - let sym: *const std::os::raw::c_char = - unsafe { libpd_sys::libpd_get_symbol(ptr_to_inner) }; - let result = unsafe { CStr::from_ptr(sym) }; - Atom::Symbol(result.to_str().unwrap().to_owned()) - } - // TODO: See if there are more cases to be covered. - _ => unimplemented!(), - }) - .collect::>() - }; -} - -pub(crate) use {make_atom_list_from_t_atom_list, make_t_atom_list_from_atom_list}; diff --git a/src/instance.rs b/src/instance.rs index 9b704dc..ea9b6a8 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -55,18 +55,26 @@ impl PdInstance { /// If this is the first instance created, it will be the main instance. /// The main instance is always valid. /// - /// If this is not the first instance created, it will be a new instance. + /// - If this is not the first instance created, it will be a new instance. + /// - All instances created come initialized. + /// - Dropping an instance will free its resources properly. /// - /// All instances created come initialized. + /// # The main instance /// - /// Dropping an instance will free its resources properly. + /// The main instance is the first instance created and it is sort of special. + /// Libpd will not let you free the main instance it has guards for it in the source code. + /// + /// So if you drop this struct and if it was pointing to the main instance you'll have a dangling main instance and will not be able to access it. + /// + /// **The advice is to keep the main instance alive until the application lives.** /// /// # Errors /// /// A list of errors that can occur: /// - [`InstanceFailedToCreate`](crate::error::InstanceError::InstanceFailedToCreate) pub fn new() -> Result { - // First instance as the main instance. + // This means that libpd has not been initialized yet and the main instance is not created. + // We create it then. if instance_count() == 0 { functions::init() .map_err(|err| InstanceError::InstanceFailedToCreate(err.to_string()))?; @@ -87,7 +95,7 @@ impl PdInstance { let currently_set_instance_ptr = unsafe { libpd_this_instance() }; let new_instance_ptr = unsafe { libpd_new_instance() }; - if currently_set_instance_ptr.is_null() || new_instance_ptr.is_null() { + if new_instance_ptr.is_null() { return Err(InstanceError::InstanceFailedToCreate( "Returned instance pointer is null.".to_owned(), )); @@ -101,9 +109,16 @@ impl PdInstance { // TODO: Learn why it is required to be called after each instance creation and returns like the global init. (low priority) functions::init().map_err(|err| InstanceError::InstanceFailedToCreate(err.to_string()))?; - // Set the current instance back to the previous instance. - unsafe { - libpd_set_instance(currently_set_instance_ptr); + // Set the current instance back to the previous instance or if not to main instance which should be always valid. + if currently_set_instance_ptr.is_null() { + let main_instance_ptr = unsafe { libpd_main_instance() }; + unsafe { + libpd_set_instance(main_instance_ptr); + } + } else { + unsafe { + libpd_set_instance(currently_set_instance_ptr); + } } Ok(Self { @@ -130,7 +145,7 @@ impl PdInstance { /// Makes this instance the current instance. /// /// So that all subsequent calls to libpd functions will be made on this instance. - pub fn set_as_current(&mut self) { + pub fn set_as_current(&self) { unsafe { libpd_set_instance(self.inner) } } @@ -242,10 +257,14 @@ impl PdInstance { impl Drop for PdInstance { fn drop(&mut self) { - if self.inner.is_null() { + // Libpd will not let you free the main instance it has guards for it in the source code. + // Once it is created so it lives until the application lives! + // This is why we don't mess with it here. + if self.inner.is_null() || self.is_main_instance() { return; } + // For all the others: // The advice below can be found in z_queued.h: // free the queued ringbuffers @@ -265,6 +284,8 @@ impl Drop for PdInstance { reason = "The instance count can not be negative." )] /// Gets the number of instances of Pd registered. +/// +/// Since the main instance can not be freed after creation, the count will always be at least 1 after the creation of main instance. pub fn instance_count() -> usize { unsafe { libpd_num_instances() as usize } } diff --git a/src/lib.rs b/src/lib.rs index 1e77dc7..fe8cb6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -456,11 +456,7 @@ doc = ::embed_doc_image::embed_image!("phasor_patch", "assets/phasor_patch.png") //! //! Low level and mid level layers can be used together since one is just a safe wrapper over the other. //! -//! Mixed usage of mid and the high level layer requires significant understanding of the library and [libpd](https://github.com/libpd). -//! -//! So if you don't know what you're doing, it's advised to stick with the high level layer and not mix it with the others. -//! -//! The library tries to make this as convenient as possible. +//! Mixed usage of mid and the high level layer requires significant understanding of the library and [libpd](https://github.com/libpd) for some functions. //! //! ## Plans and Support //! @@ -490,10 +486,26 @@ doc = ::embed_doc_image::embed_image!("phasor_patch", "assets/phasor_patch.png") //! [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause). //! See [LICENSE](https://raw.githubusercontent.com/alisomay/libpd-rs/main/LICENCE) file. -/// TODO: +/// This module exposes [`PdInstance`](crate::instance::PdInstance) struct which covers all the functionality related to pd instances. +/// +/// Instances of pd are stored in a thread local way and there can be only one instance at a time could be active per thread. +/// +/// The active instance for the thread can be set by calling `set_as_current` method on the instance. +/// +/// [`PdInstance`](crate::instance::PdInstance) also has a `Drop` implementation which frees the resources of the instance when it goes out of scope. pub mod instance; -/// TODO: +/// The functions module could be considered as the mid level layer of the library. +/// +/// The exhaustive list of functions here reflect the ones exist in [libpd](https://github.com/libpd) directly. +/// +/// Since mutating the state of the active instance can also be done through these functions, mixing the high level layer with this layer is not advised. +/// +/// If you're familiar with the inner workings of [libpd](https://github.com/libpd) and the library itself, you can use this layer to create your own abstractions. +/// +/// There are some functions exposed here which can be safely mixed with the high level layer and some needs more understanding of the internals. +/// +/// As long as you know what you're doing though, you can mix the high level layer with this layer when necessary. pub mod functions; /// Types for working with pd @@ -511,7 +523,8 @@ pub mod types; /// This module contains all the errors which can be returned by the library. pub mod error; -pub(crate) mod helpers; +/// The atom module contains the Atom enum which is used to represent pd's atom type in Rust. +pub mod atom; use error::PdError; use libpd_sys::_pdinstance; @@ -526,6 +539,7 @@ use crate::{ types::{PatchFileHandle, ReceiverHandle}, }; +pub use atom::Atom; /// Re-exports of the libpd-sys crate. pub use libpd_sys; @@ -533,6 +547,12 @@ pub use libpd_sys; /// /// This struct represents a single instance of pd. /// +/// You may create as many instances you like but only one of them can be active at a time. +/// +/// **It is strongly advised to keep the very first instance alive through the lifetime of the application.** +/// +/// See [`PdInstance`](crate::instance::PdInstance) for more details about this topic. +/// /// After created and registered internally the instance lives in libpd's memory. /// Dropping this struct will free the resources of the instance. /// @@ -586,52 +606,12 @@ pub struct Pd { } impl Pd { - // TODO: Document these functions maybe even add other impls exposing other functions through the instance. - - pub const fn inner(&self) -> &PdInstance { - &self.inner - } - - pub fn inner_mut(&mut self) -> &mut PdInstance { - &mut self.inner - } - - pub fn audio_context(&self) -> PdAudioContext { - PdAudioContext { - instance: self.inner.clone(), - } - } - - pub fn set_as_current(&mut self) { - self.inner.set_as_current(); - } - - pub const fn instance_number(&self) -> i32 { - self.inner.number() - } - - pub fn is_main_instance(&self) -> bool { - self.inner.is_main_instance() - } - - pub fn is_current_instance(&self) -> bool { - self.inner.is_current_instance() - } - - pub(crate) fn set_as_active_instance(&mut self) -> ActiveInstanceGuard { - if self.inner.is_current_instance() { - // This would render the guard useless and as a no-op on drop which is what we want. - return ActiveInstanceGuard::wrap(ptr::null_mut::<_pdinstance>()); - } - let previous_instance = unsafe { libpd_sys::libpd_this_instance() }; - self.inner.set_as_current(); - ActiveInstanceGuard::wrap(previous_instance) - } - /// Initializes a pd instance. /// /// It calls [`PdInstance::new`](crate::instance::PdInstance::new) and [`initialize_audio`](crate::initialize_audio) with the provided arguments and returns an instance where a user can keep simple state and call some convenience methods. /// + /// This method will not set the newly created instance as the active instance for the thread. + /// /// You may crate any number of instances of [`Pd`] but only one of them can be active at a time. /// Many of the methods in this struct would set it as the active instance before operating and reset it to the last set active after. /// @@ -673,6 +653,60 @@ impl Pd { }) } + /// Returns a reference to the inner pd instance. + pub const fn inner(&self) -> &PdInstance { + &self.inner + } + + /// Returns a mutable reference to the inner pd instance. + pub fn inner_mut(&mut self) -> &mut PdInstance { + &mut self.inner + } + + /// Creates an audio context for this instance to be easily passed in to the audio thread. + pub fn audio_context(&self) -> PdAudioContext { + PdAudioContext { + instance: self.inner.clone(), + } + } + + /// Set this instance as the current active instance for the thread. + pub fn set_as_current(&self) { + self.inner.set_as_current(); + } + + /// Returns the number of the instance. + pub const fn instance_number(&self) -> i32 { + self.inner.number() + } + + /// Checks if this instance is the main instance. + /// + /// The main instance is always valid. + pub fn is_main_instance(&self) -> bool { + self.inner.is_main_instance() + } + + /// Checks if this instance is the current active instance for the thread. + pub fn is_current_instance(&self) -> bool { + self.inner.is_current_instance() + } + + /// Sets this instance as the active instance for the thread until the returned guard is dropped. + /// + /// If the guard is dropped, the previously active instance will be set as the active instance. + /// + /// If the previous instance is null this guard will set the main instance as the active instance since that is always valid. + pub(crate) fn set_as_active_instance(&self) -> ActiveInstanceGuard { + if self.inner.is_current_instance() { + // This would render the guard useless and as a no-op on drop which is what we want. + return ActiveInstanceGuard::wrap(ptr::null_mut::<_pdinstance>()); + } + let previous_instance = unsafe { libpd_sys::libpd_this_instance() }; + self.inner.set_as_current(); + ActiveInstanceGuard::wrap(previous_instance) + } + /// Adds a path to the list of paths where this instance searches in. /// /// Relative paths are relative to the current working directory. @@ -1041,54 +1075,69 @@ impl Pd { } } -/// TODO: Document +/// This struct encapsulates a clone of the [`PdInstance`](crate::instance::PdInstance) to be used in the audio thread. +/// +/// Since the instances are thread local, this is just a convenience struct to ensure that the instance is set as the current one before calling any functions. +/// +/// If you don't set at least one instance as the current one, the functions in the library will panic. #[derive(Debug, Clone)] pub struct PdAudioContext { instance: PdInstance, } impl PdAudioContext { + /// Sets the instance as the current one and calls [`receive_messages_from_pd`](crate::functions::receive::receive_messages_from_pd). pub fn receive_messages_from_pd(&mut self) { self.instance.set_as_current(); functions::receive::receive_messages_from_pd(); } + /// Sets the instance as the current one and calls [`receive_midi_messages_from_pd`](crate::functions::receive::receive_midi_messages_from_pd). pub fn receive_midi_messages_from_pd(&mut self) { self.instance.set_as_current(); functions::receive::receive_midi_messages_from_pd(); } + /// Sets the instance as the current one and calls [`process_float`](crate::functions::process::process_float). pub fn process_float(&mut self, ticks: i32, input: &[f32], output: &mut [f32]) { self.instance.set_as_current(); functions::process::process_float(ticks, input, output); } + /// Sets the instance as the current one and calls [`process_double`](crate::functions::process::process_double). pub fn process_double(&mut self, ticks: i32, input: &[f64], output: &mut [f64]) { self.instance.set_as_current(); functions::process::process_double(ticks, input, output); } + /// Sets the instance as the current one and calls [`process_short`](crate::functions::process::process_short). pub fn process_short(&mut self, ticks: i32, input: &[i16], output: &mut [i16]) { self.instance.set_as_current(); functions::process::process_short(ticks, input, output); } + /// Sets the instance as the current one and calls [`process_raw`](crate::functions::process::process_raw). pub fn process_raw(&mut self, input: &[f32], output: &mut [f32]) { self.instance.set_as_current(); functions::process::process_raw(input, output); } + /// Sets the instance as the current one and calls [`process_raw_short`](crate::functions::process::process_raw_short). pub fn process_raw_short(&mut self, input: &[i16], output: &mut [i16]) { self.instance.set_as_current(); functions::process::process_raw_short(input, output); } + /// Sets the instance as the current one and calls [`process_raw_double`](crate::functions::process::process_raw_double). pub fn process_raw_double(&mut self, input: &[f64], output: &mut [f64]) { self.instance.set_as_current(); functions::process::process_raw_double(input, output); } } +/// When an instance is set as the active instance for the thread, this guard is returned. +/// +/// When the guard is dropped, the previously active instance will be set as the active instance. struct ActiveInstanceGuard { previous_instance: *mut _pdinstance, } @@ -1102,10 +1151,13 @@ impl ActiveInstanceGuard { impl Drop for ActiveInstanceGuard { fn drop(&mut self) { if self.previous_instance.is_null() { - // TODO: Maybe inform the user about this? + // Main instance is always valid. + let main_instance = unsafe { libpd_sys::libpd_main_instance() }; + unsafe { + libpd_sys::libpd_set_instance(main_instance); + } return; } - unsafe { libpd_sys::libpd_set_instance(self.previous_instance); } diff --git a/src/types.rs b/src/types.rs index 88df4c2..a26c2b4 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,90 +1,4 @@ use core::ffi; -use std::fmt::{self, Display}; - -/// A type to represent a pd Atom type in Rust side. -/// -/// Pd has floating point numbers and symbols as primitive types. -/// This enum maps those to their Rust counterparts. -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub enum Atom { - /// A floating point number from pd. - Float(f64), - /// A symbol from pd. Symbols are interned in pd, but it can be treated as Strings in Rust. - Symbol(String), -} - -macro_rules! atom_from_number_type { - ($type:ty) => { - impl From<$type> for Atom { - fn from(value: $type) -> Self { - Self::Float(value.into()) - } - } - }; -} - -macro_rules! atom_from_reference_number_type { - ($type:ty) => { - impl From<$type> for Atom { - fn from(value: $type) -> Self { - Self::Float((*value).into()) - } - } - }; -} - -atom_from_number_type!(i8); -atom_from_number_type!(i16); -atom_from_number_type!(i32); -atom_from_number_type!(u8); -atom_from_number_type!(u16); -atom_from_number_type!(u32); -atom_from_number_type!(f32); -atom_from_number_type!(f64); -atom_from_reference_number_type!(&i8); -atom_from_reference_number_type!(&i16); -atom_from_reference_number_type!(&i32); -atom_from_reference_number_type!(&u8); -atom_from_reference_number_type!(&u16); -atom_from_reference_number_type!(&u32); -atom_from_reference_number_type!(&f32); -atom_from_reference_number_type!(&f64); - -impl From for Atom { - fn from(s: String) -> Self { - Self::Symbol(s) - } -} -impl From<&String> for Atom { - fn from(s: &String) -> Self { - Self::Symbol(s.clone()) - } -} -impl From<&str> for Atom { - fn from(s: &str) -> Self { - Self::Symbol(s.to_owned()) - } -} -impl From for Atom { - fn from(c: char) -> Self { - Self::Symbol(c.to_string()) - } -} -impl From<&char> for Atom { - fn from(c: &char) -> Self { - Self::Symbol(c.to_string()) - } -} - -impl Display for Atom { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), fmt::Error> { - match self { - Self::Float(float) => write!(f, "{float}"), - Self::Symbol(s) => write!(f, "{s}"), - } - } -} /// The handle which is returned from opening a patch. /// @@ -128,60 +42,3 @@ impl From<*mut ffi::c_void> for ReceiverHandle { Self(ptr) } } - -// pub const t_atomtype_A_SEMI: t_atomtype = 4; -// pub const t_atomtype_A_COMMA: t_atomtype = 5; -// pub const t_atomtype_A_DOLLAR: t_atomtype = 8; -// pub const t_atomtype_A_DOLLSYM: t_atomtype = 9; - -// #define SETDOLLAR(atom, n) ((atom)->a_type = A_DOLLAR, \ -// (atom)->a_w.w_index = (n)) -// #define SETDOLLSYM(atom, s) ((atom)->a_type = A_DOLLSYM, \ -// (atom)->a_w.w_symbol= (s)) -// #define SETSEMI(atom) ((atom)->a_type = A_SEMI, (atom)->a_w.w_index = 0) -// #define SETCOMMA(atom) ((atom)->a_type = A_COMMA, (atom)->a_w.w_index = 0) - -// Appendix, types related to atoms. -// -// pub type t_word = word; -// pub const t_atomtype_A_NULL: t_atomtype = 0; -// pub const t_atomtype_A_FLOAT: t_atomtype = 1; -// pub const t_atomtype_A_SYMBOL: t_atomtype = 2; -// pub const t_atomtype_A_POINTER: t_atomtype = 3; -// pub const t_atomtype_A_SEMI: t_atomtype = 4; -// pub const t_atomtype_A_COMMA: t_atomtype = 5; -// pub const t_atomtype_A_DEFFLOAT: t_atomtype = 6; -// pub const t_atomtype_A_DEFSYM: t_atomtype = 7; -// pub const t_atomtype_A_DOLLAR: t_atomtype = 8; -// pub const t_atomtype_A_DOLLSYM: t_atomtype = 9; -// pub const t_atomtype_A_GIMME: t_atomtype = 10; -// pub const t_atomtype_A_CANT: t_atomtype = 11; -// pub type t_atomtype = ::std::os::raw::c_uint; - -// #define SETSEMI(atom) ((atom)->a_type = A_SEMI, (atom)->a_w.w_index = 0) -// #define SETCOMMA(atom) ((atom)->a_type = A_COMMA, (atom)->a_w.w_index = 0) -// #define SETPOINTER(atom, gp) ((atom)->a_type = A_POINTER, \ -// (atom)->a_w.w_gpointer = (gp)) -// #define SETFLOAT(atom, f) ((atom)->a_type = A_FLOAT, (atom)->a_w.w_float = (f)) -// #define SETSYMBOL(atom, s) ((atom)->a_type = A_SYMBOL, \ -// (atom)->a_w.w_symbol = (s)) -// #define SETDOLLAR(atom, n) ((atom)->a_type = A_DOLLAR, \ -// (atom)->a_w.w_index = (n)) -// #define SETDOLLSYM(atom, s) ((atom)->a_type = A_DOLLSYM, \ -// (atom)->a_w.w_symbol= (s)) - -// #[repr(C)] -// #[derive(Copy, Clone)] -// pub struct _atom { -// pub a_type: t_atomtype, -// pub a_w: word, -// } - -// pub union word { -// pub w_float: t_float, -// pub w_symbol: *mut t_symbol, -// pub w_gpointer: *mut t_gpointer, -// pub w_array: *mut _array, -// pub w_binbuf: *mut _binbuf, -// pub w_index: ::std::os::raw::c_int, -// } diff --git a/tests/process.rs b/tests/process.rs index b55a3ef..a7a30d4 100644 --- a/tests/process.rs +++ b/tests/process.rs @@ -3,13 +3,12 @@ use std::any::type_name; use libpd_rs::functions::{ - block_size, close_patch, - util::dsp_on, - init, initialize_audio, open_patch, + block_size, close_patch, init, initialize_audio, open_patch, process::{ process_double, process_float, process_raw, process_raw_double, process_raw_short, process_short, }, + util::dsp_on, }; fn type_of(_: T) -> &'static str { diff --git a/tests/send_and_receive_list.rs b/tests/send_and_receive_list.rs index 8180b91..1852a01 100644 --- a/tests/send_and_receive_list.rs +++ b/tests/send_and_receive_list.rs @@ -15,7 +15,7 @@ use libpd_rs::{ Pd, }; -use libpd_rs::types::Atom; +use libpd_rs::Atom; #[test] fn send_and_receive_list() {