hapsi 0.0.3

A music theory package
Documentation
use std::fmt::Debug;

use super::{Degree, Distance, Keyboard, Octave, Pitch, Scale, Scaled};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Chord<T> {
    root: T,
    other: Vec<T>,
}

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

    pub fn into_vec(self) -> Vec<T> {
        let mut vec = self.other;
        vec.insert(0, self.root);
        vec
    }
}

impl Chord<Degree> {
    pub fn to_pitch<'a, O: Octave>(&'a self, key: &'a Keyboard<O>) -> Chord<Pitch<&O::PitchClass>> {
        let root = key.get_pitch(&self.root);
        let other = self
            .other
            .iter()
            .map(|&x| key.get_pitch(&(self.root.value() + x.value() - 2)))
            .collect();
        Chord { root, other }
    }
}

impl<T: Clone> Chord<Pitch<T>> {
    pub fn to_degree<'a, O: Octave<PitchClass = T>>(
        &'a self,
        keyboard: &'a Keyboard<Scaled<impl Scale, O>>,
    ) -> Option<Chord<Degree>> {
        let key = keyboard.get_pitch(&0_usize).deref();
        let Some(root_deg) = keyboard.measure(&key, &self.root) else {
            return None;
        };
        let others = self.other.iter().map(|p| keyboard.measure(&self.root, p));
        if others.clone().any(|o| o.is_none()) {
            return None;
        }
        Some(Chord {
            root: root_deg,
            other: others.map(|o| o.unwrap()).collect(),
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        prelude::{Degree, Keyboard, Pitch, Scaled, Tone, Twelve},
        scale::Diatonic,
    };

    use super::Chord;

    #[test]
    fn to_pitch() {
        let scale = Diatonic::minor(&"A".parse().unwrap());
        let scaled = Scaled::new(scale, Twelve);
        let keyboard = Keyboard::new(scaled);
        let chord = Chord::<Degree> {
            root: Degree::new(1).unwrap(),
            other: vec![Degree::new(3).unwrap(), Degree::new(5).unwrap()],
        };
        let c = chord.to_pitch(&keyboard);
        assert_eq!(c.root, Pitch::new(&"A".parse().unwrap(), 0));
        assert_eq!(c.other.get(0), Some(&Pitch::new(&"C".parse().unwrap(), 0)));
        assert_eq!(c.other.get(1), Some(&Pitch::new(&"E".parse().unwrap(), 0)));
    }

    #[test]
    fn to_degree() {
        let scale = Diatonic::minor(&"A".parse().unwrap());
        let scaled = Scaled::new(scale, Twelve);
        let keyboard = Keyboard::new(scaled);
        let chord = Chord::<Pitch<Tone>> {
            root: Pitch::new("A".parse().unwrap(), 0),
            other: vec![
                Pitch::new("C".parse().unwrap(), 1),
                Pitch::new("E".parse().unwrap(), 1),
            ],
        };
        let c = chord.to_degree(&keyboard).unwrap();
        assert_eq!(c.root, Degree::new(1).unwrap());
        assert_eq!(c.other.get(0), Some(&Degree::new(3).unwrap()));
        assert_eq!(c.other.get(1), Some(&Degree::new(5).unwrap()));
    }
}