1use std::sync::Arc;
7
8use truce_core::buffer::AudioBuffer;
9use truce_core::bus::BusLayout;
10use truce_core::config::AudioConfig;
11use truce_core::events::{EventBody, EventList};
12use truce_core::info::PluginInfo;
13use truce_core::meters::MeterStore;
14use truce_core::plugin::PluginRuntime;
15use truce_core::process::{ProcessContext, ProcessStatus};
16use truce_core::snapshot::SnapshotSlot;
17use truce_core::state::{ForeignState, MigratedState, StateLoadError};
18use truce_params::Params;
19use truce_params::sample::Sample;
20use truce_plugin::PluginLogicCore;
21
22pub struct StaticShell<P: Params, L: PluginLogicCore<S, Params = P>, S: Sample = f32> {
32 pub params: Arc<P>,
33 state: L::DspState,
36 meters: Arc<MeterStore>,
37 snapshots: Arc<SnapshotSlot>,
39 try_snapshot: bool,
44 sample_rate: f64,
45 _sample: std::marker::PhantomData<fn() -> S>,
46}
47
48unsafe impl<P: Params, L: PluginLogicCore<S, Params = P>, S: Sample> Send for StaticShell<P, L, S> {}
58
59impl<P: Params + Default + 'static, L: PluginLogicCore<S, Params = P> + 'static, S: Sample>
60 StaticShell<P, L, S>
61{
62 pub fn from_parts(params: Arc<P>) -> Self {
66 let state = L::init(¶ms);
67 Self {
68 params,
69 state,
70 meters: MeterStore::new(),
71 snapshots: SnapshotSlot::new(),
72 try_snapshot: true,
73 sample_rate: 44100.0,
74 _sample: std::marker::PhantomData,
75 }
76 }
77
78 pub fn meter_store(&self) -> Arc<MeterStore> {
81 Arc::clone(&self.meters)
82 }
83
84 pub fn snapshot_slot(&self) -> Arc<SnapshotSlot> {
87 Arc::clone(&self.snapshots)
88 }
89
90 pub fn state_ref(&self) -> &L::DspState {
92 &self.state
93 }
94
95 pub fn state_ref_mut(&mut self) -> &mut L::DspState {
97 &mut self.state
98 }
99}
100
101impl<P: Params + Default + 'static, L: PluginLogicCore<S, Params = P> + 'static, S: Sample>
102 PluginRuntime for StaticShell<P, L, S>
103{
104 type Sample = S;
105
106 fn info() -> PluginInfo
107 where
108 Self: Sized,
109 {
110 unreachable!("StaticShell::info() should not be called statically")
111 }
112
113 fn bus_layouts() -> Vec<BusLayout>
114 where
115 Self: Sized,
116 {
117 unreachable!("StaticShell::bus_layouts() should not be called statically")
118 }
119
120 fn init(&mut self) {}
121
122 fn reset(&mut self, config: &AudioConfig) {
123 self.sample_rate = config.sample_rate;
124 self.params.set_sample_rate(config.sample_rate);
128 self.params.snap_smoothers();
129 L::reset(&mut self.state, &self.params, config);
130 }
131
132 fn process(
133 &mut self,
134 buffer: &mut AudioBuffer<S>,
135 events: &EventList,
136 context: &mut ProcessContext,
137 ) -> ProcessStatus {
138 for e in events.iter() {
142 if let EventBody::ParamChange { id, value } = &e.body {
143 self.params.set_plain(*id, *value);
144 }
145 }
146
147 let params = &self.params;
151 let meters = &self.meters;
152 let param_fn = |id: u32| -> f64 { params.get_plain(id).unwrap_or(0.0) };
153 let meter_fn = |id: u32, v: f32| meters.write(id, v);
154 let mut ctx = ProcessContext::new(
155 context.transport,
156 context.sample_rate,
157 buffer.num_samples(),
158 &mut *context.output_events,
159 )
160 .with_process_mode(context.process_mode)
161 .with_params(¶m_fn)
162 .with_meters(&meter_fn);
163
164 let status = L::process(&mut self.state, &self.params, buffer, events, &mut ctx);
165 publish_snapshot::<S, L>(&self.state, &self.snapshots, &mut self.try_snapshot);
166 status
167 }
168
169 fn save_state(&self) -> Vec<u8> {
170 L::save_state(&self.state)
171 }
172
173 fn load_state(&mut self, data: &[u8]) -> Result<(), StateLoadError> {
174 let result = L::load_state(&mut self.state, data);
175 L::state_changed(&mut self.state, &self.params);
180 result
181 }
182
183 fn migrate_state(foreign: &ForeignState) -> Option<MigratedState>
184 where
185 Self: Sized,
186 {
187 <L as PluginLogicCore<S>>::migrate_state(foreign)
188 }
189
190 fn latency(&self) -> u32 {
191 L::latency(&self.state)
192 }
193 fn tail(&self) -> u32 {
194 L::tail(&self.state)
195 }
196
197 fn get_meter(&self, meter_id: u32) -> f32 {
198 self.meters.read(meter_id)
199 }
200}
201
202pub(crate) fn publish_snapshot<S, L>(
215 state: &L::DspState,
216 slot: &SnapshotSlot,
217 try_snapshot: &mut bool,
218) where
219 S: Sample,
220 L: PluginLogicCore<S>,
221{
222 publish_snapshot_with(slot, try_snapshot, |buf| L::snapshot_into(state, buf));
223}
224
225pub(crate) fn publish_snapshot_with(
230 slot: &SnapshotSlot,
231 try_snapshot: &mut bool,
232 snapshot_into: impl FnOnce(&mut Vec<u8>) -> bool,
233) {
234 if !*try_snapshot {
235 return;
236 }
237 let ran_unsupported = std::cell::Cell::new(false);
238 slot.publish(|buf| {
239 let wrote = snapshot_into(buf);
240 ran_unsupported.set(!wrote);
241 wrote
242 });
243 if ran_unsupported.get() && !slot.is_supported() {
246 *try_snapshot = false;
247 }
248}
249
250#[macro_export]
273macro_rules! export_static {
274 (
275 params: $params:ty,
276 info: $info:expr,
277 logic: $logic:ty,
278 ) => {
279 pub struct __HotShellWrapper {
280 inner: $crate::static_shell::StaticShell<$params, $logic, Sample>,
287 }
288
289 impl $crate::__macro_deps::truce_core::plugin::PluginRuntime for __HotShellWrapper {
290 type Sample = Sample;
291
292 fn supports_in_place() -> bool
293 where
294 Self: Sized,
295 {
296 <$logic as $crate::__macro_deps::truce_plugin::PluginLogicCore<Sample>>::supports_in_place()
305 }
306
307 fn info() -> $crate::__macro_deps::truce_core::info::PluginInfo
308 where
309 Self: Sized,
310 {
311 $info
312 }
313
314 fn bus_layouts() -> Vec<$crate::__macro_deps::truce_core::bus::BusLayout>
315 where
316 Self: Sized,
317 {
318 <$logic as $crate::__macro_deps::truce_plugin::PluginLogicCore<Sample>>::bus_layouts()
319 }
320
321 fn init(&mut self) {
322 self.inner.init();
323 }
324
325 fn reset(&mut self, config: &$crate::__macro_deps::truce_core::config::AudioConfig) {
326 self.inner.reset(config);
327 }
328
329 fn process(
330 &mut self,
331 buffer: &mut $crate::__macro_deps::truce_core::buffer::AudioBuffer<Sample>,
332 events: &$crate::__macro_deps::truce_core::events::EventList,
333 context: &mut $crate::__macro_deps::truce_core::process::ProcessContext,
334 ) -> $crate::__macro_deps::truce_core::process::ProcessStatus {
335 self.inner.process(buffer, events, context)
336 }
337
338 fn save_state(&self) -> Vec<u8> {
339 self.inner.save_state()
340 }
341
342 fn load_state(
343 &mut self,
344 data: &[u8],
345 ) -> Result<(), $crate::__macro_deps::truce_core::state::StateLoadError> {
346 self.inner.load_state(data)
347 }
348
349 fn migrate_state(
350 foreign: &$crate::__macro_deps::truce_core::state::ForeignState,
351 ) -> Option<$crate::__macro_deps::truce_core::state::MigratedState>
352 where
353 Self: Sized,
354 {
355 <$logic as $crate::__macro_deps::truce_plugin::PluginLogicCore<Sample>>::migrate_state(foreign)
356 }
357
358 fn latency(&self) -> u32 {
359 self.inner.latency()
360 }
361 fn tail(&self) -> u32 {
362 self.inner.tail()
363 }
364 fn get_meter(&self, meter_id: u32) -> f32 {
365 self.inner.get_meter(meter_id)
366 }
367 }
368
369 impl $crate::__macro_deps::truce_core::export::PluginExport for __HotShellWrapper {
370 type Params = $params;
371
372 fn create() -> Self {
373 let params = std::sync::Arc::new(<$params>::new());
374 Self {
377 inner: $crate::static_shell::StaticShell::from_parts(params),
378 }
379 }
380
381 fn params(&self) -> &$params {
382 &self.inner.params
383 }
384
385 fn params_arc(&self) -> std::sync::Arc<$params> {
386 std::sync::Arc::clone(&self.inner.params)
387 }
388
389 fn meter_store(
390 &self,
391 ) -> std::sync::Arc<$crate::__macro_deps::truce_core::meters::MeterStore> {
392 self.inner.meter_store()
393 }
394
395 fn snapshot_slot(
396 &self,
397 ) -> std::sync::Arc<$crate::__macro_deps::truce_core::snapshot::SnapshotSlot> {
398 self.inner.snapshot_slot()
399 }
400
401 fn editor_builder(
402 &self,
403 ) -> $crate::__macro_deps::truce_core::editor::EditorBuilder<$params> {
404 Box::new(|params| {
408 Some(
409 <$logic as $crate::__macro_deps::truce_plugin::PluginEditor<Sample>>::editor(
410 params,
411 ),
412 )
413 })
414 }
415 }
416 };
417}
418
419#[cfg(test)]
420mod tests {
421 use super::publish_snapshot_with;
422 use truce_core::snapshot::SnapshotSlot;
423
424 #[test]
425 fn non_opt_in_latches_off_on_first_block() {
426 let slot = SnapshotSlot::new();
427 let mut try_snapshot = true;
428
429 publish_snapshot_with(&slot, &mut try_snapshot, |_| false);
432 assert!(!try_snapshot, "first false must latch off");
433 assert!(!slot.is_supported());
434
435 let mut called = false;
437 publish_snapshot_with(&slot, &mut try_snapshot, |_| {
438 called = true;
439 false
440 });
441 assert!(!called, "latched-off slot must not call snapshot_into");
442 }
443
444 #[test]
445 fn opt_in_then_contract_violation_stays_subscribed() {
446 let slot = SnapshotSlot::new();
447 let mut try_snapshot = true;
448
449 publish_snapshot_with(&slot, &mut try_snapshot, |buf| {
451 buf.clear();
452 buf.extend_from_slice(&[1, 2, 3]);
453 true
454 });
455 assert!(try_snapshot);
456 assert!(slot.is_supported());
457 assert_eq!(slot.read(), Some(vec![1, 2, 3]));
458
459 publish_snapshot_with(&slot, &mut try_snapshot, |_| false);
462 assert!(try_snapshot, "a post-opt-in false must not latch off");
463
464 publish_snapshot_with(&slot, &mut try_snapshot, |buf| {
466 buf.clear();
467 buf.extend_from_slice(&[4]);
468 true
469 });
470 assert_eq!(slot.read(), Some(vec![4]));
471 }
472}