Skip to main content

pfx_sound/
clip.rs

1#[derive(Clone, Debug)]
2pub struct Clip {
3    pub rate: u32,
4    pub channels: u16,
5    pub samples: Vec<f32>,
6}
7
8impl Clip {
9    pub fn from_wav(bytes: &[u8]) -> Option<Clip> {
10        crate::wav::parse(bytes)
11    }
12
13    pub fn to_wav(&self) -> Vec<u8> {
14        crate::wav::write_f32(self)
15    }
16
17    pub fn to_wav_i16(&self) -> Vec<u8> {
18        crate::wav::write_i16(self)
19    }
20
21    pub fn reverse(&mut self) {
22        let channels = self.channels as usize;
23        if channels == 0 {
24            return;
25        }
26        let frames: Vec<&[f32]> = self.samples.chunks_exact(channels).collect();
27        let mut reversed = Vec::with_capacity(frames.len().saturating_mul(channels));
28        for frame in frames.into_iter().rev() {
29            reversed.extend_from_slice(frame);
30        }
31        self.samples = reversed;
32    }
33
34    pub fn seconds(&self) -> f32 {
35        if self.rate == 0 || self.channels == 0 {
36            return 0.0;
37        }
38        self.samples.len() as f32 / (self.rate as f32 * self.channels as f32)
39    }
40
41    pub fn frames(&self) -> u64 {
42        if self.channels == 0 {
43            return 0;
44        }
45        (self.samples.len() / self.channels as usize) as u64
46    }
47
48    pub fn for_rate(&self, rate: u32) -> Clip {
49        if self.rate == rate {
50            return self.clone();
51        }
52        Clip {
53            rate,
54            channels: self.channels,
55            samples: self.at_rate(rate, self.channels),
56        }
57    }
58
59    pub fn at_rate(&self, rate: u32, channels: u16) -> Vec<f32> {
60        if self.rate == rate && self.channels == channels {
61            return self.samples.clone();
62        }
63        if self.rate == 0 || self.channels == 0 || rate == 0 || channels == 0 {
64            return Vec::new();
65        }
66        if self.rate == rate {
67            return remap(&self.samples, self.channels, channels);
68        }
69        resample(self, rate, channels)
70    }
71}
72
73fn remap(samples: &[f32], src_channels: u16, dst_channels: u16) -> Vec<f32> {
74    let src_channels = src_channels as usize;
75    let dst_channels = dst_channels as usize;
76    if src_channels == 0 || dst_channels == 0 {
77        return Vec::new();
78    }
79    let frames = samples.len() / src_channels;
80    let mut out = Vec::with_capacity(frames.saturating_mul(dst_channels));
81    for frame in 0..frames {
82        for channel in 0..dst_channels {
83            let src = channel.min(src_channels - 1);
84            out.push(samples[frame * src_channels + src]);
85        }
86    }
87    out
88}
89
90fn resample(clip: &Clip, rate: u32, channels: u16) -> Vec<f32> {
91    let src_channels = clip.channels as usize;
92    let frames_in = clip.samples.len() / src_channels;
93    let frames_out = scaled_frames(frames_in, clip.rate, rate);
94    if frames_out == 0 || clip.samples.is_empty() {
95        return Vec::new();
96    }
97    let dst_channels = channels as usize;
98    let last = clip.samples.len() - 1;
99    let mut out = Vec::with_capacity(frames_out.saturating_mul(dst_channels));
100    for i in 0..frames_out {
101        let pos = i as f64 * f64::from(clip.rate) / f64::from(rate);
102        let base = pos.floor().max(0.0) as usize;
103        let frac = (pos - base as f64) as f32;
104        for channel in 0..dst_channels {
105            let src = channel.min(src_channels - 1);
106            let a = clip.samples[(base.saturating_mul(src_channels).saturating_add(src)).min(last)];
107            let b = clip.samples[(base
108                .saturating_add(1)
109                .saturating_mul(src_channels)
110                .saturating_add(src))
111            .min(last)];
112            out.push(a + (b - a) * frac);
113        }
114    }
115    out
116}
117
118fn scaled_frames(frames_in: usize, from: u32, to: u32) -> usize {
119    if from == 0 {
120        return 0;
121    }
122    let frames = (frames_in as u128).saturating_mul(u128::from(to)) / u128::from(from);
123    usize::try_from(frames).unwrap_or(usize::MAX)
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    fn bits(samples: &[f32]) -> Vec<u32> {
131        samples.iter().map(|sample| sample.to_bits()).collect()
132    }
133
134    fn pcm16(rate: u32, channels: u16, frames: &[i16]) -> Vec<u8> {
135        let data: Vec<u8> = frames
136            .iter()
137            .flat_map(|sample| sample.to_le_bytes())
138            .collect();
139        let mut out = Vec::new();
140        out.extend_from_slice(b"RIFF");
141        out.extend_from_slice(&(36 + data.len() as u32).to_le_bytes());
142        out.extend_from_slice(b"WAVE");
143        out.extend_from_slice(b"fmt ");
144        out.extend_from_slice(&16u32.to_le_bytes());
145        out.extend_from_slice(&1u16.to_le_bytes());
146        out.extend_from_slice(&channels.to_le_bytes());
147        out.extend_from_slice(&rate.to_le_bytes());
148        out.extend_from_slice(&(rate * channels as u32 * 2).to_le_bytes());
149        out.extend_from_slice(&(channels * 2).to_le_bytes());
150        out.extend_from_slice(&16u16.to_le_bytes());
151        out.extend_from_slice(b"fact");
152        out.extend_from_slice(&4u32.to_le_bytes());
153        out.extend_from_slice(&(frames.len() as u32 / u32::from(channels.max(1))).to_le_bytes());
154        out.extend_from_slice(b"data");
155        out.extend_from_slice(&(data.len() as u32).to_le_bytes());
156        out.extend_from_slice(&data);
157        out
158    }
159
160    #[test]
161    fn a_pcm16_wav_parses_to_unit_samples() {
162        let clip = Clip::from_wav(&pcm16(48_000, 1, &[0, 16384, -32768])).unwrap();
163        assert_eq!(clip.rate, 48_000);
164        assert_eq!(clip.channels, 1);
165        assert_eq!(bits(&clip.samples), bits(&[0.0, 0.5, -1.0]));
166    }
167
168    #[test]
169    fn garbage_is_not_a_clip() {
170        assert!(Clip::from_wav(b"not a wav").is_none());
171        assert!(Clip::from_wav(&[]).is_none());
172        assert!(Clip::from_wav(&pcm16(0, 1, &[0])).is_none());
173    }
174
175    #[test]
176    fn eight_bit_samples_are_centred_on_128() {
177        let data = [0u8, 128, 255];
178        let mut bytes = Vec::new();
179        bytes.extend_from_slice(b"RIFF");
180        bytes.extend_from_slice(&(36 + data.len() as u32).to_le_bytes());
181        bytes.extend_from_slice(b"WAVE");
182        bytes.extend_from_slice(b"fmt ");
183        bytes.extend_from_slice(&16u32.to_le_bytes());
184        bytes.extend_from_slice(&1u16.to_le_bytes());
185        bytes.extend_from_slice(&1u16.to_le_bytes());
186        bytes.extend_from_slice(&8_000u32.to_le_bytes());
187        bytes.extend_from_slice(&8_000u32.to_le_bytes());
188        bytes.extend_from_slice(&1u16.to_le_bytes());
189        bytes.extend_from_slice(&8u16.to_le_bytes());
190        bytes.extend_from_slice(b"data");
191        bytes.extend_from_slice(&(data.len() as u32).to_le_bytes());
192        bytes.extend_from_slice(&data);
193        let clip = Clip::from_wav(&bytes).unwrap();
194        assert_eq!(bits(&clip.samples), bits(&[-1.0, 0.0, 127.0 / 128.0]));
195    }
196
197    #[test]
198    fn wav_f32_and_pcm16_round_trip() {
199        let clip = Clip {
200            rate: 48_000,
201            channels: 2,
202            samples: vec![0.0, 1.0, -1.0, 0.1, -0.3],
203        };
204        let back = Clip::from_wav(&clip.to_wav()).unwrap();
205        assert_eq!(back.rate, 48_000);
206        assert_eq!(back.channels, 2);
207        assert_eq!(bits(&back.samples), bits(&clip.samples));
208        assert_eq!(&clip.to_wav()[20..22], &3u16.to_le_bytes());
209        assert_eq!(&clip.to_wav()[34..36], &32u16.to_le_bytes());
210
211        let pcm = Clip {
212            rate: 44_100,
213            channels: 1,
214            samples: vec![0.0, 0.5, -1.0],
215        };
216        let back = Clip::from_wav(&pcm.to_wav_i16()).unwrap();
217        assert_eq!(bits(&back.samples), bits(&[0.0, 0.5, -1.0]));
218        assert_eq!(&pcm.to_wav_i16()[20..22], &1u16.to_le_bytes());
219        assert_eq!(&pcm.to_wav_i16()[34..36], &16u16.to_le_bytes());
220    }
221
222    #[test]
223    fn reversing_keeps_stereo_frames_paired() {
224        let mut clip = Clip::from_wav(&pcm16(48_000, 2, &[1, 2, 3, 4, 5, 6])).unwrap();
225        clip.reverse();
226        let back: Vec<i32> = clip
227            .samples
228            .iter()
229            .map(|sample| (sample * 32768.0).round() as i32)
230            .collect();
231        assert_eq!(back, vec![5, 6, 3, 4, 1, 2]);
232    }
233
234    #[test]
235    fn matching_rates_copy_samples_and_a_rate_change_interpolates() {
236        let clip = Clip {
237            rate: 24_000,
238            channels: 1,
239            samples: vec![0.0, 1.0, 0.0, -1.0],
240        };
241        assert_eq!(bits(&clip.at_rate(24_000, 1)), bits(&clip.samples));
242        let spread = clip.at_rate(24_000, 2);
243        assert_eq!(
244            bits(&spread),
245            bits(&[0.0, 0.0, 1.0, 1.0, 0.0, 0.0, -1.0, -1.0])
246        );
247        let up = clip.at_rate(48_000, 1);
248        assert_eq!(
249            bits(&up),
250            bits(&[0.0, 0.5, 1.0, 0.5, 0.0, -0.5, -1.0, -1.0])
251        );
252        let tone = Clip::from_wav(&pcm16(24_000, 1, &[16384; 240])).unwrap();
253        let stereo = tone.at_rate(48_000, 2);
254        assert_eq!(stereo.len(), 960);
255        assert_eq!(bits(&stereo[..2]), bits(&[0.5, 0.5]));
256        assert!((tone.seconds() - 0.01).abs() < 0.0001);
257    }
258}