libkeynotation 0.2.0

A (musical) key notation parser and transposer
Documentation
pub fn wrap(number: i32) -> i32 {
    (number - 1).rem_euclid(24) + 1
}

macro_rules! generate_key {
    ($(($chromatic_num: tt, $open_key_num: tt, $chromatic: ident, $chromatic_str: tt, $lancelot_str: tt, $open_key_str: tt),)*) => {
        #[derive(Debug, PartialEq, Clone, Copy)]
        pub enum Key {
            $(
                $chromatic
            ),*
        }

        impl Key {
            pub fn from_number(number: u8) -> Result<Self, ()> {
                Ok(match number {
                    $(
                        $chromatic_num => Self::$chromatic,
                    )*
                    _ => return Err(())
                })
            }
            pub fn from_number_wrapping(number: i32) -> Self {
                let number = wrap(number);
                Self::from_number(number.abs() as u8).expect("unreachable")
            }

            pub fn number(&self) -> u8 {
                match self {
                    $(
                        Self::$chromatic => $chromatic_num
                    ),*
                }
            }

            pub fn from_open_key(number: u8, is_major: bool) -> Result<Self, ()> {
                Ok(match number {
                    $(
                        $open_key_num => Self::$chromatic,
                    )*
                    _ => return Err(())
                })

            }

            pub fn from_open_key_number_wrapping(number: i32) -> Result<Self, ()> {
                let number = wrap(number);
                Self::from_open_key_number(number as u8)
            }

            pub fn open_key_number(&self) -> u8 {
                match self {
                    $(
                        Self::$chromatic => $open_key_num
                    ),*
                }
            }

            pub fn from_lancelot_number(number: u8) -> Result<Self, ()> {
                if number > 24 {
                    return Err(());
                }
                Self::from_lancelot_number_wrapping(number as i32 + 6)

            }

            pub fn from_lancelot_number_wrapping(number: i32) -> Result<Self, ()> {
                let number = wrap(number);
                Self::from_open_key_number_wrapping(number as i32 + 6)
            }

            pub fn lancelot_number(&self) -> u8 {
                wrap(self.number() as i32 + 6) as u8
            }

            pub fn transpose_semitones(&self, semitones: i32) -> Self {
                Self::from_number_wrapping(self.number() as i32 + semitones * 2)
            }
             pub fn transpose_factor(&self, factor: f32) -> Self {
                Self::from_number_wrapping(self.number() as i32 + factor_to_semitones(factor))
            }


            pub fn chromatic(&self) -> String {
                match self {
                    $(
                        Self::$chromatic => $chromatic_str
                    ),*
                }.to_string()
            }

            pub fn open_key(&self) -> String {
                match self {
                    $(
                        Self::$chromatic => $open_key_str
                    ),*
                }.to_string()

            }

            pub fn lancelot(&self) -> String {
                match self {
                    $(
                        Self::$chromatic => $lancelot_str
                    ),*
                }.to_string()

            }
        }

        impl std::fmt::Display for Key {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "{}", self.chromatic())
            }
        }
    }
}
/*
- First column describes the chromatic ordering. This is helpful for semitone tansposing.
- Second column describes the open key ordering. This is helpful for
circle of fifths/knowing which keys are harmonic to each other.
- Third colum is the identifier of the Key enum variant
- Fourth column is the string representation of the chromatic key variant
- Sixth column is the lancelot string representation
- Seventh column is the open key string representation
*/
generate_key! {
    (1,   1, CMajor,       "C",   "8B",  "1d"),
    (2,  15, DFlatMajor,   "Db",  "3B",  "8d"),
    (3,   5, DMajor,       "D",   "10B", "3d"),
    (4,  19, EFlatMajor,   "Eb",  "5B",  "10d"),
    (5,   9, EMajor,       "E",   "12B", "5d"),
    (6,  23, FMajor,       "F",   "7B",  "12d"),
    (7,  13, FSharpMajor,  "F#",  "2B",  "7d"), // Same as GFlatMajor (Gb)
    (8,   3, GMajor,       "G",   "9B",  "2d"),
    (9,  17, AFlatMajor,   "Ab",  "4B",  "9d"),
    (10,  7, AMajor,       "A",   "11B", "4d"),
    (11, 21, BFlatMajor,   "Bb",  "6B",  "11d"),
    (12, 11, BMajor,       "B",   "1B",  "6m"),
    (13, 20, CMinor,       "Cm",  "5A",  "10m"),
    (14, 10, CSharpMinor,  "C#m", "12A", "5m"), // Same as Dbm
    (15, 24, DMinor,       "Dm",  "7A",  "12m"),
    (16, 14, EFlatMinor,   "Ebm", "2A",  "7m"), // Same as DSharpMinor (D#m)
    (17,  4, EMinor,       "Em",  "9A",  "2m"),
    (18, 18, FMinor,       "Fm",  "4A",  "9m"),
    (19,  8, FSharpMinor,  "F#m", "11A", "4m"),
    (20, 22, GMinor,       "Gm",  "6A",  "11m"),
    (21, 12, GSharpMinor,  "G#m", "1A",  "6m"),
    (22,  2, AMinor,       "Am",  "8A",  "1m"),
    (23, 16, BFlatMinor,   "Bbm", "3A",  "8m"),
    (24,  6, BMinor,       "Bm",  "10A", "3m"),
}

// 2^(1/12)^n =
pub fn factor_to_semitones(factor: f32) -> i32 {
    (factor.log2() * 12.0) as i32
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn from_number() {
        assert_eq!(Key::from_number_wrapping(0), Key::BMinor);
        assert_eq!(Key::from_number_wrapping(1), Key::CMajor);
        assert_eq!(
            Key::from_number_wrapping(2),
            Key::DFlatMajor
        );
        assert_eq!(Key::from_number_wrapping(3), Key::DMajor);
        assert_eq!(
            Key::from_number_wrapping(23),
            Key::BFlatMinor
        );
        assert_eq!(Key::from_number_wrapping(24), Key::BMinor);
        assert_eq!(Key::from_number_wrapping(25), Key::CMajor);
    }

    #[test]
    fn test_transpose_semitones() {
        let root = Key::from_number_wrapping(1);
        println!("root={root:?}");
        assert_eq!(root.transpose_semitones(12), root);
        assert_eq!(root.transpose_semitones(1), Key::DMajor);
    }

    #[test]
    fn test_transpose_factor() {
        let root = Key::CMajor;
        println!("root={root:?}");

        assert_eq!(root.transpose_factor(2.0), root);
    }

    #[test]
    fn test_factor_to_semitones() {
        assert_eq!(factor_to_semitones(1.0), 0);
        assert_eq!(factor_to_semitones(2.0), 12);
        assert_eq!(factor_to_semitones(1.0595), 1);
    }
}