#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyRoot {
C,
Cs, D,
Ds, E,
F,
Fs, G,
Gs, A,
As, B,
Df, Ef, Gf, Af, Bf, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyMode {
Major, Minor, Dorian, Phrygian, Lydian, Mixolydian, Aeolian, Locrian, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KeySignature {
pub root: KeyRoot,
pub mode: KeyMode,
}
impl KeySignature {
pub fn new(root: KeyRoot, mode: KeyMode) -> Self {
Self { root, mode }
}
fn modal_parent(&self, semitones: i8) -> KeyRoot {
use KeyRoot::*;
let root_value = match self.root {
C => 0,
Cs | Df => 1,
D => 2,
Ds | Ef => 3,
E => 4,
F => 5,
Fs | Gf => 6,
G => 7,
Gs | Af => 8,
A => 9,
As | Bf => 10,
B => 11,
};
let new_value = (root_value + semitones).rem_euclid(12);
match new_value {
0 => C,
1 => Cs,
2 => D,
3 => Ef, 4 => E,
5 => F,
6 => Fs,
7 => G,
8 => Af, 9 => A,
10 => Bf, 11 => B,
_ => C, }
}
pub fn to_midi_sharps_flats(self) -> i8 {
use KeyMode::*;
use KeyRoot::*;
let (effective_root, effective_mode) = match self.mode {
Dorian => (self.modal_parent(-2), Major),
Phrygian => (self.modal_parent(-4), Major),
Lydian => (self.modal_parent(-5), Major),
Mixolydian => (self.modal_parent(-7), Major),
Aeolian => (self.modal_parent(3), Major),
Locrian => (self.modal_parent(1), Major),
_ => (self.root, self.mode),
};
match (effective_root, effective_mode) {
(C, Major) => 0, (G, Major) => 1, (D, Major) => 2, (A, Major) => 3, (E, Major) => 4, (B, Major) => 5, (Fs, Major) | (Gf, Major) => 6, (Cs, Major) | (Df, Major) => 7,
(F, Major) => -1, (Bf, Major) => -2, (Ef, Major) => -3, (Af, Major) => -4,
(A, Minor) => 0, (E, Minor) => 1, (B, Minor) => 2, (Fs, Minor) => 3, (Cs, Minor) => 4, (Gs, Minor) => 5, (Ds, Minor) | (Ef, Minor) => 6, (As, Minor) | (Bf, Minor) => 7,
(D, Minor) => -1, (G, Minor) => -2, (C, Minor) => -3, (F, Minor) => -4,
_ => 0,
}
}
pub fn is_minor(&self) -> bool {
matches!(
self.mode,
KeyMode::Minor
| KeyMode::Dorian
| KeyMode::Phrygian
| KeyMode::Aeolian
| KeyMode::Locrian
)
}
pub fn name(&self) -> String {
let root_name = match self.root {
KeyRoot::C => "C",
KeyRoot::Cs => "C#",
KeyRoot::D => "D",
KeyRoot::Ds => "D#",
KeyRoot::E => "E",
KeyRoot::F => "F",
KeyRoot::Fs => "F#",
KeyRoot::G => "G",
KeyRoot::Gs => "G#",
KeyRoot::A => "A",
KeyRoot::As => "A#",
KeyRoot::B => "B",
KeyRoot::Df => "Db",
KeyRoot::Ef => "Eb",
KeyRoot::Gf => "Gb",
KeyRoot::Af => "Ab",
KeyRoot::Bf => "Bb",
};
let mode_name = match self.mode {
KeyMode::Major => "Major",
KeyMode::Minor => "Minor",
KeyMode::Dorian => "Dorian",
KeyMode::Phrygian => "Phrygian",
KeyMode::Lydian => "Lydian",
KeyMode::Mixolydian => "Mixolydian",
KeyMode::Aeolian => "Aeolian",
KeyMode::Locrian => "Locrian",
};
format!("{} {}", root_name, mode_name)
}
}
impl KeySignature {
pub const C_MAJOR: Self = Self {
root: KeyRoot::C,
mode: KeyMode::Major,
};
pub const G_MAJOR: Self = Self {
root: KeyRoot::G,
mode: KeyMode::Major,
};
pub const D_MAJOR: Self = Self {
root: KeyRoot::D,
mode: KeyMode::Major,
};
pub const A_MAJOR: Self = Self {
root: KeyRoot::A,
mode: KeyMode::Major,
};
pub const E_MAJOR: Self = Self {
root: KeyRoot::E,
mode: KeyMode::Major,
};
pub const B_MAJOR: Self = Self {
root: KeyRoot::B,
mode: KeyMode::Major,
};
pub const F_SHARP_MAJOR: Self = Self {
root: KeyRoot::Fs,
mode: KeyMode::Major,
};
pub const C_SHARP_MAJOR: Self = Self {
root: KeyRoot::Cs,
mode: KeyMode::Major,
};
pub const F_MAJOR: Self = Self {
root: KeyRoot::F,
mode: KeyMode::Major,
};
pub const B_FLAT_MAJOR: Self = Self {
root: KeyRoot::Bf,
mode: KeyMode::Major,
};
pub const E_FLAT_MAJOR: Self = Self {
root: KeyRoot::Ef,
mode: KeyMode::Major,
};
pub const A_FLAT_MAJOR: Self = Self {
root: KeyRoot::Af,
mode: KeyMode::Major,
};
pub const D_FLAT_MAJOR: Self = Self {
root: KeyRoot::Df,
mode: KeyMode::Major,
};
pub const G_FLAT_MAJOR: Self = Self {
root: KeyRoot::Gf,
mode: KeyMode::Major,
};
pub const A_MINOR: Self = Self {
root: KeyRoot::A,
mode: KeyMode::Minor,
};
pub const E_MINOR: Self = Self {
root: KeyRoot::E,
mode: KeyMode::Minor,
};
pub const B_MINOR: Self = Self {
root: KeyRoot::B,
mode: KeyMode::Minor,
};
pub const F_SHARP_MINOR: Self = Self {
root: KeyRoot::Fs,
mode: KeyMode::Minor,
};
pub const C_SHARP_MINOR: Self = Self {
root: KeyRoot::Cs,
mode: KeyMode::Minor,
};
pub const G_SHARP_MINOR: Self = Self {
root: KeyRoot::Gs,
mode: KeyMode::Minor,
};
pub const D_SHARP_MINOR: Self = Self {
root: KeyRoot::Ds,
mode: KeyMode::Minor,
};
pub const D_MINOR: Self = Self {
root: KeyRoot::D,
mode: KeyMode::Minor,
};
pub const G_MINOR: Self = Self {
root: KeyRoot::G,
mode: KeyMode::Minor,
};
pub const C_MINOR: Self = Self {
root: KeyRoot::C,
mode: KeyMode::Minor,
};
pub const F_MINOR: Self = Self {
root: KeyRoot::F,
mode: KeyMode::Minor,
};
pub const B_FLAT_MINOR: Self = Self {
root: KeyRoot::Bf,
mode: KeyMode::Minor,
};
pub const E_FLAT_MINOR: Self = Self {
root: KeyRoot::Ef,
mode: KeyMode::Minor,
};
pub const D_DORIAN: Self = Self {
root: KeyRoot::D,
mode: KeyMode::Dorian,
};
pub const E_DORIAN: Self = Self {
root: KeyRoot::E,
mode: KeyMode::Dorian,
};
pub const A_DORIAN: Self = Self {
root: KeyRoot::A,
mode: KeyMode::Dorian,
};
pub const G_DORIAN: Self = Self {
root: KeyRoot::G,
mode: KeyMode::Dorian,
};
pub const E_PHRYGIAN: Self = Self {
root: KeyRoot::E,
mode: KeyMode::Phrygian,
};
pub const A_PHRYGIAN: Self = Self {
root: KeyRoot::A,
mode: KeyMode::Phrygian,
};
pub const B_PHRYGIAN: Self = Self {
root: KeyRoot::B,
mode: KeyMode::Phrygian,
};
pub const F_LYDIAN: Self = Self {
root: KeyRoot::F,
mode: KeyMode::Lydian,
};
pub const C_LYDIAN: Self = Self {
root: KeyRoot::C,
mode: KeyMode::Lydian,
};
pub const G_LYDIAN: Self = Self {
root: KeyRoot::G,
mode: KeyMode::Lydian,
};
pub const G_MIXOLYDIAN: Self = Self {
root: KeyRoot::G,
mode: KeyMode::Mixolydian,
};
pub const D_MIXOLYDIAN: Self = Self {
root: KeyRoot::D,
mode: KeyMode::Mixolydian,
};
pub const A_MIXOLYDIAN: Self = Self {
root: KeyRoot::A,
mode: KeyMode::Mixolydian,
};
pub const C_MIXOLYDIAN: Self = Self {
root: KeyRoot::C,
mode: KeyMode::Mixolydian,
};
pub const A_AEOLIAN: Self = Self {
root: KeyRoot::A,
mode: KeyMode::Aeolian,
};
pub const E_AEOLIAN: Self = Self {
root: KeyRoot::E,
mode: KeyMode::Aeolian,
};
pub const D_AEOLIAN: Self = Self {
root: KeyRoot::D,
mode: KeyMode::Aeolian,
};
pub const B_LOCRIAN: Self = Self {
root: KeyRoot::B,
mode: KeyMode::Locrian,
};
pub const E_LOCRIAN: Self = Self {
root: KeyRoot::E,
mode: KeyMode::Locrian,
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_major_key_sharps() {
assert_eq!(KeySignature::C_MAJOR.to_midi_sharps_flats(), 0);
assert_eq!(KeySignature::G_MAJOR.to_midi_sharps_flats(), 1);
assert_eq!(KeySignature::D_MAJOR.to_midi_sharps_flats(), 2);
assert_eq!(KeySignature::A_MAJOR.to_midi_sharps_flats(), 3);
assert_eq!(KeySignature::E_MAJOR.to_midi_sharps_flats(), 4);
assert_eq!(KeySignature::B_MAJOR.to_midi_sharps_flats(), 5);
assert_eq!(KeySignature::F_SHARP_MAJOR.to_midi_sharps_flats(), 6);
}
#[test]
fn test_major_key_flats() {
assert_eq!(KeySignature::F_MAJOR.to_midi_sharps_flats(), -1);
assert_eq!(KeySignature::B_FLAT_MAJOR.to_midi_sharps_flats(), -2);
assert_eq!(KeySignature::E_FLAT_MAJOR.to_midi_sharps_flats(), -3);
assert_eq!(KeySignature::A_FLAT_MAJOR.to_midi_sharps_flats(), -4);
assert_eq!(KeySignature::D_FLAT_MAJOR.to_midi_sharps_flats(), 7); assert_eq!(KeySignature::G_FLAT_MAJOR.to_midi_sharps_flats(), 6); }
#[test]
fn test_minor_keys() {
assert_eq!(KeySignature::A_MINOR.to_midi_sharps_flats(), 0);
assert_eq!(KeySignature::E_MINOR.to_midi_sharps_flats(), 1);
assert_eq!(KeySignature::B_MINOR.to_midi_sharps_flats(), 2);
assert_eq!(KeySignature::D_MINOR.to_midi_sharps_flats(), -1);
assert_eq!(KeySignature::G_MINOR.to_midi_sharps_flats(), -2);
assert_eq!(KeySignature::C_MINOR.to_midi_sharps_flats(), -3);
}
#[test]
fn test_is_minor() {
assert!(!KeySignature::C_MAJOR.is_minor());
assert!(KeySignature::A_MINOR.is_minor());
assert!(!KeySignature::G_MAJOR.is_minor());
assert!(KeySignature::E_MINOR.is_minor());
}
#[test]
fn test_key_names() {
assert_eq!(KeySignature::C_MAJOR.name(), "C Major");
assert_eq!(KeySignature::A_MINOR.name(), "A Minor");
assert_eq!(KeySignature::F_SHARP_MAJOR.name(), "F# Major");
assert_eq!(KeySignature::B_FLAT_MAJOR.name(), "Bb Major");
assert_eq!(KeySignature::E_FLAT_MINOR.name(), "Eb Minor");
}
#[test]
fn test_enharmonic_equivalents() {
let fs_major = KeySignature::new(KeyRoot::Fs, KeyMode::Major);
let gf_major = KeySignature::new(KeyRoot::Gf, KeyMode::Major);
assert_eq!(fs_major.to_midi_sharps_flats(), 6);
assert_eq!(gf_major.to_midi_sharps_flats(), 6);
}
#[test]
fn test_custom_key_signature() {
let key = KeySignature::new(KeyRoot::D, KeyMode::Major);
assert_eq!(key.to_midi_sharps_flats(), 2);
assert!(!key.is_minor());
assert_eq!(key.name(), "D Major");
}
#[test]
fn test_dorian_mode() {
assert_eq!(KeySignature::D_DORIAN.to_midi_sharps_flats(), 0);
assert!(KeySignature::D_DORIAN.is_minor());
assert_eq!(KeySignature::D_DORIAN.name(), "D Dorian");
assert_eq!(KeySignature::E_DORIAN.to_midi_sharps_flats(), 2);
assert_eq!(KeySignature::G_DORIAN.to_midi_sharps_flats(), -1);
}
#[test]
fn test_phrygian_mode() {
assert_eq!(KeySignature::E_PHRYGIAN.to_midi_sharps_flats(), 0);
assert!(KeySignature::E_PHRYGIAN.is_minor());
assert_eq!(KeySignature::E_PHRYGIAN.name(), "E Phrygian");
assert_eq!(KeySignature::A_PHRYGIAN.to_midi_sharps_flats(), -1);
assert_eq!(KeySignature::B_PHRYGIAN.to_midi_sharps_flats(), 1);
}
#[test]
fn test_lydian_mode() {
assert_eq!(KeySignature::F_LYDIAN.to_midi_sharps_flats(), 0);
assert!(!KeySignature::F_LYDIAN.is_minor());
assert_eq!(KeySignature::F_LYDIAN.name(), "F Lydian");
assert_eq!(KeySignature::C_LYDIAN.to_midi_sharps_flats(), 1);
assert_eq!(KeySignature::G_LYDIAN.to_midi_sharps_flats(), 2);
}
#[test]
fn test_mixolydian_mode() {
assert_eq!(KeySignature::G_MIXOLYDIAN.to_midi_sharps_flats(), 0);
assert!(!KeySignature::G_MIXOLYDIAN.is_minor());
assert_eq!(KeySignature::G_MIXOLYDIAN.name(), "G Mixolydian");
assert_eq!(KeySignature::D_MIXOLYDIAN.to_midi_sharps_flats(), 1);
assert_eq!(KeySignature::A_MIXOLYDIAN.to_midi_sharps_flats(), 2);
assert_eq!(KeySignature::C_MIXOLYDIAN.to_midi_sharps_flats(), -1);
}
#[test]
fn test_aeolian_mode() {
assert_eq!(KeySignature::A_AEOLIAN.to_midi_sharps_flats(), 0);
assert!(KeySignature::A_AEOLIAN.is_minor());
assert_eq!(KeySignature::A_AEOLIAN.name(), "A Aeolian");
assert_eq!(KeySignature::E_AEOLIAN.to_midi_sharps_flats(), 1);
assert_eq!(KeySignature::D_AEOLIAN.to_midi_sharps_flats(), -1);
}
#[test]
fn test_locrian_mode() {
assert_eq!(KeySignature::B_LOCRIAN.to_midi_sharps_flats(), 0);
assert!(KeySignature::B_LOCRIAN.is_minor());
assert_eq!(KeySignature::B_LOCRIAN.name(), "B Locrian");
assert_eq!(KeySignature::E_LOCRIAN.to_midi_sharps_flats(), -1);
}
#[test]
fn test_all_modes_of_c() {
assert_eq!(
KeySignature::new(KeyRoot::C, KeyMode::Major).to_midi_sharps_flats(),
0
); assert_eq!(
KeySignature::new(KeyRoot::D, KeyMode::Dorian).to_midi_sharps_flats(),
0
); assert_eq!(
KeySignature::new(KeyRoot::E, KeyMode::Phrygian).to_midi_sharps_flats(),
0
); assert_eq!(
KeySignature::new(KeyRoot::F, KeyMode::Lydian).to_midi_sharps_flats(),
0
); assert_eq!(
KeySignature::new(KeyRoot::G, KeyMode::Mixolydian).to_midi_sharps_flats(),
0
); assert_eq!(
KeySignature::new(KeyRoot::A, KeyMode::Aeolian).to_midi_sharps_flats(),
0
); assert_eq!(
KeySignature::new(KeyRoot::B, KeyMode::Locrian).to_midi_sharps_flats(),
0
); }
}