Skip to main content

truce_core/
info.rs

1/// Static metadata about a plugin.
2#[derive(Clone, Debug)]
3pub struct PluginInfo {
4    pub name: &'static str,
5    pub vendor: &'static str,
6    pub url: &'static str,
7    pub version: &'static str,
8    pub category: PluginCategory,
9
10    /// Whether the host should route MIDI / note events *into* this
11    /// plugin. Defaults to `true` for instruments and note effects;
12    /// `truce.toml`'s `midi_input` overrides the derived value (e.g.
13    /// an audio effect that reacts to CC). Every format wrapper gates
14    /// its MIDI input port / bus / capability on this one flag.
15    pub accepts_midi_in: bool,
16
17    /// Whether this plugin emits MIDI / note events *to* the host.
18    /// Defaults to `true` for note effects only; `truce.toml`'s
19    /// `midi_output` overrides the derived value (e.g. an instrument
20    /// or effect that also emits MIDI). Every format wrapper gates its
21    /// MIDI output port / bus / capability on this one flag, so the
22    /// host actually reads what `process()` pushes to `output_events`.
23    pub emits_midi: bool,
24
25    /// Short identifier (`bundle_id` in `truce.toml`). Used to derive
26    /// the LV2 plugin URI (`{vendor.url}/lv2/{bundle_id}`); also a
27    /// stable, vendor-agnostic key for "this plugin" that doesn't
28    /// drift with display-name changes the way `clap_id` does.
29    pub bundle_id: &'static str,
30
31    // Format-specific IDs
32    pub vst3_id: &'static str,
33    pub clap_id: &'static str,
34    pub fourcc: [u8; 4],
35    pub au_type: [u8; 4],
36    pub au_manufacturer: [u8; 4],
37    pub aax_id: Option<&'static str>,
38    /// AAX plugin category string (e.g. "EQ", "Dynamics", "Reverb").
39    /// Maps to `AAX_ePlugInCategory` constants.
40    pub aax_category: Option<&'static str>,
41    /// VST3 "Plugin Type Categories" secondary token. The wrapper
42    /// emits this after the primary token (`Fx|<sub>`,
43    /// `Instrument|<sub>`) so hosts like Cubase route to the right
44    /// submenu. Values from the VST3 SDK list: `Delay`, `Distortion`,
45    /// `Dynamics`, `EQ`, `Filter`, `Mastering`, `Modulation`,
46    /// `Pitch Shift`, `Restoration`, `Reverb`, `Analyzer`, `Tools`,
47    /// `Surround`. Optional; when `None` only the primary token is
48    /// emitted and Cubase will fall back to "Other".
49    pub vst3_subcategory: Option<&'static str>,
50
51    /// Per-format display-name overrides, populated by
52    /// `truce::plugin_info!()` from the matching `truce.toml` keys.
53    /// Format wrappers fall back to `name` when the override is `None`.
54    /// Baked at compile time so back-to-back plugin builds with
55    /// different overrides don't invalidate the format wrapper's
56    /// build fingerprint.
57    ///
58    /// `au3_name` is exposed for parity with the other formats and
59    /// for user introspection, but `truce-au`'s `resolved_plugin_name`
60    /// reads `au_name` for both v2 and v3 builds - the v3 host's
61    /// displayed label comes from the appex `Info.plist`'s `AUNAME`
62    /// (which `cargo truce install --au3` populates from `au3_name`),
63    /// not from `g_descriptor->name`.
64    /// `[plugin.presets]` `user_dir` from `truce.toml`: replaces the
65    /// `truce/<vendor>/<plugin>` subpath of the user-scope preset
66    /// root. `truce_utils::presets::user_preset_root` documents
67    /// where the path resolves on each OS. Effectively permanent
68    /// once a plugin ships - changing it orphans previously saved
69    /// user presets and packs.
70    pub preset_user_dir: Option<&'static str>,
71
72    pub vst3_name: Option<&'static str>,
73    pub clap_name: Option<&'static str>,
74    pub vst2_name: Option<&'static str>,
75    pub au_name: Option<&'static str>,
76    pub au3_name: Option<&'static str>,
77    pub aax_name: Option<&'static str>,
78    pub lv2_name: Option<&'static str>,
79
80    /// Standalone-only. Format wrappers MUST NOT read this - it
81    /// exists for preview hosts (truce-standalone, the iOS `AUv3`
82    /// container app) that need a TOML-driven way to mute the
83    /// plug-in's audio output while keeping `process()` ticking, so
84    /// editors that visualise an input signal (analyzers, tuners,
85    /// spectrum displays) update from mic / file input without
86    /// closing a mic → speakers feedback loop. Set from
87    /// `mute_preview_output` in `truce.toml`. Real DAW hosts own
88    /// their own output graph; consulting this flag from a wrapper
89    /// would let plug-in authors silence the DAW's mix bus, which
90    /// is never what they want.
91    #[doc(hidden)]
92    pub mute_preview_output: bool,
93
94    /// Sample-accurate automation chunking tunables. Read by the
95    /// `chunked_process::process_chunked` helper that every format
96    /// wrapper routes `process()` through. Populated by
97    /// `truce::plugin_info!()` from `truce.toml`'s `[automation]`
98    /// table; defaults to [`AutomationConfig::DEFAULT`] when the
99    /// table is absent.
100    pub automation: AutomationConfig,
101}
102
103/// Sample-accurate chunking tunables baked into [`PluginInfo`] at
104/// compile time. Mirrors `truce_build::AutomationConfig` (the
105/// derive-time view of the same TOML key) but lives in `truce-core`
106/// so wrappers can read it without a `truce-build` dep.
107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108pub struct AutomationConfig {
109    /// Smallest sub-block size in samples. The chunker only splits
110    /// the audio block at split-eligible events whose `sample_offset`
111    /// is at least `block_start + min_subblock_samples` past the
112    /// current sub-block start; closer events are coalesced. Default
113    /// 32 (set via [`AutomationConfig::DEFAULT`]).
114    pub min_subblock_samples: u32,
115}
116
117impl AutomationConfig {
118    /// Default used when `truce.toml` omits the `[automation]` table.
119    pub const DEFAULT: Self = Self {
120        min_subblock_samples: 32,
121    };
122}
123
124impl Default for AutomationConfig {
125    fn default() -> Self {
126        Self::DEFAULT
127    }
128}
129
130/// Resolve a format's display-name override. Each wrapper picks its
131/// own `<format>_name` field off `PluginInfo` and passes the result
132/// here along with the `PluginInfo::name` fallback. Empty overrides
133/// (unset or set to `""`) fall through to `fallback`.
134#[must_use]
135pub fn resolve_name_override(
136    override_value: Option<&'static str>,
137    fallback: &'static str,
138) -> &'static str {
139    match override_value {
140        Some(s) if !s.is_empty() => s,
141        _ => fallback,
142    }
143}
144
145#[derive(Clone, Copy, Debug, PartialEq, Eq)]
146pub enum PluginCategory {
147    Effect,
148    Instrument,
149    /// MIDI note effect (e.g., transpose, arpeggiator). Processes MIDI events.
150    NoteEffect,
151    Analyzer,
152    Tool,
153}
154
155/// Convert a category string to [`PluginCategory`] at compile time.
156/// Used by the `plugin_info!()` macro.
157#[must_use]
158pub const fn category_from_str(s: &str) -> PluginCategory {
159    match s.as_bytes() {
160        b"Instrument" => PluginCategory::Instrument,
161        b"NoteEffect" => PluginCategory::NoteEffect,
162        b"Analyzer" => PluginCategory::Analyzer,
163        b"Tool" => PluginCategory::Tool,
164        _ => PluginCategory::Effect,
165    }
166}
167
168/// Helper to convert a 4-char string literal to `[u8; 4]` at compile time.
169/// Panics if the string is not exactly 4 ASCII bytes.
170///
171/// # Panics
172///
173/// Panics at compile time when used in a `const` context (preferred)
174/// or at runtime if `s.len() != 4`. ASCII-ness isn't checked here -
175/// callers that need it should validate separately.
176#[must_use]
177pub const fn fourcc(s: &[u8]) -> [u8; 4] {
178    assert!(s.len() == 4, "FourCC must be exactly 4 bytes");
179    [s[0], s[1], s[2], s[3]]
180}