Skip to main content

audiobox/
audio.rs

1use crate::math::sin_turns;
2use crate::{DecodeInfo, Error, Limits, Result, TimePosition};
3use std::fmt;
4
5/// Planar floating-point audio. All channels have the same length.
6/// Samples may exceed ±1 to retain headroom; encoders clamp at quantization.
7#[derive(Clone, Debug)]
8pub struct Audio {
9    pub(crate) data: Vec<Vec<f32>>,
10    pub(crate) rate: u32,
11    pub(crate) info: DecodeInfo,
12    /// Unique for the life of the process; clones share it, since audio never changes.
13    /// Only the mixer uses it, which also needs 64-bit atomics.
14    #[cfg(feature = "mixer")]
15    id: u64,
16}
17// Audio and event identities share the upper half of the mixer instance key space.
18#[cfg(feature = "mixer")]
19use crate::identity::next as next_id;
20impl Audio {
21    /// This audio's identity: unique among the audio values made by the process, and
22    /// shared by clones.
23    #[cfg(feature = "mixer")]
24    pub(crate) fn id(&self) -> u64 {
25        self.id
26    }
27    pub fn new(data: Vec<Vec<f32>>, sample_rate: u32) -> Result<Self> {
28        let frames = data.first().map_or(0, Vec::len);
29        Limits::default().check(frames, data.len(), sample_rate)?;
30        if data
31            .iter()
32            .any(|c| c.len() != frames || c.iter().any(|s| !s.is_finite()))
33        {
34            return Err(Error::invalid(
35                "Channels must have equal lengths and finite samples",
36            ));
37        }
38        Ok(Self {
39            data,
40            rate: sample_rate,
41            info: DecodeInfo::default(),
42            #[cfg(feature = "mixer")]
43            id: next_id(),
44        })
45    }
46    pub(crate) fn decoded(data: Vec<Vec<f32>>, rate: u32, info: DecodeInfo) -> Result<Self> {
47        if data.is_empty()
48            || rate == 0
49            || data
50                .iter()
51                .any(|c| c.len() != data[0].len() || c.iter().any(|s| !s.is_finite()))
52        {
53            return Err(Error::decode("Invalid decoded PCM"));
54        }
55        Ok(Self {
56            data,
57            rate,
58            info,
59            #[cfg(feature = "mixer")]
60            id: next_id(),
61        })
62    }
63    pub(crate) fn with_data(&self, data: Vec<Vec<f32>>) -> Self {
64        Self {
65            data,
66            rate: self.rate,
67            info: self.info.clone(),
68            #[cfg(feature = "mixer")]
69            id: next_id(),
70        }
71    }
72    pub fn sample_rate(&self) -> u32 {
73        self.rate
74    }
75    pub fn channels(&self) -> usize {
76        self.data.len()
77    }
78    pub fn frames(&self) -> usize {
79        self.data[0].len()
80    }
81    pub fn duration(&self) -> f64 {
82        self.frames() as f64 / self.rate as f64
83    }
84    pub fn info(&self) -> &DecodeInfo {
85        &self.info
86    }
87    pub fn channel_data(&self, channel: usize) -> Result<&[f32]> {
88        self.data
89            .get(channel)
90            .map(Vec::as_slice)
91            .ok_or_else(|| Error::invalid("Channel index out of range"))
92    }
93    pub fn all_channels(&self) -> &[Vec<f32>] {
94        &self.data
95    }
96    pub fn to_interleaved(&self) -> Vec<f32> {
97        let mut out = Vec::with_capacity(self.frames() * self.channels());
98        for frame in 0..self.frames() {
99            for channel in &self.data {
100                out.push(channel[frame]);
101            }
102        }
103        out
104    }
105    pub fn from_interleaved(samples: &[f32], channels: usize, rate: u32) -> Result<Self> {
106        if channels == 0 || samples.len() % channels != 0 {
107            return Err(Error::invalid("Incomplete interleaved frames"));
108        }
109        Limits::default().check(samples.len() / channels, channels, rate)?;
110        let mut data = vec![vec![0.0; samples.len() / channels]; channels];
111        for (i, frame) in samples.chunks_exact(channels).enumerate() {
112            for (c, &v) in frame.iter().enumerate() {
113                data[c][i] = v;
114            }
115        }
116        Self::new(data, rate)
117    }
118    pub fn silence(duration: impl Into<TimePosition>, channels: usize, rate: u32) -> Result<Self> {
119        let frames = duration.into().duration(rate)?;
120        Limits::default().check(frames, channels, rate)?;
121        Self::new(vec![vec![0.0; frames]; channels], rate)
122    }
123    pub fn cut(&self, from: impl Into<TimePosition>, to: impl Into<TimePosition>) -> Result<Self> {
124        let start = from.into().index(self.rate, self.frames())?;
125        let end = to.into().index(self.rate, self.frames())?.max(start);
126        Ok(self.with_data(self.data.iter().map(|c| c[start..end].to_vec()).collect()))
127    }
128    pub fn remove(
129        &self,
130        from: impl Into<TimePosition>,
131        to: impl Into<TimePosition>,
132    ) -> Result<Self> {
133        let start = from.into().index(self.rate, self.frames())?;
134        let end = to.into().index(self.rate, self.frames())?.max(start);
135        Ok(self.with_data(
136            self.data
137                .iter()
138                .map(|c| c[..start].iter().chain(&c[end..]).copied().collect())
139                .collect(),
140        ))
141    }
142    pub(crate) fn same_shape(&self, other: &Self) -> Result<()> {
143        if self.rate != other.rate || self.channels() != other.channels() {
144            return Err(Error::invalid("Sample rates and channel counts must match"));
145        }
146        Ok(())
147    }
148    pub fn concat(&self, other: &Self) -> Result<Self> {
149        self.same_shape(other)?;
150        let frames = self
151            .frames()
152            .checked_add(other.frames())
153            .ok_or_else(|| Error::limit("Frame count overflow"))?;
154        Limits::default().check(frames, self.channels(), self.rate)?;
155        Ok(self.with_data(
156            self.data
157                .iter()
158                .zip(&other.data)
159                .map(|(a, b)| a.iter().chain(b).copied().collect())
160                .collect(),
161        ))
162    }
163    pub fn pad(
164        &self,
165        before: impl Into<TimePosition>,
166        after: impl Into<TimePosition>,
167    ) -> Result<Self> {
168        let before = before.into().duration(self.rate)?;
169        let after = after.into().duration(self.rate)?;
170        let frames = before
171            .checked_add(self.frames())
172            .and_then(|n| n.checked_add(after))
173            .ok_or_else(|| Error::limit("Frame count overflow"))?;
174        Limits::default().check(frames, self.channels(), self.rate)?;
175        Ok(self.with_data(
176            self.data
177                .iter()
178                .map(|c| {
179                    let mut out = vec![0.0; frames];
180                    out[before..before + c.len()].copy_from_slice(c);
181                    out
182                })
183                .collect(),
184        ))
185    }
186    pub fn reverse(&self) -> Self {
187        self.with_data(
188            self.data
189                .iter()
190                .map(|c| c.iter().rev().copied().collect())
191                .collect(),
192        )
193    }
194    pub fn mix(&self, overlay: &Self, offset: i64, gain: f32, extend: bool) -> Result<Self> {
195        self.same_shape(overlay)?;
196        if !gain.is_finite() {
197            return Err(Error::invalid("Gain must be finite"));
198        }
199        let needed = (self.frames() as i128).max(offset as i128 + overlay.frames() as i128);
200        let frames = if extend {
201            usize::try_from(needed).map_err(|_| Error::limit("Frame count overflow"))?
202        } else {
203            self.frames()
204        };
205        Limits::default().check(frames, self.channels(), self.rate)?;
206        let mut out = self.data.clone();
207        for (c, dst) in out.iter_mut().enumerate() {
208            dst.resize(frames, 0.0);
209            for (i, &s) in overlay.data[c].iter().enumerate() {
210                let at = offset as i128 + i as i128;
211                if at >= 0 && at < frames as i128 {
212                    dst[at as usize] += s * gain;
213                }
214            }
215        }
216        if out.iter().flatten().any(|s| !s.is_finite()) {
217            return Err(Error::invalid("Mix overflow"));
218        }
219        Ok(self.with_data(out))
220    }
221    pub fn crossfade_to(&self, other: &Self, duration: impl Into<TimePosition>) -> Result<Self> {
222        self.same_shape(other)?;
223        let n = duration
224            .into()
225            .duration(self.rate)?
226            .min(self.frames())
227            .min(other.frames());
228        let frames = self
229            .frames()
230            .checked_add(other.frames())
231            .and_then(|v| v.checked_sub(n))
232            .ok_or_else(|| Error::limit("Frame count overflow"))?;
233        Limits::default().check(frames, self.channels(), self.rate)?;
234        let mut data = Vec::new();
235        for (a, b) in self.data.iter().zip(&other.data) {
236            let mut out = a[..a.len() - n].to_vec();
237            for i in 0..n {
238                // An equal-power crossfade over a quarter turn: cos and sin of t·π/2.
239                let turns = i as f64 / n.saturating_sub(1).max(1) as f64 / 4.0;
240                let (fade_out, fade_in) = (sin_turns(0.25 - turns), sin_turns(turns));
241                out.push(a[a.len() - n + i] * fade_out as f32 + b[i] * fade_in as f32);
242            }
243            out.extend_from_slice(&b[n..]);
244            data.push(out);
245        }
246        Ok(self.with_data(data))
247    }
248    pub fn to_mono(&self) -> Self {
249        let mut mono = vec![0.0; self.frames()];
250        for c in &self.data {
251            for (v, s) in mono.iter_mut().zip(c) {
252                *v += *s / self.channels() as f32;
253            }
254        }
255        self.with_data(vec![mono])
256    }
257    pub fn to_stereo(&self) -> Self {
258        if self.channels() == 2 {
259            return self.clone();
260        }
261        let mono = self.to_mono();
262        self.with_data(vec![mono.data[0].clone(), mono.data[0].clone()])
263    }
264    pub fn to_channels(&self, count: usize) -> Result<Self> {
265        Limits::default().check(self.frames(), count, self.rate)?;
266        match count {
267            1 => Ok(self.to_mono()),
268            2 => Ok(self.to_stereo()),
269            _ => Ok(self.with_data(
270                (0..count)
271                    .map(|c| self.data[c % self.channels()].clone())
272                    .collect(),
273            )),
274        }
275    }
276    pub fn map_channels(&self, mapping: &[usize]) -> Result<Self> {
277        Limits::default().check(self.frames(), mapping.len(), self.rate)?;
278        let data = mapping
279            .iter()
280            .map(|&i| self.channel_data(i).map(<[f32]>::to_vec))
281            .collect::<Result<_>>()?;
282        Ok(self.with_data(data))
283    }
284    pub fn pan(&self, position: f64) -> Result<Self> {
285        if !position.is_finite() {
286            return Err(Error::invalid("Pan must be finite"));
287        }
288        // cos θ and sin θ with θ = (position + 1)·π/4, as the mixer pans.
289        let turns = (position.clamp(-1.0, 1.0) + 1.0) / 8.0;
290        let (left, right) = (sin_turns(0.25 - turns) as f32, sin_turns(turns) as f32);
291        let mut out = self.to_stereo();
292        // Stereo input comes back as a clone; the panned samples are a new sound.
293        #[cfg(feature = "mixer")]
294        {
295            out.id = next_id();
296        }
297        for s in &mut out.data[0] {
298            *s *= left;
299        }
300        for s in &mut out.data[1] {
301            *s *= right;
302        }
303        Ok(out)
304    }
305    pub fn split(&self) -> Vec<Self> {
306        self.data
307            .iter()
308            .map(|c| self.with_data(vec![c.clone()]))
309            .collect()
310    }
311    pub fn merge(parts: &[Self]) -> Result<Self> {
312        let first = parts
313            .first()
314            .ok_or_else(|| Error::invalid("No audio to merge"))?;
315        if parts.iter().any(|p| p.rate != first.rate) {
316            return Err(Error::invalid("Sample rates must match"));
317        }
318        let frames = parts.iter().map(Self::frames).max().unwrap_or(0);
319        let channels = parts
320            .iter()
321            .try_fold(0usize, |n, p| n.checked_add(p.channels()))
322            .ok_or_else(|| Error::limit("Channel count overflow"))?;
323        Limits::default().check(frames, channels, first.rate)?;
324        let data = parts
325            .iter()
326            .flat_map(|p| {
327                p.data.iter().map(|c| {
328                    let mut out = c.clone();
329                    out.resize(frames, 0.0);
330                    out
331                })
332            })
333            .collect();
334        Ok(first.with_data(data))
335    }
336    pub fn duration_formatted(&self) -> String {
337        let ms = (self.duration() * 1000.0).round() as u64;
338        let h = ms / 3600000;
339        let m = ms / 60000 % 60;
340        let s = ms / 1000 % 60;
341        if h > 0 {
342            format!("{h}:{m:02}:{s:02}.{:03}", ms % 1000)
343        } else {
344            format!("{m}:{s:02}.{:03}", ms % 1000)
345        }
346    }
347}
348impl fmt::Display for Audio {
349    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350        write!(
351            f,
352            "Audio({}, {} Hz, {}ch)",
353            self.duration_formatted(),
354            self.rate,
355            self.channels()
356        )
357    }
358}