use crate::KeyParser;
macro_rules! generate_key {
($(($variant:tt, $traditional_num: tt, $lancelot_num: tt, $open_key_num: tt, $is_major: tt, $traditional_str: tt),)*) => {
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Key {
$(
$variant
),*
}
impl Key {
pub fn from_string(s: &str) -> Result<Self, ()> {
Ok(KeyParser::try_from_string(s).map_err(|_| ())?.key())
}
pub fn from_numeric(number: i32, is_major: bool) -> Result<Self, ()> {
Ok(match (number, is_major) {
$(
($traditional_num, $is_major) => Self::$variant,
)*
_ => return Err(())
})
}
pub fn from_numeric_wrapping(number: i32, is_major: bool) -> Self {
Self::from_numeric(wrap(number), is_major).expect("unreachable")
}
pub fn numeric(&self) -> (i32, bool) {
match self {
$(
Self::$variant => ($traditional_num, $is_major)
),*
}
}
pub fn is_major(&self) -> bool {
match self {
$(
Self::$variant => $is_major
),*
}
}
pub fn from_open_key_numeric(number: i32, is_major: bool) -> Result<Self, ()> {
Ok(match (number, is_major) {
$(
($open_key_num, $is_major) => Self::$variant,
)*
_ => return Err(())
})
}
pub fn from_open_key_numeric_wrapping(number: i32, is_major: bool) -> Result<Self, ()> {
let number = wrap(number);
Self::from_open_key_numeric(number, is_major)
}
pub fn open_key_numeric(&self) -> (i32, bool) {
match self {
$(
Self::$variant => ($open_key_num, $is_major)
),*
}
}
pub fn from_lancelot_numeric(number: i32, is_major: bool) -> Result<Self, ()> {
Ok(match (number, is_major) {
$(
($lancelot_num, $is_major) => Self::$variant,
)*
_ => return Err(())
})
}
pub fn from_lancelot_numeric_wrapping(number: i32, is_major: bool) -> Result<Self, ()> {
Self::from_lancelot_numeric(wrap(number), is_major)
}
pub fn lancelot_numeric(&self) -> (i32, bool) {
match self {
$(
Self::$variant => ($lancelot_num, $is_major)
),*
}
}
pub fn traditional(&self) -> String {
match self {
$(
Self::$variant => $traditional_str
),*
}.to_string()
}
pub fn open_key(&self) -> String {
match self {
$(
Self::$variant => format!("{}{}", $open_key_num, if $is_major { "d" } else { "m" })
),*
}.to_string()
}
pub fn lancelot(&self) -> String {
match self {
$(
Self::$variant => format!("{}{}", $lancelot_num, if $is_major { "B" } else { "A" })
),*
}.to_string()
}
}
}
}
generate_key! {
(CMajor , 1, 8, 1, true, "C"),
(DFlatMajor , 2, 3, 8, true, "Db"),
(DMajor , 3, 10, 3, true, "D"),
(EFlatMajor , 4, 5, 10, true, "Eb"),
(EMajor , 5, 12, 5, true, "E"),
(FMajor , 6, 7, 12, true, "F"),
(FSharpMajor , 7, 2, 7, true, "F#"),
(GMajor , 8, 9, 2, true, "G"),
(AFlatMajor , 9, 4, 9, true, "Ab"),
(AMajor , 10, 11, 4, true, "A"),
(BFlatMajor , 11, 6, 11, true, "Bb"),
(BMajor , 12, 1, 6, true, "B"),
(CMinor , 1, 5, 10, false, "Cm"),
(CSharpMinor , 2, 12, 5, false, "C#m"),
(DMinor , 3, 7, 12, false, "Dm"),
(EFlatMinor , 4, 2, 7, false, "Ebm"),
(EMinor , 5, 9, 2, false, "Em"),
(FMinor , 6, 4, 9, false, "Fm"),
(FSharpMinor , 7, 11, 4, false, "F#m"),
(GMinor , 8, 6, 11, false, "Gm"),
(GSharpMinor , 9, 1, 6, false, "G#m"),
(AMinor , 10, 8, 1, false, "Am"),
(BFlatMinor , 11, 3, 8, false, "Bbm"),
(BMinor , 12, 10, 3, false, "Bm"),
}
impl Key {
pub fn transpose_semitones(&self, semitones: i32) -> Self {
let (number, is_major) = self.numeric();
Self::from_numeric_wrapping(number + semitones, is_major)
}
pub fn transpose_tones(&self, tones: i32) -> Self {
let (number, is_major) = self.numeric();
Self::from_numeric_wrapping(number + tones * 2, is_major)
}
pub fn transpose_factor(&self, factor: f32) -> Self {
self.transpose_semitones(factor_to_semitones(factor).round() as i32)
}
pub fn transpose_bpm(&self, initial_bpm: f32, bpm: f32) -> Self {
self.transpose_factor(bpm_to_factor(initial_bpm, bpm))
}
pub fn to_lancelot_i32(&self) -> i32 {
match self.lancelot_numeric() {
(number, true) => 12 + number - 1,
(number, false) => number - 1,
}
}
pub fn try_from_lancelot_i32(number: i32) -> Result<Self, ()> {
let number = number + 1;
if number > 24 {
return Err(());
}
if number > 12 {
Self::from_lancelot_numeric(number - 12, true)
} else {
Self::from_lancelot_numeric(number, false)
}
}
}
impl std::fmt::Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.traditional())
}
}
pub fn wrap(number: i32) -> i32 {
((number - 1).rem_euclid(12) + 1).abs()
}
pub fn factor_to_semitones(factor: f32) -> f32 {
factor.log2() * 12.0
}
pub fn bpm_to_factor(initial_bpm: f32, bpm: f32) -> f32 {
bpm / initial_bpm
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_wrap() {
assert_eq!(wrap(1), 1);
assert_eq!(wrap(12), 12);
assert_eq!(wrap(0), 12);
assert_eq!(wrap(-1), 11);
}
#[test]
fn test_from_numeric_wrapping() {
assert_eq!(Key::from_numeric_wrapping(1, true), Key::CMajor);
assert_eq!(Key::from_numeric_wrapping(2, true), Key::DFlatMajor);
assert_eq!(Key::from_numeric_wrapping(3, true), Key::DMajor);
assert_eq!(Key::from_numeric_wrapping(11, false), Key::BFlatMinor);
assert_eq!(Key::from_numeric_wrapping(12, false), Key::BMinor);
assert_eq!(Key::from_numeric_wrapping(13, false), Key::CMinor);
assert_eq!(Key::from_numeric_wrapping(24, false), Key::BMinor);
}
#[test]
fn test_transpose_semitones() {
macro_rules! test_case {
($lancelot_init: tt, $tones: expr, $lancelot_transposed: tt) => {
let key_init = Key::from_string($lancelot_init).expect("Failed parsing key init");
assert_eq!(
key_init.transpose_semitones($tones).lancelot(),
$lancelot_transposed
);
};
}
test_case!("8A", 12, "8A");
test_case!("8A", -1, "1A");
test_case!("8A", -2, "6A");
test_case!("8A", -2, "6A");
test_case!("8A", 1, "3A");
test_case!("8A", 2, "10A");
test_case!("8A", 3, "5A");
}
#[test]
fn test_transpose_tones() {
macro_rules! test_case {
($lancelot_init: tt, $tones: expr, $lancelot_transposed: tt) => {
let key_init = Key::from_string($lancelot_init).expect("Failed parsing key init");
assert_eq!(
key_init.transpose_tones($tones).lancelot(),
$lancelot_transposed
);
};
}
test_case!("3A", -1, "1A");
}
#[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.0);
assert_eq!(factor_to_semitones(2.0), 12.0);
assert_eq!(factor_to_semitones(1.0595).round(), 1.0);
assert_eq!(factor_to_semitones(0.9659259).round(), -1.0)
}
#[test]
fn test_bpm_to_factor() {
assert_eq!(bpm_to_factor(120.0, 120.0), 1.0);
assert_eq!(bpm_to_factor(120.0, 240.0), 2.0);
assert_eq!(bpm_to_factor(240.0, 120.0), 0.5);
assert_eq!(bpm_to_factor(135.0, 130.4), 0.9659259);
}
#[test]
fn test_transpose_bpm() {
let root = Key::CMajor;
println!("root={root:?}");
assert_eq!(root.transpose_bpm(90.0, 180.0), root);
macro_rules! test_case_lancelot {
($lancelot_init:tt, $bpm_init: tt, $lancelot_transposed: tt, $bpm_transposed: tt) => {
let key_init = Key::from_string($lancelot_init).expect("Failed parsing key init");
assert_eq!(
key_init
.transpose_bpm($bpm_init, $bpm_transposed)
.lancelot(),
$lancelot_transposed
);
};
}
test_case_lancelot!("3A", 135.0, "8A", 130.4);
test_case_lancelot!("3A", 135.0, "10A", 140.4);
test_case_lancelot!("3A", 135.0, "5A", 148.9);
test_case_lancelot!("3A", 135.0, "11A", 13.5);
}
#[test]
fn test_lancelot_i32_representation() {
for i in 0..24 {
let key = Key::try_from_lancelot_i32(i).expect("Failed turning i32 test case into Key");
assert_eq!(key.to_lancelot_i32(), i);
}
assert_eq!(Key::try_from_lancelot_i32(-1), Err(()));
assert_eq!(Key::try_from_lancelot_i32(24), Err(()));
macro_rules! test_case_lancelot {
($lancelot:tt, $i32:tt) => {
let key = Key::from_string($lancelot).expect("Failed parsing key init");
assert_eq!(key.to_lancelot_i32(), $i32);
};
}
test_case_lancelot!("1A", 0);
test_case_lancelot!("3A", 2);
test_case_lancelot!("12A", 11);
test_case_lancelot!("1B", 12);
test_case_lancelot!("12B", 23);
}
}