Skip to main content

truce_core/
buffer.rs

1/// Non-interleaved audio buffer. Zero-copy — borrows host memory
2/// through the format wrapper.
3pub struct AudioBuffer<'a> {
4    inputs: &'a [&'a [f32]],
5    outputs: &'a mut [&'a mut [f32]],
6    offset: usize,
7    num_samples: usize,
8}
9
10impl<'a> AudioBuffer<'a> {
11    /// Create a buffer from pre-split channel slices.
12    /// Used by format wrappers after converting from host-specific buffer types.
13    ///
14    /// # Safety
15    /// The caller must ensure the slices are valid for the lifetime `'a`
16    /// and that `num_samples` does not exceed any slice's length.
17    pub unsafe fn from_slices(
18        inputs: &'a [&'a [f32]],
19        outputs: &'a mut [&'a mut [f32]],
20        num_samples: usize,
21    ) -> Self {
22        #[cfg(debug_assertions)]
23        {
24            // Verify no input channel aliases any output channel.
25            for (i, inp) in inputs.iter().enumerate() {
26                let i_start = inp.as_ptr() as usize;
27                let i_end = i_start + inp.len() * std::mem::size_of::<f32>();
28                for (o, out) in outputs.iter().enumerate() {
29                    let o_start = out.as_ptr() as usize;
30                    let o_end = o_start + out.len() * std::mem::size_of::<f32>();
31                    assert!(
32                        i_end <= o_start || o_end <= i_start,
33                        "AudioBuffer: input channel {i} and output channel {o} alias \
34                         (input: {i_start:#x}..{i_end:#x}, output: {o_start:#x}..{o_end:#x})"
35                    );
36                }
37            }
38            // Verify num_samples doesn't exceed any slice.
39            for (i, inp) in inputs.iter().enumerate() {
40                assert!(
41                    num_samples <= inp.len(),
42                    "AudioBuffer: num_samples ({num_samples}) exceeds input channel {i} length ({})",
43                    inp.len()
44                );
45            }
46            for (o, out) in outputs.iter().enumerate() {
47                assert!(
48                    num_samples <= out.len(),
49                    "AudioBuffer: num_samples ({num_samples}) exceeds output channel {o} length ({})",
50                    out.len()
51                );
52            }
53        }
54        Self {
55            inputs,
56            outputs,
57            offset: 0,
58            num_samples,
59        }
60    }
61
62    pub fn num_samples(&self) -> usize {
63        self.num_samples
64    }
65
66    pub fn num_input_channels(&self) -> usize {
67        self.inputs.len()
68    }
69
70    pub fn num_output_channels(&self) -> usize {
71        self.outputs.len()
72    }
73
74    pub fn input(&self, channel: usize) -> &[f32] {
75        let end = self.offset + self.num_samples;
76        &self.inputs[channel][self.offset..end]
77    }
78
79    pub fn output(&mut self, channel: usize) -> &mut [f32] {
80        let end = self.offset + self.num_samples;
81        &mut self.outputs[channel][self.offset..end]
82    }
83
84    /// Number of channels (min of input and output).
85    pub fn channels(&self) -> usize {
86        self.inputs.len().min(self.outputs.len())
87    }
88
89    /// Get an input/output pair for a channel. Useful for in-place processing.
90    pub fn io_pair(&mut self, in_ch: usize, out_ch: usize) -> (&[f32], &mut [f32]) {
91        let end = self.offset + self.num_samples;
92        let input = &self.inputs[in_ch][self.offset..end];
93        let output = &mut self.outputs[out_ch][self.offset..end];
94        (input, output)
95    }
96
97    /// Get an input/output pair for the same channel index. Shorthand for `io_pair(ch, ch)`.
98    pub fn io(&mut self, ch: usize) -> (&[f32], &mut [f32]) {
99        self.io_pair(ch, ch)
100    }
101
102    /// Peak absolute value across an output channel.
103    pub fn output_peak(&self, ch: usize) -> f32 {
104        let end = self.offset + self.num_samples;
105        self.outputs[ch][self.offset..end]
106            .iter()
107            .fold(0.0f32, |a, &b| a.max(b.abs()))
108    }
109
110    /// Return a sub-block view covering samples `start..start+len`.
111    ///
112    /// The returned buffer borrows `self` exclusively — you cannot use
113    /// the original buffer while the slice is alive.
114    ///
115    /// # Panics
116    /// Panics if `start + len > self.num_samples()`.
117    ///
118    /// # Example
119    /// ```ignore
120    /// let mut offset = 0;
121    /// for event in events.iter() {
122    ///     let at = event.sample_offset as usize;
123    ///     if at > offset {
124    ///         let mut sub = buffer.slice(offset, at - offset);
125    ///         process_sub_block(&mut sub);
126    ///     }
127    ///     handle_event(&event.body);
128    ///     offset = at;
129    /// }
130    /// if offset < buffer.num_samples() {
131    ///     let mut sub = buffer.slice(offset, buffer.num_samples() - offset);
132    ///     process_sub_block(&mut sub);
133    /// }
134    /// ```
135    pub fn slice(&mut self, start: usize, len: usize) -> AudioBuffer<'_> {
136        assert!(
137            start + len <= self.num_samples,
138            "slice({start}, {len}) out of bounds for buffer of {} samples",
139            self.num_samples,
140        );
141        let new_offset = self.offset + start;
142        // SAFETY: We construct an AudioBuffer<'a> and transmute to AudioBuffer<'_>.
143        // These have identical memory layout (lifetimes are erased at runtime).
144        // This is sound because:
145        // 1. &mut self prevents the caller from using self while the slice exists
146        // 2. The underlying channel memory lives for 'a which outlives '_
147        // 3. Bounds are checked by the assert above
148        let self_ptr: *mut Self = self;
149        unsafe {
150            let s = &mut *self_ptr;
151            std::mem::transmute::<AudioBuffer<'a>, AudioBuffer<'_>>(AudioBuffer {
152                inputs: s.inputs,
153                outputs: &mut *s.outputs,
154                offset: new_offset,
155                num_samples: len,
156            })
157        }
158    }
159}
160
161/// Scratch space for `AudioBuffer::from_raw_ptrs`.
162///
163/// Callers allocate this on the stack and pass it to `from_raw_ptrs`.
164/// The buffer borrows the slices stored here, so this struct must
165/// outlive the returned `AudioBuffer`.
166pub struct RawBufferScratch {
167    pub input_slices: Vec<&'static [f32]>,
168    pub output_slices: Vec<&'static mut [f32]>,
169}
170
171impl RawBufferScratch {
172    /// Build an `AudioBuffer` from raw C pointers.
173    ///
174    /// This is the common FFI pattern used by VST3, VST2, AU, and AAX
175    /// wrappers. It:
176    /// 1. Converts raw `*const f32` / `*mut f32` channel pointers to slices
177    /// 2. Copies input channels to output channels (in-place effect processing)
178    /// 3. Returns an `AudioBuffer` borrowing the scratch slices
179    ///
180    /// # Safety
181    /// - `inputs` must point to `num_in` valid `*const f32` pointers,
182    ///   each pointing to `num_frames` samples.
183    /// - `outputs` must point to `num_out` valid `*mut f32` pointers,
184    ///   each pointing to `num_frames` samples.
185    /// - The pointed-to memory must remain valid for the lifetime of
186    ///   the returned `AudioBuffer`.
187    pub unsafe fn build<'a>(
188        &'a mut self,
189        inputs: *const *const f32,
190        outputs: *mut *mut f32,
191        num_in: u32,
192        num_out: u32,
193        num_frames: u32,
194    ) -> AudioBuffer<'a> {
195        let nf = num_frames as usize;
196
197        self.input_slices.clear();
198        for ch in 0..num_in as usize {
199            let ptr = *inputs.add(ch);
200            if !ptr.is_null() {
201                self.input_slices
202                    .push(std::slice::from_raw_parts(ptr, nf));
203            }
204        }
205
206        self.output_slices.clear();
207        for ch in 0..num_out as usize {
208            let ptr = *outputs.add(ch);
209            if !ptr.is_null() {
210                self.output_slices
211                    .push(std::slice::from_raw_parts_mut(ptr, nf));
212            }
213        }
214
215        // Copy input to output for in-place effect processing.
216        let copy_ch = self.input_slices.len().min(self.output_slices.len());
217        for ch in 0..copy_ch {
218            self.output_slices[ch][..nf]
219                .copy_from_slice(&self.input_slices[ch][..nf]);
220        }
221
222        // SAFETY: Same transmute pattern as AudioBuffer::slice().
223        // RawBufferScratch stores 'static slices but we return AudioBuffer<'a>.
224        // Sound because the caller's raw pointers must outlive 'a, and
225        // &'a mut self prevents aliasing.
226        let self_ptr: *mut Self = self;
227        let s = &mut *self_ptr;
228        std::mem::transmute::<AudioBuffer<'static>, AudioBuffer<'a>>(
229            AudioBuffer::from_slices(&s.input_slices, &mut s.output_slices, nf)
230        )
231    }
232}
233
234impl Default for RawBufferScratch {
235    fn default() -> Self {
236        Self {
237            input_slices: Vec::with_capacity(2),
238            output_slices: Vec::with_capacity(2),
239        }
240    }
241}