chordcalc 0.1.0

Chord related utilities
Documentation
#![no_std]
mod extended;
mod seventh;
mod suspended;
mod triad;

use core::ops::{Add, Sub};

use micromath::F32Ext;

pub mod chord_type {
    pub mod triad {
        pub use crate::triad::Mode;
    }
    pub mod seventh {
        pub use crate::seventh::Mode;
    }
    pub mod extended {
        pub use crate::extended::Mode;
    }
    pub mod suspended {
        pub use crate::suspended::Mode;
    }
}

trait Mode {
    fn make_chord(&self, tonality: MusicalNote) -> Chord;
}

#[derive(Debug, Copy, Clone)]
pub enum Chord {
    Triad([MusicalNote; 3]),
    Seventh([MusicalNote; 4]),
    FiveNotesChord([MusicalNote; 5]),
    SixNotesChord([MusicalNote; 6]),
    SevenNotesChord([MusicalNote; 7]),
}
impl From<[MusicalNote; 3]> for Chord {
    fn from(c: [MusicalNote; 3]) -> Self {
        Self::Triad(c)
    }
}
impl From<[MusicalNote; 4]> for Chord {
    fn from(c: [MusicalNote; 4]) -> Self {
        Self::Seventh(c)
    }
}
impl From<[MusicalNote; 5]> for Chord {
    fn from(c: [MusicalNote; 5]) -> Self {
        Self::FiveNotesChord(c)
    }
}
impl From<[MusicalNote; 6]> for Chord {
    fn from(c: [MusicalNote; 6]) -> Self {
        Self::SixNotesChord(c)
    }
}
impl From<[MusicalNote; 7]> for Chord {
    fn from(c: [MusicalNote; 7]) -> Self {
        Self::SevenNotesChord(c)
    }
}

#[derive(Debug, Copy, Clone)]
pub enum Note {
    A,
    B,
    C,
    D,
    E,
    F,
    G,
}

#[derive(Debug, Copy, Clone)]
pub enum Accidental {
    Sharp,
    Flat,
    Natural,
    DoubleFlat,
    DoubleSharp,
}

#[derive(Debug, Copy, Clone)]
pub struct Interval(pub i8);

impl From<i8> for Interval {
    fn from(i: i8) -> Self {
        Self(i)
    }
}

#[derive(Debug, Copy, Clone)]
pub struct MusicalNote(pub Note, pub Accidental);

impl Sub for MusicalNote {
    type Output = Interval;

    fn sub(self, rhs: Self) -> Self::Output {
        Interval(SemitonesFromC::from(self) - SemitonesFromC::from(rhs))
    }
}

impl<T> Add<T> for MusicalNote
where
    T: Into<Interval>,
{
    type Output = Self;

    fn add(self, rhs: T) -> Self::Output {
        Self::from(SemitonesFromC(
            (SemitonesFromC::from(self).0 + rhs.into().0) % 12,
        ))
    }
}

impl From<(Note, Accidental)> for MusicalNote {
    fn from(n: (Note, Accidental)) -> Self {
        Self(n.0, n.1)
    }
}

impl MusicalNote {
    pub fn get_frequency(&self, octave: i8) -> f32 {
        let a: f32 = (2f32).powf(1.0 / 12.0);
        let a4 = SemitonesFromC::from(MusicalNote(Note::A, Accidental::Natural)).0 + 4 * 12;
        440.0 * a.powf(((octave * 12 + SemitonesFromC::from(*self).0) - a4).into())
    }
}

struct SemitonesFromC(i8);

impl Sub for SemitonesFromC {
    type Output = i8;

    fn sub(self, rhs: Self) -> Self::Output {
        self.0 - rhs.0
    }
}

impl From<MusicalNote> for SemitonesFromC {
    fn from(n: MusicalNote) -> Self {
        let (note, accidental) = (n.0, n.1);
        let number = match note {
            Note::A => 9,
            Note::B => 11,
            Note::C => 0,
            Note::D => 2,
            Note::E => 4,
            Note::F => 5,
            Note::G => 7,
        };
        let delta = match accidental {
            Accidental::Sharp => 1,
            Accidental::Flat => -1,
            Accidental::Natural => 0,
            Accidental::DoubleFlat => -2,
            Accidental::DoubleSharp => 2,
        };
        SemitonesFromC(number + delta)
    }
}
impl From<SemitonesFromC> for MusicalNote {
    fn from(n: SemitonesFromC) -> Self {
        match n.0 % 12 {
            0 => (Note::C, Accidental::Natural),
            1 => (Note::C, Accidental::Sharp),
            2 => (Note::D, Accidental::Natural),
            3 => (Note::D, Accidental::Sharp),
            4 => (Note::E, Accidental::Natural),
            5 => (Note::F, Accidental::Natural),
            6 => (Note::F, Accidental::Sharp),
            7 => (Note::G, Accidental::Natural),
            8 => (Note::G, Accidental::Sharp),
            9 => (Note::A, Accidental::Natural),
            10 => (Note::A, Accidental::Sharp),
            11 => (Note::B, Accidental::Natural),
            _ => panic!("Unreachable code reached"),
        }
        .into()
    }
}

impl<M: Mode> From<(MusicalNote, M)> for Chord {
    fn from(n: (MusicalNote, M)) -> Self {
        let (tonality, mode) = n;
        mode.make_chord(tonality)
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(4, 4);
    }
}