truce_core/plugin.rs
1use crate::buffer::AudioBuffer;
2use crate::bus::BusLayout;
3use crate::editor::Editor;
4use crate::events::EventList;
5use crate::info::PluginInfo;
6use crate::process::{ProcessContext, ProcessStatus};
7use truce_params::sample::Sample;
8
9/// The format-facing plugin runtime trait. **Plugin authors do NOT
10/// implement this directly.**
11///
12/// `PluginRuntime` is the surface every format wrapper (CLAP, VST3,
13/// VST2, LV2, AU, AAX) consumes. The `truce::plugin!` macro generates
14/// an `impl PluginRuntime for __HotShellWrapper` from the user's
15/// `truce_plugin::PluginLogic` impl, bridging the user-facing trait
16/// into this GUI-free format-wrapper surface so `truce-core` doesn't
17/// pull in `truce-gui` types.
18///
19/// What plugin authors implement instead:
20///
21/// ```ignore
22/// impl truce::prelude::PluginLogic for MyPlugin {
23/// fn reset(&mut self, sr: f64, bs: usize) { /* ... */ }
24/// fn process(&mut self, /* ... */) -> ProcessStatus { /* ... */ }
25/// fn editor(&self) -> Box<dyn Editor> { /* ... */ }
26/// }
27///
28/// truce::plugin! { logic: MyPlugin, params: MyPluginParams }
29/// ```
30///
31/// The macro-emitted `impl PluginRuntime` routes each method directly
32/// to the user's impl.
33pub trait PluginRuntime: Send + 'static {
34 /// The plugin's chosen audio sample precision. Either `f32` (the
35 /// default - matches host wire format for nearly all formats) or
36 /// `f64` (for plugins whose DSP path runs in `f64` end-to-end:
37 /// high-order biquads, oscillator phase accumulators, long-running
38 /// cumulative state).
39 ///
40 /// The format wrapper bridges between host buffer precision and
41 /// `Self::Sample` at the block boundary - so the plugin's
42 /// `process()` always receives `AudioBuffer<Self::Sample>`
43 /// regardless of what the host sent. See
44 /// `truce_core::RawBufferScratch` for the conversion machinery.
45 ///
46 /// Drive this from the prelude: `truce::prelude` / `truce::prelude32`
47 /// implies `f32`, `truce::prelude64` implies `f64`. The
48 /// `truce::plugin!` macro emits `type Sample = …;` based on
49 /// which prelude is in scope at the macro call site.
50 type Sample: Sample;
51
52 /// Opt into zero-copy in-place I/O. When this returns `true`,
53 /// the format wrapper skips its safety memcpy on host-aliased
54 /// buffers and hands the plugin the raw shared memory through
55 /// `AudioBuffer::in_out_mut(ch)`. The plugin must check
56 /// `AudioBuffer::is_in_place(ch)` per channel before reading
57 /// `input(ch)` - for in-place channels `input(ch)` returns an
58 /// empty slice, and the data lives only in the shared buffer.
59 ///
60 /// Default `false`: the wrapper copies aliased inputs into scratch
61 /// so `input(ch)` and `output(ch)` are always disjoint. Costs one
62 /// memcpy per aliased channel per block (a few hundred KB/sec at
63 /// audio rates) and lets plugin code stay format-agnostic.
64 ///
65 /// `where Self: Sized` so a `dyn PluginRuntime` trait object stays
66 /// dyn-compatible - the format wrappers consume `P: PluginRuntime`
67 /// generically and call the method statically.
68 #[must_use]
69 fn supports_in_place() -> bool
70 where
71 Self: Sized,
72 {
73 false
74 }
75
76 /// Static metadata about the plugin.
77 ///
78 /// Use `plugin_info!()` for zero-boilerplate (reads from truce.toml
79 /// + Cargo.toml at compile time - no `build.rs` required).
80 fn info() -> PluginInfo
81 where
82 Self: Sized;
83
84 /// Supported bus layouts. The host picks one.
85 #[must_use]
86 fn bus_layouts() -> Vec<BusLayout>
87 where
88 Self: Sized,
89 {
90 vec![BusLayout::stereo()]
91 }
92
93 /// Called once after construction. Not real-time safe.
94 fn init(&mut self) {}
95
96 /// Called when sample rate or max block size changes.
97 /// Reset filters, delay lines, etc. Not real-time safe.
98 fn reset(&mut self, sample_rate: f64, max_block_size: usize);
99
100 /// Real-time audio processing.
101 fn process(
102 &mut self,
103 buffer: &mut AudioBuffer<Self::Sample>,
104 events: &EventList,
105 context: &mut ProcessContext,
106 ) -> ProcessStatus;
107
108 /// Save extra state beyond parameter values. Empty `Vec` means
109 /// "no extra state": matches the user-facing
110 /// `truce_plugin::PluginLogic::save_state` shape so the wrapper
111 /// bridge is a passthrough rather than an `Option<Vec<u8>>` to
112 /// `Vec<u8>` translation.
113 fn save_state(&self) -> Vec<u8> {
114 Vec::new()
115 }
116
117 /// Restore extra state. Matches the user-facing
118 /// `truce_plugin::PluginLogic::load_state` `Result` shape so the
119 /// wrapper bridge is a passthrough.
120 ///
121 /// # Errors
122 ///
123 /// Returns `Err` when the macro-generated impl forwards a
124 /// `PluginLogic::load_state` failure (malformed bytes, version
125 /// skew between session file and plugin build, etc).
126 fn load_state(&mut self, _data: &[u8]) -> Result<(), crate::state::StateLoadError> {
127 Ok(())
128 }
129
130 /// GUI editor. Return None for headless plugins.
131 fn editor(&mut self) -> Option<Box<dyn Editor>> {
132 None
133 }
134
135 /// Processing latency in samples. Host uses this for delay compensation.
136 /// Return 0 if the plugin adds no latency (default).
137 fn latency(&self) -> u32 {
138 0
139 }
140
141 /// Tail time in samples. Return `u32::MAX` for infinite tail.
142 /// Return 0 for no tail (default).
143 fn tail(&self) -> u32 {
144 0
145 }
146
147 /// Read a meter value by ID (0.0–1.0). Called by the GUI at ~60fps.
148 /// Override to expose level meters, gain reduction, etc.
149 fn get_meter(&self, _meter_id: u32) -> f32 {
150 0.0
151 }
152}