truce_rack_core/scanner.rs
1//! Plugin discovery trait.
2//!
3//! Each format wrapper exposes a `*Scanner` type that
4//! implements [`PluginScanner`]. Hosts may also implement
5//! their own composite scanners that aggregate results across
6//! formats (`MultiScanner { clap, vst3, au }` etc.) — there's
7//! nothing format-specific in the trait.
8
9use crate::error::Result;
10use crate::info::PluginInfo;
11use std::path::Path;
12
13/// Discover and load audio plugins of a single format.
14///
15/// # Thread safety
16///
17/// Scans are **off the audio thread**. They walk the filesystem,
18/// open dylibs, and can take seconds (a fresh AU scan touches
19/// 100+ plugins on a typical Mac). Hosts should never call
20/// `scan` from a real-time context — wrap in a worker thread
21/// if you need a non-blocking discovery flow.
22pub trait PluginScanner {
23 /// Concrete plugin type this scanner produces. Always
24 /// implements [`crate::Plugin`] for at least one sample
25 /// precision.
26 type Plugin;
27
28 /// Scan default OS plugin directories for this format.
29 /// Each format wrapper picks the conventional paths
30 /// (`~/Library/Audio/Plug-Ins/CLAP` and `/Library/...`
31 /// for CLAP on macOS, the registry on Windows, etc.).
32 ///
33 /// # Errors
34 /// I/O errors propagate from the directory walk; per-plugin
35 /// load failures are logged and skipped rather than
36 /// aborting the scan.
37 fn scan(&self) -> Result<Vec<PluginInfo>>;
38
39 /// Scan a specific directory. Useful for hosts that bundle
40 /// their own plugins or that want to test against a known
41 /// fixtures directory.
42 ///
43 /// # Errors
44 /// Same as [`PluginScanner::scan`].
45 fn scan_path(&self, path: &Path) -> Result<Vec<PluginInfo>>;
46
47 /// Materialise an instance from the [`PluginInfo`] returned
48 /// by `scan` / `scan_path`. Most wrappers actually dlopen
49 /// the plugin's dylib at this point; expect file I/O.
50 ///
51 /// # Errors
52 /// [`crate::Error::PluginNotFound`] when `info.unique_id`
53 /// doesn't match anything in this scanner's index;
54 /// [`crate::Error::LoadFailed`] on dylib / signature
55 /// errors.
56 fn load(&self, info: &PluginInfo) -> Result<Self::Plugin>;
57}