use std::sync::atomic::{AtomicU32, Ordering};
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::process::{ProcessContext, ProcessStatus};
use truce_core::plugin::Plugin;
use truce_params::Params;
use crate::traits::PluginLogic;
pub struct StaticShell<P: Params, L: PluginLogic> {
pub params: Arc<P>,
logic: L,
meters: Arc<[AtomicU32; 256]>,
sample_rate: f64,
}
unsafe impl<P: Params, L: PluginLogic> Send for StaticShell<P, L> {}
impl<P: Params + Default + 'static, L: PluginLogic + 'static> StaticShell<P, L> {
pub fn new(params: P) -> Self {
Self {
params: Arc::new(params),
logic: L::new(),
meters: Arc::new(std::array::from_fn(|_| AtomicU32::new(0))),
sample_rate: 44100.0,
}
}
pub fn logic_ref(&self) -> &L {
&self.logic
}
pub fn logic_ref_mut(&mut self) -> &mut L {
&mut self.logic
}
pub fn try_custom_editor(&self) -> Option<Box<dyn truce_core::editor::Editor>> {
self.logic.custom_editor()
}
pub fn try_builtin_editor(&self) -> Option<truce_gui::editor::BuiltinEditor<P>> {
let layout = self.logic.layout();
if layout.width == 0 || layout.height == 0 {
return None;
}
Some(truce_gui::editor::BuiltinEditor::new_grid(Arc::clone(&self.params), layout))
}
}
impl<P: Params + Default + 'static, L: PluginLogic + 'static> Plugin for StaticShell<P, L> {
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,
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);
}
}
if let Some(plugin_params) = self.logic.params_mut() {
for info in self.params.param_infos() {
if let Some(value) = self.params.get_plain(info.id) {
plugin_params.set_plain(info.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| {
if let Some(slot) = meters.get(id as usize) {
slot.store(v.to_bits(), Ordering::Relaxed);
}
};
let mut output_events = EventList::new();
let mut ctx = ProcessContext::new(
context.transport,
context.sample_rate,
buffer.num_samples(),
&mut output_events,
)
.with_params(¶m_fn)
.with_meters(&meter_fn);
let result = self.logic.process(buffer, events, &mut ctx);
for event in output_events.iter() {
context.output_events.push(event.clone());
}
result
}
fn save_state(&self) -> Option<Vec<u8>> {
let data = self.logic.save_state();
if data.is_empty() { None } else { Some(data) }
}
fn load_state(&mut self, data: &[u8]) {
self.logic.load_state(data);
}
fn editor(&mut self) -> Option<Box<dyn truce_core::editor::Editor>> {
if let Some(editor) = self.logic.custom_editor() {
return Some(editor);
}
self.try_builtin_editor()
.map(|e| Box::new(e) as Box<dyn truce_core::editor::Editor>)
}
fn latency(&self) -> u32 { self.logic.latency() }
fn tail(&self) -> u32 { self.logic.tail() }
fn get_meter(&self, meter_id: u32) -> f32 {
if let Some(slot) = self.meters.get(meter_id as usize) {
f32::from_bits(slot.load(Ordering::Relaxed))
} else {
0.0
}
}
}
#[macro_export]
macro_rules! export_static {
(
params: $params:ty,
info: $info:expr,
bus_layouts: [$($layout:expr),* $(,)?],
logic: $logic:ty,
editor: { $($editor_body:tt)* },
) => {
struct __HotShellWrapper {
inner: $crate::static_shell::StaticShell<$params, $logic>,
}
impl truce_core::plugin::Plugin for __HotShellWrapper {
fn info() -> truce_core::info::PluginInfo where Self: Sized {
$info
}
fn bus_layouts() -> Vec<truce_core::bus::BusLayout> where Self: Sized {
vec![$($layout),*]
}
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 truce_core::buffer::AudioBuffer,
events: &truce_core::events::EventList,
context: &mut truce_core::process::ProcessContext,
) -> truce_core::process::ProcessStatus {
self.inner.process(buffer, events, context)
}
fn save_state(&self) -> Option<Vec<u8>> {
self.inner.save_state()
}
fn load_state(&mut self, data: &[u8]) {
self.inner.load_state(data);
}
fn editor(&mut self) -> Option<Box<dyn truce_core::editor::Editor>> {
if let Some(e) = self.inner.try_custom_editor() {
return Some(e);
}
if let Some(builtin) = self.inner.try_builtin_editor() {
return Some($($editor_body)*(builtin));
}
None
}
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 truce_core::export::PluginExport for __HotShellWrapper {
type Params = $params;
fn create() -> Self {
Self {
inner: $crate::static_shell::StaticShell::new(<$params>::new()),
}
}
fn params(&self) -> &$params {
&self.inner.params
}
fn params_mut(&mut self) -> &mut $params {
std::sync::Arc::get_mut(&mut self.inner.params)
.expect("params_mut called while Arc has other refs")
}
fn params_arc(&self) -> std::sync::Arc<$params> {
std::sync::Arc::clone(&self.inner.params)
}
}
};
}