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 previously 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 the instance's reported latency in frames.
39 ///
40 /// The default reports the descriptor's [`PluginDescriptor::latency_frames`].
41 fn latency_frames(&self) -> u32 {
42 self.descriptor().latency_frames
43 }
44}
45
46/// Adapts a [`PluginInstance`] into an audio-graph [`Processor`].
47///
48/// The wrapper forwards prepare/reset/process to the held instance and maps the
49/// instance's reported latency onto the graph's tail-frame contract.
50#[derive(Clone, Debug)]
51pub struct HostedPluginProcessor<I> {
52 instance: I,
53}
54
55impl<I> HostedPluginProcessor<I> {
56 /// Wraps an instance so it can be inserted into an audio graph.
57 pub fn new(instance: I) -> Self {
58 Self { instance }
59 }
60
61 /// Returns a shared reference to the wrapped instance.
62 pub fn instance(&self) -> &I {
63 &self.instance
64 }
65
66 /// Returns a mutable reference to the wrapped instance.
67 pub fn instance_mut(&mut self) -> &mut I {
68 &mut self.instance
69 }
70
71 /// Consumes the wrapper and returns the wrapped instance.
72 pub fn into_inner(self) -> I {
73 self.instance
74 }
75}
76
77impl<I: PluginInstance> Processor for HostedPluginProcessor<I> {
78 fn prepare(&mut self, cfg: PrepareConfig) {
79 self.instance.prepare(cfg);
80 }
81
82 fn reset(&mut self) {
83 self.instance.reset();
84 }
85
86 fn process(&mut self, block: &mut ProcessBlock<'_>) {
87 self.instance.process(block);
88 }
89
90 fn tail_frames(&self) -> u64 {
91 u64::from(self.instance.latency_frames())
92 }
93}
94
95/// Presents an audio-graph [`Processor`] as a [`PluginInstance`].
96///
97/// This is the inverse adapter of [`HostedPluginProcessor`]: it pairs a bare
98/// processor with a descriptor and a held [`PluginState`], letting any
99/// processor be hosted as a native (`sim`-format) plugin. State is stored on the
100/// wrapper rather than pushed into the processor.
101#[derive(Clone, Debug)]
102pub struct ProcessorPlugin<P> {
103 descriptor: PluginDescriptor,
104 processor: P,
105 state: PluginState,
106}
107
108impl<P> ProcessorPlugin<P> {
109 /// Pairs a descriptor with a processor, starting from empty state.
110 pub fn new(descriptor: PluginDescriptor, processor: P) -> Self {
111 Self {
112 descriptor,
113 processor,
114 state: PluginState::new(),
115 }
116 }
117
118 /// Returns a shared reference to the wrapped processor.
119 pub fn processor(&self) -> &P {
120 &self.processor
121 }
122
123 /// Returns a mutable reference to the wrapped processor.
124 pub fn processor_mut(&mut self) -> &mut P {
125 &mut self.processor
126 }
127
128 /// Consumes the wrapper and returns the wrapped processor.
129 pub fn into_processor(self) -> P {
130 self.processor
131 }
132}
133
134impl<P: Processor> PluginInstance for ProcessorPlugin<P> {
135 fn descriptor(&self) -> &PluginDescriptor {
136 &self.descriptor
137 }
138
139 fn state(&self) -> PluginState {
140 self.state.clone()
141 }
142
143 fn set_state(&mut self, state: PluginState) {
144 self.state = state;
145 }
146
147 fn prepare(&mut self, cfg: PrepareConfig) {
148 self.processor.prepare(cfg);
149 }
150
151 fn reset(&mut self) {
152 self.processor.reset();
153 }
154
155 fn process(&mut self, block: &mut ProcessBlock<'_>) {
156 self.processor.process(block);
157 }
158}
159
160/// Implement [`PluginInstance`] for a `$ty<P>` newtype whose only relevant field
161/// is `inner: ProcessorPlugin<P>`, forwarding all six methods to it. The clap,
162/// lv2, and vst3 exported-processor adapters shared this forward block verbatim
163/// before OVERLAP6.15. Call it at each adapter, where `Processor`, `ProcessBlock`,
164/// and `PrepareConfig` (from `sim_lib_audio_graph_core`) and the plugin-core
165/// trait types are already in scope.
166#[macro_export]
167macro_rules! forward_plugin_instance {
168 ($ty:ident) => {
169 impl<P: Processor> PluginInstance for $ty<P> {
170 fn descriptor(&self) -> &PluginDescriptor {
171 self.inner.descriptor()
172 }
173
174 fn state(&self) -> PluginState {
175 self.inner.state()
176 }
177
178 fn set_state(&mut self, state: PluginState) {
179 self.inner.set_state(state);
180 }
181
182 fn prepare(&mut self, cfg: PrepareConfig) {
183 self.inner.prepare(cfg);
184 }
185
186 fn reset(&mut self) {
187 self.inner.reset();
188 }
189
190 fn process(&mut self, block: &mut ProcessBlock<'_>) {
191 self.inner.process(block);
192 }
193 }
194 };
195}