Skip to main content

clack_host/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/prokopyl/clack/main/logo.svg")]
3
4//! A low-level library to create [CLAP](https://github.com/free-audio/clap) audio hosts in safe Rust.
5//!
6//! # Plugin Lifecycle
7//!
8//! CLAP hosts and plugins go through a specific set of steps before they are able to process audio.
9//! The general lifecycle is explained below, with links to more detailed documentation for each
10//! step. Also see the [example](#example) section below for a code snippet presenting how this
11//! lifecycle can be generally implemented using Clack.
12//!
13//! Before starting to deal with the plugins themselves, CLAP hosts need implementations for all the
14//! different callbacks a plugin may use to interact with the host. This is done by implementing the
15//! [`Host`](host::HostHandlers) trait, and its associated sub-types (one for each thread specification).
16//! See the [`host`] module documentation for more information about how to implement the
17//! [`Host`](host::HostHandlers) trait, and on CLAP's thread specifications model.
18//!
19//! 1. CLAP plugins are distributed in dynamic library files. These are prebuilt
20//!    dynamically-loaded libraries (with a `.clap` extension), and can contain the implementations
21//!    of multiple plugins. They expose an [entry](entry::PluginEntry) that can be loaded and initialized
22//!    with the [`PluginEntry::load`](entry::PluginEntry::load) method. See the [`entry`] module
23//!    documentation for more information.
24//! 2. Plugins' entry points can expose multiple [factories](factory::Factory). These are singleton
25//!    objects that can provide various functionality. The one of main interest is the
26//!    [`PluginFactory`](factory::plugin::PluginFactory), which exposes methods to list and instantiate plugins.
27//!    It can be retrieved from a [`PluginEntry`](entry::PluginEntry) using the
28//!    [`PluginEntry::get_plugin_factory`](entry::PluginEntry::get_plugin_factory) method.
29//!
30//!    See the [`factory`] module documentation to learn more about factories.
31//! 3. The [`PluginFactory`](factory::plugin::PluginFactory) can be used to list
32//!    [`PluginDescriptor`s](plugin::PluginDescriptor), each of which contains various information
33//!    (displayed name, author, etc.) about the plugins included in this entry, including their
34//!    unique IDs. These can be displayed in a list for the user to chose from.
35//! 4. The selected plugin's ID can now be used to create a new
36//!    [`PluginInstance`](plugin::PluginInstance) using its
37//!    [`new`](plugin::PluginInstance::new) method. This is also where the [`Host`](host::HostHandlers)
38//!    types come into play, as they need to be ready to handle the plugin instance's callbacks.
39//!
40//!    See the [`PluginInstance::new`](plugin::PluginInstance::new) method's documentation for
41//!    for more detail.
42//! 5. The plugin instance now needs to be activated for audio processing, using the
43//!    [`activate`](plugin::PluginInstance::activate) method. This method receives the current
44//!    [`PluginAudioConfiguration`](process::PluginAudioConfiguration), which allows it to
45//!    allocate its buffers with proper sizes depending on sample rate, for instance. This is also
46//!    where the host should allocate its own audio and event buffers (see
47//!    [`EventBuffers`](events::io::EventBuffer) and
48//!    [`AudioPorts`](process::audio_buffers::AudioPorts)), and also where the
49//!    [`AudioProcessor`](host::HostHandlers::AudioProcessor) type is created to handle audio processing
50//!    callbacks.
51//!
52//!    If the plugin activation is successful, the plugin's
53//!    [`StoppedAudioProcessor`](process::StoppedPluginAudioProcessor) is returned.
54//!
55//!    All of this allocation always happens on the main thread. Also, because the allocated buffers
56//!    are dependent on the given configuration, plugins have to be deactivated and then
57//!    re-activated whenever it changes.
58//! 6. Once the [`StoppedAudioProcessor`](process::StoppedPluginAudioProcessor) is
59//!    created and active, we can send it to another thread dedicated to audio processing, while the
60//!    main instance type has to stay on the main thread (it is not [`Send`], while the audio
61//!    processor is). Once there, all we need to do is to indicate that continuous processing
62//!    is about to start, using the
63//!    [`start_processing`](process::StoppedPluginAudioProcessor::start_processing)
64//!    method, which consumes the
65//!    [`StoppedAudioProcessor`](process::StoppedPluginAudioProcessor) and returns a
66//!    [`StartedAudioProcessor`](process::StartedPluginAudioProcessor).
67//!
68//!    Note that if this pattern of consuming the audio processors is too cumbersome, they can be
69//!    converted into a [`PluginAudioProcessor`](process::PluginAudioProcessor) using
70//!    the [`Into`] trait, which handles switching between both states using only a `&mut` reference,
71//!    at the cost of making the
72//!    [`start_processing`](process::PluginAudioProcessor::start_processing),
73//!    [`process`](process::StartedPluginAudioProcessor::process), and
74//!    [`stop_processing`](process::PluginAudioProcessor::stop_processing) operations
75//!    fallible at runtime if the lifecycle isn't properly handled. See the
76//!    [`PluginAudioProcessor`](process::PluginAudioProcessor) documentation for more
77//!    information.
78//! 7. Perform the processing of a block of audio and events using the
79//!    [`process`](process::StartedPluginAudioProcessor::process) method.
80//!    
81//!    Because CLAP Audio and Event buffers are generic, some cheap, short-lived wrappers around the
82//!    audio and event buffers must be crated for each process call, in order to be passed to the
83//!    plugin's [`process`](process::StartedPluginAudioProcessor::process) method.
84//!
85//!    Those buffer wrappers are [`InputEvents`](events::io::InputEvents) and
86//!    [`OutputEvents`](events::io::OutputEvents) for events, and
87//!    [`InputAudioBuffers`](process::audio_buffers::InputAudioBuffers) and
88//!    [`OutputAudioBuffers`](process::audio_buffers::OutputAudioBuffers) for audio (obtained via a call to
89//!    [`AudioPorts::with_input_buffers`](process::audio_buffers::AudioPorts::with_input_buffers) and.
90//!    [`AudioPorts::with_output_buffers`](process::audio_buffers::AudioPorts::with_output_buffers)
91//!    respectively).
92//!
93//!    See the documentation of those buffer types for more detail on what types they support, as
94//!    well as the [`process`](process::StartedPluginAudioProcessor::process) method's
95//!    documentation for more information.
96//! 8. Once continuous processing has stopped, the host needs to call
97//!    [`start_processing`](process::StartedPluginAudioProcessor::stop_processing),
98//!    in a similar fashion to Step 6 above.
99//!
100//! 9. The audio processor now has to be sent back to the main thread to be deactivated (in order to
101//!    not perform de-allocations in the realtime audio thread).
102//!    The [`PluginInstance::deactivate`](plugin::PluginInstance::deactivate) can then be used
103//!    to consume the Audio Processor. Only then, the
104//!    [`PluginInstance`](plugin::PluginInstance) itself can be dropped to destroy the instance
105//!    entirely.
106//!
107//! # Example
108//!
109//! Note that this example isn't a full implementation and many details are left out: it is meant to
110//! give an overview of Clack's APIs and how they relate to a CLAP plugin's lifecycle.
111//!
112//! For more details instruction on how to create a full, correct Clack host implementation, refer
113//! to the submodules' and types' documentation on each topic linked above.
114//!
115//!```rust
116//! use clack_host::events::event_types::*;
117//! use clack_host::prelude::*;
118//!
119//! // Prepare our (extremely basic) host implementation
120//!
121//! struct MyHostShared;
122//!
123//! impl<'a> SharedHandler<'a> for MyHostShared {
124//!   /* ... */
125//!     # fn request_restart(&self) { unimplemented!() }
126//!     # fn request_process(&self) { unimplemented!() }
127//!     # fn request_callback(&self) { unimplemented!() }
128//! }
129//!
130//! struct MyHost;
131//! impl HostHandlers for MyHost {
132//!     type Shared<'a> = MyHostShared;
133//!
134//!     type MainThread<'a> = ();
135//!     type AudioProcessor<'a> = ();
136//! }
137//! # pub fn main() -> Result<(), Box<dyn std::error::Error>> {
138//!
139//! // Information about our totally legit host.
140//! let host_info = HostInfo::new("Legit Studio", "Legit Ltd.", "https://example.com", "4.3.2")?;
141//!
142//! // Step 1: Load the entry in memory.
143//! # mod diva { include!("./entry/diva_stub.rs"); }
144//! # let entry = PluginEntry::load_from_clack::<diva::Entry>(c"/home/user/.clap/u-he/libdiva.so")?;
145//! # /*
146//! let entry = PluginEntry::load("/home/user/.clap/u-he/libdiva.so")?;
147//! # */
148//!
149//! // Step 2: Get the Plugin factory of this entry.
150//! let plugin_factory = entry.get_plugin_factory().unwrap();
151//!
152//! // Step 3: Find the descriptor of plugin we're interested in.
153//! let plugin_descriptor = plugin_factory.plugin_descriptors()
154//!     // We're assuming this specific plugin is in this entry for this example.
155//!     // A real host would store all descriptors in a list and show them to the user.
156//!     .find(|d| d.id().unwrap().to_bytes() == b"com.u-he.diva")
157//!     .unwrap();
158//!
159//! // Let's check we are indeed loading the right plugin.
160//! assert_eq!(plugin_descriptor.name().unwrap().to_bytes(), b"Diva");
161//!
162//! // Step 4: Create the plugin instance
163//! let mut plugin_instance = PluginInstance::<MyHost>::new(
164//!     |_| MyHostShared,
165//!     |_| (),
166//!     &entry,
167//!     plugin_descriptor.id().unwrap(),
168//!     &host_info
169//! )?;
170//!
171//! // Step 5: Activate the plugin (allocating the audio processor),
172//! // and allocate associated buffers.
173//!
174//! // In this example, we will only process 4 samples at a time
175//! let audio_configuration = PluginAudioConfiguration {
176//!     sample_rate: 48_000.0,
177//!     min_frames_count: 4,
178//!     max_frames_count: 4,
179//! };
180//! let audio_processor = plugin_instance.activate(|_, _| (), audio_configuration)?;
181//!
182//! // Event buffers
183//! // For this example, we'll only have a single input event.
184//! let note_on_event = NoteOnEvent::new(0, Pckn::new(0u16, 0u16, 12u16, 60u32), 4.2);
185//! let input_events_buffer = [note_on_event];
186//! let mut output_events_buffer = EventBuffer::new();
187//!
188//! // Audio buffers
189//! let mut input_audio_buffers = [[0.0f32; 4]; 2]; // 2 channels (stereo), 1 port
190//! let mut output_audio_buffers = [[0.0f32; 4]; 2];
191//!
192//! // Audio port buffers
193//! let mut input_ports = AudioPorts::with_capacity(2, 1); // 2 channels (stereo), 1 port
194//! let mut output_ports = AudioPorts::with_capacity(2, 1);
195//!
196//! // Let's send the audio processor to a dedicated audio processing thread.
197//! let audio_processor = std::thread::scope(|s| s.spawn(|| {
198//!    // Step 6: Start the audio processor.
199//!    let mut audio_processor = audio_processor.start_processing().unwrap();
200//!
201//!    // Step 7: Process audio and events.
202//!    // Borrow all buffers, and wrap them in cheap CLAP-compatible structs.
203//!    let input_events = InputEvents::from_buffer(&input_events_buffer);
204//!    let mut output_events = OutputEvents::from_buffer(&mut output_events_buffer);
205//!
206//!    let mut input_audio = input_ports.with_input_buffers([AudioPortBuffer {
207//!        latency: 0,
208//!        // We only use F32 (32-bit floating point) audio
209//!        channels: AudioPortBufferType::f32_input_only(
210//!            // These buffers can be marked constant, as they only contain zeros
211//!            input_audio_buffers.iter_mut().map(|b| InputChannel::constant(b))
212//!        )
213//!    }]);
214//!
215//!    let mut output_audio = output_ports.with_output_buffers([AudioPortBuffer {
216//!        latency: 0,
217//!        channels: AudioPortBufferType::f32_output_only(
218//!            output_audio_buffers.iter_mut().map(|b| b.as_mut_slice())
219//!        )
220//!    }]);
221//!
222//!    // Finally do the processing itself.
223//!    let status = audio_processor.process(
224//!        &input_audio,
225//!        &mut output_audio,
226//!        &input_events,
227//!        &mut output_events,
228//!        None,
229//!        None
230//!    ).unwrap();
231//!
232//!    // This plugin has finished processing and requests to be put to sleep.
233//!    assert_eq!(status, ProcessStatus::Sleep);
234//!
235//!    // Step 8: if requested by the plugin or required to by the host or user, stop processing
236//!    let audio_processor = audio_processor.stop_processing();
237//!
238//!    // Send the audio processor back to be deallocated by the main thread.
239//!    audio_processor
240//! }).join().unwrap());
241//!
242//! // A real host implementation might forward the results to an output device,
243//! // or pass it along a processing chain.
244//! // Here, we are only going to check the results manually.
245//!
246//!
247//! // The raw audio for each channel
248//! assert_eq!(&[42.0f32, 69.0, 21.0, 34.5], &output_audio_buffers[0]);
249//! assert_eq!(&[42.0f32, 69.0, 21.0, 34.5], &output_audio_buffers[1]);
250//!
251//! // The input note event has been passed through
252//! assert_eq!(output_events_buffer.get(0).unwrap(), &note_on_event);
253//! assert_eq!(output_events_buffer.len(), 1);
254//!
255//! // Step 9: stop and deactivate the plugin.
256//!
257//! plugin_instance.deactivate(audio_processor);
258//! # Ok(()) }
259//! ```
260
261pub mod entry;
262pub mod extensions;
263pub use clack_common::factory;
264pub mod host;
265pub mod plugin;
266pub mod process;
267mod util;
268
269pub use clack_common::events;
270pub use clack_common::stream;
271pub use clack_common::utils;
272
273/// A helpful prelude re-exporting all the types related to host implementation.
274pub mod prelude {
275    pub use crate::{
276        entry::PluginEntry,
277        events::{
278            Event, EventHeader, Pckn, UnknownEvent,
279            io::{EventBuffer, InputEvents, OutputEvents},
280        },
281        host::{
282            AudioProcessorHandler, HostError, HostExtensions, HostHandlers, HostInfo,
283            MainThreadHandler, SharedHandler,
284        },
285        plugin::{
286            InitializedPluginHandle, InitializingPluginHandle, PluginAudioProcessorHandle,
287            PluginInstance, PluginInstanceError, PluginMainThreadHandle, PluginSharedHandle,
288        },
289        process::{
290            AudioPortProcessingInfo, PluginAudioConfiguration, PluginAudioProcessor, ProcessStatus,
291            StartedPluginAudioProcessor, StoppedPluginAudioProcessor,
292            audio_buffers::{
293                AudioPortBuffer, AudioPortBufferType, AudioPorts, InputAudioBuffers, InputChannel,
294                OutputAudioBuffers,
295            },
296        },
297        utils::ClapId,
298    };
299}
300
301#[doc = include_str!("../README.md")]
302const _README_TEST: () = {};