Skip to main content

resonant_stream/
chunk.rs

1extern crate alloc;
2
3use alloc::vec::Vec;
4
5/// A buffer of audio samples with associated metadata.
6///
7/// `Chunk` is the unit of data that flows between [`DspNode`](crate::DspNode)s
8/// in a pipeline. It owns its sample buffer so that nodes can process in-place
9/// without extra allocations.
10///
11/// Samples are stored interleaved when `channels > 1`:
12/// `[L0, R0, L1, R1, ...]` for stereo.
13///
14/// # Examples
15///
16/// ```
17/// use resonant_stream::Chunk;
18///
19/// let chunk = Chunk::new(vec![0.0_f32; 1024], 44100, 1);
20/// assert_eq!(chunk.frames(), 1024);
21/// assert_eq!(chunk.sample_rate(), 44100);
22/// ```
23#[derive(Debug, Clone, PartialEq)]
24pub struct Chunk {
25    data: Vec<f32>,
26    sample_rate: u32,
27    channels: u16,
28}
29
30impl Chunk {
31    /// Creates a new chunk from interleaved sample data.
32    ///
33    /// # Panics
34    ///
35    /// Panics if `channels` is zero or `sample_rate` is zero.
36    #[must_use]
37    pub fn new(data: Vec<f32>, sample_rate: u32, channels: u16) -> Self {
38        assert!(channels > 0, "channels must be > 0");
39        assert!(sample_rate > 0, "sample_rate must be > 0");
40        Self {
41            data,
42            sample_rate,
43            channels,
44        }
45    }
46
47    /// Creates an empty chunk with the given format.
48    ///
49    /// # Panics
50    ///
51    /// Panics if `channels` is zero or `sample_rate` is zero.
52    #[must_use]
53    pub fn empty(sample_rate: u32, channels: u16) -> Self {
54        Self::new(Vec::new(), sample_rate, channels)
55    }
56
57    /// Returns a reference to the interleaved sample data.
58    #[inline]
59    #[must_use]
60    pub fn data(&self) -> &[f32] {
61        &self.data
62    }
63
64    /// Returns a mutable reference to the interleaved sample data.
65    #[inline]
66    pub fn data_mut(&mut self) -> &mut [f32] {
67        &mut self.data
68    }
69
70    /// Consumes the chunk, returning the underlying sample buffer.
71    #[inline]
72    #[must_use]
73    pub fn into_data(self) -> Vec<f32> {
74        self.data
75    }
76
77    /// Replaces the sample data, reusing this chunk's metadata.
78    #[doc(hidden)]
79    #[inline]
80    pub fn set_data(&mut self, data: Vec<f32>) {
81        self.data = data;
82    }
83
84    /// The sample rate in Hz.
85    #[inline]
86    #[must_use]
87    pub fn sample_rate(&self) -> u32 {
88        self.sample_rate
89    }
90
91    /// The number of interleaved channels.
92    #[inline]
93    #[must_use]
94    pub fn channels(&self) -> u16 {
95        self.channels
96    }
97
98    /// The number of sample frames (total samples / channels).
99    #[inline]
100    #[must_use]
101    pub fn frames(&self) -> usize {
102        if self.channels == 0 {
103            return 0;
104        }
105        self.data.len() / self.channels as usize
106    }
107
108    /// Returns `true` if the chunk contains no samples.
109    #[inline]
110    #[must_use]
111    pub fn is_empty(&self) -> bool {
112        self.data.is_empty()
113    }
114
115    /// The total number of samples (frames * channels).
116    #[inline]
117    #[must_use]
118    pub fn len(&self) -> usize {
119        self.data.len()
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn new_mono_chunk() {
129        let chunk = Chunk::new(vec![1.0, 2.0, 3.0, 4.0], 44100, 1);
130        assert_eq!(chunk.frames(), 4);
131        assert_eq!(chunk.len(), 4);
132        assert_eq!(chunk.channels(), 1);
133        assert_eq!(chunk.sample_rate(), 44100);
134        assert!(!chunk.is_empty());
135    }
136
137    #[test]
138    fn new_stereo_chunk() {
139        let chunk = Chunk::new(vec![1.0, 2.0, 3.0, 4.0], 48000, 2);
140        assert_eq!(chunk.frames(), 2);
141        assert_eq!(chunk.len(), 4);
142        assert_eq!(chunk.channels(), 2);
143    }
144
145    #[test]
146    fn empty_chunk() {
147        let chunk = Chunk::empty(44100, 1);
148        assert!(chunk.is_empty());
149        assert_eq!(chunk.frames(), 0);
150        assert_eq!(chunk.len(), 0);
151    }
152
153    #[test]
154    fn data_access() {
155        let chunk = Chunk::new(vec![0.5, -0.5], 44100, 1);
156        assert_eq!(chunk.data(), &[0.5, -0.5]);
157    }
158
159    #[test]
160    fn data_mut_allows_modification() {
161        let mut chunk = Chunk::new(vec![0.0; 4], 44100, 1);
162        chunk.data_mut()[0] = 1.0;
163        assert_eq!(chunk.data()[0], 1.0);
164    }
165
166    #[test]
167    fn into_data_returns_buffer() {
168        let chunk = Chunk::new(vec![1.0, 2.0], 44100, 1);
169        let data = chunk.into_data();
170        assert_eq!(data, vec![1.0, 2.0]);
171    }
172
173    #[test]
174    fn set_data_replaces_buffer() {
175        let mut chunk = Chunk::new(vec![1.0], 44100, 1);
176        chunk.set_data(vec![2.0, 3.0]);
177        assert_eq!(chunk.data(), &[2.0, 3.0]);
178        assert_eq!(chunk.frames(), 2);
179    }
180
181    #[test]
182    fn clone_and_eq() {
183        let a = Chunk::new(vec![1.0, 2.0], 44100, 2);
184        let b = a.clone();
185        assert_eq!(a, b);
186    }
187
188    #[test]
189    #[should_panic(expected = "channels must be > 0")]
190    fn zero_channels_panics() {
191        let _ = Chunk::new(vec![1.0], 44100, 0);
192    }
193
194    #[test]
195    #[should_panic(expected = "sample_rate must be > 0")]
196    fn zero_sample_rate_panics() {
197        let _ = Chunk::new(vec![1.0], 0, 1);
198    }
199}