Skip to main content

beat_this/
audio.rs

1use anyhow::{anyhow, Result};
2use rubato::{
3    Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction,
4};
5use std::fs::File;
6use std::path::Path;
7use symphonia::core::audio::SampleBuffer;
8use symphonia::core::codecs::{DecoderOptions, CODEC_TYPE_NULL};
9use symphonia::core::errors::Error;
10use symphonia::core::formats::FormatOptions;
11use symphonia::core::io::MediaSourceStream;
12use symphonia::core::meta::MetadataOptions;
13use symphonia::core::probe::Hint;
14
15/// Mono audio data at a known sample rate.
16pub struct AudioData {
17    pub samples: Vec<f32>,
18    pub sample_rate: u32,
19}
20
21/// Load an audio file, convert to mono, and resample to `target_sr`.
22///
23/// Supports MP3, WAV, FLAC, OGG, and other formats via symphonia.
24/// Uses high-quality sinc resampling via rubato when the source rate
25/// differs from `target_sr`.
26pub fn load_audio(path: &Path, target_sr: u32) -> Result<AudioData> {
27    let (samples, source_sr, channels) = decode(path)?;
28    let mono = to_mono(&samples, channels);
29    let resampled = resample(mono, source_sr, target_sr)?;
30
31    Ok(AudioData {
32        samples: resampled,
33        sample_rate: target_sr,
34    })
35}
36
37/// Decode an audio file into interleaved f32 samples.
38/// Returns (samples, sample_rate, channel_count).
39fn decode(path: &Path) -> Result<(Vec<f32>, u32, usize)> {
40    let src = File::open(path)?;
41    let mss = MediaSourceStream::new(Box::new(src), Default::default());
42
43    // Provide file extension hint for better format detection
44    let mut hint = Hint::new();
45    if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
46        hint.with_extension(ext);
47    }
48
49    let probed = symphonia::default::get_probe().format(
50        &hint,
51        mss,
52        &FormatOptions::default(),
53        &MetadataOptions::default(),
54    )?;
55    let mut format = probed.format;
56
57    let track = format
58        .tracks()
59        .iter()
60        .find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
61        .ok_or_else(|| anyhow!("no supported audio tracks"))?;
62
63    let track_id = track.id;
64    let mut decoder =
65        symphonia::default::get_codecs().make(&track.codec_params, &DecoderOptions::default())?;
66
67    let mut samples: Vec<f32> = Vec::new();
68    let mut source_sr = 0u32;
69    let mut channels = 0usize;
70
71    loop {
72        let packet = match format.next_packet() {
73            Ok(packet) => packet,
74            Err(Error::IoError(_)) => break,
75            Err(err) => return Err(anyhow!(err)),
76        };
77
78        if packet.track_id() != track_id {
79            continue;
80        }
81
82        match decoder.decode(&packet) {
83            Ok(decoded) => {
84                let spec = *decoded.spec();
85                source_sr = spec.rate;
86                channels = spec.channels.count();
87
88                let mut sample_buf = SampleBuffer::<f32>::new(decoded.capacity() as u64, spec);
89                sample_buf.copy_interleaved_ref(decoded);
90                samples.extend_from_slice(sample_buf.samples());
91            }
92            Err(Error::IoError(_)) => break,
93            Err(Error::DecodeError(_)) => (), // skip corrupted packets
94            Err(err) => return Err(anyhow!(err)),
95        }
96    }
97
98    if source_sr == 0 {
99        return Err(anyhow!("failed to decode any audio packets"));
100    }
101
102    Ok((samples, source_sr, channels))
103}
104
105/// Convert interleaved multi-channel audio to mono by averaging channels.
106fn to_mono(samples: &[f32], channels: usize) -> Vec<f32> {
107    if channels == 1 {
108        return samples.to_vec();
109    }
110    samples
111        .chunks(channels)
112        .map(|frame| frame.iter().sum::<f32>() / channels as f32)
113        .collect()
114}
115
116/// Resample mono audio from `source_sr` to `target_sr` using sinc interpolation.
117/// Returns samples unchanged if rates already match.
118pub fn resample(samples: Vec<f32>, source_sr: u32, target_sr: u32) -> Result<Vec<f32>> {
119    if source_sr == target_sr {
120        return Ok(samples);
121    }
122
123    let params = SincInterpolationParameters {
124        sinc_len: 256,
125        f_cutoff: 0.95,
126        interpolation: SincInterpolationType::Linear,
127        oversampling_factor: 256,
128        window: WindowFunction::BlackmanHarris2,
129    };
130
131    let mut resampler = SincFixedIn::<f32>::new(
132        target_sr as f64 / source_sr as f64,
133        2.0,
134        params,
135        samples.len(),
136        1, // mono
137    )?;
138
139    let waves_out = resampler.process(&[samples], None)?;
140    Ok(waves_out.into_iter().next().unwrap())
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_to_mono_passthrough() {
149        let samples = vec![1.0, 2.0, 3.0];
150        let mono = to_mono(&samples, 1);
151        assert_eq!(mono, vec![1.0, 2.0, 3.0]);
152    }
153
154    #[test]
155    fn test_to_mono_stereo() {
156        // Two frames of stereo: (0.5, 1.5) and (1.0, 3.0)
157        let samples = vec![0.5, 1.5, 1.0, 3.0];
158        let mono = to_mono(&samples, 2);
159        assert_eq!(mono, vec![1.0, 2.0]);
160    }
161
162    #[test]
163    fn test_resample_identity() {
164        // Same rate should return unchanged
165        let samples = vec![1.0, 2.0, 3.0, 4.0];
166        let result = resample(samples.clone(), 22050, 22050).unwrap();
167        assert_eq!(result, samples);
168    }
169}