Skip to main content

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