Skip to main content

sva_samples/measure/
sharpness.rs

1// Concern: sharpness per frame, DIN 45692's weighting of the loudness pattern | Non-concern: specific loudness (loudness_pattern.rs) | IO: (planes, rate, start, hop, spl) -> Sharpness
2
3use super::loudness_pattern;
4
5pub const MODEL: &str = "sharpness per frame of `loudness_pattern` at the same `frame` and \
6                         `spl`, DIN 45692 (2009)'s weighted first moment: S = 0.11 sum N'(z) \
7                         g(z) z / sum N'(z) acum, z each band's centre in Bark by Traunmüller \
8                         (1990), z = 26.81 f / (1960 + f) - 0.53, raised by 0.15 (2 - z) \
9                         under 2 and by 0.22 (z - 20.1) over 20.1 as he corrects its ends, so \
10                         every band stands above 0 Bark, g(z) = 1 to 15.8 Bark and \
11                         0.15 e^(0.42 (z - 15.8)) + 0.85 above, N' each Cam band's specific \
12                         loudness standing for its stretch of the Bark scale; null where no \
13                         band is over the pattern's `threshold_sone`. Measured, not DIN 45692: \
14                         the standard weights Zwicker's specific loudness per Bark (ISO 532-1) \
15                         and this one Moore and Glasberg's per Cam, so its acum are the \
16                         standard's weighting on another loudness model.";
17
18#[derive(Clone, Copy, Debug, PartialEq)]
19pub struct SharpnessFrame {
20    pub t_secs: f64,
21    pub acum: Option<f64>,
22}
23
24#[derive(Clone, Debug, PartialEq)]
25pub struct Sharpness {
26    pub frame_secs: f64,
27    pub window_secs: f64,
28    pub spl_db: f64,
29    pub frames: Vec<SharpnessFrame>,
30}
31
32pub fn measure(planes: &[&[f64]], sr: f64, start: f64, hop_secs: f64, spl_db: f64) -> Sharpness {
33    let pattern = loudness_pattern::measure(planes, sr, start, hop_secs, spl_db);
34    let frames = pattern
35        .frames
36        .iter()
37        .map(|f| SharpnessFrame {
38            t_secs: f.t_secs,
39            acum: f
40                .specific
41                .iter()
42                .any(|n| *n > pattern.threshold_sone)
43                .then(|| acum(&pattern.centres_hz, &f.specific)),
44        })
45        .collect();
46    Sharpness {
47        frame_secs: pattern.frame_secs,
48        window_secs: pattern.window_secs,
49        spl_db,
50        frames,
51    }
52}
53
54fn bark(hz: f64) -> f64 {
55    let z = 26.81 * hz / (1960.0 + hz) - 0.53;
56    match z {
57        z if z < 2.0 => z + 0.15 * (2.0 - z),
58        z if z > 20.1 => z + 0.22 * (z - 20.1),
59        z => z,
60    }
61}
62
63fn acum(centres_hz: &[f64], specific: &[f64]) -> f64 {
64    let moment: f64 = centres_hz
65        .iter()
66        .zip(specific)
67        .map(|(&hz, &n)| {
68            let z = bark(hz);
69            let g = match z > 15.8 {
70                true => 0.15 * sva_formula::exp(0.42 * (z - 15.8)) + 0.85,
71                false => 1.0,
72            };
73            n * g * z
74        })
75        .sum();
76    0.11 * moment / specific.iter().sum::<f64>()
77}