use std::iter::Sum;
use std::ops::{Add, AddAssign};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "redact-composer")]
use redact_composer_core::derive::Element;
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "redact-composer", derive(Element))]
pub struct Interval(pub u8);
#[allow(non_upper_case_globals)]
impl Interval {
pub const P1: Interval = Interval(0);
pub const m2: Interval = Interval(1);
pub const M2: Interval = Interval(2);
pub const m3: Interval = Interval(3);
pub const M3: Interval = Interval(4);
pub const P4: Interval = Interval(5);
pub const TT: Interval = Interval(6);
pub const A4: Interval = Interval(6);
pub const d5: Interval = Interval(6);
pub const P5: Interval = Interval(7);
pub const m6: Interval = Interval(8);
pub const A5: Interval = Interval(8);
pub const M6: Interval = Interval(9);
pub const d7: Interval = Interval(9);
pub const m7: Interval = Interval(10);
pub const M7: Interval = Interval(11);
pub const P8: Interval = Interval(12);
pub const Octave: Interval = Self::P8;
pub const m9: Interval = Interval(13);
pub const M9: Interval = Interval(14);
pub const m10: Interval = Interval(15);
pub const M10: Interval = Interval(16);
pub const P11: Interval = Interval(17);
pub const P12: Interval = Interval(19);
pub const m13: Interval = Interval(20);
pub const M13: Interval = Interval(21);
pub fn is_simple(&self) -> bool {
self.0 <= 12
}
pub fn to_simple(self) -> Interval {
Interval(self.0 % 12)
}
pub fn is_compound(&self) -> bool {
!self.is_simple()
}
pub fn to_compound(self) -> Interval {
if self.is_simple() {
Interval(self.0 + 12)
} else {
self
}
}
pub fn inversion(&self) -> Interval {
if self.is_simple() {
Interval(12 - self.0)
} else {
let octaves = self.0 / 12 + 1;
Interval(12 * octaves - self.0)
}
}
}
impl Add for Interval {
type Output = Interval;
fn add(self, rhs: Self) -> Self::Output {
Interval(self.0 + rhs.0)
}
}
impl AddAssign for Interval {
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;
}
}
impl Sum for Interval {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Interval::default(), |i1, i2| i1 + i2)
}
}
#[cfg(test)]
mod tests {
use crate::Interval as I;
#[test]
fn check_simple_interval() {
assert!(I(0).is_simple());
assert!(I(12).is_simple());
assert!(!I(13).is_simple());
}
#[test]
fn check_compound_interval() {
assert!(I(13).is_compound());
assert!(I(24).is_compound());
assert!(!I(12).is_compound());
}
#[test]
fn check_simple_inversions() {
assert_eq!(I::P1.inversion(), I::P8);
assert_eq!(I::P8.inversion(), I::P1);
assert_eq!(I::m2.inversion(), I::M7);
assert_eq!(I::M7.inversion(), I::m2);
assert_eq!(I::M2.inversion(), I::m7);
assert_eq!(I::m7.inversion(), I::M2);
assert_eq!(I::m3.inversion(), I::M6);
assert_eq!(I::M6.inversion(), I::m3);
assert_eq!(I::M3.inversion(), I::m6);
assert_eq!(I::m6.inversion(), I::M3);
assert_eq!(I::P4.inversion(), I::P5);
assert_eq!(I::P5.inversion(), I::P4);
assert_eq!(I::A4.inversion(), I::A4);
}
#[test]
fn check_compound_inversions() {
assert_eq!(I::m9.inversion(), I::M7);
assert_eq!(I::M9.inversion(), I::m7);
assert_eq!(I::m10.inversion(), I::M6);
assert_eq!(I::M10.inversion(), I::m6);
assert_eq!(I::P11.inversion(), I::P5);
assert_eq!(I::P12.inversion(), I::P4);
assert_eq!(I::m13.inversion(), I::M3);
assert_eq!(I::M13.inversion(), I::m3);
}
}