From 3895c8eb81439b76474ec5e6e7d2b8626144c88b Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Sat, 31 Jul 2021 08:34:42 +0200 Subject: [PATCH 1/8] Initial WIP for in-place compatible processing --- atom/src/port.rs | 16 +++ core/src/cell.rs | 173 ++++++++++++++++++++++++ core/src/lib.rs | 1 + core/src/port.rs | 116 ++++++++++++++-- core/tests/amp.rs | 10 +- docs/fifths/eg-fifths-rs.lv2/fifths.ttl | 2 +- 6 files changed, 298 insertions(+), 20 deletions(-) create mode 100644 core/src/cell.rs diff --git a/atom/src/port.rs b/atom/src/port.rs index 633fbcb9..cab2601e 100644 --- a/atom/src/port.rs +++ b/atom/src/port.rs @@ -101,19 +101,35 @@ impl<'a> PortWriter<'a> { /// [See also the module documentation.](index.html) pub struct AtomPort; +const ATOM_IN_PLACE_ERROR: &'static str = "Atom ports cannot be used in-place"; + impl PortType for AtomPort { type InputPortType = PortReader<'static>; type OutputPortType = PortWriter<'static>; + type InPlaceInputPortType = (); + type InPlaceOutputPortType = (); + #[inline] unsafe fn input_from_raw(pointer: NonNull, _sample_count: u32) -> PortReader<'static> { let space = Space::from_atom(pointer.cast().as_ref()); PortReader::new(space) } + #[inline] unsafe fn output_from_raw(pointer: NonNull, _sample_count: u32) -> PortWriter<'static> { let space = RootMutSpace::from_atom(pointer.cast().as_mut()); PortWriter::new(space) } + + #[inline] + unsafe fn in_place_input_from_raw(_pointer: NonNull, _sample_count: u32) -> Self::InPlaceInputPortType { + panic!("{}", ATOM_IN_PLACE_ERROR); + } + + #[inline] + unsafe fn in_place_output_from_raw(_pointer: NonNull, _sample_count: u32) -> Self::InPlaceOutputPortType { + panic!("{}", ATOM_IN_PLACE_ERROR); + } } #[cfg(test)] diff --git a/core/src/cell.rs b/core/src/cell.rs new file mode 100644 index 00000000..b52c802e --- /dev/null +++ b/core/src/cell.rs @@ -0,0 +1,173 @@ +//! An utility module exposing `Cell`-like utilities for realtime overwritable buffers. +//! +//! Those type are useful to enforce the safety guarantees of e.g. audio buffers that are both the +//! read-only input buffer and the write-only output buffer, i.e. for audio plugins that do in-place +//! processing. + +use std::cell::Cell; + +/// A wrapper around `Cell` that only allows reading a `Copy` value out of a buffer. +/// +/// This type is useful to enforce the guarantees of a read-only buffer that may be overwritten at +/// any time, such as a +#[repr(transparent)] +pub struct ReadCell { + value: Cell, +} + +unsafe impl Send for ReadCell where T: Send {} + +impl ReadCell { + /// Creates a new `ReadCell` containing the given value. + /// + /// # Examples + /// + /// ``` + /// use lv2_core::cell::ReadCell; + /// + /// let c = ReadCell::new(5); + /// ``` + #[inline] + pub const fn new(value: T) -> ReadCell { + ReadCell { value: Cell::new(value) } + } +} + +impl ReadCell { + /// Returns a `&ReadCell` from a `&T` + /// + /// # Examples + /// + /// ``` + /// use lv2_core::cell::ReadCell; + /// + /// let slice: &[i32] = &[1, 2, 3]; + /// let cell_slice: &ReadCell<[i32]> = ReadCell::from_ref(slice); + /// let slice_cell: &[ReadCell] = cell_slice.as_slice_of_cells(); + /// + /// assert_eq!(slice_cell.len(), 3); + /// ``` + #[inline] + pub fn from_ref(t: &T) -> &ReadCell { + // SAFETY: `&mut` ensures unique access. + unsafe { &*(t as *const T as *const ReadCell) } + } +} + +impl ReadCell { + /// Returns a copy of the contained value. + /// + /// # Examples + /// + /// ``` + /// use lv2_core::cell::ReadCell; + /// + /// let c = ReadCell::new(5); + /// + /// let five = c.get(); + /// ``` + #[inline] + pub fn get(&self) -> T { + self.value.get() + } +} + +impl ReadCell<[T]> { + /// Returns a `&[ReadCell]` from a `&ReadCell<[T]>` + /// + /// # Examples + /// + /// ``` + /// use lv2_core::cell::ReadCell; + /// + /// let slice: &mut [i32] = &mut [1, 2, 3]; + /// let cell_slice: &ReadCell<[i32]> = ReadCell::from_ref(slice); + /// let slice_cell: &[ReadCell] = cell_slice.as_slice_of_cells(); + /// + /// assert_eq!(slice_cell.len(), 3); + /// ``` + #[inline] + pub fn as_slice_of_cells(&self) -> &[ReadCell] { + // SAFETY: `Cell` has the same memory layout as `T`. + unsafe { &*(self as *const ReadCell<[T]> as *const [ReadCell]) } + } +} + +/// A wrapper around `Cell` that only allows writing a value into a buffer. +#[repr(transparent)] +pub struct WriteCell { + value: Cell, +} + +impl WriteCell { + /// Returns a `&WriteCell` from a `&mut T` + /// + /// # Examples + /// + /// ``` + /// use lv2_core::cell::WriteCell; + /// + /// let slice: &mut [i32] = &mut [1, 2, 3]; + /// let cell_slice: &WriteCell<[i32]> = WriteCell::from_mut(slice); + /// let slice_cell: &[WriteCell] = cell_slice.as_slice_of_cells(); + /// + /// assert_eq!(slice_cell.len(), 3); + /// ``` + #[inline] + pub fn from_mut(t: &mut T) -> &WriteCell { + // SAFETY: `&mut` ensures unique access, and WriteCell has the same memory layout as T. + unsafe { &*(t as *mut T as *const WriteCell) } + } +} + +impl WriteCell { + /// Creates a new `WriteCell` containing the given value. + /// + /// # Examples + /// + /// ``` + /// use lv2_core::cell::WriteCell; + /// + /// let c = WriteCell::new(5); + /// ``` + #[inline] + pub const fn new(value: T) -> WriteCell { + WriteCell { value: Cell::new(value) } + } + + /// Sets the contained value. + /// + /// # Examples + /// + /// ``` + /// use lv2_core::cell::WriteCell; + /// + /// let c = WriteCell::new(5); + /// + /// c.set(10); + /// ``` + #[inline] + pub fn set(&self, val: T) { + self.value.set(val); + } +} + +impl WriteCell<[T]> { + /// Returns a `&[WriteCell]` from a `&WriteCell<[T]>` + /// + /// # Examples + /// + /// ``` + /// use lv2_core::cell::WriteCell; + /// + /// let slice: &mut [i32] = &mut [1, 2, 3]; + /// let cell_slice: &WriteCell<[i32]> = WriteCell::from_mut(slice); + /// let slice_cell: &[WriteCell] = cell_slice.as_slice_of_cells(); + /// + /// assert_eq!(slice_cell.len(), 3); + /// ``` + pub fn as_slice_of_cells(&self) -> &[WriteCell] { + // SAFETY: `Cell` has the same memory layout as `T`. + unsafe { &*(self as *const WriteCell<[T]> as *const [WriteCell]) } + } +} \ No newline at end of file diff --git a/core/src/lib.rs b/core/src/lib.rs index 369b2245..1cb1f9f1 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -63,3 +63,4 @@ pub mod feature; pub mod plugin; pub mod port; pub mod prelude; +pub mod cell; diff --git a/core/src/port.rs b/core/src/port.rs index 52eccbc3..9350ff46 100644 --- a/core/src/port.rs +++ b/core/src/port.rs @@ -7,6 +7,7 @@ use std::ptr::NonNull; use urid::UriBound; pub use lv2_core_derive::*; +use crate::cell::{ReadCell, WriteCell}; /// Generalization of port types. /// @@ -17,6 +18,11 @@ pub trait PortType { /// The type of output reference created by the port. type OutputPortType: Sized; + /// The type of input read by the port. + type InPlaceInputPortType: Sized; + /// The type of output reference created by the port. + type InPlaceOutputPortType: Sized; + /// Read data from the pointer or create a reference to the input. /// /// If the resulting data is a slice, `sample_count` is the length of the slice. @@ -34,6 +40,9 @@ pub trait PortType { /// /// This method is unsafe because one needs to de-reference a raw pointer to implement this method. unsafe fn output_from_raw(pointer: NonNull, sample_count: u32) -> Self::OutputPortType; + + unsafe fn in_place_input_from_raw(pointer: NonNull, sample_count: u32) -> Self::InPlaceInputPortType; + unsafe fn in_place_output_from_raw(pointer: NonNull, sample_count: u32) -> Self::InPlaceOutputPortType; } /// Audio port type. @@ -48,6 +57,8 @@ unsafe impl UriBound for Audio { impl PortType for Audio { type InputPortType = &'static [f32]; type OutputPortType = &'static mut [f32]; + type InPlaceInputPortType = &'static [ReadCell]; + type InPlaceOutputPortType = &'static [WriteCell]; #[inline] unsafe fn input_from_raw(pointer: NonNull, sample_count: u32) -> Self::InputPortType { @@ -58,6 +69,16 @@ impl PortType for Audio { unsafe fn output_from_raw(pointer: NonNull, sample_count: u32) -> Self::OutputPortType { std::slice::from_raw_parts_mut(pointer.as_ptr() as *mut f32, sample_count as usize) } + + #[inline] + unsafe fn in_place_input_from_raw(pointer: NonNull, sample_count: u32) -> Self::InPlaceInputPortType { + ReadCell::from_ref(std::slice::from_raw_parts_mut(pointer.as_ptr() as *mut f32, sample_count as usize)).as_slice_of_cells() + } + + #[inline] + unsafe fn in_place_output_from_raw(pointer: NonNull, sample_count: u32) -> Self::InPlaceOutputPortType { + WriteCell::from_mut(std::slice::from_raw_parts_mut(pointer.as_ptr() as *mut f32, sample_count as usize)).as_slice_of_cells() + } } /// Control value port type. @@ -74,15 +95,28 @@ unsafe impl UriBound for Control { impl PortType for Control { type InputPortType = f32; type OutputPortType = &'static mut f32; + type InPlaceInputPortType = &'static ReadCell; + type InPlaceOutputPortType = &'static WriteCell; #[inline] unsafe fn input_from_raw(pointer: NonNull, _sample_count: u32) -> f32 { *(pointer.cast().as_ref()) } + #[inline] unsafe fn output_from_raw(pointer: NonNull, _sample_count: u32) -> &'static mut f32 { (pointer.as_ptr() as *mut f32).as_mut().unwrap() } + + #[inline] + unsafe fn in_place_input_from_raw(pointer: NonNull, _sample_count: u32) -> Self::InPlaceInputPortType { + ReadCell::from_ref(&mut *(pointer.as_ptr() as *mut f32)) + } + + #[inline] + unsafe fn in_place_output_from_raw(pointer: NonNull, _sample_count: u32) -> Self::InPlaceOutputPortType { + WriteCell::from_mut(&mut *(pointer.as_ptr() as *mut f32)) + } } /// CV port type. @@ -97,6 +131,8 @@ unsafe impl UriBound for CV { impl PortType for CV { type InputPortType = &'static [f32]; type OutputPortType = &'static mut [f32]; + type InPlaceInputPortType = &'static [ReadCell]; + type InPlaceOutputPortType = &'static [WriteCell]; #[inline] unsafe fn input_from_raw(pointer: NonNull, sample_count: u32) -> Self::InputPortType { @@ -107,6 +143,16 @@ impl PortType for CV { unsafe fn output_from_raw(pointer: NonNull, sample_count: u32) -> Self::OutputPortType { std::slice::from_raw_parts_mut(pointer.as_ptr() as *mut f32, sample_count as usize) } + + #[inline] + unsafe fn in_place_input_from_raw(pointer: NonNull, sample_count: u32) -> Self::InPlaceInputPortType { + ReadCell::from_ref(std::slice::from_raw_parts_mut(pointer.as_ptr() as *mut f32, sample_count as usize)).as_slice_of_cells() + } + + #[inline] + unsafe fn in_place_output_from_raw(pointer: NonNull, sample_count: u32) -> Self::InPlaceOutputPortType { + WriteCell::from_mut(std::slice::from_raw_parts_mut(pointer.as_ptr() as *mut f32, sample_count as usize)).as_slice_of_cells() + } } /// Abstraction of safe port handles. @@ -140,13 +186,9 @@ impl Deref for InputPort { impl PortHandle for InputPort { #[inline] unsafe fn from_raw(pointer: *mut c_void, sample_count: u32) -> Option { - if let Some(pointer) = NonNull::new(pointer) { - Some(Self { - port: T::input_from_raw(pointer, sample_count), - }) - } else { - None - } + Some(Self { + port: T::input_from_raw( NonNull::new(pointer)?, sample_count), + }) } } @@ -176,13 +218,9 @@ impl DerefMut for OutputPort { impl PortHandle for OutputPort { #[inline] unsafe fn from_raw(pointer: *mut c_void, sample_count: u32) -> Option { - if let Some(pointer) = NonNull::new(pointer) { - Some(Self { - port: T::output_from_raw(pointer, sample_count), - }) - } else { - None - } + Some(Self { + port: T::output_from_raw(NonNull::new(pointer)?, sample_count), + }) } } @@ -192,6 +230,56 @@ impl PortHandle for Option { } } +/// Handle for input ports. +/// +/// Fields of this type can be dereferenced to the input type of the port type. +pub struct InPlaceInputPort { + port: T::InPlaceInputPortType, +} + +impl Deref for InPlaceInputPort { + type Target = T::InPlaceInputPortType; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.port + } +} + +impl PortHandle for InPlaceInputPort { + #[inline] + unsafe fn from_raw(pointer: *mut c_void, sample_count: u32) -> Option { + Some(Self { + port: T::in_place_input_from_raw(NonNull::new(pointer)?, sample_count), + }) + } +} + +/// Handle for output ports. +/// +/// Fields of this type can be dereferenced to the output type of the port type. +pub struct InPlaceOutputPort { + port: T::InPlaceOutputPortType, +} + +impl Deref for InPlaceOutputPort { + type Target = T::InPlaceOutputPortType; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.port + } +} + +impl PortHandle for InPlaceOutputPort { + #[inline] + unsafe fn from_raw(pointer: *mut c_void, sample_count: u32) -> Option { + Some(Self { + port: T::in_place_output_from_raw(NonNull::new(pointer)?, sample_count), + }) + } +} + /// Collection of IO ports. /// /// Plugins do not handle port management on their own. Instead, they define a struct with all of the required ports. Then, the plugin instance will collect the port pointers from the host and create a `PortCollection` instance for every `run` call. Using this instance, plugins have access to all of their required ports. diff --git a/core/tests/amp.rs b/core/tests/amp.rs index 25c9e94d..8534918a 100644 --- a/core/tests/amp.rs +++ b/core/tests/amp.rs @@ -13,8 +13,8 @@ struct Amp { #[derive(PortCollection)] struct AmpPorts { gain: InputPort, - input: InputPort