use serde::{Deserialize, Serialize};
use super::pitch::Pitch;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum IntervalQuality {
Perfect,
Major,
Minor,
Augmented,
Diminished,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Interval {
semitones: i16,
}
impl Interval {
pub fn between(a: &Pitch, b: &Pitch) -> Self {
Self { semitones: b.to_midi() - a.to_midi() }
}
pub fn semitones(&self) -> i16 { self.semitones }
pub fn abs_semitones(&self) -> u16 { self.semitones.unsigned_abs() }
pub fn is_ascending(&self) -> bool { self.semitones > 0 }
pub fn simple_number(&self) -> u8 {
const TABLE: [u8; 12] = [1, 2, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7];
TABLE[(self.semitones.unsigned_abs() as usize) % 12]
}
pub fn quality(&self) -> IntervalQuality {
match (self.semitones.unsigned_abs() as usize) % 12 {
0 => IntervalQuality::Perfect,
1 => IntervalQuality::Minor,
2 => IntervalQuality::Major,
3 => IntervalQuality::Minor,
4 => IntervalQuality::Major,
5 => IntervalQuality::Perfect,
6 => IntervalQuality::Augmented,
7 => IntervalQuality::Perfect,
8 => IntervalQuality::Minor,
9 => IntervalQuality::Major,
10 => IntervalQuality::Minor,
11 => IntervalQuality::Major,
_ => IntervalQuality::Perfect,
}
}
pub fn display(&self) -> String {
let abs = self.semitones.unsigned_abs() as usize;
let q = match self.quality() {
IntervalQuality::Perfect => "P",
IntervalQuality::Major => "M",
IntervalQuality::Minor => "m",
IntervalQuality::Augmented => "A",
IntervalQuality::Diminished => "d",
};
let n = if abs.is_multiple_of(12) && abs >= 12 { 8 } else { self.simple_number() };
format!("{}{}", q, n)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Pitch, Step};
#[test]
fn interval_unison_p1() {
let c4 = Pitch::new(Step::C, 4);
let iv = Interval::between(&c4, &c4);
assert_eq!(iv.semitones(), 0);
assert_eq!(iv.display(), "P1");
}
#[test]
fn interval_major_third_c4_e4() {
let a = Pitch::new(Step::C, 4);
let b = Pitch::new(Step::E, 4);
let iv = Interval::between(&a, &b);
assert_eq!(iv.semitones(), 4);
assert_eq!(iv.quality(), IntervalQuality::Major);
assert_eq!(iv.display(), "M3");
assert!(iv.is_ascending());
}
#[test]
fn interval_perfect_fifth_c4_g4() {
let a = Pitch::new(Step::C, 4);
let b = Pitch::new(Step::G, 4);
let iv = Interval::between(&a, &b);
assert_eq!(iv.semitones(), 7);
assert_eq!(iv.display(), "P5");
}
#[test]
fn interval_minor_seventh_descending() {
let a = Pitch::new(Step::A, 4);
let b = Pitch::new(Step::B, 3);
let iv = Interval::between(&a, &b);
assert_eq!(iv.semitones(), -10);
assert_eq!(iv.display(), "m7");
assert!(!iv.is_ascending());
}
#[test]
fn interval_tritone() {
let a = Pitch::new(Step::C, 4);
let b = Pitch::with_alter(Step::F, 4, 1); let iv = Interval::between(&a, &b);
assert_eq!(iv.semitones(), 6);
assert_eq!(iv.quality(), IntervalQuality::Augmented);
assert_eq!(iv.display(), "A4");
}
#[test]
fn interval_octave() {
let a = Pitch::new(Step::C, 4);
let b = Pitch::new(Step::C, 5);
let iv = Interval::between(&a, &b);
assert_eq!(iv.semitones(), 12);
assert_eq!(iv.display(), "P8");
}
#[test]
fn interval_minor_second() {
let a = Pitch::new(Step::E, 4);
let b = Pitch::new(Step::F, 4);
let iv = Interval::between(&a, &b);
assert_eq!(iv.semitones(), 1);
assert_eq!(iv.display(), "m2");
}
}