hapsi 0.0.3

A music theory package
Documentation
use std::collections::HashMap;

use once_cell::sync::Lazy;

use crate::{
    core::Chord as CoreChord,
    core::*,
    prelude::{Tone, Twelve},
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Chord<T> {
    root: T,
    quality: Quality,
}

impl<T> Chord<T> {
    pub fn new(root: T, quality: Quality) -> Self {
        Self { root, quality }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Quality {
    Major,
    Minor,
    Dim,
    Aug,
    Major7,
    Minor7,
}

static ENUM_QUALITY: Lazy<[Quality; 6]> = Lazy::new(|| {
    [
        Quality::Major,
        Quality::Minor,
        Quality::Dim,
        Quality::Aug,
        Quality::Major7,
        Quality::Minor7,
    ]
});

impl Quality {
    pub fn enumerate() -> std::slice::Iter<'static, Quality> {
        ENUM_QUALITY.iter()
    }
}

static QUALITY_TO_INTERVAL: Lazy<HashMap<Quality, Vec<Interval>>> = Lazy::new(|| {
    let mut hash = HashMap::<Quality, Vec<Interval>>::default();
    hash.insert(Quality::Major, vec![4.into(), 7.into()]);
    hash.insert(Quality::Minor, vec![3.into(), 7.into()]);
    hash.insert(Quality::Dim, vec![3.into(), 6.into()]);
    hash.insert(Quality::Aug, vec![4.into(), 8.into()]);
    hash.insert(Quality::Major7, vec![4.into(), 7.into(), 11.into()]);
    hash.insert(Quality::Minor7, vec![3.into(), 7.into(), 11.into()]);
    hash
});

impl Chord<Tone> {
    pub fn into_class(self) -> CoreChord<Tone> {
        let keyboard = Keyboard::new(Twelve);
        let intervals = QUALITY_TO_INTERVAL.get(&self.quality).unwrap();
        let root_value: usize = self.root.into();
        let others = intervals
            .iter()
            .map(|i| keyboard.get_class(&(root_value + i.value())).clone())
            .collect();
        CoreChord::new(self.root, others)
    }
}

impl Chord<Pitch<Tone>> {
    pub fn into_pitch(self) -> CoreChord<Pitch<Tone>> {
        let keyboard = Keyboard::new(Twelve);
        let intervals = QUALITY_TO_INTERVAL.get(&self.quality).unwrap();
        let root_value: usize = keyboard.as_number(&self.root).unwrap();
        let others = intervals
            .iter()
            .map(|i| keyboard.get_pitch(&(root_value + i.value())).deref())
            .collect();
        CoreChord::new(self.root, others)
    }
}

#[cfg(test)]
mod tests {

    use super::Chord;

    #[test]
    fn into_class() {
        let mut chord = Chord::new("A".parse().unwrap(), super::Quality::Major7)
            .into_class()
            .into_vec()
            .into_iter();
        assert_eq!(chord.next(), Some("A".parse().unwrap()));
        assert_eq!(chord.next(), Some("Cs".parse().unwrap()));
        assert_eq!(chord.next(), Some("E".parse().unwrap()));
        assert_eq!(chord.next(), Some("Gs".parse().unwrap()));
    }
}