use std::sync::Arc;
use truce_core::buffer::AudioBuffer;
use truce_core::bus::BusLayout;
use truce_core::events::{EventBody, EventList};
use truce_core::info::PluginInfo;
use truce_core::meters::MeterStore;
use truce_core::plugin::PluginRuntime;
use truce_core::process::{ProcessContext, ProcessStatus};
use truce_core::snapshot::SnapshotSlot;
use truce_core::state::{ForeignState, MigratedState, StateLoadError};
use truce_params::Params;
use truce_params::sample::Sample;
use truce_plugin::PluginLogicCore;
pub struct StaticShell<P: Params, L: PluginLogicCore<S>, S: Sample = f32> {
pub params: Arc<P>,
logic: L,
meters: Arc<MeterStore>,
snapshots: Arc<SnapshotSlot>,
try_snapshot: bool,
sample_rate: f64,
_sample: std::marker::PhantomData<fn() -> S>,
}
unsafe impl<P: Params, L: PluginLogicCore<S>, S: Sample> Send for StaticShell<P, L, S> {}
impl<P: Params + Default + 'static, L: PluginLogicCore<S> + 'static, S: Sample>
StaticShell<P, L, S>
{
pub fn from_parts(params: Arc<P>, logic: L) -> Self {
Self {
params,
logic,
meters: MeterStore::new(),
snapshots: SnapshotSlot::new(),
try_snapshot: true,
sample_rate: 44100.0,
_sample: std::marker::PhantomData,
}
}
pub fn meter_store(&self) -> Arc<MeterStore> {
Arc::clone(&self.meters)
}
pub fn snapshot_slot(&self) -> Arc<SnapshotSlot> {
Arc::clone(&self.snapshots)
}
pub fn logic_ref(&self) -> &L {
&self.logic
}
pub fn logic_ref_mut(&mut self) -> &mut L {
&mut self.logic
}
}
impl<P: Params + Default + 'static, L: PluginLogicCore<S> + 'static, S: Sample> PluginRuntime
for StaticShell<P, L, S>
{
type Sample = S;
fn info() -> PluginInfo
where
Self: Sized,
{
unreachable!("StaticShell::info() should not be called statically")
}
fn bus_layouts() -> Vec<BusLayout>
where
Self: Sized,
{
unreachable!("StaticShell::bus_layouts() should not be called statically")
}
fn init(&mut self) {}
fn reset(&mut self, sample_rate: f64, max_block_size: usize) {
self.sample_rate = sample_rate;
self.params.set_sample_rate(sample_rate);
self.logic.reset(sample_rate, max_block_size);
}
fn process(
&mut self,
buffer: &mut AudioBuffer<S>,
events: &EventList,
context: &mut ProcessContext,
) -> ProcessStatus {
for e in events.iter() {
if let EventBody::ParamChange { id, value } = &e.body {
self.params.set_plain(*id, *value);
}
}
let params = &self.params;
let meters = &self.meters;
let param_fn = |id: u32| -> f64 { params.get_plain(id).unwrap_or(0.0) };
let meter_fn = |id: u32, v: f32| meters.write(id, v);
let mut ctx = ProcessContext::new(
context.transport,
context.sample_rate,
buffer.num_samples(),
&mut *context.output_events,
)
.with_params(¶m_fn)
.with_meters(&meter_fn);
let status = self.logic.process(buffer, events, &mut ctx);
publish_snapshot(&self.logic, &self.snapshots, &mut self.try_snapshot);
status
}
fn save_state(&self) -> Vec<u8> {
self.logic.save_state()
}
fn load_state(&mut self, data: &[u8]) -> Result<(), StateLoadError> {
let result = self.logic.load_state(data);
PluginLogicCore::state_changed(&mut self.logic);
result
}
fn migrate_state(foreign: &ForeignState) -> Option<MigratedState>
where
Self: Sized,
{
<L as PluginLogicCore<S>>::migrate_state(foreign)
}
fn latency(&self) -> u32 {
self.logic.latency()
}
fn tail(&self) -> u32 {
self.logic.tail()
}
fn get_meter(&self, meter_id: u32) -> f32 {
self.meters.read(meter_id)
}
}
pub(crate) fn publish_snapshot<S, L>(logic: &L, slot: &SnapshotSlot, try_snapshot: &mut bool)
where
S: Sample,
L: PluginLogicCore<S> + ?Sized,
{
publish_snapshot_with(slot, try_snapshot, |buf| logic.snapshot_into(buf));
}
fn publish_snapshot_with(
slot: &SnapshotSlot,
try_snapshot: &mut bool,
snapshot_into: impl FnOnce(&mut Vec<u8>) -> bool,
) {
if !*try_snapshot {
return;
}
let ran_unsupported = std::cell::Cell::new(false);
slot.publish(|buf| {
let wrote = snapshot_into(buf);
ran_unsupported.set(!wrote);
wrote
});
if ran_unsupported.get() && !slot.is_supported() {
*try_snapshot = false;
}
}
#[macro_export]
macro_rules! export_static {
(
params: $params:ty,
info: $info:expr,
logic: $logic:ty,
) => {
pub struct __HotShellWrapper {
inner: $crate::static_shell::StaticShell<$params, $logic, Sample>,
}
impl $crate::__macro_deps::truce_core::plugin::PluginRuntime for __HotShellWrapper {
type Sample = Sample;
fn supports_in_place() -> bool
where
Self: Sized,
{
<$logic as $crate::__macro_deps::truce_plugin::PluginLogicCore<Sample>>::supports_in_place()
}
fn info() -> $crate::__macro_deps::truce_core::info::PluginInfo
where
Self: Sized,
{
$info
}
fn bus_layouts() -> Vec<$crate::__macro_deps::truce_core::bus::BusLayout>
where
Self: Sized,
{
<$logic as $crate::__macro_deps::truce_plugin::PluginLogicCore<Sample>>::bus_layouts()
}
fn init(&mut self) {
self.inner.init();
}
fn reset(&mut self, sample_rate: f64, max_block_size: usize) {
self.inner.reset(sample_rate, max_block_size);
}
fn process(
&mut self,
buffer: &mut $crate::__macro_deps::truce_core::buffer::AudioBuffer<Sample>,
events: &$crate::__macro_deps::truce_core::events::EventList,
context: &mut $crate::__macro_deps::truce_core::process::ProcessContext,
) -> $crate::__macro_deps::truce_core::process::ProcessStatus {
self.inner.process(buffer, events, context)
}
fn save_state(&self) -> Vec<u8> {
self.inner.save_state()
}
fn load_state(
&mut self,
data: &[u8],
) -> Result<(), $crate::__macro_deps::truce_core::state::StateLoadError> {
self.inner.load_state(data)
}
fn migrate_state(
foreign: &$crate::__macro_deps::truce_core::state::ForeignState,
) -> Option<$crate::__macro_deps::truce_core::state::MigratedState>
where
Self: Sized,
{
<$logic as $crate::__macro_deps::truce_plugin::PluginLogicCore<Sample>>::migrate_state(foreign)
}
fn latency(&self) -> u32 {
self.inner.latency()
}
fn tail(&self) -> u32 {
self.inner.tail()
}
fn get_meter(&self, meter_id: u32) -> f32 {
self.inner.get_meter(meter_id)
}
}
impl $crate::__macro_deps::truce_core::export::PluginExport for __HotShellWrapper {
type Params = $params;
fn create() -> Self {
let params = std::sync::Arc::new(<$params>::new());
let logic = <$logic>::new(std::sync::Arc::clone(¶ms));
Self {
inner: $crate::static_shell::StaticShell::from_parts(params, logic),
}
}
fn params(&self) -> &$params {
&self.inner.params
}
fn params_arc(&self) -> std::sync::Arc<$params> {
std::sync::Arc::clone(&self.inner.params)
}
fn meter_store(
&self,
) -> std::sync::Arc<$crate::__macro_deps::truce_core::meters::MeterStore> {
self.inner.meter_store()
}
fn snapshot_slot(
&self,
) -> std::sync::Arc<$crate::__macro_deps::truce_core::snapshot::SnapshotSlot> {
self.inner.snapshot_slot()
}
fn editor_builder(
&self,
) -> $crate::__macro_deps::truce_core::editor::EditorBuilder<$params> {
Box::new(|params| {
Some(
<$logic as $crate::__macro_deps::truce_plugin::PluginEditor<Sample>>::editor(
params,
),
)
})
}
}
};
}
#[cfg(test)]
mod tests {
use super::publish_snapshot_with;
use truce_core::snapshot::SnapshotSlot;
#[test]
fn non_opt_in_latches_off_on_first_block() {
let slot = SnapshotSlot::new();
let mut try_snapshot = true;
publish_snapshot_with(&slot, &mut try_snapshot, |_| false);
assert!(!try_snapshot, "first false must latch off");
assert!(!slot.is_supported());
let mut called = false;
publish_snapshot_with(&slot, &mut try_snapshot, |_| {
called = true;
false
});
assert!(!called, "latched-off slot must not call snapshot_into");
}
#[test]
fn opt_in_then_contract_violation_stays_subscribed() {
let slot = SnapshotSlot::new();
let mut try_snapshot = true;
publish_snapshot_with(&slot, &mut try_snapshot, |buf| {
buf.clear();
buf.extend_from_slice(&[1, 2, 3]);
true
});
assert!(try_snapshot);
assert!(slot.is_supported());
assert_eq!(slot.read(), Some(vec![1, 2, 3]));
publish_snapshot_with(&slot, &mut try_snapshot, |_| false);
assert!(try_snapshot, "a post-opt-in false must not latch off");
publish_snapshot_with(&slot, &mut try_snapshot, |buf| {
buf.clear();
buf.extend_from_slice(&[4]);
true
});
assert_eq!(slot.read(), Some(vec![4]));
}
}