Skip to main content

truce_rack_core/
plugin.rs

1//! The host-facing [`Plugin`] trait and its supporting
2//! per-block types.
3//!
4//! Format wrappers (`truce-rack-clap`, `truce-rack-vst3`, `truce-rack-au`, …)
5//! implement [`Plugin`] for their per-format instance type. Host
6//! applications then hold a `Box<dyn Plugin<S>>` (or a generic
7//! `<P: Plugin<S>>`) without caring which format produced it.
8
9use crate::buffer::AudioBuffer;
10use crate::bus::BusLayout;
11use crate::error::Result;
12use crate::events::EventList;
13use crate::info::{ParameterInfo, PluginInfo, PresetInfo};
14use crate::sample::Sample;
15use crate::transport::TransportInfo;
16
17/// Per-block context carrying host state into `process` and
18/// returning per-block side-channel data from it.
19///
20/// Plugins write outbound events (MIDI thru, parameter touches)
21/// into `output_events`; the wrapper drains them after the call
22/// returns. Hosts that don't care about outbound events pass an
23/// empty list and ignore whatever the plugin pushes.
24pub struct ProcessContext<'a> {
25    /// Sample rate active for this block. Plugins should
26    /// recompute coefficients when this changes between blocks
27    /// (rare but legal — host sample-rate change without a
28    /// full deactivate / activate cycle).
29    pub sample_rate: f64,
30    /// Maximum frames the plugin was prepared for. The buffer's
31    /// `num_frames()` may be less; never more.
32    pub max_block_size: usize,
33    /// Host transport snapshot for this block. `None` when the
34    /// host doesn't expose transport (most CLAP hosts via the
35    /// optional `clap.transport` extension only on hosts that
36    /// support it).
37    pub transport: Option<TransportInfo>,
38    /// Outbound event sink the plugin pushes parameter touches /
39    /// MIDI thru into. Cleared by the wrapper at the start of
40    /// each block.
41    pub output_events: &'a mut EventList,
42}
43
44/// Hint from the plugin about whether more output is coming.
45///
46/// Mirrors CLAP's `clap_process_status`. Hosts use the hint to
47/// decide whether to keep calling `process` on an idle channel.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum ProcessStatus {
50    /// Normal output — the plugin has more work in subsequent
51    /// blocks regardless of input. Default for live processing.
52    Continue,
53    /// The plugin has no output and won't produce any until
54    /// fresh input or events arrive. Host may skip `process`
55    /// calls until then.
56    Sleep,
57    /// Tail-out — the plugin will keep producing audio for
58    /// `tail_samples` more samples even with silent input
59    /// (reverb, delay).
60    Tail {
61        /// Remaining tail length in samples.
62        tail_samples: u32,
63    },
64    /// Hard error during processing. Wrapper logs and the host
65    /// should treat the block's output as garbage (silence is a
66    /// safer fallback for live audio).
67    Error,
68}
69
70/// Core sample-precision-erased interface every plugin exposes.
71///
72/// Methods that don't touch audio samples are here so a host can
73/// query metadata before deciding whether to instantiate as
74/// `Plugin<f32>` or `Plugin<f64>`. Mirrors truce's
75/// `PluginLogicCore` shape — the leaf [`Plugin<S>`] adds the
76/// sample-typed `process`.
77pub trait PluginCore: Send {
78    /// Plugin metadata as the wrapper scanned it.
79    fn info(&self) -> &PluginInfo;
80
81    /// The bus layout currently active. `None` until
82    /// [`PluginCore::activate`] picks one.
83    fn active_layout(&self) -> Option<&BusLayout>;
84
85    /// All bus layouts the plugin supports. The host picks one
86    /// and passes it to `activate`. Returned by reference into
87    /// internally-cached metadata; cheap to call repeatedly.
88    fn supported_layouts(&self) -> &[BusLayout];
89
90    /// Number of parameters this plugin exposes.
91    fn parameter_count(&self) -> usize;
92
93    /// Metadata for parameter at `index` (0-based into the
94    /// plugin's declared list).
95    ///
96    /// # Errors
97    /// Returns [`crate::Error::InvalidParameter`] when `index >=
98    /// parameter_count()`.
99    fn parameter_info(&self, index: usize) -> Result<ParameterInfo>;
100
101    /// Current value of parameter `index` in its native unit.
102    ///
103    /// # Errors
104    /// Returns [`crate::Error::InvalidParameter`] when out of
105    /// range or [`crate::Error::NotActivated`] when called before
106    /// `activate`.
107    fn parameter_value(&self, index: usize) -> Result<f64>;
108
109    /// Format the parameter value at `index` as the plugin
110    /// would render it in its own UI. Many formats supply this
111    /// directly (`clap_param_info_value_to_text`); others
112    /// require host-side formatting.
113    ///
114    /// # Errors
115    /// Returns [`crate::Error::InvalidParameter`] when `index` is
116    /// out of range or [`crate::Error::NotActivated`] when called
117    /// before `activate`.
118    fn parameter_value_string(&self, index: usize, value: f64) -> Result<String>;
119
120    /// Set parameter `index` to `value` in native units. Set
121    /// outside `process` (this is the host-thread setter); the
122    /// plugin may smooth toward the new value over subsequent
123    /// blocks.
124    ///
125    /// # Errors
126    /// Returns [`crate::Error::InvalidParameter`] when out of
127    /// range or [`crate::Error::NotActivated`] when called before
128    /// `activate`.
129    fn set_parameter(&mut self, index: usize, value: f64) -> Result<()>;
130
131    /// Number of factory presets, if the plugin exposes any.
132    fn preset_count(&self) -> usize;
133
134    /// Metadata for preset at `index`.
135    ///
136    /// # Errors
137    /// Returns [`crate::Error::InvalidParameter`] when out of
138    /// range.
139    fn preset_info(&self, index: usize) -> Result<PresetInfo>;
140
141    /// Load preset by the format-specific id from
142    /// [`PresetInfo::preset_number`].
143    ///
144    /// # Errors
145    /// Wrapper-specific — typically when the id is unknown.
146    fn load_preset(&mut self, preset_number: i32) -> Result<()>;
147
148    /// Snapshot plugin state to a byte blob. Wrap in
149    /// [`crate::StateEnvelope`] before persisting if the host
150    /// wants the version / format header.
151    ///
152    /// # Errors
153    /// Wrapper-specific.
154    fn save_state(&self) -> Result<Vec<u8>>;
155
156    /// Restore plugin state from bytes previously returned by
157    /// [`PluginCore::save_state`]. The host strips its own
158    /// envelope before calling this — the bytes here are
159    /// plugin-opaque.
160    ///
161    /// # Errors
162    /// Wrapper-specific.
163    fn load_state(&mut self, bytes: &[u8]) -> Result<()>;
164
165    /// Pick a bus layout and prepare the plugin for processing
166    /// at `sample_rate` with blocks up to `max_block_size`
167    /// frames.
168    ///
169    /// Hosts must call `activate` before any `process` call.
170    /// Subsequent reconfiguration (sample-rate change, layout
171    /// switch) requires a `deactivate` + `activate` cycle.
172    ///
173    /// # Errors
174    /// Wrapper-specific — typically when the requested layout
175    /// isn't in [`PluginCore::supported_layouts`].
176    fn activate(
177        &mut self,
178        layout: BusLayout,
179        sample_rate: f64,
180        max_block_size: usize,
181    ) -> Result<()>;
182
183    /// Tear down the active processing config. After this call
184    /// the plugin holds no per-activation resources and `process`
185    /// won't be called until the next `activate`.
186    fn deactivate(&mut self);
187
188    /// `true` when [`PluginCore::activate`] has been called and
189    /// [`PluginCore::deactivate`] hasn't been called since.
190    fn is_active(&self) -> bool;
191
192    /// Borrow the plugin's editor controller if the plugin
193    /// exposes a custom GUI. Returns `None` for headless plugins
194    /// or plugins whose editor extension is missing.
195    ///
196    /// The returned reference borrows `&mut self`, which means the
197    /// host can't call `process` (which also needs `&mut self`)
198    /// while holding it. That's the Rust-level enforcement of the
199    /// "audio thread vs UI thread" discipline.
200    fn editor(&mut self) -> Option<&mut dyn crate::editor::PluginEditor> {
201        None
202    }
203}
204
205/// Sample-precision-typed leaf trait. Pairs `PluginCore` with the
206/// `process` callback at a specific sample type.
207///
208/// Most format wrappers implement `Plugin<f32>`; ones that
209/// support host-chosen 64-bit (VST3, AU v2/v3, AAX) implement
210/// both `Plugin<f32>` and `Plugin<f64>` on the same instance
211/// type or on a precision-specialised wrapper.
212pub trait Plugin<S: Sample>: PluginCore {
213    /// Process one audio block.
214    ///
215    /// # Real-time-safety contract
216    ///
217    /// This callback runs on the host's audio thread. The
218    /// wrapper guarantees no allocator-touching work inside this
219    /// crate on the call edge; the *plugin* code itself is
220    /// expected to honor the same: no `Box::new`, no
221    /// `Vec::push` past pre-grown capacity, no mutex locking,
222    /// no I/O, no `panic!`. A panic is caught by the wrapper
223    /// (see [`crate::wrapper::run_audio_block_with`]) and turned
224    /// into [`ProcessStatus::Error`] but the host's block is
225    /// still lost.
226    ///
227    /// # Errors
228    /// Returns [`crate::Error::NotActivated`] when the plugin
229    /// hasn't been activated; wrapper-specific errors otherwise.
230    fn process(
231        &mut self,
232        buffer: &mut AudioBuffer<'_, S>,
233        events: &EventList,
234        context: &mut ProcessContext<'_>,
235    ) -> Result<ProcessStatus>;
236}