Skip to main content

sesh_sdk/
lib.rs

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
9/// Trait for Sesh audio plugins.
10///
11/// Implement this trait on your plugin struct, then pass the type to `sesh_plugin!`.
12///
13/// # Example
14///
15/// ```rust
16/// use sesh_sdk::{sesh_plugin, Plugin, Scratch};
17///
18/// struct MyPlugin {
19///     scratch: Scratch,
20/// }
21///
22/// impl Plugin for MyPlugin {
23///     fn new() -> Self {
24///         Self { scratch: Scratch::new() }
25///     }
26///
27///     fn process(&mut self, channels: &mut [&mut [f32]]) {
28///         let frames = channels[0].len();
29///         let tmp = self.scratch.buf(frames);
30///         for ch in channels.iter_mut() {
31///             for sample in ch.iter_mut() {
32///                 *sample *= 0.5;
33///             }
34///         }
35///     }
36/// }
37///
38/// sesh_plugin!(MyPlugin);
39/// ```
40pub trait Plugin {
41    fn new() -> Self where Self: Sized;
42    fn process(&mut self, channels: &mut [&mut [f32]]);
43}
44
45/// Define a Sesh audio plugin.
46///
47/// Generates the required WASM exports: `sesh_sdk_version`, `sesh_alloc`,
48/// `sesh_free`, `sesh_create`, `sesh_destroy`, and `sesh_process`.
49///
50/// The type must implement the `Plugin` trait.
51#[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}