Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
scanner.hpp
Go to the documentation of this file.
1#pragma once
2
3// Plugin Scanner for Pulp Host
4// Scans standard system directories for VST3, AU, CLAP, and LV2 plugins.
5// Returns a list of PluginInfo descriptors that can be used to load plugins.
6//
7// Usage:
8// auto scanner = PluginScanner();
9// auto plugins = scanner.scan();
10// for (auto& p : plugins) { ... p.name, p.path, p.format ... }
11
13#include <cstdint>
14#include <functional>
15#include <string>
16#include <string_view>
17#include <vector>
18
19namespace pulp::host {
20
21// ── Plugin format identifier ────────────────────────────────────────────
22
23enum class PluginFormat {
24 VST3 = 0,
25 AudioUnit = 1, // AU v2 (macOS)
26 AudioUnitV3 = 2, // AUv3 (macOS/iOS)
27 CLAP = 3,
28 LV2 = 4,
31};
32
33// ── Plugin descriptor from scanning ─────────────────────────────────────
34
35struct PluginInfo {
36 std::string name;
37 std::string manufacturer;
38 std::string version;
39 std::string path; // Full path to the plugin bundle/binary
40 std::string unique_id; // Format-specific unique ID (FUID, bundle ID, etc.)
41 // Deterministic default: Processor nodes never set it, and the graph
42 // serializer writes it, so an indeterminate value broke JSON roundtrips.
44 bool is_instrument = false;
45 bool is_effect = true;
46 int num_inputs = 2;
47 int num_outputs = 2;
48
49 // Each scanner populates the subset it can cheaply extract. Callers
50 // filter plugins on these instead of parsing `name` strings. All
51 // fields are additive — older cache blobs that don't carry them
52 // deserialize with defaults, so scan_cache.hpp's round-trip stays
53 // backwards-compatible.
54 //
55 // category — broad taxonomy: "Fx", "Instrument", "Analyzer",
56 // "MidiEffect". Sourced from VST3 PClassInfo::category,
57 // AU AUSysExCategory, CLAP descriptor category.
58 // features — free-form tags (CLAP `features` array, VST3
59 // subcategories joined, AU tag list).
60 // description — longer human-readable description if the format
61 // surfaces one; empty otherwise.
62 // has_editor — plugin declares an embedded editor view.
63 // supports_sidechain — declares a second input bus.
64 // supports_midi_in / supports_midi_out — MIDI wiring.
65 std::string category;
66 std::vector<std::string> features;
67 std::string description;
68 bool has_editor = false;
69 bool supports_sidechain = false;
70 bool supports_midi_in = false;
71 bool supports_midi_out = false;
72
73 // Optional descriptor capture; legacy/cache metadata alone has no authority.
74 std::optional<PluginIdentityEvidence> identity_evidence;
75};
76
77// ── Scanner configuration ───────────────────────────────────────────────
78
80 bool scan_vst3 = true;
81 bool scan_au = true;
82 bool scan_clap = true;
83 bool scan_lv2 = true;
84 std::vector<std::string> extra_paths; // Additional scan directories
85
86 // Restrict scanning to `extra_paths`, skipping the platform defaults.
87 // Tests and hermetic tools use this so they never walk a user's installed
88 // plugin collection or execute third-party CLAP entry points.
89 bool only_extra_paths = false;
90
91 // Callback for progress reporting during scan
92 using ProgressCallback = std::function<void(const std::string& current_path, int scanned, int total)>;
94
95 // Optional blacklist of bundle paths the scanner should skip, usually
96 // populated from a prior out-of-process scanner crash. When non-null,
97 // scan() short-circuits any path reported by ScanBlacklist::is_blacklisted
98 // and pushes nothing into the result for it, and an isolated CLAP scan
99 // records every bundle whose worker crashed or hung into it. Caller-owned;
100 // must outlive scan().
101 class ScanBlacklist* blacklist = nullptr;
102
103 // CLAP enumeration runs the bundle's `clap_entry` (dlopen + init +
104 // factory walk). Each bundle runs in a disposable `pulp-scan-worker`
105 // child process, so a bundle that crashes or hangs is reported into
106 // `blacklist` instead of taking the scanning process down. Entry points
107 // run in this process only when this is true; the worker itself scans
108 // this way, and `pulp scan --in-process` exposes it. When it is false and
109 // no worker can be found, CLAP enumeration is skipped and ScanReport says
110 // so: a host process never runs plugin code it did not ask to run.
111 bool in_process_clap = false;
112
113 // Worker binary for isolated CLAP scanning. Empty resolves the
114 // PULP_SCAN_WORKER environment variable, then `pulp-scan-worker` beside
115 // the running executable or in a `bin/` or `tools/scan-worker/` directory
116 // above it (see find_scan_worker).
117 std::string scan_worker_path;
118
119 // Per-bundle budget for the isolated worker; a bundle that exceeds it is
120 // recorded as a timeout in `blacklist`.
122
123 // Optional cache of earlier CLAP scan results keyed by bundle path and
124 // file stamp. A bundle whose entry still matches is served from here
125 // and never handed to a worker; a bundle the worker enumerates to exactly
126 // one descriptor is recorded. With a cache and no worker, cached bundles
127 // are still listed; only the uncached ones are skipped. Caller-owned;
128 // must outlive scan().
129 class HostScanCache* scan_cache = nullptr;
130};
131
132// What a scan found, and what it refused to do.
134 std::vector<PluginInfo> plugins;
135 // CLAP enumeration was skipped (except for bundles ScanOptions::scan_cache
136 // already knew) because no `pulp-scan-worker` was found and
137 // ScanOptions::in_process_clap was false. `error` says where the scanner
138 // looked and how to opt into in-process enumeration.
140 std::string error;
141};
142
143// ── Plugin Scanner ──────────────────────────────────────────────────────
144
146public:
147 PluginScanner() = default;
148
149 // Scan all standard plugin directories and return found plugins.
150 // This performs I/O and may take seconds on large collections. Anything
151 // the scan refused to do is logged; scan_report() returns it as data.
152 std::vector<PluginInfo> scan(const ScanOptions& options = {});
153 ScanReport scan_report(const ScanOptions& options = {});
154
155 // Get default scan paths for a specific format on the current platform
156 static std::vector<std::string> default_paths(PluginFormat format);
157
158 // Quick check if a specific path looks like a valid plugin bundle
159 static bool is_plugin_bundle(const std::string& path, PluginFormat format);
160
161 // Enumerate exactly one bundle in this process, with no blacklist and no
162 // worker: the in-process half of scanning that `pulp-scan-worker` runs on
163 // the parent's behalf. A CLAP bundle runs its entry point here. Returns
164 // nothing for an Audio Unit (component discovery has no bundle path) or
165 // a path that is not a bundle of `format`.
166 static std::vector<PluginInfo> scan_bundle(const std::string& path, PluginFormat format);
167
168 private:
169 std::vector<PluginInfo> scan_directory(const std::string& dir, PluginFormat format,
170 const ScanOptions& options,
171 const class IsolatedPluginScanner* isolated);
172 PluginInfo scan_vst3_bundle(const std::string& path);
173 PluginInfo scan_clap_bundle(const std::string& path);
174 PluginInfo scan_lv2_bundle(const std::string& path);
175#ifdef __APPLE__
176 std::vector<PluginInfo> scan_audio_units();
177#endif
178};
179
180// Clamp an untrusted CLAP plugin count — a malformed bundle's factory can
181// return an absurd value that would make reserve()/iteration throw and abort
182// the whole scan. Exposed for testing.
183uint32_t cap_clap_plugin_count(uint32_t count) noexcept;
184
185// Build a loadable PluginInfo from an Audio Unit component identity of the
186// form TYPE:SUBT:MANU (three four-byte OSTypes, e.g. "aumu:Vas7:RoCl").
187//
188// An AU identity is already the complete loader descriptor — unlike VST3/CLAP
189// it carries no filesystem path to resolve — so a caller that knows which
190// component it wants does not need discovery at all. Scanning to reach a known
191// component means walking every installed plugin, which is slow and exposes
192// the caller to unrelated third-party bundles that instantiate badly.
193//
194// Returns false and leaves `info` untouched when the string is not a
195// well-formed identity, so callers can fall back to scanning for other formats.
196// Only the four component types Pulp can host are accepted: `aumu` (MIDI
197// instrument), `augn` (no-input generator), and `aufx`/`aumf` (effects).
198bool plugin_info_from_au_identity(std::string_view identity, PluginInfo& info);
199
200} // namespace pulp::host
Definition scan_cache.hpp:32
Definition isolated_scanner.hpp:92
ScanReport scan_report(const ScanOptions &options={})
std::vector< PluginInfo > scan(const ScanOptions &options={})
static std::vector< PluginInfo > scan_bundle(const std::string &path, PluginFormat format)
static bool is_plugin_bundle(const std::string &path, PluginFormat format)
static std::vector< std::string > default_paths(PluginFormat format)
Definition scan_blacklist.hpp:28
What the product grants a device: the quota table indexed by capability tier and live thermal state.
Definition aax_editor.hpp:31
Bounded event contracts shared by host ingress and graph-authored nodes.
Definition anticipation_eligibility.hpp:9
@ VST3
Definition native_handle_visitor.hpp:48
@ LV2
Definition native_handle_visitor.hpp:52
@ CLAP
Definition native_handle_visitor.hpp:51
@ AudioUnit
Definition native_handle_visitor.hpp:49
@ AudioUnitV3
Definition native_handle_visitor.hpp:50
PluginFormat
Definition scanner.hpp:23
@ VST3
Definition scanner.hpp:24
@ BuiltIn
Pulp-owned, pathless device resolved without external discovery.
Definition scanner.hpp:30
uint32_t cap_clap_plugin_count(uint32_t count) noexcept
bool plugin_info_from_au_identity(std::string_view identity, PluginInfo &info)
Definition scanner.hpp:35
std::string version
Definition scanner.hpp:38
std::optional< PluginIdentityEvidence > identity_evidence
Definition scanner.hpp:74
bool supports_sidechain
Definition scanner.hpp:69
std::string unique_id
Definition scanner.hpp:40
std::string description
Definition scanner.hpp:67
bool has_editor
Definition scanner.hpp:68
bool supports_midi_out
Definition scanner.hpp:71
std::vector< std::string > features
Definition scanner.hpp:66
bool is_instrument
Definition scanner.hpp:44
bool is_effect
Definition scanner.hpp:45
PluginFormat format
Definition scanner.hpp:43
std::string path
Definition scanner.hpp:39
int num_inputs
Definition scanner.hpp:46
std::string category
Definition scanner.hpp:65
std::string name
Definition scanner.hpp:36
std::string manufacturer
Definition scanner.hpp:37
bool supports_midi_in
Definition scanner.hpp:70
int num_outputs
Definition scanner.hpp:47
Definition scanner.hpp:79
class ScanBlacklist * blacklist
Definition scanner.hpp:101
std::string scan_worker_path
Definition scanner.hpp:117
bool scan_vst3
Definition scanner.hpp:80
class HostScanCache * scan_cache
Definition scanner.hpp:129
ProgressCallback on_progress
Definition scanner.hpp:93
bool scan_lv2
Definition scanner.hpp:83
std::vector< std::string > extra_paths
Definition scanner.hpp:84
bool in_process_clap
Definition scanner.hpp:111
int scan_worker_timeout_ms
Definition scanner.hpp:121
bool only_extra_paths
Definition scanner.hpp:89
bool scan_clap
Definition scanner.hpp:82
bool scan_au
Definition scanner.hpp:81
std::function< void(const std::string &current_path, int scanned, int total)> ProgressCallback
Definition scanner.hpp:92
Definition scanner.hpp:133
std::string error
Definition scanner.hpp:140
std::vector< PluginInfo > plugins
Definition scanner.hpp:134
bool clap_skipped_no_worker
Definition scanner.hpp:139