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())
}
}
}
}
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"), (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"), (15, 24, DMinor, "Dm", "7A", "12m"),
(16, 14, EFlatMinor, "Ebm", "2A", "7m"), (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"),
}
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);
}
}