#[derive(Debug, Clone, Copy)]
pub struct Edo {
pub divisions: u32,
pub step_cents: f32,
}
impl Edo {
pub fn new(divisions: u32) -> Self {
Self {
divisions,
step_cents: 1200.0 / divisions as f32,
}
}
pub fn step(&self, base_freq: f32, steps: i32) -> f32 {
base_freq * 2.0_f32.powf(steps as f32 / self.divisions as f32)
}
pub fn octave(&self, base_freq: f32) -> Vec<f32> {
(0..self.divisions)
.map(|step| self.step(base_freq, step as i32))
.collect()
}
}
pub const EDO12: Edo = Edo {
divisions: 12,
step_cents: 100.0,
};
pub const EDO19: Edo = Edo {
divisions: 19,
step_cents: 63.157894,
};
pub const EDO24: Edo = Edo {
divisions: 24,
step_cents: 50.0,
};
pub const EDO31: Edo = Edo {
divisions: 31,
step_cents: 38.70968,
};
pub const EDO53: Edo = Edo {
divisions: 53,
step_cents: 22.641_51,
};
pub fn edo19_step(base_freq: f32, steps: i32) -> f32 {
EDO19.step(base_freq, steps)
}
pub fn edo24_step(base_freq: f32, steps: i32) -> f32 {
EDO24.step(base_freq, steps)
}
pub fn edo31_step(base_freq: f32, steps: i32) -> f32 {
EDO31.step(base_freq, steps)
}
pub fn edo53_step(base_freq: f32, steps: i32) -> f32 {
EDO53.step(base_freq, steps)
}
pub fn cents_to_ratio(cents: f32) -> f32 {
2.0_f32.powf(cents / 1200.0)
}
pub fn ratio_to_cents(ratio: f32) -> f32 {
1200.0 * ratio.log2()
}
pub fn freq_from_cents(base_freq: f32, cents: f32) -> f32 {
base_freq * cents_to_ratio(cents)
}
pub fn just_ratio(base_freq: f32, numerator: u32, denominator: u32) -> f32 {
base_freq * (numerator as f32 / denominator as f32)
}
pub fn just_scale(base_freq: f32, ratios: &[(u32, u32)]) -> Vec<f32> {
ratios
.iter()
.map(|&(num, den)| just_ratio(base_freq, num, den))
.collect()
}
pub mod just_ratios {
pub const UNISON: (u32, u32) = (1, 1);
pub const MINOR_SECOND: (u32, u32) = (16, 15);
pub const MAJOR_SECOND: (u32, u32) = (9, 8);
pub const MINOR_THIRD: (u32, u32) = (6, 5);
pub const MAJOR_THIRD: (u32, u32) = (5, 4);
pub const PERFECT_FOURTH: (u32, u32) = (4, 3);
pub const TRITONE: (u32, u32) = (45, 32);
pub const PERFECT_FIFTH: (u32, u32) = (3, 2);
pub const MINOR_SIXTH: (u32, u32) = (8, 5);
pub const MAJOR_SIXTH: (u32, u32) = (5, 3);
pub const MINOR_SEVENTH: (u32, u32) = (9, 5);
pub const MAJOR_SEVENTH: (u32, u32) = (15, 8);
pub const OCTAVE: (u32, u32) = (2, 1);
}
pub fn just_major_scale(root: f32) -> Vec<f32> {
use just_ratios::*;
just_scale(
root,
&[
UNISON,
MAJOR_SECOND,
MAJOR_THIRD,
PERFECT_FOURTH,
PERFECT_FIFTH,
MAJOR_SIXTH,
MAJOR_SEVENTH,
OCTAVE,
],
)
}
pub fn just_minor_scale(root: f32) -> Vec<f32> {
use just_ratios::*;
just_scale(
root,
&[
UNISON,
MAJOR_SECOND,
MINOR_THIRD,
PERFECT_FOURTH,
PERFECT_FIFTH,
MINOR_SIXTH,
MINOR_SEVENTH,
OCTAVE,
],
)
}
pub fn pythagorean_scale(root: f32) -> Vec<f32> {
vec![
root * 1.0, root * 256.0 / 243.0, root * 9.0 / 8.0, root * 32.0 / 27.0, root * 81.0 / 64.0, root * 4.0 / 3.0, root * 729.0 / 512.0, root * 3.0 / 2.0, root * 128.0 / 81.0, root * 27.0 / 16.0, root * 16.0 / 9.0, root * 243.0 / 128.0, root * 2.0, ]
}
pub fn quarter_sharp(freq: f32) -> f32 {
edo24_step(freq, 1)
}
pub fn half_sharp(freq: f32) -> f32 {
edo24_step(freq, 2)
}
pub fn three_quarter_sharp(freq: f32) -> f32 {
edo24_step(freq, 3)
}
pub fn quarter_flat(freq: f32) -> f32 {
edo24_step(freq, -1)
}
pub fn half_flat(freq: f32) -> f32 {
edo24_step(freq, -2)
}
pub fn three_quarter_flat(freq: f32) -> f32 {
edo24_step(freq, -3)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_edo12_equals_standard_tuning() {
let base = 440.0; let edo12 = Edo::new(12);
assert!((edo12.step(base, 12) - 880.0).abs() < 0.01);
let fifth = edo12.step(base, 7);
let just_fifth = base * 1.5;
assert!((fifth - just_fifth).abs() < 2.0);
}
#[test]
fn test_edo24_quarter_tones() {
let base = 440.0;
let semitone_12 = base * 2.0_f32.powf(1.0 / 12.0);
let semitone_24 = edo24_step(base, 2);
assert!((semitone_12 - semitone_24).abs() < 0.01);
let quarter = edo24_step(base, 1);
assert!(quarter > base && quarter < semitone_24);
}
#[test]
fn test_cents_conversion() {
assert!((cents_to_ratio(1200.0) - 2.0).abs() < 0.0001);
assert!((ratio_to_cents(2.0) - 1200.0).abs() < 0.01);
let fifth_ratio = 1.5;
let cents = ratio_to_cents(fifth_ratio);
assert!((cents - 701.955).abs() < 0.01);
let original = 1.25;
let cents = ratio_to_cents(original);
let back = cents_to_ratio(cents);
assert!((original - back).abs() < 0.0001);
}
#[test]
fn test_just_intonation() {
let base = 440.0;
let fifth = just_ratio(base, 3, 2);
assert!((fifth - 660.0).abs() < 0.01);
let third = just_ratio(base, 5, 4);
assert!((third - 550.0).abs() < 0.01);
let octave = just_ratio(base, 2, 1);
assert!((octave - 880.0).abs() < 0.01);
}
#[test]
fn test_just_major_scale() {
let scale = just_major_scale(440.0);
assert_eq!(scale.len(), 8);
assert_eq!(scale[0], 440.0);
assert_eq!(scale[7], 880.0);
for i in 0..scale.len() - 1 {
assert!(scale[i] < scale[i + 1]);
}
}
#[test]
fn test_pythagorean_scale() {
let scale = pythagorean_scale(440.0);
assert_eq!(scale.len(), 13);
assert_eq!(scale[7], 440.0 * 1.5);
assert_eq!(scale[5], 440.0 * 4.0 / 3.0);
}
#[test]
fn test_quarter_tone_helpers() {
let base = 440.0;
let qs = quarter_sharp(base);
let hs = half_sharp(base);
assert!(qs < hs);
assert!(qs > base);
assert!((hs - edo24_step(base, 2)).abs() < 0.01);
}
#[test]
fn test_edo_octave() {
let edo19 = Edo::new(19);
let octave = edo19.octave(440.0);
assert_eq!(octave.len(), 19);
for i in 0..octave.len() - 1 {
assert!(octave[i] < octave[i + 1]);
}
let next_octave = edo19.step(440.0, 19);
assert!((next_octave - 880.0).abs() < 0.01);
}
}