sesh_sdk/scratch.rs
1use std::cell::UnsafeCell;
2
3/// Pre-allocated buffer pool for intermediate results during audio processing.
4///
5/// Const-constructed, zero-initialized in the data segment. Uses `UnsafeCell` for
6/// interior mutability — safe because WASM is single-threaded.
7///
8/// Each call to `buf(frames)` returns the next available `&mut [f32]` of the
9/// requested length. The cursor wraps around when all slots are used.
10///
11/// # Example
12///
13/// ```rust
14/// use sesh_sdk::Scratch;
15///
16/// static SCRATCH: Scratch = Scratch::new();
17///
18/// fn process(channels: &mut [&mut [f32]]) {
19/// let frames = channels[0].len();
20/// let cutoff = SCRATCH.buf(frames);
21/// let q = SCRATCH.buf(frames);
22/// let temp = SCRATCH.buf(frames);
23/// // ...
24/// }
25/// ```
26pub struct ScratchPool<const N: usize, const FRAMES: usize> {
27 bufs: UnsafeCell<[[f32; FRAMES]; N]>,
28 cursor: UnsafeCell<usize>,
29}
30
31// SAFETY: WASM is single-threaded. No concurrent access possible.
32unsafe impl<const N: usize, const FRAMES: usize> Sync for ScratchPool<N, FRAMES> {}
33
34impl<const N: usize, const FRAMES: usize> ScratchPool<N, FRAMES> {
35 pub const fn new() -> Self {
36 Self {
37 bufs: UnsafeCell::new([[0.0; FRAMES]; N]),
38 cursor: UnsafeCell::new(0),
39 }
40 }
41
42 /// Get the next scratch buffer, sliced to `frames`. Wraps when all slots are used.
43 pub fn buf(&self, frames: usize) -> &mut [f32] {
44 unsafe {
45 let cursor = &mut *self.cursor.get();
46 let idx = *cursor % N;
47 *cursor = idx + 1;
48 let bufs = &mut *self.bufs.get();
49 &mut bufs[idx][..frames]
50 }
51 }
52}
53
54/// Default scratch pool: 8 buffers of 2048 frames each (64KB).
55pub type Scratch = ScratchPool<8, 2048>;