use truce_core::buffer::AudioBuffer;
use truce_core::bus::BusLayout;
use truce_core::denormal::DenormalGuard;
use truce_core::editor::Editor;
use truce_core::events::EventList;
use truce_core::process::{ProcessContext, ProcessStatus};
use truce_core::state::StateLoadError;
use truce_gui_types::interaction::WidgetRegion;
use truce_gui_types::layout::GridLayout;
use truce_gui_types::render::RenderBackend;
use truce_gui_types::widgets::WidgetType;
use truce_params::sample::Sample;
pub trait PluginLogicCore<S: Sample = f32>: Send + 'static {
#[must_use]
fn supports_in_place() -> bool
where
Self: Sized;
#[must_use]
fn bus_layouts() -> Vec<BusLayout>
where
Self: Sized;
fn reset(&mut self, sample_rate: f64, max_block_size: usize);
fn process(
&mut self,
buffer: &mut AudioBuffer<S>,
events: &EventList,
context: &mut ProcessContext,
) -> ProcessStatus;
fn save_state(&self) -> Vec<u8>;
fn load_state(&mut self, data: &[u8]) -> Result<(), StateLoadError>;
fn state_changed(&mut self);
fn latency(&self) -> u32;
fn tail(&self) -> u32;
fn layout(&self) -> GridLayout;
fn render(&self, backend: &mut dyn RenderBackend);
fn uses_custom_render(&self) -> bool;
fn hit_test(&self, widgets: &[WidgetRegion], x: f32, y: f32) -> Option<usize>;
fn custom_editor(&self) -> Option<Box<dyn Editor>>;
}
#[doc(hidden)]
#[macro_export]
macro_rules! plugin_logic_leaf_trait {
($(#[$attr:meta])* $vis:vis trait $name:ident<sample = $sample:ty>) => {
$(#[$attr])*
$vis trait $name: Send + 'static {
#[must_use]
fn supports_in_place() -> bool
where
Self: Sized,
{
false
}
#[must_use]
fn bus_layouts() -> Vec<$crate::__plugin_logic_deps::BusLayout>
where
Self: Sized,
{
vec![$crate::__plugin_logic_deps::BusLayout::stereo()]
}
fn reset(&mut self, sample_rate: f64, max_block_size: usize);
fn process(
&mut self,
buffer: &mut $crate::__plugin_logic_deps::AudioBuffer<$sample>,
events: &$crate::__plugin_logic_deps::EventList,
context: &mut $crate::__plugin_logic_deps::ProcessContext,
) -> $crate::__plugin_logic_deps::ProcessStatus;
fn save_state(&self) -> Vec<u8> {
Vec::new()
}
fn load_state(
&mut self,
_data: &[u8],
) -> Result<(), $crate::__plugin_logic_deps::StateLoadError> {
Ok(())
}
fn state_changed(&mut self) {}
fn latency(&self) -> u32 {
0
}
fn tail(&self) -> u32 {
0
}
fn layout(&self) -> $crate::__plugin_logic_deps::GridLayout {
$crate::__plugin_logic_deps::GridLayout::build(vec![])
}
fn render(&self, _backend: &mut dyn $crate::__plugin_logic_deps::RenderBackend) {}
fn uses_custom_render(&self) -> bool {
false
}
fn hit_test(
&self,
widgets: &[$crate::__plugin_logic_deps::WidgetRegion],
x: f32,
y: f32,
) -> Option<usize> {
$crate::__plugin_logic_deps::default_hit_test(widgets, x, y)
}
fn custom_editor(&self) -> Option<Box<dyn $crate::__plugin_logic_deps::Editor>> {
None
}
}
};
}
#[doc(hidden)]
pub mod __plugin_logic_deps {
pub use truce_core::buffer::AudioBuffer;
pub use truce_core::bus::BusLayout;
pub use truce_core::editor::Editor;
pub use truce_core::events::EventList;
pub use truce_core::process::{ProcessContext, ProcessStatus};
pub use truce_core::state::StateLoadError;
pub use truce_gui_types::interaction::WidgetRegion;
pub use truce_gui_types::layout::GridLayout;
pub use truce_gui_types::render::RenderBackend;
pub use crate::default_hit_test;
}
plugin_logic_leaf_trait! {
pub trait PluginLogic<sample = f32>
}
plugin_logic_leaf_trait! {
pub trait PluginLogic64<sample = f64>
}
macro_rules! plugin_logic_bridge {
($leaf:ident, $sample:ty) => {
impl<T: $leaf> PluginLogicCore<$sample> for T {
fn supports_in_place() -> bool
where
Self: Sized,
{
<Self as $leaf>::supports_in_place()
}
fn bus_layouts() -> Vec<BusLayout>
where
Self: Sized,
{
<Self as $leaf>::bus_layouts()
}
fn reset(&mut self, sample_rate: f64, max_block_size: usize) {
<Self as $leaf>::reset(self, sample_rate, max_block_size);
}
fn process(
&mut self,
buffer: &mut AudioBuffer<$sample>,
events: &EventList,
context: &mut ProcessContext,
) -> ProcessStatus {
let _denormal_guard = DenormalGuard::new();
<Self as $leaf>::process(self, buffer, events, context)
}
fn save_state(&self) -> Vec<u8> {
<Self as $leaf>::save_state(self)
}
fn load_state(&mut self, data: &[u8]) -> Result<(), StateLoadError> {
<Self as $leaf>::load_state(self, data)
}
fn state_changed(&mut self) {
<Self as $leaf>::state_changed(self);
}
fn latency(&self) -> u32 {
<Self as $leaf>::latency(self)
}
fn tail(&self) -> u32 {
<Self as $leaf>::tail(self)
}
fn layout(&self) -> GridLayout {
<Self as $leaf>::layout(self)
}
fn render(&self, backend: &mut dyn RenderBackend) {
<Self as $leaf>::render(self, backend);
}
fn uses_custom_render(&self) -> bool {
<Self as $leaf>::uses_custom_render(self)
}
fn hit_test(&self, widgets: &[WidgetRegion], x: f32, y: f32) -> Option<usize> {
<Self as $leaf>::hit_test(self, widgets, x, y)
}
fn custom_editor(&self) -> Option<Box<dyn Editor>> {
<Self as $leaf>::custom_editor(self)
}
}
};
}
plugin_logic_bridge!(PluginLogic, f32);
plugin_logic_bridge!(PluginLogic64, f64);
#[must_use]
pub fn default_hit_test(widgets: &[WidgetRegion], x: f32, y: f32) -> Option<usize> {
for (i, w) in widgets.iter().enumerate() {
if w.widget_type == WidgetType::Meter {
continue;
}
if w.widget_type == WidgetType::Knob {
let dx = x - w.cx;
let dy = y - w.cy;
if dx * dx + dy * dy <= w.radius * w.radius {
return Some(i);
}
} else if x >= w.x && x <= w.x + w.w && y >= w.y && y <= w.y + w.h {
return Some(i);
}
}
None
}