beamer_core/buffer.rs
1//! Audio buffer abstractions for plugin processing.
2//!
3//! This module provides [`Buffer`] for main audio I/O and [`AuxiliaryBuffers`]
4//! for sidechain and auxiliary bus access.
5//!
6//! # Architecture
7//!
8//! Audio processing in Beamer uses two separate buffer types:
9//!
10//! - **[`Buffer`]**: Main stereo/surround I/O - used by all plugins
11//! - **[`AuxiliaryBuffers`]**: Sidechain and aux buses - used by multi-bus plugins
12//!
13//! This separation solves Rust's lifetime variance constraints with nested mutable
14//! references while providing a clean, ergonomic API.
15//!
16//! # Real-Time Safety
17//!
18//! All buffer types use fixed-size stack storage with no heap allocations.
19//! This guarantees real-time safety in audio processing callbacks.
20//!
21//! # Generic Sample Type
22//!
23//! All buffer types are generic over `S: Sample`, defaulting to `f32`. This enables
24//! zero-cost generic processing for both 32-bit and 64-bit audio.
25//!
26//! # Example: Simple Gain Plugin
27//!
28//! ```ignore
29//! fn process(&mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers) {
30//! let gain = self.parameters.gain();
31//! for (input, output) in buffer.zip_channels() {
32//! for (i, o) in input.iter().zip(output.iter_mut()) {
33//! *o = *i * gain;
34//! }
35//! }
36//! }
37//! ```
38//!
39//! # Example: Block-Based Sidechain (RMS)
40//!
41//! ```ignore
42//! fn process(&mut self, buffer: &mut Buffer, aux: &mut AuxiliaryBuffers) {
43//! // Analyze sidechain input (block-level RMS)
44//! let key_level = aux.sidechain()
45//! .map(|sc| sc.rms(0)) // RMS of first channel
46//! .unwrap_or(0.0);
47//!
48//! // Apply compression based on sidechain
49//! let reduction = self.compute_gain_reduction(key_level);
50//! for output in buffer.outputs_mut() {
51//! for sample in output {
52//! *sample *= reduction;
53//! }
54//! }
55//! }
56//! ```
57//!
58//! # Example: Sample-Accurate Sidechain Processing
59//!
60//! For sample-by-sample sidechain access (e.g., gates, duckers, lookahead compressors):
61//!
62//! ```ignore
63//! fn process(&mut self, buffer: &mut Buffer, aux: &mut AuxiliaryBuffers) {
64//! let sc = aux.sidechain();
65//!
66//! for i in 0..buffer.num_samples() {
67//! // Get sidechain sample (returns S::ZERO if disconnected)
68//! let key = sc.as_ref()
69//! .map(|s| s.sample(0, i).to_f64().abs())
70//! .unwrap_or_else(|| buffer.input(0)[i].to_f64().abs());
71//!
72//! // Compute per-sample gain reduction
73//! let gain = self.envelope.process(key);
74//!
75//! // Apply to output
76//! buffer.output(0)[i] = buffer.input(0)[i] * Sample::from_f64(gain);
77//! }
78//! }
79//! ```
80
81use crate::sample::Sample;
82use crate::types::{MAX_AUX_BUSES, MAX_CHANNELS};
83
84// =============================================================================
85// Buffer - Main Audio I/O
86// =============================================================================
87
88/// Main audio buffer for plugin processing.
89///
90/// Contains input and output channel slices for the primary audio bus.
91/// This is what most plugins interact with - simple stereo or surround I/O.
92///
93/// # Type Parameter
94///
95/// `S` is the sample type, defaulting to `f32`. Use `Buffer<f64>` for
96/// 64-bit double precision processing.
97///
98/// # Lifetime
99///
100/// The `'a` lifetime ties the buffer to the host's audio data. Buffers are
101/// only valid within a single `process()` call.
102///
103/// # Channel Layout
104///
105/// Channels are indexed starting from 0:
106/// - Stereo: 0 = Left, 1 = Right
107/// - Surround: 0 = Left, 1 = Right, 2 = Center, etc.
108///
109/// # Real-Time Safety
110///
111/// This struct uses fixed-size stack storage. No heap allocations occur
112/// during construction or use.
113pub struct Buffer<'a, S: Sample = f32> {
114 /// Input channel slices (immutable audio from host)
115 /// Option<&[S]> is Copy, so [None; N] works
116 inputs: [Option<&'a [S]>; MAX_CHANNELS],
117 /// Output channel slices (mutable audio to host)
118 outputs: [Option<&'a mut [S]>; MAX_CHANNELS],
119 /// Number of active input channels
120 num_input_channels: usize,
121 /// Number of active output channels
122 num_output_channels: usize,
123 /// Number of samples in this processing block
124 num_samples: usize,
125}
126
127impl<'a, S: Sample> Buffer<'a, S> {
128 /// Create a new buffer from channel slices.
129 ///
130 /// This is called by the VST3 wrapper, not by plugin code.
131 /// Channels beyond [`MAX_CHANNELS`] are silently ignored.
132 #[inline]
133 pub fn new(
134 inputs: impl IntoIterator<Item = &'a [S]>,
135 outputs: impl IntoIterator<Item = &'a mut [S]>,
136 num_samples: usize,
137 ) -> Self {
138 let mut input_arr: [Option<&'a [S]>; MAX_CHANNELS] = [None; MAX_CHANNELS];
139 let mut num_input_channels = 0;
140 for (i, slice) in inputs.into_iter().take(MAX_CHANNELS).enumerate() {
141 input_arr[i] = Some(slice);
142 num_input_channels = i + 1;
143 }
144
145 // Can't use [None; N] for &mut because it's not Copy
146 let mut output_arr: [Option<&'a mut [S]>; MAX_CHANNELS] = std::array::from_fn(|_| None);
147 let mut num_output_channels = 0;
148 for (i, slice) in outputs.into_iter().take(MAX_CHANNELS).enumerate() {
149 output_arr[i] = Some(slice);
150 num_output_channels = i + 1;
151 }
152
153 Self {
154 inputs: input_arr,
155 outputs: output_arr,
156 num_input_channels,
157 num_output_channels,
158 num_samples,
159 }
160 }
161
162 // =========================================================================
163 // Buffer Info
164 // =========================================================================
165
166 /// Number of samples in this processing block.
167 #[inline]
168 pub fn num_samples(&self) -> usize {
169 self.num_samples
170 }
171
172 /// Number of input channels.
173 #[inline]
174 pub fn num_input_channels(&self) -> usize {
175 self.num_input_channels
176 }
177
178 /// Number of output channels.
179 #[inline]
180 pub fn num_output_channels(&self) -> usize {
181 self.num_output_channels
182 }
183
184 /// Returns true if this is a stereo buffer (2 in, 2 out).
185 #[inline]
186 pub fn is_stereo(&self) -> bool {
187 self.num_input_channels == 2 && self.num_output_channels == 2
188 }
189
190 /// Returns true if this is a mono buffer (1 in, 1 out).
191 #[inline]
192 pub fn is_mono(&self) -> bool {
193 self.num_input_channels == 1 && self.num_output_channels == 1
194 }
195
196 // =========================================================================
197 // Channel Access
198 // =========================================================================
199
200 /// Get an input channel by index.
201 ///
202 /// Returns an empty slice if the channel doesn't exist.
203 #[inline]
204 pub fn input(&self, channel: usize) -> &[S] {
205 self.inputs
206 .get(channel)
207 .and_then(|opt| opt.as_ref())
208 .map(|ch| &ch[..self.num_samples])
209 .unwrap_or(&[])
210 }
211
212 /// Get a mutable output channel by index.
213 ///
214 /// # Panics
215 ///
216 /// Panics if the channel index is out of bounds.
217 #[inline]
218 pub fn output(&mut self, channel: usize) -> &mut [S] {
219 let n = self.num_samples;
220 self.outputs[channel]
221 .as_mut()
222 .map(|ch| &mut ch[..n])
223 .expect("output channel out of bounds")
224 }
225
226 /// Try to get a mutable output channel by index.
227 ///
228 /// Returns `None` if the channel doesn't exist.
229 #[inline]
230 pub fn output_checked(&mut self, channel: usize) -> Option<&mut [S]> {
231 let n = self.num_samples;
232 self.outputs
233 .get_mut(channel)
234 .and_then(|opt| opt.as_mut())
235 .map(|ch| &mut ch[..n])
236 }
237
238 // =========================================================================
239 // Iterators
240 // =========================================================================
241
242 /// Iterate over all input channels.
243 #[inline]
244 pub fn inputs(&self) -> impl Iterator<Item = &[S]> + '_ {
245 let n = self.num_samples;
246 self.inputs[..self.num_input_channels]
247 .iter()
248 .filter_map(move |opt| opt.as_ref().map(|ch| &ch[..n]))
249 }
250
251 /// Iterate over all output channels mutably.
252 #[inline]
253 pub fn outputs_mut(&mut self) -> impl Iterator<Item = &mut [S]> + use<'_, 'a, S> {
254 let n = self.num_samples;
255 self.outputs[..self.num_output_channels]
256 .iter_mut()
257 .filter_map(move |opt| opt.as_mut().map(|ch| &mut ch[..n]))
258 }
259
260 /// Iterate over paired (input, output) channels.
261 ///
262 /// This is the most common pattern for in-place processing.
263 /// Only yields channels that exist in both input and output.
264 ///
265 /// # Example
266 ///
267 /// ```ignore
268 /// for (input, output) in buffer.zip_channels() {
269 /// for (i, o) in input.iter().zip(output.iter_mut()) {
270 /// *o = *i * gain;
271 /// }
272 /// }
273 /// ```
274 #[inline]
275 pub fn zip_channels(&mut self) -> impl Iterator<Item = (&[S], &mut [S])> + use<'_, 'a, S> {
276 let n = self.num_samples;
277 let num_pairs = self.num_input_channels.min(self.num_output_channels);
278 self.inputs[..num_pairs]
279 .iter()
280 .zip(self.outputs[..num_pairs].iter_mut())
281 .filter_map(move |(i_opt, o_opt)| {
282 match (i_opt.as_ref(), o_opt.as_mut()) {
283 (Some(i), Some(o)) => Some((&i[..n], &mut o[..n])),
284 _ => None,
285 }
286 })
287 }
288
289 // =========================================================================
290 // Bulk Operations
291 // =========================================================================
292
293 /// Copy all input channels to output channels.
294 ///
295 /// Useful for bypass or passthrough. Only copies channels that exist
296 /// in both input and output.
297 pub fn copy_to_output(&mut self) {
298 let num_channels = self.num_input_channels.min(self.num_output_channels);
299 let n = self.num_samples;
300 for ch in 0..num_channels {
301 if let (Some(input), Some(output)) = (self.inputs[ch].as_ref(), self.outputs[ch].as_mut()) {
302 output[..n].copy_from_slice(&input[..n]);
303 }
304 }
305 }
306
307 /// Clear all output channels to silence.
308 pub fn clear_outputs(&mut self) {
309 let n = self.num_samples;
310 for opt in self.outputs[..self.num_output_channels].iter_mut() {
311 if let Some(output) = opt.as_mut() {
312 output[..n].fill(S::ZERO);
313 }
314 }
315 }
316
317 /// Apply a gain factor to all output channels.
318 pub fn apply_output_gain(&mut self, gain: S) {
319 let n = self.num_samples;
320 for opt in self.outputs[..self.num_output_channels].iter_mut() {
321 if let Some(output) = opt.as_mut() {
322 for sample in &mut output[..n] {
323 *sample = *sample * gain;
324 }
325 }
326 }
327 }
328}
329
330// =============================================================================
331// AuxiliaryBuffers - Sidechain and Aux Buses
332// =============================================================================
333
334/// Auxiliary audio buffers for sidechain and multi-bus processing.
335///
336/// Contains all non-main audio buses. Most plugins don't need this -
337/// they only use the main [`Buffer`].
338///
339/// # Type Parameter
340///
341/// `S` is the sample type, defaulting to `f32`. Use `AuxiliaryBuffers<f64>` for
342/// 64-bit double precision processing.
343///
344/// # Bus Indexing
345///
346/// Auxiliary buses are indexed starting from 0:
347/// - Bus 0: Sidechain (most common aux use case)
348/// - Bus 1+: Additional auxiliary I/O
349///
350/// # Real-Time Safety
351///
352/// This struct uses fixed-size stack storage. No heap allocations occur
353/// during construction or use.
354///
355/// # Example: Sidechain Access
356///
357/// ```ignore
358/// if let Some(sidechain) = aux.sidechain() {
359/// let key_signal = sidechain.input(0);
360/// // Use for compression keying, ducking, etc.
361/// }
362/// ```
363pub struct AuxiliaryBuffers<'a, S: Sample = f32> {
364 /// Auxiliary input buses (e.g., sidechain inputs)
365 /// Outer array: buses, Inner array: channels per bus
366 inputs: [[Option<&'a [S]>; MAX_CHANNELS]; MAX_AUX_BUSES],
367 /// Number of channels per input bus
368 input_channel_counts: [usize; MAX_AUX_BUSES],
369 /// Number of active input buses
370 num_input_buses: usize,
371
372 /// Auxiliary output buses (e.g., aux sends)
373 outputs: [[Option<&'a mut [S]>; MAX_CHANNELS]; MAX_AUX_BUSES],
374 /// Number of channels per output bus
375 output_channel_counts: [usize; MAX_AUX_BUSES],
376 /// Number of active output buses
377 num_output_buses: usize,
378
379 /// Number of samples in this processing block
380 num_samples: usize,
381}
382
383impl<'a, S: Sample> AuxiliaryBuffers<'a, S> {
384 /// Create new auxiliary buffers.
385 ///
386 /// This is called by the VST3 wrapper, not by plugin code.
387 /// Buses/channels beyond the limits are silently ignored.
388 #[inline]
389 pub fn new(
390 inputs: impl IntoIterator<Item = impl IntoIterator<Item = &'a [S]>>,
391 outputs: impl IntoIterator<Item = impl IntoIterator<Item = &'a mut [S]>>,
392 num_samples: usize,
393 ) -> Self {
394 // Initialize input buses
395 let mut input_arr: [[Option<&'a [S]>; MAX_CHANNELS]; MAX_AUX_BUSES] =
396 [[None; MAX_CHANNELS]; MAX_AUX_BUSES];
397 let mut input_channel_counts = [0usize; MAX_AUX_BUSES];
398 let mut num_input_buses = 0;
399
400 for (bus_idx, bus) in inputs.into_iter().take(MAX_AUX_BUSES).enumerate() {
401 let mut ch_count = 0;
402 for (ch_idx, slice) in bus.into_iter().take(MAX_CHANNELS).enumerate() {
403 input_arr[bus_idx][ch_idx] = Some(slice);
404 ch_count = ch_idx + 1;
405 }
406 input_channel_counts[bus_idx] = ch_count;
407 if ch_count > 0 {
408 num_input_buses = bus_idx + 1;
409 }
410 }
411
412 // Initialize output buses - need from_fn because &mut is not Copy
413 let mut output_arr: [[Option<&'a mut [S]>; MAX_CHANNELS]; MAX_AUX_BUSES] =
414 std::array::from_fn(|_| std::array::from_fn(|_| None));
415 let mut output_channel_counts = [0usize; MAX_AUX_BUSES];
416 let mut num_output_buses = 0;
417
418 for (bus_idx, bus) in outputs.into_iter().take(MAX_AUX_BUSES).enumerate() {
419 let mut ch_count = 0;
420 for (ch_idx, slice) in bus.into_iter().take(MAX_CHANNELS).enumerate() {
421 output_arr[bus_idx][ch_idx] = Some(slice);
422 ch_count = ch_idx + 1;
423 }
424 output_channel_counts[bus_idx] = ch_count;
425 if ch_count > 0 {
426 num_output_buses = bus_idx + 1;
427 }
428 }
429
430 Self {
431 inputs: input_arr,
432 input_channel_counts,
433 num_input_buses,
434 outputs: output_arr,
435 output_channel_counts,
436 num_output_buses,
437 num_samples,
438 }
439 }
440
441 /// Create empty auxiliary buffers (no aux buses).
442 #[inline]
443 pub fn empty() -> Self {
444 Self {
445 inputs: [[None; MAX_CHANNELS]; MAX_AUX_BUSES],
446 input_channel_counts: [0; MAX_AUX_BUSES],
447 num_input_buses: 0,
448 outputs: std::array::from_fn(|_| std::array::from_fn(|_| None)),
449 output_channel_counts: [0; MAX_AUX_BUSES],
450 num_output_buses: 0,
451 num_samples: 0,
452 }
453 }
454
455 // =========================================================================
456 // Info
457 // =========================================================================
458
459 /// Number of samples in this processing block.
460 #[inline]
461 pub fn num_samples(&self) -> usize {
462 self.num_samples
463 }
464
465 /// Number of auxiliary input buses.
466 #[inline]
467 pub fn num_input_buses(&self) -> usize {
468 self.num_input_buses
469 }
470
471 /// Number of auxiliary output buses.
472 #[inline]
473 pub fn num_output_buses(&self) -> usize {
474 self.num_output_buses
475 }
476
477 /// Returns true if there are no auxiliary buses.
478 #[inline]
479 pub fn is_empty(&self) -> bool {
480 self.num_input_buses == 0 && self.num_output_buses == 0
481 }
482
483 // =========================================================================
484 // Bus Access
485 // =========================================================================
486
487 /// Get the sidechain input bus (auxiliary input bus 0).
488 ///
489 /// This is the most common aux use case. Returns `None` if no
490 /// sidechain is connected.
491 ///
492 /// # Example
493 ///
494 /// ```ignore
495 /// let key_level = aux.sidechain()
496 /// .map(|sc| sc.rms(0))
497 /// .unwrap_or(0.0);
498 /// ```
499 #[inline]
500 pub fn sidechain(&self) -> Option<AuxInput<'_, S>> {
501 self.input(0)
502 }
503
504 /// Get an auxiliary input bus by index.
505 ///
506 /// Returns `None` if the bus doesn't exist or has no channels.
507 #[inline]
508 pub fn input(&self, bus: usize) -> Option<AuxInput<'_, S>> {
509 if bus >= MAX_AUX_BUSES {
510 return None;
511 }
512 let num_channels = self.input_channel_counts[bus];
513 if num_channels == 0 {
514 return None;
515 }
516 Some(AuxInput {
517 channels: &self.inputs[bus][..num_channels],
518 num_samples: self.num_samples,
519 })
520 }
521
522 /// Get a mutable auxiliary output bus by index.
523 ///
524 /// Returns `None` if the bus doesn't exist or has no channels.
525 #[inline]
526 pub fn output(&mut self, bus: usize) -> Option<AuxOutput<'_, 'a, S>> {
527 if bus >= MAX_AUX_BUSES {
528 return None;
529 }
530 let num_channels = self.output_channel_counts[bus];
531 if num_channels == 0 {
532 return None;
533 }
534 let num_samples = self.num_samples;
535 Some(AuxOutput {
536 channels: &mut self.outputs[bus][..num_channels],
537 num_samples,
538 })
539 }
540
541 // =========================================================================
542 // Iterators
543 // =========================================================================
544
545 /// Iterate over all auxiliary input buses.
546 #[inline]
547 pub fn iter_inputs(&self) -> impl Iterator<Item = AuxInput<'_, S>> + '_ {
548 let num_samples = self.num_samples;
549 self.inputs[..self.num_input_buses]
550 .iter()
551 .zip(self.input_channel_counts[..self.num_input_buses].iter())
552 .filter(|(_, &count)| count > 0)
553 .map(move |(channels, &count)| AuxInput {
554 channels: &channels[..count],
555 num_samples,
556 })
557 }
558
559 /// Iterate over all auxiliary output buses mutably.
560 #[inline]
561 pub fn iter_outputs(&mut self) -> impl Iterator<Item = AuxOutput<'_, 'a, S>> + '_ {
562 let num_samples = self.num_samples;
563 let num_buses = self.num_output_buses;
564 self.outputs[..num_buses]
565 .iter_mut()
566 .zip(self.output_channel_counts[..num_buses].iter())
567 .filter(|(_, &count)| count > 0)
568 .map(move |(channels, &count)| AuxOutput {
569 channels: &mut channels[..count],
570 num_samples,
571 })
572 }
573}
574
575// =============================================================================
576// AuxInput - Immutable Auxiliary Input Bus
577// =============================================================================
578
579/// Immutable view of an auxiliary input bus.
580///
581/// Provides access to input channels for a single auxiliary bus
582/// (typically sidechain). Created via [`AuxiliaryBuffers::sidechain()`]
583/// or [`AuxiliaryBuffers::input()`].
584///
585/// # Type Parameter
586///
587/// `S` is the sample type, defaulting to `f32`.
588///
589/// # Example: Block-Based Analysis
590///
591/// ```ignore
592/// if let Some(sc) = aux.sidechain() {
593/// // Calculate RMS of sidechain for keying
594/// let rms = sc.rms(0);
595///
596/// // Or iterate over all channels
597/// for ch in sc.iter_inputs() {
598/// // Process channel...
599/// }
600/// }
601/// ```
602///
603/// # Example: Sample-by-Sample Access
604///
605/// ```ignore
606/// if let Some(sc) = aux.sidechain() {
607/// for i in 0..buffer.num_samples() {
608/// // .sample() returns S::ZERO if channel/index missing
609/// let key_l = sc.sample(0, i).to_f64().abs();
610/// let key_r = sc.sample(1, i).to_f64().abs();
611/// let key = (key_l + key_r) * 0.5;
612///
613/// // Use for envelope follower, gate, ducker, etc.
614/// }
615/// }
616/// ```
617pub struct AuxInput<'a, S: Sample = f32> {
618 channels: &'a [Option<&'a [S]>],
619 num_samples: usize,
620}
621
622impl<'a, S: Sample> AuxInput<'a, S> {
623 /// Number of samples in each channel.
624 #[inline]
625 pub fn num_samples(&self) -> usize {
626 self.num_samples
627 }
628
629 /// Number of channels in this bus.
630 #[inline]
631 pub fn num_channels(&self) -> usize {
632 self.channels.len()
633 }
634
635 /// Get an input channel by index.
636 ///
637 /// Returns an empty slice if the channel doesn't exist.
638 /// Matches [`Buffer::input()`] naming for API consistency.
639 #[inline]
640 pub fn input(&self, index: usize) -> &[S] {
641 self.channels
642 .get(index)
643 .and_then(|opt| opt.as_ref())
644 .map(|ch| &ch[..self.num_samples])
645 .unwrap_or(&[])
646 }
647
648 /// Get a single sample from a channel.
649 ///
650 /// Returns [`S::ZERO`] if the channel or sample index doesn't exist.
651 /// This is a convenience method for sample-by-sample processing.
652 ///
653 /// # Example
654 ///
655 /// ```ignore
656 /// // Instead of:
657 /// let sc = sidechain.input(0).get(i).copied().unwrap_or(S::ZERO);
658 ///
659 /// // Use:
660 /// let sc = sidechain.sample(0, i);
661 /// ```
662 #[inline]
663 pub fn sample(&self, channel: usize, index: usize) -> S {
664 self.input(channel)
665 .get(index)
666 .copied()
667 .unwrap_or(S::ZERO)
668 }
669
670 /// Iterate over all input channels.
671 #[inline]
672 pub fn iter_inputs(&self) -> impl Iterator<Item = &[S]> + '_ {
673 let n = self.num_samples;
674 self.channels
675 .iter()
676 .filter_map(move |opt| opt.as_ref().map(|ch| &ch[..n]))
677 }
678
679 // =========================================================================
680 // Analysis Utilities
681 // =========================================================================
682
683 /// Calculate the RMS (root mean square) level of a channel.
684 ///
685 /// Returns zero if the channel doesn't exist or is empty.
686 pub fn rms(&self, channel: usize) -> S {
687 let ch = self.input(channel);
688 if ch.is_empty() {
689 return S::ZERO;
690 }
691 let sum: S = ch.iter().fold(S::ZERO, |acc, &s| acc + s * s);
692 let len = S::from_f32(ch.len() as f32);
693 (sum / len).sqrt()
694 }
695
696 /// Calculate the peak level of a channel.
697 ///
698 /// Returns zero if the channel doesn't exist or is empty.
699 pub fn peak(&self, channel: usize) -> S {
700 self.input(channel)
701 .iter()
702 .map(|&s| s.abs())
703 .fold(S::ZERO, |a, b| a.max(b))
704 }
705
706 /// Calculate the average absolute level of a channel.
707 ///
708 /// Returns zero if the channel doesn't exist or is empty.
709 pub fn average(&self, channel: usize) -> S {
710 let ch = self.input(channel);
711 if ch.is_empty() {
712 return S::ZERO;
713 }
714 let sum: S = ch.iter().map(|&s| s.abs()).fold(S::ZERO, |a, b| a + b);
715 let len = S::from_f32(ch.len() as f32);
716 sum / len
717 }
718}
719
720// =============================================================================
721// AuxOutput - Mutable Auxiliary Output Bus
722// =============================================================================
723
724/// Mutable view of an auxiliary output bus.
725///
726/// Provides access to output channels for a single auxiliary bus
727/// (e.g., aux sends, multi-out). Created via [`AuxiliaryBuffers::output()`].
728///
729/// # Type Parameter
730///
731/// `S` is the sample type, defaulting to `f32`.
732///
733/// # Lifetime Parameters
734///
735/// - `'borrow` - The borrow lifetime (from `&mut self` on `AuxiliaryBuffers`)
736/// - `'data` - The underlying audio data lifetime
737///
738/// This separation is required because `&'a mut [&'a mut T]` is invariant
739/// in Rust, which prevents returning borrowed data from methods.
740///
741/// # Example
742///
743/// ```ignore
744/// if let Some(mut aux_out) = aux.output(0) {
745/// // Write to aux output
746/// for sample in aux_out.output(0) {
747/// *sample = processed_signal;
748/// }
749/// }
750/// ```
751pub struct AuxOutput<'borrow, 'data, S: Sample = f32> {
752 channels: &'borrow mut [Option<&'data mut [S]>],
753 num_samples: usize,
754}
755
756impl<'borrow, 'data, S: Sample> AuxOutput<'borrow, 'data, S> {
757 /// Number of samples in each channel.
758 #[inline]
759 pub fn num_samples(&self) -> usize {
760 self.num_samples
761 }
762
763 /// Number of channels in this bus.
764 #[inline]
765 pub fn num_channels(&self) -> usize {
766 self.channels.len()
767 }
768
769 /// Get a mutable output channel by index.
770 ///
771 /// Matches [`Buffer::output()`] naming for API consistency.
772 ///
773 /// # Panics
774 ///
775 /// Panics if the channel index is out of bounds.
776 #[inline]
777 pub fn output(&mut self, index: usize) -> &mut [S] {
778 let n = self.num_samples;
779 self.channels[index]
780 .as_mut()
781 .map(|ch| &mut ch[..n])
782 .expect("aux output channel out of bounds")
783 }
784
785 /// Try to get a mutable output channel by index.
786 ///
787 /// Returns `None` if the channel doesn't exist.
788 #[inline]
789 pub fn output_checked(&mut self, index: usize) -> Option<&mut [S]> {
790 let n = self.num_samples;
791 self.channels
792 .get_mut(index)
793 .and_then(|opt| opt.as_mut())
794 .map(|ch| &mut ch[..n])
795 }
796
797 /// Iterate over all output channels mutably.
798 #[inline]
799 pub fn iter_outputs(&mut self) -> impl Iterator<Item = &mut [S]> + use<'_, 'data, S> {
800 let n = self.num_samples;
801 self.channels
802 .iter_mut()
803 .filter_map(move |opt| opt.as_mut().map(|ch| &mut ch[..n]))
804 }
805
806 /// Clear all channels to silence.
807 pub fn clear(&mut self) {
808 let n = self.num_samples;
809 for opt in self.channels.iter_mut() {
810 if let Some(ch) = opt.as_mut() {
811 ch[..n].fill(S::ZERO);
812 }
813 }
814 }
815
816 /// Fill all channels with a constant value.
817 pub fn fill(&mut self, value: S) {
818 let n = self.num_samples;
819 for opt in self.channels.iter_mut() {
820 if let Some(ch) = opt.as_mut() {
821 ch[..n].fill(value);
822 }
823 }
824 }
825}
826