From 28b0a62fbdd711fa3f8298938433acef79459d5d Mon Sep 17 00:00:00 2001 From: Ali Somay Date: Thu, 28 Nov 2024 16:26:05 +0100 Subject: [PATCH] Adapt examples --- Cargo.toml | 18 +- examples/README.md | 8 + examples/communicate.rs | 155 +++++++++ examples/simple.rs | 3 +- examples/with_nannou/bubble.rs | 3 +- examples/with_nannou/main.rs | 16 +- src/atom.rs | 381 +++++++++++++++++++++ src/error.rs | 5 +- src/functions/receive.rs | 12 +- src/functions/send.rs | 39 ++- src/helpers.rs | 66 ---- src/instance.rs | 41 ++- src/lib.rs | 184 ++++++---- src/types.rs | 143 -------- tests/pd_open_close_patch.rs | 2 +- tests/process.rs | 5 +- tests/send_and_receive_after_touch.rs | 2 +- tests/send_and_receive_bang.rs | 2 +- tests/send_and_receive_control_change.rs | 2 +- tests/send_and_receive_double.rs | 2 +- tests/send_and_receive_float.rs | 2 +- tests/send_and_receive_list.rs | 4 +- tests/send_and_receive_midi_byte.rs | 2 +- tests/send_and_receive_note_on.rs | 2 +- tests/send_and_receive_pitch_bend.rs | 2 +- tests/send_and_receive_poly_after_touch.rs | 2 +- tests/send_and_receive_program_change.rs | 2 +- tests/send_and_receive_sys_realtime.rs | 2 +- tests/send_and_receive_sysex.rs | 2 +- tests/send_and_receive_typed_message.rs | 2 +- 30 files changed, 767 insertions(+), 344 deletions(-) create mode 100644 examples/communicate.rs create mode 100644 src/atom.rs delete mode 100644 src/helpers.rs diff --git a/Cargo.toml b/Cargo.toml index e921201..8ce6191 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libpd-rs" -version = "0.3.0" +version = "0.2.0" authors = ["alisomay "] edition = "2021" license = "BSD-3-Clause" @@ -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" @@ -28,18 +24,20 @@ edition = "2021" crate-type = ["lib"] [dependencies] -libpd-sys = "0.3" +# libpd-sys = "0.3" +libpd-sys = { git = "https://github.com/alisomay/libpd-sys.git", branch = "main" } thiserror = "2" libffi = "3.0.0" 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 +46,3 @@ rand = "0.8.5" # For local development, # [patch."https://github.com/alisomay/libpd-sys"] # libpd-sys = { path = "../libpd-sys" } - - - - diff --git a/examples/README.md b/examples/README.md index e48c551..76c1066 100644 --- a/examples/README.md +++ b/examples/README.md @@ -14,6 +14,14 @@ This one is the exact same program which is provided in the main `README.md` fil cargo run --example simple ``` +## `communicate` + +This one is the exact same program which is provided in the docs as the second example. + +```sh +cargo run --example communicate +``` + ## `with_nannou` [nannou](https://github.com/nannou-org/nannou) is a fantastic creative coding framework for Rust. diff --git a/examples/communicate.rs b/examples/communicate.rs new file mode 100644 index 0000000..85fc23e --- /dev/null +++ b/examples/communicate.rs @@ -0,0 +1,155 @@ +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use libpd_rs::{ + functions::{receive::on_float, send::send_list_to, util::calculate_ticks}, + Pd, +}; +use sys_info::loadavg; + +fn main() -> Result<(), Box> { + // Initialize cpal + // This could have been another cross platform audio library + // basically anything which gets you the audio callback of the os. + let host = cpal::default_host(); + + // Currently we're only going to output to the default device + let device = host.default_output_device().unwrap(); + + // Using the default config + let config = device.default_output_config()?; + + // Let's get the default configuration from the audio driver. + let sample_rate = config.sample_rate().0 as i32; + let output_channels = config.channels() as i32; + + // 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 ctx = pd.audio_context(); + + // Let's evaluate another pd patch. + // We could have opened a `.pd` file also. + pd.eval_patch( + r#" + #N canvas 832 310 625 448 12; + #X obj 18 27 r cpu_load; + #X obj 55 394 s response; + #X obj 13 261 *~; + #X obj 112 240 vline~; + #X obj 118 62 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1 + -1; + #X obj 14 395 dac~; + #X obj 50 299 sig~; + #X floatatom 50 268 5 0 0 0 - - -; + #X obj 13 228 phasor~ 120; + #X obj 139 61 metro 2000; + #X obj 139 38 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 1 + 1; + #X obj 18 52 unpack f f; + #X obj 14 362 *~ 2; + #X obj 14 336 vcf~ 12; + #X obj 139 12 loadbang; + #X msg 118 86 1 8 \, 0 0 10; + #X obj 149 197 expr (480 + 80) * ($f1 - 8) / (4 - 16) + 480; + #X obj 29 128 * 20; + #X obj 167 273 expr (520 + 120) * ($f1 - 5) / (12 - 5) + 120; + #X connect 0 0 11 0; + #X connect 2 0 13 0; + #X connect 3 0 2 1; + #X connect 4 0 15 0; + #X connect 6 0 13 1; + #X connect 7 0 6 0; + #X connect 8 0 2 0; + #X connect 9 0 15 0; + #X connect 10 0 9 0; + #X connect 11 0 16 0; + #X connect 11 0 18 0; + #X connect 11 1 17 0; + #X connect 12 0 5 0; + #X connect 12 0 5 1; + #X connect 13 0 12 0; + #X connect 14 0 10 0; + #X connect 15 0 3 0; + #X connect 16 0 9 1; + #X connect 17 0 1 0; + #X connect 17 0 13 2; + #X connect 18 0 7 0; + "#, + )?; + + // Here we are registering a listener (hook in libpd lingo) for + // float values which are received from the pd patch. + on_float(|source, value| { + if source == "response" { + print!("\r"); + print!("Pd says that the q value of the vcf~ is: {value}"); + } + }); + + // Pd can send data to many different endpoints at a time. + // This is why we need to declare our subscription to one or more first. + // In this case we're subscribing to one, but it could have been many, + pd.subscribe_to("response")?; + + // Build the audio stream. + let output_stream = device.build_output_stream( + &config.into(), + move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { + // Provide the ticks to advance per iteration for the internal scheduler. + let ticks = calculate_ticks(output_channels, data.len() as i32); + + // Here if we had an input buffer + // we could have modified it to do pre-processing. + + // To receive messages from the pd patch we need to read the ring buffers + // filled by the pd patch repeatedly to check if there are messages there. + // Audio callback is a nice place to do that. + ctx.receive_messages_from_pd(); + + // Process audio, advance internal scheduler. + ctx.process_float(ticks, &[], data); + + // Here we could have done post processing after + // pd processed our output buffer in place. + }, + |err| eprintln!("an error occurred on stream: {}", err), + None, + )?; + + // Turn audio processing on + pd.activate_audio(true)?; + + // Run the stream + output_stream.play()?; + + // This program does not terminate. + // You would need to explicitly quit it. + loop { + // We sample in 2 hz. + std::thread::sleep(std::time::Duration::from_millis(500)); + + // Read the average load of the cpu. + let load = loadavg()?; + + let one_minute_cpu_load_average = load.one; + let five_minutes_cpu_load_average = load.five; + + // Lists are one of the types we can send to pd. + // Although pd allows for heterogeneous lists, + // even if we're not using them heterogeneously in this example, + // we still need to send it as a list of Atoms. + + // Atom is an encapsulating type in pd to unify + // various types of data together under the same umbrella. + // Check out `libpd_rs::types` module for more details. + + // Atoms have From trait implemented for them for + // floats and strings. + send_list_to( + "cpu_load", + &[ + one_minute_cpu_load_average.into(), + five_minutes_cpu_load_average.into(), + ], + )?; + } +} diff --git a/examples/simple.rs b/examples/simple.rs index 0e1af11..2e69d15 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 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..39b2d56 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(); @@ -368,12 +367,17 @@ pub fn finish_message_as_typed_message_and_send_to, S: AsRef> /// # Errors /// /// 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> { +/// - [`SendError`](crate::error::SendError) +/// - [`MissingDestination`](crate::error::SendError::MissingDestination) +/// - [`StringConversion`](crate::error::SendError::StringConversion) +/// - [`InstanceError`](crate::error::InstanceError) +/// - [`NoCurrentInstanceSet`](crate::error::InstanceError::NoCurrentInstanceSet) +/// - [`PdError`](crate::error::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 +392,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 +406,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(); @@ -419,17 +423,22 @@ pub fn send_list_to>(receiver: T, list: &[Atom]) -> Result<(), Sen /// # Errors /// /// A list of errors that can occur: -/// - [`MissingDestination`](crate::error::SendError::MissingDestination) -/// - [`StringConversion`](crate::error::SendError::StringConversion) +/// - [`SendError`](crate::error::SendError) +/// - [`MissingDestination`](crate::error::SendError::MissingDestination) +/// - [`StringConversion`](crate::error::SendError::StringConversion) +/// - [`InstanceError`](crate::error::InstanceError) +/// - [`NoCurrentInstanceSet`](crate::error::InstanceError::NoCurrentInstanceSet) +/// - [`PdError`](crate::error::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 +455,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..7d3023b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -150,6 +150,7 @@ doc = ::embed_doc_image::embed_image!("phasor_patch", "assets/phasor_patch.png") //! // 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 ctx = pd.audio_context(); //! //! // Let's evaluate a pd patch. //! // We could have opened a `.pd` file also. @@ -179,7 +180,7 @@ doc = ::embed_doc_image::embed_image!("phasor_patch", "assets/phasor_patch.png") //! // 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. @@ -239,7 +240,7 @@ doc = ::embed_doc_image::embed_image!("phasor_patch", "assets/phasor_patch.png") //! ```no_run //! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; //! use libpd_rs::{ -//! Pd, functions::{util::calculate_ticks, receive::on_float, receive::receive_messages_from_pd, send::send_list_to} +//! Pd, functions::{util::calculate_ticks, receive::on_float, send::send_list_to} //! }; //! use sys_info::loadavg; //! @@ -262,6 +263,7 @@ doc = ::embed_doc_image::embed_image!("phasor_patch", "assets/phasor_patch.png") //! // 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 ctx = pd.audio_context(); //! //! // Let's evaluate another pd patch. //! // We could have opened a `.pd` file also. @@ -340,10 +342,10 @@ doc = ::embed_doc_image::embed_image!("phasor_patch", "assets/phasor_patch.png") //! // To receive messages from the pd patch we need to read the ring buffers //! // filled by the pd patch repeatedly to check if there are messages there. //! // Audio callback is a nice place to do that. -//! receive_messages_from_pd(); +//! ctx.receive_messages_from_pd(); //! //! // 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. @@ -456,11 +458,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 +488,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 +525,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 +541,7 @@ use crate::{ types::{PatchFileHandle, ReceiverHandle}, }; +pub use atom::Atom; /// Re-exports of the libpd-sys crate. pub use libpd_sys; @@ -533,6 +549,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 +608,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 +655,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 +1077,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 { - pub fn receive_messages_from_pd(&mut self) { + /// 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(&self) { self.instance.set_as_current(); functions::receive::receive_messages_from_pd(); } - pub fn receive_midi_messages_from_pd(&mut self) { + /// 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(&self) { self.instance.set_as_current(); functions::receive::receive_midi_messages_from_pd(); } - pub fn process_float(&mut self, ticks: i32, input: &[f32], output: &mut [f32]) { + /// Sets the instance as the current one and calls [`process_float`](crate::functions::process::process_float). + pub fn process_float(&self, ticks: i32, input: &[f32], output: &mut [f32]) { self.instance.set_as_current(); functions::process::process_float(ticks, input, output); } - pub fn process_double(&mut self, ticks: i32, input: &[f64], output: &mut [f64]) { + /// Sets the instance as the current one and calls [`process_double`](crate::functions::process::process_double). + pub fn process_double(&self, ticks: i32, input: &[f64], output: &mut [f64]) { self.instance.set_as_current(); functions::process::process_double(ticks, input, output); } - pub fn process_short(&mut self, ticks: i32, input: &[i16], output: &mut [i16]) { + /// Sets the instance as the current one and calls [`process_short`](crate::functions::process::process_short). + pub fn process_short(&self, ticks: i32, input: &[i16], output: &mut [i16]) { self.instance.set_as_current(); functions::process::process_short(ticks, input, output); } - pub fn process_raw(&mut self, input: &[f32], output: &mut [f32]) { + /// Sets the instance as the current one and calls [`process_raw`](crate::functions::process::process_raw). + pub fn process_raw(&self, input: &[f32], output: &mut [f32]) { self.instance.set_as_current(); functions::process::process_raw(input, output); } - pub fn process_raw_short(&mut self, input: &[i16], output: &mut [i16]) { + /// Sets the instance as the current one and calls [`process_raw_short`](crate::functions::process::process_raw_short). + pub fn process_raw_short(&self, input: &[i16], output: &mut [i16]) { self.instance.set_as_current(); functions::process::process_raw_short(input, output); } - pub fn process_raw_double(&mut self, input: &[f64], output: &mut [f64]) { + /// Sets the instance as the current one and calls [`process_raw_double`](crate::functions::process::process_raw_double). + pub fn process_raw_double(&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 +1153,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/pd_open_close_patch.rs b/tests/pd_open_close_patch.rs index 285a3b1..b47d33c 100644 --- a/tests/pd_open_close_patch.rs +++ b/tests/pd_open_close_patch.rs @@ -6,7 +6,7 @@ use libpd_rs::Pd; #[test] fn open_close_patch() { let mut pd = Pd::init_and_configure(0, 2, 44100).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); assert!(pd.open_patch("tests/patches/sine.pd").is_ok()); 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_after_touch.rs b/tests/send_and_receive_after_touch.rs index bc8dbf1..d70d12c 100644 --- a/tests/send_and_receive_after_touch.rs +++ b/tests/send_and_receive_after_touch.rs @@ -18,7 +18,7 @@ fn send_and_receive_after_touch() { let after_touch_messages_received: Arc>> = Arc::new(Mutex::new(vec![])); let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap(); diff --git a/tests/send_and_receive_bang.rs b/tests/send_and_receive_bang.rs index 5dc5f39..57c12c8 100644 --- a/tests/send_and_receive_bang.rs +++ b/tests/send_and_receive_bang.rs @@ -20,7 +20,7 @@ fn send_and_receive_bang() { let bangs: Arc>> = Arc::new(Mutex::new(vec![])); let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap(); diff --git a/tests/send_and_receive_control_change.rs b/tests/send_and_receive_control_change.rs index 2518e9d..29a6556 100644 --- a/tests/send_and_receive_control_change.rs +++ b/tests/send_and_receive_control_change.rs @@ -19,7 +19,7 @@ fn send_and_receive_control_change() { Arc::new(Mutex::new(vec![])); let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap(); diff --git a/tests/send_and_receive_double.rs b/tests/send_and_receive_double.rs index 0996a9f..b19875f 100644 --- a/tests/send_and_receive_double.rs +++ b/tests/send_and_receive_double.rs @@ -20,7 +20,7 @@ fn send_and_receive_double() { let floats: Arc>> = Arc::new(Mutex::new(vec![])); let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap(); diff --git a/tests/send_and_receive_float.rs b/tests/send_and_receive_float.rs index 8f8ea8c..41c12ad 100644 --- a/tests/send_and_receive_float.rs +++ b/tests/send_and_receive_float.rs @@ -20,7 +20,7 @@ fn send_and_receive_float() { let floats: Arc>> = Arc::new(Mutex::new(vec![])); let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap(); diff --git a/tests/send_and_receive_list.rs b/tests/send_and_receive_list.rs index 8180b91..ccd7ec0 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() { @@ -25,7 +25,7 @@ fn send_and_receive_list() { let list_received: Arc>> = Arc::new(Mutex::new(vec![])); let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap(); diff --git a/tests/send_and_receive_midi_byte.rs b/tests/send_and_receive_midi_byte.rs index d621008..11cc4dd 100644 --- a/tests/send_and_receive_midi_byte.rs +++ b/tests/send_and_receive_midi_byte.rs @@ -18,7 +18,7 @@ fn send_and_receive_midi_byte() { let midi_byte_messages_received: Arc>> = Arc::new(Mutex::new(vec![])); let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap(); diff --git a/tests/send_and_receive_note_on.rs b/tests/send_and_receive_note_on.rs index 59b0193..6d8e62c 100644 --- a/tests/send_and_receive_note_on.rs +++ b/tests/send_and_receive_note_on.rs @@ -18,7 +18,7 @@ fn send_and_receive_note_on() { let note_on_messages_received: Arc>> = Arc::new(Mutex::new(vec![])); let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap(); diff --git a/tests/send_and_receive_pitch_bend.rs b/tests/send_and_receive_pitch_bend.rs index 11a3a7d..a056c1a 100644 --- a/tests/send_and_receive_pitch_bend.rs +++ b/tests/send_and_receive_pitch_bend.rs @@ -18,7 +18,7 @@ fn send_and_receive_pitch_bend() { let pitch_bend_messages_received: Arc>> = Arc::new(Mutex::new(vec![])); let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap(); diff --git a/tests/send_and_receive_poly_after_touch.rs b/tests/send_and_receive_poly_after_touch.rs index 5295d75..18e9d5e 100644 --- a/tests/send_and_receive_poly_after_touch.rs +++ b/tests/send_and_receive_poly_after_touch.rs @@ -19,7 +19,7 @@ fn send_and_receive_poly_after_touch() { Arc::new(Mutex::new(vec![])); let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap(); diff --git a/tests/send_and_receive_program_change.rs b/tests/send_and_receive_program_change.rs index ba958eb..038fc40 100644 --- a/tests/send_and_receive_program_change.rs +++ b/tests/send_and_receive_program_change.rs @@ -19,7 +19,7 @@ fn send_and_receive_program_change() { Arc::new(Mutex::new(vec![])); let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap(); diff --git a/tests/send_and_receive_sys_realtime.rs b/tests/send_and_receive_sys_realtime.rs index c84561b..4b52c57 100644 --- a/tests/send_and_receive_sys_realtime.rs +++ b/tests/send_and_receive_sys_realtime.rs @@ -18,7 +18,7 @@ fn send_and_receive_sys_realtime() { let sys_realtime_messages_received: Arc>> = Arc::new(Mutex::new(vec![])); let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap(); diff --git a/tests/send_and_receive_sysex.rs b/tests/send_and_receive_sysex.rs index 6c766fb..f039b02 100644 --- a/tests/send_and_receive_sysex.rs +++ b/tests/send_and_receive_sysex.rs @@ -17,7 +17,7 @@ fn send_and_receive_sysex() { let sysex_messages_received: Arc>> = Arc::new(Mutex::new(vec![])); let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap(); diff --git a/tests/send_and_receive_typed_message.rs b/tests/send_and_receive_typed_message.rs index b4cef0e..ed9f126 100644 --- a/tests/send_and_receive_typed_message.rs +++ b/tests/send_and_receive_typed_message.rs @@ -19,7 +19,7 @@ fn send_and_receive_typed_message() { let output_channels = 2; let pd = Pd::init_and_configure(0, output_channels, sample_rate).unwrap(); - let mut ctx = pd.audio_context(); + let ctx = pd.audio_context(); dsp_on().unwrap();