truce_rack_core/buffer.rs
1//! Planar audio buffer passed into [`crate::Plugin::process`].
2//!
3//! `AudioBuffer<S>` is the host-side view of the slices the
4//! plugin reads from and writes into. Channels are organised
5//! by bus to match how host SDKs natively expose audio — no
6//! interleave / deinterleave on the hot path.
7//!
8//! The lifetime `'a` is the block lifetime: every slice borrows
9//! from buffers the host owns. Plugins receive `&mut AudioBuffer`
10//! and may write through the output slices but cannot extend
11//! the borrows past the `process` call.
12
13use crate::sample::Sample;
14
15/// One channel of input audio.
16pub type InputChannel<'a, S> = &'a [S];
17
18/// One channel of output audio.
19pub type OutputChannel<'a, S> = &'a mut [S];
20
21/// Per-bus channel range into the buffer's flat channel arrays.
22///
23/// Format wrappers construct slices of these and hand them to
24/// [`AudioBuffer::new`]; plugin code does not need to look at
25/// them directly (use [`AudioBuffer::bus_inputs`] /
26/// [`AudioBuffer::bus_outputs`]).
27#[derive(Debug, Clone, Copy)]
28pub struct BusRange {
29 /// Inclusive start index into the flat channel array.
30 start: usize,
31 /// Number of channels in this bus.
32 len: usize,
33}
34
35/// Mutable, planar audio buffer for one `process` block.
36///
37/// Channels are flat across buses internally; `bus_inputs(0)` /
38/// `bus_outputs(0)` slice into the main bus, higher indices into
39/// sidechains and auxiliaries. The flat-then-sliced shape matches
40/// host SDK conventions (CLAP's `clap_audio_buffer`, VST3's
41/// `ProcessData`, AU's `AudioBufferList`).
42///
43/// `S` is the sample precision — `f32` for CLAP / VST2 / LV2 /
44/// AAX, `f32` or `f64` for VST3 / AU at the host's choice.
45pub struct AudioBuffer<'a, S: Sample> {
46 /// One slice per input channel, in bus-order.
47 inputs: &'a [&'a [S]],
48 /// One slice per output channel, in bus-order. The outer slice
49 /// is mutable so the plugin can write through it; the inner
50 /// `[S]` borrows are independent so two output buses don't
51 /// alias.
52 outputs: &'a mut [&'a mut [S]],
53 /// Number of frames in this block. All channel slices are
54 /// exactly this long.
55 num_frames: usize,
56 /// Per-input-bus channel ranges. `bus_inputs[k]` gives the
57 /// `start..start+len` range into `inputs`.
58 bus_inputs: &'a [BusRange],
59 /// Per-output-bus channel ranges into `outputs`.
60 bus_outputs: &'a [BusRange],
61}
62
63impl<'a, S: Sample> AudioBuffer<'a, S> {
64 /// Build a buffer from raw slices.
65 ///
66 /// Format wrappers call this once per block from their
67 /// `process` callback. Plugin code receives the buffer; only
68 /// wrappers construct one.
69 ///
70 /// # Panics
71 ///
72 /// Panics in debug builds if any channel slice is shorter
73 /// than `num_frames` or the bus ranges don't cover the
74 /// channel arrays exactly. Release builds elide the checks —
75 /// wrappers are expected to maintain the invariants.
76 #[must_use]
77 pub fn new(
78 inputs: &'a [&'a [S]],
79 outputs: &'a mut [&'a mut [S]],
80 num_frames: usize,
81 bus_inputs: &'a [BusRange],
82 bus_outputs: &'a [BusRange],
83 ) -> Self {
84 debug_assert!(
85 inputs.iter().all(|c| c.len() >= num_frames),
86 "all input channels must have at least num_frames samples"
87 );
88 debug_assert!(
89 outputs.iter().all(|c| c.len() >= num_frames),
90 "all output channels must have at least num_frames samples"
91 );
92 debug_assert_eq!(
93 bus_inputs.iter().map(|r| r.len).sum::<usize>(),
94 inputs.len(),
95 "input bus ranges must partition the channel array"
96 );
97 debug_assert_eq!(
98 bus_outputs.iter().map(|r| r.len).sum::<usize>(),
99 outputs.len(),
100 "output bus ranges must partition the channel array"
101 );
102 Self {
103 inputs,
104 outputs,
105 num_frames,
106 bus_inputs,
107 bus_outputs,
108 }
109 }
110
111 /// Block length in samples (frames). Every channel slice is
112 /// exactly this long.
113 #[must_use]
114 pub fn num_frames(&self) -> usize {
115 self.num_frames
116 }
117
118 /// Number of input buses (including main + sidechains).
119 #[must_use]
120 pub fn num_input_buses(&self) -> usize {
121 self.bus_inputs.len()
122 }
123
124 /// Number of output buses.
125 #[must_use]
126 pub fn num_output_buses(&self) -> usize {
127 self.bus_outputs.len()
128 }
129
130 /// Total input channels across every bus. Useful for the
131 /// "loop over flat channels" pattern when bus layout doesn't
132 /// matter for the operation.
133 #[must_use]
134 pub fn total_input_channels(&self) -> usize {
135 self.inputs.len()
136 }
137
138 /// Total output channels across every bus.
139 #[must_use]
140 pub fn total_output_channels(&self) -> usize {
141 self.outputs.len()
142 }
143
144 /// Input channels for one bus. `bus_index` is 0 for the main
145 /// input; higher indices for sidechains / auxiliaries.
146 ///
147 /// # Panics
148 ///
149 /// Panics if `bus_index >= num_input_buses()`.
150 #[must_use]
151 pub fn bus_inputs(&self, bus_index: usize) -> &[InputChannel<'a, S>] {
152 let range = self.bus_inputs[bus_index];
153 &self.inputs[range.start..range.start + range.len]
154 }
155
156 /// Mutable output channels for one bus.
157 ///
158 /// # Panics
159 ///
160 /// Panics if `bus_index >= num_output_buses()`.
161 pub fn bus_outputs(&mut self, bus_index: usize) -> &mut [&'a mut [S]] {
162 let range = self.bus_outputs[bus_index];
163 &mut self.outputs[range.start..range.start + range.len]
164 }
165
166 /// Shortcut: input channels of the main bus (index 0). Most
167 /// effects use this; reach for [`Self::bus_inputs`] when you
168 /// need sidechains too.
169 #[must_use]
170 pub fn main_inputs(&self) -> &[InputChannel<'a, S>] {
171 if self.bus_inputs.is_empty() {
172 &[]
173 } else {
174 self.bus_inputs(0)
175 }
176 }
177
178 /// Shortcut: mutable output channels of the main bus.
179 pub fn main_outputs(&mut self) -> &mut [&'a mut [S]] {
180 debug_assert!(
181 !self.bus_outputs.is_empty(),
182 "main_outputs called on a buffer with no output buses"
183 );
184 self.bus_outputs(0)
185 }
186}
187
188impl BusRange {
189 /// Construct a range from a `(start, len)` pair. Format
190 /// wrappers call this once per bus when building the slice
191 /// they pass to [`AudioBuffer::new`].
192 #[must_use]
193 pub const fn new(start: usize, len: usize) -> Self {
194 Self { start, len }
195 }
196}