1pub use smallvec;
2
3pub mod scratch;
4pub mod vec;
5
6pub use scratch::{Scratch, ScratchPool};
7pub use vec::{BiquadState, EnvelopeMode, EnvelopeState, FilterType, OnePoleState, Waveform};
8
9pub trait Plugin {
41 fn new() -> Self where Self: Sized;
42 fn process(&mut self, channels: &mut [&mut [f32]]);
43}
44
45#[macro_export]
52macro_rules! sesh_plugin {
53 ($ty:ident) => {
54 #[no_mangle]
55 pub extern "C" fn sesh_sdk_version() -> u32 {
56 3
57 }
58
59 #[no_mangle]
60 pub extern "C" fn sesh_alloc(size: usize, align: usize) -> *mut u8 {
61 let layout = ::std::alloc::Layout::from_size_align(size, align).expect("invalid layout");
62 unsafe { ::std::alloc::alloc(layout) }
63 }
64
65 #[no_mangle]
66 pub extern "C" fn sesh_free(ptr: *mut u8, size: usize, align: usize) {
67 let layout = ::std::alloc::Layout::from_size_align(size, align).expect("invalid layout");
68 unsafe { ::std::alloc::dealloc(ptr, layout) }
69 }
70
71 #[no_mangle]
72 pub extern "C" fn sesh_create() -> *mut u8 {
73 let plugin = Box::new(<$ty as $crate::Plugin>::new());
74 Box::into_raw(plugin) as *mut u8
75 }
76
77 #[no_mangle]
78 pub extern "C" fn sesh_destroy(instance: *mut u8) {
79 if !instance.is_null() {
80 unsafe { drop(Box::from_raw(instance as *mut $ty)) };
81 }
82 }
83
84 #[no_mangle]
85 pub extern "C" fn sesh_process(instance: *mut u8, ptr: *mut f32, frames: usize, channels: usize) {
86 let plugin = unsafe { &mut *(instance as *mut $ty) };
87 let buffer = unsafe { ::std::slice::from_raw_parts_mut(ptr, frames * channels) };
88 let mut chunks: $crate::smallvec::SmallVec<[&mut [f32]; 32]> =
89 buffer.chunks_mut(frames).collect();
90 plugin.process(&mut chunks);
91 }
92
93 #[cfg(test)]
94 mod sesh_auto_tests {
95 use $crate::Plugin;
96
97 #[test]
98 fn impulse_response_safety() {
99 const SR: usize = 44100;
100 const DURATION: usize = SR * 10;
101 const CHUNK: usize = 128;
102
103 let mut plugin = <super::$ty as $crate::Plugin>::new();
104
105 let mut left = vec![0.0f32; DURATION];
106 let mut right = vec![0.0f32; DURATION];
107 left[0] = 1.0;
108 right[0] = 1.0;
109
110 for start in (0..DURATION).step_by(CHUNK) {
111 let end = (start + CHUNK).min(DURATION);
112 let mut channels: [&mut [f32]; 2] = [
113 &mut left[start..end],
114 &mut right[start..end],
115 ];
116 plugin.process(&mut channels);
117 }
118
119 let peak = left.iter().chain(right.iter())
120 .fold(0.0f32, |m, s| m.max(s.abs()));
121
122 assert!(peak < 10.0,
123 "impulse response peak {:.1} exceeds 10.0 — \
124 likely runaway feedback (check feedback gain, \
125 input injection levels, and mixing matrix energy)",
126 peak);
127 }
128 }
129 };
130}