mecomp_analysis/decoder/
mecomp.rs1use std::{fs::File, io::BufReader};
4
5use rodio::Source;
6use rubato::{FastFixedIn, PolynomialDegree, Resampler};
7
8use crate::{errors::AnalysisError, errors::AnalysisResult, ResampledAudio, SAMPLE_RATE};
9
10use super::Decoder;
11
12#[allow(clippy::module_name_repetitions)]
13pub struct MecompDecoder();
14
15impl Decoder for MecompDecoder {
16 #[allow(clippy::missing_inline_in_public_items)]
22 fn decode(path: &std::path::Path) -> AnalysisResult<ResampledAudio> {
23 let file = BufReader::new(File::open(path)?);
24 let source = rodio::Decoder::new(file)?.convert_samples::<f32>();
25
26 let num_channels = source.channels() as usize;
34 let sample_rate = source.sample_rate();
35 let Some(total_duration) = source.total_duration() else {
36 return Err(AnalysisError::InfiniteAudioSource);
37 };
38 let mut mono_sample_array = if num_channels == 1 {
39 source.into_iter().collect()
40 } else {
41 source.into_iter().enumerate().fold(
42 #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
44 Vec::with_capacity((total_duration.as_secs() as usize + 1) * sample_rate as usize),
45 |mut acc, (i, sample)| {
47 let channel = i % num_channels;
48 #[allow(clippy::cast_precision_loss)]
49 if channel == 0 {
50 acc.push(sample / num_channels as f32);
51 } else {
52 let last_index = acc.len() - 1;
53 acc[last_index] = sample.mul_add(1. / num_channels as f32, acc[last_index]);
54 }
55 acc
56 },
57 )
58 };
59
60 let resampled_array = if sample_rate == SAMPLE_RATE {
62 mono_sample_array.shrink_to_fit();
63 mono_sample_array
64 } else {
65 let mut resampler = FastFixedIn::new(
66 f64::from(SAMPLE_RATE) / f64::from(sample_rate),
67 1.0,
68 PolynomialDegree::Cubic,
69 mono_sample_array.len(),
70 1,
71 )?;
72 resampler.process(&[&mono_sample_array], None)?[0].clone()
73 };
74
75 Ok(ResampledAudio {
76 path: path.to_owned(),
77 samples: resampled_array,
78 })
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::{Decoder as DecoderTrait, MecompDecoder as Decoder};
85 use adler32::RollingAdler32;
86 use pretty_assertions::assert_eq;
87 use rstest::rstest;
88 use std::path::Path;
89
90 fn _test_decode(path: &Path, expected_hash: u32) {
91 let song = Decoder::decode(path).unwrap();
92 let mut hasher = RollingAdler32::new();
93 for sample in &song.samples {
94 hasher.update_buffer(&sample.to_le_bytes());
95 }
96
97 assert_eq!(expected_hash, hasher.hash());
98 }
99
100 #[rstest]
103 #[ignore = "fails when asked to convert stereo to mono, ig ffmpeg does it differently, but I'm not sure what the difference actually is"]
104 #[case::resample_multi(Path::new("data/s32_stereo_44_1_kHz.flac"), 0xbbcb_a1cf)]
105 #[ignore = "fails when asked to convert stereo to mono, ig ffmpeg does it differently, but I'm not sure what the difference actually is"]
106 #[case::resample_stereo(Path::new("data/s16_stereo_22_5kHz.flac"), 0x1d7b_2d6d)]
107 #[case::decode_mono(Path::new("data/s16_mono_22_5kHz.flac"), 0x5e01_930b)]
108 #[ignore = "fails when asked to convert stereo to mono, ig ffmpeg does it differently, but I'm not sure what the difference actually is"]
109 #[case::decode_mp3(Path::new("data/s32_stereo_44_1_kHz.mp3"), 0x69ca_6906)]
110 #[case::decode_wav(Path::new("data/piano.wav"), 0xde83_1e82)]
111 fn test_decode(#[case] path: &Path, #[case] expected_hash: u32) {
112 _test_decode(path, expected_hash);
113 }
114
115 #[test]
116 fn test_dont_panic_no_channel_layout() {
117 let path = Path::new("data/no_channel.wav");
118 Decoder::decode(path).unwrap();
119 }
120
121 #[test]
122 fn test_decode_right_capacity_vec() {
123 let path = Path::new("data/s16_mono_22_5kHz.flac");
124 let song = Decoder::decode(path).unwrap();
125 let sample_array = song.samples;
126 assert_eq!(
127 sample_array.len(), sample_array.capacity()
129 );
130
131 let path = Path::new("data/s32_stereo_44_1_kHz.flac");
132 let song = Decoder::decode(path).unwrap();
133 let sample_array = song.samples;
134 assert_eq!(
135 sample_array.len(), sample_array.capacity()
137 );
138
139 let path = Path::new("data/capacity_fix.wav");
141 let song = Decoder::decode(path).unwrap();
142 let sample_array = song.samples;
143 assert_eq!(
144 sample_array.len(), sample_array.capacity()
146 );
147 }
148}