spectrusty_audio/
music.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8//! Music-related functions.
9use core::convert::TryInto;
10/// Returns an iterator of equal-tempered scale frequencies (in a single octave) obtained from a given base frequency.
11///
12/// `hz` base frequency is in Hz (use 440.0 as a good default).
13/// `n0` an index from the base frequency to the first note in the table: 0 is for "A" note, -9 for "C".
14/// `steps` determines how many halftones will be rendered, (12 is the usual number).
15pub fn equal_tempered_scale_note_freqs(hz: f32, n0: i16, steps: i16)
16                                         -> impl IntoIterator<Item=f32> + Clone + ExactSizeIterator
17{
18    (0..steps).map(move |n| {
19        hz * (2.0f32).powf( (n + n0) as f32 / steps as f32 )
20    })
21}
22
23/// Renders an array of equal-tempered scale frequencies (in a single octave) obtained from a given base frequency.
24///
25/// `hz` base frequency is in Hz (use 440.0 as a good default).
26/// `n0` an index from the base frequency to the first note in the table: 0 is for "A" note, -9 for "C".
27/// The size of the `target` determines how many halftones will be rendered, (12 is the usual number).
28pub fn render_equal_tempered_scale_note_freqs(hz: f32, n0: i16, target: &mut [f32]) {
29    let steps = target.len().try_into().expect("target is too large");
30    for (t, hz) in target.iter_mut()
31                   .zip(equal_tempered_scale_note_freqs(hz, n0, steps)) {
32        *t = hz
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    fn nearly_equal(a: f32, b: f32, epsilon: f32) -> bool {
41        let abs_a = a.abs();
42        let abs_b = b.abs();
43        let diff = (a - b).abs();
44        if a == b {
45            true
46        }
47        else if a == 0.0 || b == 0.0 || (abs_a + abs_b < f32::MIN_POSITIVE) {
48            diff < epsilon * f32::MIN_POSITIVE
49        }
50        else {
51            diff / (abs_a + abs_b).min(f32::MAX) < epsilon
52        }
53    }
54
55    #[test]
56    fn music_works() {
57        let mut freqs = vec![0.0f32;12];
58        render_equal_tempered_scale_note_freqs(440.0, 0, &mut freqs);
59        let freqs0 = vec![440.0, 466.1638, 493.8833, 523.2511, 554.3653, 587.3295,
60                          622.25397, 659.2551, 698.4565, 739.98883, 783.99084, 830.6094];
61        for (freq1, freq0) in freqs.into_iter().zip(freqs0.into_iter()) {
62            assert!(nearly_equal(freq1, freq0, f32::EPSILON));
63        }
64    }
65}