mecomp_analysis/
misc.rs

1//! Miscellaneous feature extraction module.
2//!
3//! Contains various descriptors that don't fit in one of the
4//! existing categories.
5
6use bliss_audio_aubio_rs::level_lin;
7use ndarray::{arr1, Axis};
8
9use crate::Feature;
10
11use super::utils::{mean, Normalize};
12
13/**
14 * Loudness (in dB) detection object.
15 *
16 * It indicates how "loud" a recording of a song is. For a given audio signal,
17 * this value increases if the amplitude of the signal, and nothing else, is
18 * increased.
19 *
20 * Of course, this makes this result dependent of the recording, meaning
21 * the same song would yield different loudness on different recordings. Which
22 * is exactly what we want, given that this is not a music theory project, but
23 * one that aims at giving the best real-life results.
24 *
25 * Ranges between -90 dB (~silence) and 0 dB.
26 *
27 * (This is technically the sound pressure level of the track, but loudness is
28 * way more visual)
29 */
30#[derive(Default, Clone)]
31pub struct LoudnessDesc {
32    pub values: Vec<f32>,
33}
34
35impl LoudnessDesc {
36    pub const WINDOW_SIZE: usize = 1024;
37
38    #[inline]
39    pub fn do_(&mut self, chunk: &[f32]) {
40        let level = level_lin(chunk);
41        self.values.push(level);
42    }
43
44    #[inline]
45    pub fn get_value(&mut self) -> Vec<Feature> {
46        const MIN_VALUE: f32 = 1e-9;
47
48        // Make sure the dB don't go less than -90dB
49        let std_value = Feature::from(
50            arr1(&self.values)
51                .std_axis(Axis(0), 0.)
52                .into_scalar()
53                .max(MIN_VALUE),
54        );
55        let mean_value = Feature::from(mean(&self.values).max(MIN_VALUE));
56        vec![
57            self.normalize(10.0 * mean_value.log10()),
58            self.normalize(10.0 * std_value.log10()),
59        ]
60    }
61}
62
63impl Normalize for LoudnessDesc {
64    const MAX_VALUE: Feature = 0.;
65    const MIN_VALUE: Feature = -90.;
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use crate::decoder::{Decoder as DecoderTrait, MecompDecoder as Decoder};
72    use std::path::Path;
73
74    #[test]
75    fn test_loudness() {
76        let song = Decoder::new()
77            .unwrap()
78            .decode(Path::new("data/s16_mono_22_5kHz.flac"))
79            .unwrap();
80        let mut loudness_desc = LoudnessDesc::default();
81        for chunk in song.samples.chunks_exact(LoudnessDesc::WINDOW_SIZE) {
82            loudness_desc.do_(chunk);
83        }
84        let expected_values = [0.271_263, 0.257_718_1];
85        for (expected, actual) in expected_values.iter().zip(loudness_desc.get_value().iter()) {
86            assert!(0.01 > (expected - actual).abs(), "{expected} !~= {actual}");
87        }
88    }
89
90    #[test]
91    fn test_loudness_boundaries() {
92        let mut loudness_desc = LoudnessDesc::default();
93        let silence_chunk = vec![0.; 1024];
94        loudness_desc.do_(&silence_chunk);
95        let expected_values = [-1., -1.];
96        for (expected, actual) in expected_values.iter().zip(loudness_desc.get_value().iter()) {
97            assert!(
98                0.000_000_1 > (expected - actual).abs(),
99                "{expected} !~= {actual}"
100            );
101        }
102
103        let mut loudness_desc = LoudnessDesc::default();
104        let silence_chunk = vec![1.; 1024];
105        loudness_desc.do_(&silence_chunk);
106        let expected_values = [1., -1.];
107        for (expected, actual) in expected_values.iter().zip(loudness_desc.get_value().iter()) {
108            assert!(
109                0.000_000_1 > (expected - actual).abs(),
110                "{expected} !~= {actual}"
111            );
112        }
113
114        let mut loudness_desc = LoudnessDesc::default();
115        let silence_chunk = vec![-1.; 1024];
116        loudness_desc.do_(&silence_chunk);
117        let expected_values = [1., -1.];
118        for (expected, actual) in expected_values.iter().zip(loudness_desc.get_value().iter()) {
119            assert!(
120                0.000_000_1 > (expected - actual).abs(),
121                "{expected} !~= {actual}"
122            );
123        }
124    }
125}