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