From da1f707dbf9daee2a7080f5ae0f915c2f147355a Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Fri, 17 May 2024 14:56:27 +0200 Subject: [PATCH] Finish documenting the clack_host::process module --- host/src/process.rs | 300 ++++++++++++++++++++++-------- host/src/process/audio_buffers.rs | 2 + 2 files changed, 226 insertions(+), 76 deletions(-) diff --git a/host/src/process.rs b/host/src/process.rs index c4aee8c0..04b94eee 100644 --- a/host/src/process.rs +++ b/host/src/process.rs @@ -1,3 +1,18 @@ +//! Plugin audio processing types and related utilities. +//! +//! A plugin's audio processor can be in two states, either `started` or `stopped`, depending on +//! whether it is currently continuously processing audio, or has been put to sleep by the host, +//! respectively. +//! +//! These states are represented by the [`StartedPluginAudioProcessor`] and [`StoppedPluginAudioProcessor`] +//! types respectively, using a type-state pattern that only expose the methods that are valid for +//! the plugin's current state. +//! +//! Alternatively, users can also use the [`PluginAudioProcessor`] convenience type that internalizes +//! the state instead, and allows it to change and be checked at runtime. + +#![deny(missing_docs)] + use self::audio_buffers::InputAudioBuffers; use crate::host::HostHandlers; use crate::host::PluginInstanceError; @@ -16,52 +31,51 @@ use std::sync::Arc; use crate::plugin::instance::PluginInstanceInner; pub use clack_common::process::*; +#[allow(missing_docs)] // TODO: doc this pub mod audio_buffers; +/// A handle to a plugin's audio processor that can be in either its `started` or `stopped` state. +/// +/// This is a convenience type that can be used where the type-states [`StartedPluginAudioProcessor`] and +/// [`StoppedPluginAudioProcessor`] are not very ergonomic, or to perform operations that are common +/// to both states. pub enum PluginAudioProcessor { + /// The audio processor is in it's `started` state. Started(StartedPluginAudioProcessor), + /// The audio processor is in it's `stopped` state. Stopped(StoppedPluginAudioProcessor), } impl PluginAudioProcessor { + /// Resets the plugin's audio processing state. + /// + /// This clears all the plugin's internal buffers, kills all voices, and resets all processing + /// state such as envelopes, LFOs, oscillators, filters, etc. + /// + /// Calling this method allows the `steady_time` parameter passed to [`process`](StartedPluginAudioProcessor::process) + /// to jump backwards. #[inline] - pub fn as_started(&self) -> Result<&StartedPluginAudioProcessor, PluginInstanceError> { - match self { - Started(s) => Ok(s), - Stopped(_) => Err(PluginInstanceError::ProcessingStopped), - } - } - - #[inline] - pub fn as_started_mut( - &mut self, - ) -> Result<&mut StartedPluginAudioProcessor, PluginInstanceError> { - match self { - Started(s) => Ok(s), - Stopped(_) => Err(PluginInstanceError::ProcessingStopped), - } - } - - #[inline] - pub fn as_stopped(&self) -> Result<&StoppedPluginAudioProcessor, PluginInstanceError> { - match self { - Stopped(s) => Ok(s), - Started(_) => Err(PluginInstanceError::ProcessingStarted), - } - } - - #[inline] - pub fn as_stopped_mut( - &mut self, - ) -> Result<&mut StoppedPluginAudioProcessor, PluginInstanceError> { + pub fn reset(&mut self) { match self { - Stopped(s) => Ok(s), - Started(_) => Err(PluginInstanceError::ProcessingStarted), + Started(s) => s.reset(), + Stopped(s) => s.reset(), } } + /// Accesses the [`SharedHandler`] for this instance, using the provided closure. + /// + /// This function returns the return value of the provided closure directly. + /// + /// Note that the lifetime of the [`SharedHandler`] cannot be statically known, as it is bound + /// to the plugin instance itself. + /// + /// Unlike [`access_handler`](self.access_handler) and + /// [`access_handler_mut`](self.access_handler_mut), there is no way to obtain a mutable + /// reference to the [`SharedHandler`], as it may be concurrently accessed by other threads. + /// + /// [`SharedHandler`]: crate::prelude::SharedHandler #[inline] - pub fn use_shared_handler<'s, R>( + pub fn access_shared_handler<'s, R>( &'s self, access: impl for<'a> FnOnce(&'s ::Shared<'a>) -> R, ) -> R { @@ -71,8 +85,19 @@ impl PluginAudioProcessor { } } + /// Accesses the [`AudioProcessorHandler`] for this instance, using the provided closure. + /// + /// This function returns the return value of the provided closure directly. + /// + /// Note that the lifetime of the [`AudioProcessorHandler`] cannot be statically known, as it is bound + /// to the plugin instance itself. + /// + /// See the [`access_handler_mut`](self.access_handler_mut) method to receive a mutable + /// reference to the [`AudioProcessorHandler`] instead. + /// + /// [`AudioProcessorHandler`]: crate::prelude::AudioProcessorHandler #[inline] - pub fn use_handler<'s, R>( + pub fn access_handler<'s, R>( &'s self, access: impl for<'a> FnOnce(&'s ::AudioProcessor<'a>) -> R, ) -> R { @@ -82,8 +107,19 @@ impl PluginAudioProcessor { } } + /// Accesses the [`AudioProcessorHandler`] for this instance, using the provided closure. + /// + /// This function returns the return value of the provided closure directly. + /// + /// Note that the lifetime of the [`AudioProcessorHandler`] cannot be statically known, as it is bound + /// to the plugin instance itself. + /// + /// See the [`access_handler`](self.access_handler) method to receive a shared + /// reference to the [`AudioProcessorHandler`] instead. + /// + /// [`AudioProcessorHandler`]: crate::prelude::AudioProcessorHandler #[inline] - pub fn use_handler_mut<'s, R>( + pub fn access_handler_mut<'s, R>( &'s mut self, access: impl for<'a> FnOnce(&'s mut ::AudioProcessor<'a>) -> R, ) -> R { @@ -93,14 +129,7 @@ impl PluginAudioProcessor { } } - #[inline] - pub fn is_started(&self) -> bool { - match self { - Stopped(_) => false, - Started(_) => true, - } - } - + /// Returns this plugin instance's audio processor handle. #[inline] pub fn plugin_handle(&mut self) -> PluginAudioProcessorHandle { match self { @@ -109,6 +138,7 @@ impl PluginAudioProcessor { } } + /// Returns this plugin instance's shared handle. #[inline] pub fn shared_plugin_handle(&self) -> PluginSharedHandle { match self { @@ -117,24 +147,96 @@ impl PluginAudioProcessor { } } + /// Returns `true` if this audio processor was created from the given plugin instance. #[inline] - pub fn reset(&mut self) { + pub fn matches(&self, instance: &PluginInstance) -> bool { + match &self { + Started(s) => s.matches(instance), + Stopped(s) => s.matches(instance), + } + } + + /// Returns `true` if this audio processor is its `started` state, `false` otherwise. + #[inline] + pub fn is_started(&self) -> bool { match self { - Started(s) => s.reset(), - Stopped(s) => s.reset(), + Stopped(_) => false, + Started(_) => true, } } + /// Returns this audio processor as a shared reference to its [`StartedPluginAudioProcessor`] + /// state, if it is in the `started` state. + /// + /// # Errors + /// + /// This returns a [`PluginInstanceError::ProcessingStopped`] error if the audio processor is + /// in the `stopped` state. #[inline] - pub fn ensure_processing_started( + pub fn as_started(&self) -> Result<&StartedPluginAudioProcessor, PluginInstanceError> { + match self { + Started(s) => Ok(s), + Stopped(_) => Err(PluginInstanceError::ProcessingStopped), + } + } + + /// Returns this audio processor as a mutable reference to its [`StartedPluginAudioProcessor`] + /// state, if it is in the `started` state. + /// + /// # Errors + /// + /// This returns a [`PluginInstanceError::ProcessingStopped`] error if the audio processor is + /// in the `stopped` state. + #[inline] + pub fn as_started_mut( &mut self, ) -> Result<&mut StartedPluginAudioProcessor, PluginInstanceError> { match self { Started(s) => Ok(s), - _ => self.start_processing(), + Stopped(_) => Err(PluginInstanceError::ProcessingStopped), } } + /// Returns this audio processor as a shared reference to its [`StoppedPluginAudioProcessor`] + /// state, if it is in the `stopped` state. + /// + /// # Errors + /// + /// This returns a [`PluginInstanceError::ProcessingStarted`] error if the audio processor is + /// in the `started` state. + #[inline] + pub fn as_stopped(&self) -> Result<&StoppedPluginAudioProcessor, PluginInstanceError> { + match self { + Stopped(s) => Ok(s), + Started(_) => Err(PluginInstanceError::ProcessingStarted), + } + } + + /// Returns this audio processor as a mutable reference to its [`StoppedPluginAudioProcessor`] + /// state, if it is in the `stopped` state. + /// + /// # Errors + /// + /// This returns a [`PluginInstanceError::ProcessingStarted`] error if the audio processor is + /// in the `started` state. + #[inline] + pub fn as_stopped_mut( + &mut self, + ) -> Result<&mut StoppedPluginAudioProcessor, PluginInstanceError> { + match self { + Stopped(s) => Ok(s), + Started(_) => Err(PluginInstanceError::ProcessingStarted), + } + } + + /// Starts this audio processor, and returns a mutable reference to its + /// [`StartedPluginAudioProcessor`] state. + /// + /// # Errors + /// + /// If the start operation failed, this returns a [`PluginInstanceError::StartProcessingFailed`] error. + /// If the audio processor was already started, this returns a + /// [`PluginInstanceError::ProcessingStarted`] error. #[inline] pub fn start_processing( &mut self, @@ -156,10 +258,19 @@ impl PluginAudioProcessor { }) } + /// Starts this audio processor, and returns a mutable reference to its + /// [`StoppedPluginAudioProcessor`] state. + /// + /// # Errors + /// + /// If the audio processor was already stopped, this returns a + /// [`PluginInstanceError::ProcessingStopped`] error. #[inline] - pub fn ensure_processing_stopped(&mut self) -> &mut StoppedPluginAudioProcessor { + pub fn stop_processing( + &mut self, + ) -> Result<&mut StoppedPluginAudioProcessor, PluginInstanceError> { let inner = match self { - Stopped(s) => return s, + Stopped(_) => return Err(PluginInstanceError::ProcessingStopped), Started(a) => a.inner.clone(), }; @@ -167,18 +278,41 @@ impl PluginAudioProcessor { *self = Stopped(stopped); - match self { + Ok(match self { Stopped(s) => s, _ => unreachable!(), - } + }) } + /// Starts this audio processor, if it is not already started, and returns a mutable reference + /// to its [`StartedPluginAudioProcessor`] state. + /// + /// If its already started, this does nothing and returns the mutable reference directly. + /// + /// # Errors + /// + /// If this audio processor was in its `stopped` state and the start operation failed, this + /// returns a [`PluginInstanceError::StartProcessingFailed`] error. #[inline] - pub fn stop_processing( + pub fn ensure_processing_started( &mut self, - ) -> Result<&mut StoppedPluginAudioProcessor, PluginInstanceError> { + ) -> Result<&mut StartedPluginAudioProcessor, PluginInstanceError> { + match self { + Started(s) => Ok(s), + _ => self.start_processing(), + } + } + + /// Stops this audio processor, if it is not already stopped, and returns a mutable reference + /// to its [`StoppedPluginAudioProcessor`] state. + /// + /// If its already stopped, this does nothing and returns the mutable reference directly. + /// + /// This operation is infallible. + #[inline] + pub fn ensure_processing_stopped(&mut self) -> &mut StoppedPluginAudioProcessor { let inner = match self { - Stopped(_) => return Err(PluginInstanceError::ProcessingStopped), + Stopped(s) => return s, Started(a) => a.inner.clone(), }; @@ -186,12 +320,23 @@ impl PluginAudioProcessor { *self = Stopped(stopped); - Ok(match self { + match self { Stopped(s) => s, _ => unreachable!(), - }) + } } + /// Starts this audio processor, if it is not already started, and returns its + /// [`StartedPluginAudioProcessor`] state, consuming the [`PluginAudioProcessor`] instance in + /// the process. + /// + /// If it is already started, this does nothing and returns the [`StartedPluginAudioProcessor`] state directly. + /// + /// # Errors + /// + /// If this audio processor was in its `stopped` state and the start operation failed, this + /// returns a [`ProcessingStartError`], from which the [`StoppedPluginAudioProcessor`] can be + /// recovered. #[inline] pub fn into_started(self) -> Result, ProcessingStartError> { match self { @@ -200,6 +345,13 @@ impl PluginAudioProcessor { } } + /// Stops this audio processor, if it is not already stopped, and returns its + /// [`StoppedPluginAudioProcessor`] state, consuming the [`PluginAudioProcessor`] instance in + /// the process. + /// + /// If it is already stopped, this does nothing and returns the [`StoppedPluginAudioProcessor`] state directly. + /// + /// This operation is infallible. #[inline] pub fn into_stopped(self) -> StoppedPluginAudioProcessor { match self { @@ -207,14 +359,6 @@ impl PluginAudioProcessor { Stopped(s) => s, } } - - #[inline] - pub fn matches(&self, instance: &PluginInstance) -> bool { - match &self { - Started(s) => s.matches(instance), - Stopped(s) => s.matches(instance), - } - } } impl From> for PluginAudioProcessor { @@ -453,6 +597,11 @@ impl StartedPluginAudioProcessor { // PANIC: This struct exists, therefore we are guaranteed the plugin is active unsafe { access(self.inner.wrapper().audio_processor().unwrap().as_mut()) } } + /// Returns this plugin instance's audio processor handle. + #[inline] + pub fn plugin_handle(&mut self) -> PluginAudioProcessorHandle { + PluginAudioProcessorHandle::new(self.inner.raw_instance().into()) + } /// Returns this plugin instance's shared handle. #[inline] @@ -460,12 +609,6 @@ impl StartedPluginAudioProcessor { self.inner.plugin_shared() } - /// Returns this plugin instance's audio processor handle. - #[inline] - pub fn plugin_handle(&mut self) -> PluginAudioProcessorHandle { - PluginAudioProcessorHandle::new(self.inner.raw_instance().into()) - } - /// Returns `true` if this audio processor was created from the given plugin instance. #[inline] pub fn matches(&self, instance: &PluginInstance) -> bool { @@ -611,18 +754,18 @@ impl StoppedPluginAudioProcessor { unsafe { access(self.inner.wrapper().audio_processor().unwrap().as_mut()) } } - /// Returns this plugin instance's shared handle. - #[inline] - pub fn shared_plugin_handle(&self) -> PluginSharedHandle { - self.inner.plugin_shared() - } - /// Returns this plugin instance's audio processor handle. #[inline] pub fn plugin_handle(&mut self) -> PluginAudioProcessorHandle { PluginAudioProcessorHandle::new(self.inner.raw_instance().into()) } + /// Returns this plugin instance's shared handle. + #[inline] + pub fn shared_plugin_handle(&self) -> PluginSharedHandle { + self.inner.plugin_shared() + } + /// Returns `true` if this audio processor was created from the given plugin instance. #[inline] pub fn matches(&self, instance: &PluginInstance) -> bool { @@ -630,11 +773,16 @@ impl StoppedPluginAudioProcessor { } } +/// An error that occurred when a plugin instance couldn't start processing. +/// +/// The [`StoppedPluginAudioProcessor`] can be recovered using the +/// [`into_stopped_processor`](Self::into_stopped_processor) method. pub struct ProcessingStartError { processor: StoppedPluginAudioProcessor, } impl ProcessingStartError { + /// Recovers the plugin instance's [`StoppedPluginAudioProcessor`] that failed to start. #[inline] pub fn into_stopped_processor(self) -> StoppedPluginAudioProcessor { self.processor diff --git a/host/src/process/audio_buffers.rs b/host/src/process/audio_buffers.rs index 82504039..295a8919 100644 --- a/host/src/process/audio_buffers.rs +++ b/host/src/process/audio_buffers.rs @@ -1,3 +1,5 @@ +//! Types to manipulate input and output audio buffers for processing. + use clack_common::process::AudioPortProcessingInfo; use clap_sys::audio_buffer::clap_audio_buffer; use core::array::IntoIter;