sim_lib_plugin_core/adapter.rs
1use sim_lib_audio_graph_core::{PrepareConfig, ProcessBlock, Processor};
2
3use crate::{PluginDescriptor, PluginState};
4
5/// A live, format-agnostic plugin instance the host can prepare and run.
6///
7/// Implementors are the format-specific backends (vst3/clap/lv2 and the native
8/// `sim` format). The trait pairs a static [`PluginDescriptor`] with the
9/// mutable, real-time processing entry points shared by every backend and is
10/// `Send` so instances can move between threads.
11pub trait PluginInstance: Send {
12 /// Returns the descriptor that identifies this instance and its port and
13 /// parameter layout.
14 fn descriptor(&self) -> &PluginDescriptor;
15
16 /// Captures the instance's current persistable state.
17 ///
18 /// The default returns an empty [`PluginState`]; backends that carry
19 /// parameter or opaque data override this.
20 fn state(&self) -> PluginState {
21 PluginState::new()
22 }
23
24 /// Restores the instance from a captured [`PluginState`].
25 ///
26 /// The default ignores the state; stateful backends override this.
27 fn set_state(&mut self, _state: PluginState) {}
28
29 /// Prepares the instance for processing under the given configuration.
30 fn prepare(&mut self, cfg: PrepareConfig);
31
32 /// Clears any internal processing state without releasing resources.
33 fn reset(&mut self);
34
35 /// Processes one audio block in place.
36 fn process(&mut self, block: &mut ProcessBlock<'_>);
37
38 /// Returns and clears the last backend error hidden behind a trait method
39 /// that cannot return [`sim_kernel::Result`].
40 ///
41 /// The default reports no latent error. Fallible backends override this so
42 /// hosts using the trait path can audit failures after `process`,
43 /// `set_state`, or other non-`Result` entry points.
44 fn take_last_error(&mut self) -> Option<String> {
45 None
46 }
47
48 /// Returns the instance's reported latency in frames.
49 ///
50 /// The default reports the descriptor's [`PluginDescriptor::latency_frames`].
51 fn latency_frames(&self) -> u32 {
52 self.descriptor().latency_frames
53 }
54}
55
56/// Adapts a [`PluginInstance`] into an audio-graph [`Processor`].
57///
58/// The wrapper forwards prepare/reset/process to the held instance and maps the
59/// instance's reported latency onto the graph's tail-frame contract.
60#[derive(Clone, Debug)]
61pub struct HostedPluginProcessor<I> {
62 instance: I,
63}
64
65impl<I> HostedPluginProcessor<I> {
66 /// Wraps an instance so it can be inserted into an audio graph.
67 pub fn new(instance: I) -> Self {
68 Self { instance }
69 }
70
71 /// Returns a shared reference to the wrapped instance.
72 pub fn instance(&self) -> &I {
73 &self.instance
74 }
75
76 /// Returns a mutable reference to the wrapped instance.
77 pub fn instance_mut(&mut self) -> &mut I {
78 &mut self.instance
79 }
80
81 /// Consumes the wrapper and returns the wrapped instance.
82 pub fn into_inner(self) -> I {
83 self.instance
84 }
85}
86
87impl<I: PluginInstance> Processor for HostedPluginProcessor<I> {
88 fn prepare(&mut self, cfg: PrepareConfig) {
89 self.instance.prepare(cfg);
90 }
91
92 fn reset(&mut self) {
93 self.instance.reset();
94 }
95
96 fn process(&mut self, block: &mut ProcessBlock<'_>) {
97 self.instance.process(block);
98 }
99
100 fn tail_frames(&self) -> u64 {
101 u64::from(self.instance.latency_frames())
102 }
103}
104
105/// Presents an audio-graph [`Processor`] as a [`PluginInstance`].
106///
107/// This is the inverse adapter of [`HostedPluginProcessor`]: it pairs a bare
108/// processor with a descriptor and a held [`PluginState`], letting any
109/// processor be hosted as a native (`sim`-format) plugin. State is stored on the
110/// wrapper rather than pushed into the processor.
111#[derive(Clone, Debug)]
112pub struct ProcessorPlugin<P> {
113 descriptor: PluginDescriptor,
114 processor: P,
115 state: PluginState,
116}
117
118impl<P> ProcessorPlugin<P> {
119 /// Pairs a descriptor with a processor, starting from empty state.
120 pub fn new(descriptor: PluginDescriptor, processor: P) -> Self {
121 Self {
122 descriptor,
123 processor,
124 state: PluginState::new(),
125 }
126 }
127
128 /// Returns a shared reference to the wrapped processor.
129 pub fn processor(&self) -> &P {
130 &self.processor
131 }
132
133 /// Returns a mutable reference to the wrapped processor.
134 pub fn processor_mut(&mut self) -> &mut P {
135 &mut self.processor
136 }
137
138 /// Consumes the wrapper and returns the wrapped processor.
139 pub fn into_processor(self) -> P {
140 self.processor
141 }
142}
143
144impl<P: Processor> PluginInstance for ProcessorPlugin<P> {
145 fn descriptor(&self) -> &PluginDescriptor {
146 &self.descriptor
147 }
148
149 fn state(&self) -> PluginState {
150 self.state.clone()
151 }
152
153 fn set_state(&mut self, state: PluginState) {
154 self.state = state;
155 }
156
157 fn prepare(&mut self, cfg: PrepareConfig) {
158 self.processor.prepare(cfg);
159 }
160
161 fn reset(&mut self) {
162 self.processor.reset();
163 }
164
165 fn process(&mut self, block: &mut ProcessBlock<'_>) {
166 self.processor.process(block);
167 }
168}
169
170/// Implement [`PluginInstance`] for a `$ty<P>` newtype whose only relevant field
171/// is `inner: ProcessorPlugin<P>`, forwarding all six methods to it. The clap,
172/// lv2, and vst3 exported-processor adapters shared this forward block verbatim
173/// Call it at each adapter, where `Processor`, `ProcessBlock`, and
174/// `PrepareConfig` (from `sim_lib_audio_graph_core`) and the plugin-core trait
175/// types are already in scope.
176#[macro_export]
177macro_rules! forward_plugin_instance {
178 ($ty:ident) => {
179 impl<P: Processor> PluginInstance for $ty<P> {
180 fn descriptor(&self) -> &PluginDescriptor {
181 self.inner.descriptor()
182 }
183
184 fn state(&self) -> PluginState {
185 self.inner.state()
186 }
187
188 fn set_state(&mut self, state: PluginState) {
189 self.inner.set_state(state);
190 }
191
192 fn prepare(&mut self, cfg: PrepareConfig) {
193 self.inner.prepare(cfg);
194 }
195
196 fn reset(&mut self) {
197 self.inner.reset();
198 }
199
200 fn process(&mut self, block: &mut ProcessBlock<'_>) {
201 self.inner.process(block);
202 }
203
204 fn take_last_error(&mut self) -> Option<String> {
205 self.inner.take_last_error()
206 }
207 }
208 };
209}