#![expect(
clippy::expect_used,
clippy::missing_panics_doc,
reason = "In closure implementations, there are CStr conversions.
The use of expect there is ok because those strings are coming from Pd and pd uses C strings."
)]
use crate::{
atom::{make_atom_list_from_t_atom_list, Atom},
error::{StringConversionError, SubscriptionError, C_STR_FAILURE},
types::ReceiverHandle,
};
use libffi::high::{
ClosureMut1, ClosureMut2, ClosureMut3, ClosureMut4, FnPtr1, FnPtr2, FnPtr3, FnPtr4,
};
use libpd_sys::{
t_libpd_aftertouchhook, t_libpd_banghook, t_libpd_controlchangehook, t_libpd_doublehook,
t_libpd_floathook, t_libpd_listhook, t_libpd_messagehook, t_libpd_midibytehook,
t_libpd_noteonhook, t_libpd_pitchbendhook, t_libpd_polyaftertouchhook, t_libpd_printhook,
t_libpd_programchangehook, t_libpd_symbolhook,
};
use std::{
ffi::{CStr, CString},
mem, os, slice,
};
type PrintHookCodePtr = *const FnPtr1<'static, *const i8, ()>;
type BangHookCodePtr = *const FnPtr1<'static, *const i8, ()>;
type FloatHookCodePtr = *const FnPtr2<'static, *const i8, f32, ()>;
type DoubleHookCodePtr = *const FnPtr2<'static, *const i8, f64, ()>;
type SymbolHookCodePtr = *const FnPtr2<'static, *const i8, *const i8, ()>;
type ListHookCodePtr = *const FnPtr3<'static, *const i8, i32, *mut libpd_sys::_atom, ()>;
type MessageHookCodePtr =
*const FnPtr4<'static, *const i8, *const i8, i32, *mut libpd_sys::_atom, ()>;
type MidiNoteOnCodePtr = *const FnPtr3<'static, i32, i32, i32, ()>;
type MidiControlChangeCodePtr = *const FnPtr3<'static, i32, i32, i32, ()>;
type MidiProgramChangeCodePtr = *const FnPtr2<'static, i32, i32, ()>;
type MidiPitchBendCodePtr = *const FnPtr2<'static, i32, i32, ()>;
type MidiAfterTouchCodePtr = *const FnPtr2<'static, i32, i32, ()>;
type MidiPolyAfterTouchCodePtr = *const FnPtr3<'static, i32, i32, i32, ()>;
type MidiByteCodePtr = *const FnPtr2<'static, i32, i32, ()>;
pub fn start_listening_from<T: AsRef<str>>(sender: T) -> Result<ReceiverHandle, SubscriptionError> {
let send = CString::new(sender.as_ref()).map_err(StringConversionError::from)?;
unsafe {
let handle = libpd_sys::libpd_bind(send.as_ptr());
if handle.is_null() {
Err(SubscriptionError::FailedToSubscribeToSender(
sender.as_ref().to_owned(),
))
} else {
Ok(ReceiverHandle::from(handle))
}
}
}
pub fn stop_listening_from(source: ReceiverHandle) {
let handle = source.into_inner();
if handle.is_null() {
return;
}
unsafe {
libpd_sys::libpd_unbind(handle);
}
}
pub fn source_to_listen_from_exists<T: AsRef<str>>(sender: T) -> Result<bool, SubscriptionError> {
let send = CString::new(sender.as_ref()).map_err(StringConversionError::from)?;
unsafe { Ok(matches!(libpd_sys::libpd_exists(send.as_ptr()), 1)) }
}
pub fn on_print<F: FnMut(&str) + Send + Sync + 'static>(mut user_provided_closure: F) {
let closure: &'static mut _ = Box::leak(Box::new(move |out: *const os::raw::c_char| {
let out = unsafe { CStr::from_ptr(out).to_str().expect(C_STR_FAILURE) };
user_provided_closure(out);
}));
let callback = ClosureMut1::new(closure);
let code = callback.code_ptr() as PrintHookCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_printhook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_printhook(Some(libpd_sys::libpd_print_concatenator));
};
unsafe {
libpd_sys::libpd_set_concatenated_printhook(ptr);
};
}
pub fn on_bang<F: FnMut(&str) + Send + Sync + 'static>(mut user_provided_closure: F) {
let closure: &'static mut _ = Box::leak(Box::new(move |source: *const os::raw::c_char| {
let source = unsafe { CStr::from_ptr(source).to_str().expect(C_STR_FAILURE) };
user_provided_closure(source);
}));
let callback = ClosureMut1::new(closure);
let code = callback.code_ptr() as BangHookCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_banghook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_banghook(ptr);
};
}
pub fn on_float<F: FnMut(&str, f32) + Send + Sync + 'static>(mut user_provided_closure: F) {
let closure: &'static mut _ = Box::leak(Box::new(
move |source: *const os::raw::c_char, float: f32| {
let source = unsafe { CStr::from_ptr(source).to_str().expect(C_STR_FAILURE) };
user_provided_closure(source, float);
},
));
let callback = ClosureMut2::new(closure);
let code = callback.code_ptr() as FloatHookCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_floathook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_floathook(ptr);
};
}
pub fn on_double<F: FnMut(&str, f64) + Send + Sync + 'static>(mut user_provided_closure: F) {
let closure: &'static mut _ = Box::leak(Box::new(
move |source: *const os::raw::c_char, double: f64| {
let source = unsafe { CStr::from_ptr(source).to_str().expect(C_STR_FAILURE) };
user_provided_closure(source, double);
},
));
let callback = ClosureMut2::new(closure);
let code = callback.code_ptr() as DoubleHookCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_doublehook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_doublehook(ptr);
};
}
pub fn on_symbol<F: FnMut(&str, &str) + Send + Sync + 'static>(mut user_provided_closure: F) {
let closure: &'static mut _ = Box::leak(Box::new(
move |source: *const os::raw::c_char, symbol: *const os::raw::c_char| {
let source = unsafe { CStr::from_ptr(source).to_str().expect(C_STR_FAILURE) };
let symbol = unsafe { CStr::from_ptr(symbol).to_str().expect(C_STR_FAILURE) };
user_provided_closure(source, symbol);
},
));
let callback = ClosureMut2::new(closure);
let code = callback.code_ptr() as SymbolHookCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_symbolhook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_symbolhook(ptr);
};
}
pub fn on_list<F: FnMut(&str, &[Atom]) + Send + Sync + 'static>(mut user_provided_closure: F) {
let closure: &'static mut _ = Box::leak(Box::new(
move |source: *const os::raw::c_char,
list_length: i32,
atom_list: *mut libpd_sys::t_atom| {
let source = unsafe { CStr::from_ptr(source).to_str().expect(C_STR_FAILURE) };
#[expect(
clippy::cast_sign_loss,
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);
user_provided_closure(source, &atoms);
},
));
let callback = ClosureMut3::new(closure);
let code = callback.code_ptr() as ListHookCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_listhook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_listhook(ptr);
};
}
pub fn on_message<F: FnMut(&str, &str, &[Atom]) + Send + Sync + 'static>(
mut user_provided_closure: F,
) {
let closure: &'static mut _ = Box::leak(Box::new(
move |source: *const os::raw::c_char,
message: *const os::raw::c_char,
list_length: i32,
atom_list: *mut libpd_sys::t_atom| {
let source = unsafe { CStr::from_ptr(source).to_str().expect(C_STR_FAILURE) };
let message = unsafe { CStr::from_ptr(message).to_str().expect(C_STR_FAILURE) };
#[expect(
clippy::cast_sign_loss,
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);
user_provided_closure(source, message, &atoms);
},
));
let callback = ClosureMut4::new(closure);
let code = callback.code_ptr() as MessageHookCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_messagehook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_messagehook(ptr);
};
}
pub fn receive_messages_from_pd() {
unsafe {
libpd_sys::libpd_queued_receive_pd_messages();
};
}
pub fn on_midi_note_on<F: FnMut(i32, i32, i32) + Send + Sync + 'static>(
mut user_provided_closure: F,
) {
let closure: &'static mut _ =
Box::leak(Box::new(move |channel: i32, pitch: i32, velocity: i32| {
user_provided_closure(channel, pitch, velocity);
}));
let callback = ClosureMut3::new(closure);
let code = callback.code_ptr() as MidiNoteOnCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_noteonhook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_noteonhook(ptr);
};
}
pub fn on_midi_control_change<F: FnMut(i32, i32, i32) + Send + Sync + 'static>(
mut user_provided_closure: F,
) {
let closure: &'static mut _ = Box::leak(Box::new(
move |channel: i32, controller: i32, value: i32| {
user_provided_closure(channel, controller, value);
},
));
let callback = ClosureMut3::new(closure);
let code = callback.code_ptr() as MidiControlChangeCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_controlchangehook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_controlchangehook(ptr);
};
}
pub fn on_midi_program_change<F: FnMut(i32, i32) + Send + Sync + 'static>(
mut user_provided_closure: F,
) {
let closure: &'static mut _ = Box::leak(Box::new(move |channel: i32, value: i32| {
user_provided_closure(channel, value);
}));
let callback = ClosureMut2::new(closure);
let code = callback.code_ptr() as MidiProgramChangeCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_programchangehook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_programchangehook(ptr);
};
}
pub fn on_midi_pitch_bend<F: FnMut(i32, i32) + Send + Sync + 'static>(
mut user_provided_closure: F,
) {
let closure: &'static mut _ = Box::leak(Box::new(move |channel: i32, value: i32| {
user_provided_closure(channel, value);
}));
let callback = ClosureMut2::new(closure);
let code = callback.code_ptr() as MidiPitchBendCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_pitchbendhook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_pitchbendhook(ptr);
};
}
pub fn on_midi_after_touch<F: FnMut(i32, i32) + Send + Sync + 'static>(
mut user_provided_closure: F,
) {
let closure: &'static mut _ = Box::leak(Box::new(move |channel: i32, value: i32| {
user_provided_closure(channel, value);
}));
let callback = ClosureMut2::new(closure);
let code = callback.code_ptr() as MidiAfterTouchCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_aftertouchhook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_aftertouchhook(ptr);
};
}
pub fn on_midi_poly_after_touch<F: FnMut(i32, i32, i32) + Send + Sync + 'static>(
mut user_provided_closure: F,
) {
let closure: &'static mut _ =
Box::leak(Box::new(move |channel: i32, pitch: i32, value: i32| {
user_provided_closure(channel, pitch, value);
}));
let callback = ClosureMut3::new(closure);
let code = callback.code_ptr() as MidiPolyAfterTouchCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_polyaftertouchhook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_polyaftertouchhook(ptr);
};
}
pub fn on_midi_byte<F: FnMut(i32, i32) + Send + Sync + 'static>(mut user_provided_closure: F) {
let closure: &'static mut _ = Box::leak(Box::new(move |port: i32, byte: i32| {
user_provided_closure(port, byte);
}));
let callback = ClosureMut2::new(closure);
let code = callback.code_ptr() as MidiByteCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_midibytehook>() };
mem::forget(callback);
unsafe {
libpd_sys::libpd_set_queued_midibytehook(ptr);
};
}
pub fn receive_midi_messages_from_pd() {
unsafe {
libpd_sys::libpd_queued_receive_midi_messages();
};
}