Skip to main content

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>;