1use std::sync::Arc;
7
8use truce_core::buffer::AudioBuffer;
9use truce_core::bus::BusLayout;
10use truce_core::events::{EventBody, EventList};
11use truce_core::info::PluginInfo;
12use truce_core::meters::MeterStore;
13use truce_core::plugin::PluginRuntime;
14use truce_core::process::{ProcessContext, ProcessStatus};
15use truce_core::snapshot::SnapshotSlot;
16use truce_core::state::{ForeignState, MigratedState, StateLoadError};
17use truce_params::Params;
18use truce_params::sample::Sample;
19use truce_plugin::PluginLogicCore;
20
21pub struct StaticShell<P: Params, L: PluginLogicCore<S>, S: Sample = f32> {
31 pub params: Arc<P>,
32 logic: L,
33 meters: Arc<MeterStore>,
34 snapshots: Arc<SnapshotSlot>,
36 try_snapshot: bool,
41 sample_rate: f64,
42 _sample: std::marker::PhantomData<fn() -> S>,
43}
44
45unsafe impl<P: Params, L: PluginLogicCore<S>, S: Sample> Send for StaticShell<P, L, S> {}
55
56impl<P: Params + Default + 'static, L: PluginLogicCore<S> + 'static, S: Sample>
57 StaticShell<P, L, S>
58{
59 pub fn from_parts(params: Arc<P>, logic: L) -> Self {
62 Self {
63 params,
64 logic,
65 meters: MeterStore::new(),
66 snapshots: SnapshotSlot::new(),
67 try_snapshot: true,
68 sample_rate: 44100.0,
69 _sample: std::marker::PhantomData,
70 }
71 }
72
73 pub fn meter_store(&self) -> Arc<MeterStore> {
76 Arc::clone(&self.meters)
77 }
78
79 pub fn snapshot_slot(&self) -> Arc<SnapshotSlot> {
82 Arc::clone(&self.snapshots)
83 }
84
85 pub fn logic_ref(&self) -> &L {
87 &self.logic
88 }
89
90 pub fn logic_ref_mut(&mut self) -> &mut L {
92 &mut self.logic
93 }
94}
95
96impl<P: Params + Default + 'static, L: PluginLogicCore<S> + 'static, S: Sample> PluginRuntime
97 for StaticShell<P, L, S>
98{
99 type Sample = S;
100
101 fn info() -> PluginInfo
102 where
103 Self: Sized,
104 {
105 unreachable!("StaticShell::info() should not be called statically")
106 }
107
108 fn bus_layouts() -> Vec<BusLayout>
109 where
110 Self: Sized,
111 {
112 unreachable!("StaticShell::bus_layouts() should not be called statically")
113 }
114
115 fn init(&mut self) {}
116
117 fn reset(&mut self, sample_rate: f64, max_block_size: usize) {
118 self.sample_rate = sample_rate;
119 self.params.set_sample_rate(sample_rate);
120 self.logic.reset(sample_rate, max_block_size);
121 }
122
123 fn process(
124 &mut self,
125 buffer: &mut AudioBuffer<S>,
126 events: &EventList,
127 context: &mut ProcessContext,
128 ) -> ProcessStatus {
129 for e in events.iter() {
133 if let EventBody::ParamChange { id, value } = &e.body {
134 self.params.set_plain(*id, *value);
135 }
136 }
137
138 let params = &self.params;
142 let meters = &self.meters;
143 let param_fn = |id: u32| -> f64 { params.get_plain(id).unwrap_or(0.0) };
144 let meter_fn = |id: u32, v: f32| meters.write(id, v);
145 let mut ctx = ProcessContext::new(
146 context.transport,
147 context.sample_rate,
148 buffer.num_samples(),
149 &mut *context.output_events,
150 )
151 .with_params(¶m_fn)
152 .with_meters(&meter_fn);
153
154 let status = self.logic.process(buffer, events, &mut ctx);
155 publish_snapshot(&self.logic, &self.snapshots, &mut self.try_snapshot);
156 status
157 }
158
159 fn save_state(&self) -> Vec<u8> {
160 self.logic.save_state()
161 }
162
163 fn load_state(&mut self, data: &[u8]) -> Result<(), StateLoadError> {
164 let result = self.logic.load_state(data);
165 PluginLogicCore::state_changed(&mut self.logic);
170 result
171 }
172
173 fn migrate_state(foreign: &ForeignState) -> Option<MigratedState>
174 where
175 Self: Sized,
176 {
177 <L as PluginLogicCore<S>>::migrate_state(foreign)
178 }
179
180 fn latency(&self) -> u32 {
181 self.logic.latency()
182 }
183 fn tail(&self) -> u32 {
184 self.logic.tail()
185 }
186
187 fn get_meter(&self, meter_id: u32) -> f32 {
188 self.meters.read(meter_id)
189 }
190}
191
192pub(crate) fn publish_snapshot<S, L>(logic: &L, slot: &SnapshotSlot, try_snapshot: &mut bool)
205where
206 S: Sample,
207 L: PluginLogicCore<S> + ?Sized,
208{
209 publish_snapshot_with(slot, try_snapshot, |buf| logic.snapshot_into(buf));
210}
211
212fn publish_snapshot_with(
216 slot: &SnapshotSlot,
217 try_snapshot: &mut bool,
218 snapshot_into: impl FnOnce(&mut Vec<u8>) -> bool,
219) {
220 if !*try_snapshot {
221 return;
222 }
223 let ran_unsupported = std::cell::Cell::new(false);
224 slot.publish(|buf| {
225 let wrote = snapshot_into(buf);
226 ran_unsupported.set(!wrote);
227 wrote
228 });
229 if ran_unsupported.get() && !slot.is_supported() {
232 *try_snapshot = false;
233 }
234}
235
236#[macro_export]
259macro_rules! export_static {
260 (
261 params: $params:ty,
262 info: $info:expr,
263 logic: $logic:ty,
264 ) => {
265 pub struct __HotShellWrapper {
266 inner: $crate::static_shell::StaticShell<$params, $logic, Sample>,
273 }
274
275 impl $crate::__macro_deps::truce_core::plugin::PluginRuntime for __HotShellWrapper {
276 type Sample = Sample;
277
278 fn supports_in_place() -> bool
279 where
280 Self: Sized,
281 {
282 <$logic as $crate::__macro_deps::truce_plugin::PluginLogicCore<Sample>>::supports_in_place()
291 }
292
293 fn info() -> $crate::__macro_deps::truce_core::info::PluginInfo
294 where
295 Self: Sized,
296 {
297 $info
298 }
299
300 fn bus_layouts() -> Vec<$crate::__macro_deps::truce_core::bus::BusLayout>
301 where
302 Self: Sized,
303 {
304 <$logic as $crate::__macro_deps::truce_plugin::PluginLogicCore<Sample>>::bus_layouts()
305 }
306
307 fn init(&mut self) {
308 self.inner.init();
309 }
310
311 fn reset(&mut self, sample_rate: f64, max_block_size: usize) {
312 self.inner.reset(sample_rate, max_block_size);
313 }
314
315 fn process(
316 &mut self,
317 buffer: &mut $crate::__macro_deps::truce_core::buffer::AudioBuffer<Sample>,
318 events: &$crate::__macro_deps::truce_core::events::EventList,
319 context: &mut $crate::__macro_deps::truce_core::process::ProcessContext,
320 ) -> $crate::__macro_deps::truce_core::process::ProcessStatus {
321 self.inner.process(buffer, events, context)
322 }
323
324 fn save_state(&self) -> Vec<u8> {
325 self.inner.save_state()
326 }
327
328 fn load_state(
329 &mut self,
330 data: &[u8],
331 ) -> Result<(), $crate::__macro_deps::truce_core::state::StateLoadError> {
332 self.inner.load_state(data)
333 }
334
335 fn migrate_state(
336 foreign: &$crate::__macro_deps::truce_core::state::ForeignState,
337 ) -> Option<$crate::__macro_deps::truce_core::state::MigratedState>
338 where
339 Self: Sized,
340 {
341 <$logic as $crate::__macro_deps::truce_plugin::PluginLogicCore<Sample>>::migrate_state(foreign)
342 }
343
344 fn latency(&self) -> u32 {
345 self.inner.latency()
346 }
347 fn tail(&self) -> u32 {
348 self.inner.tail()
349 }
350 fn get_meter(&self, meter_id: u32) -> f32 {
351 self.inner.get_meter(meter_id)
352 }
353 }
354
355 impl $crate::__macro_deps::truce_core::export::PluginExport for __HotShellWrapper {
356 type Params = $params;
357
358 fn create() -> Self {
359 let params = std::sync::Arc::new(<$params>::new());
360 let logic = <$logic>::new(std::sync::Arc::clone(¶ms));
361 Self {
362 inner: $crate::static_shell::StaticShell::from_parts(params, logic),
363 }
364 }
365
366 fn params(&self) -> &$params {
367 &self.inner.params
368 }
369
370 fn params_arc(&self) -> std::sync::Arc<$params> {
371 std::sync::Arc::clone(&self.inner.params)
372 }
373
374 fn meter_store(
375 &self,
376 ) -> std::sync::Arc<$crate::__macro_deps::truce_core::meters::MeterStore> {
377 self.inner.meter_store()
378 }
379
380 fn snapshot_slot(
381 &self,
382 ) -> std::sync::Arc<$crate::__macro_deps::truce_core::snapshot::SnapshotSlot> {
383 self.inner.snapshot_slot()
384 }
385
386 fn editor_builder(
387 &self,
388 ) -> $crate::__macro_deps::truce_core::editor::EditorBuilder<$params> {
389 Box::new(|params| {
393 Some(
394 <$logic as $crate::__macro_deps::truce_plugin::PluginEditor<Sample>>::editor(
395 params,
396 ),
397 )
398 })
399 }
400 }
401 };
402}
403
404#[cfg(test)]
405mod tests {
406 use super::publish_snapshot_with;
407 use truce_core::snapshot::SnapshotSlot;
408
409 #[test]
410 fn non_opt_in_latches_off_on_first_block() {
411 let slot = SnapshotSlot::new();
412 let mut try_snapshot = true;
413
414 publish_snapshot_with(&slot, &mut try_snapshot, |_| false);
417 assert!(!try_snapshot, "first false must latch off");
418 assert!(!slot.is_supported());
419
420 let mut called = false;
422 publish_snapshot_with(&slot, &mut try_snapshot, |_| {
423 called = true;
424 false
425 });
426 assert!(!called, "latched-off slot must not call snapshot_into");
427 }
428
429 #[test]
430 fn opt_in_then_contract_violation_stays_subscribed() {
431 let slot = SnapshotSlot::new();
432 let mut try_snapshot = true;
433
434 publish_snapshot_with(&slot, &mut try_snapshot, |buf| {
436 buf.clear();
437 buf.extend_from_slice(&[1, 2, 3]);
438 true
439 });
440 assert!(try_snapshot);
441 assert!(slot.is_supported());
442 assert_eq!(slot.read(), Some(vec![1, 2, 3]));
443
444 publish_snapshot_with(&slot, &mut try_snapshot, |_| false);
447 assert!(try_snapshot, "a post-opt-in false must not latch off");
448
449 publish_snapshot_with(&slot, &mut try_snapshot, |buf| {
451 buf.clear();
452 buf.extend_from_slice(&[4]);
453 true
454 });
455 assert_eq!(slot.read(), Some(vec![4]));
456 }
457}