use std::fmt;
use crate::core::{Accidental, Interval, Pitch, Step};
use super::Scale;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum KeyMode {
#[default]
Major,
Minor,
Dorian,
Phrygian,
Lydian,
Mixolydian,
Aeolian,
Locrian,
}
impl KeyMode {
pub fn name(&self) -> &'static str {
match self {
KeyMode::Major => "major",
KeyMode::Minor => "minor",
KeyMode::Dorian => "dorian",
KeyMode::Phrygian => "phrygian",
KeyMode::Lydian => "lydian",
KeyMode::Mixolydian => "mixolydian",
KeyMode::Aeolian => "aeolian",
KeyMode::Locrian => "locrian",
}
}
fn is_minor_like(&self) -> bool {
matches!(
self,
KeyMode::Minor
| KeyMode::Aeolian
| KeyMode::Dorian
| KeyMode::Phrygian
| KeyMode::Locrian
)
}
}
impl fmt::Display for KeyMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
const MAJOR_TONICS: [(Step, Option<Accidental>); 15] = [
(Step::C, Some(Accidental::Flat)), (Step::G, Some(Accidental::Flat)), (Step::D, Some(Accidental::Flat)), (Step::A, Some(Accidental::Flat)), (Step::E, Some(Accidental::Flat)), (Step::B, Some(Accidental::Flat)), (Step::F, None), (Step::C, None), (Step::G, None), (Step::D, None), (Step::A, None), (Step::E, None), (Step::B, None), (Step::F, Some(Accidental::Sharp)), (Step::C, Some(Accidental::Sharp)), ];
const MINOR_TONICS: [(Step, Option<Accidental>); 15] = [
(Step::A, Some(Accidental::Flat)), (Step::E, Some(Accidental::Flat)), (Step::B, Some(Accidental::Flat)), (Step::F, None), (Step::C, None), (Step::G, None), (Step::D, None), (Step::A, None), (Step::E, None), (Step::B, None), (Step::F, Some(Accidental::Sharp)), (Step::C, Some(Accidental::Sharp)), (Step::G, Some(Accidental::Sharp)), (Step::D, Some(Accidental::Sharp)), (Step::A, Some(Accidental::Sharp)), ];
pub fn sharps_to_pitch(sharps: i8, minor: bool) -> Pitch {
let idx = (sharps.clamp(-7, 7) + 7) as usize;
let (step, accidental) = if minor {
MINOR_TONICS[idx]
} else {
MAJOR_TONICS[idx]
};
Pitch::from_parts(step, None, accidental)
}
pub fn pitch_to_sharps(pitch: &Pitch, minor: bool) -> Option<i8> {
(-7..=7i8).find(|&s| {
let candidate = sharps_to_pitch(s, minor);
candidate.step() == pitch.step() && candidate.accidental() == pitch.accidental()
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Key {
tonic: Pitch,
mode: KeyMode,
}
impl Key {
pub fn new(tonic: Pitch, mode: KeyMode) -> Self {
Self { tonic, mode }
}
pub fn major(tonic: Step) -> Self {
Self::new(Pitch::from_parts(tonic, None, None), KeyMode::Major)
}
pub fn minor(tonic: Step) -> Self {
Self::new(Pitch::from_parts(tonic, None, None), KeyMode::Minor)
}
pub fn tonic(&self) -> &Pitch {
&self.tonic
}
pub fn mode(&self) -> KeyMode {
self.mode
}
pub fn relative(&self) -> Key {
match self.mode {
KeyMode::Major | KeyMode::Aeolian => match pitch_to_sharps(&self.tonic, false) {
Some(sharps) => Key::new(sharps_to_pitch(sharps, true), KeyMode::Minor),
None => self.clone(),
},
KeyMode::Minor => match pitch_to_sharps(&self.tonic, true) {
Some(sharps) => Key::new(sharps_to_pitch(sharps, false), KeyMode::Major),
None => self.clone(),
},
_ => self.clone(),
}
}
pub fn parallel(&self) -> Key {
match self.mode {
KeyMode::Major => Key::new(self.tonic.clone(), KeyMode::Minor),
KeyMode::Minor => Key::new(self.tonic.clone(), KeyMode::Major),
_ => self.clone(),
}
}
pub fn transpose(&self, interval: &Interval) -> Key {
Key::new(self.tonic.transpose(interval), self.mode)
}
pub fn name(&self) -> String {
format!("{} {}", self.tonic.name(), self.mode)
}
pub fn tonic_pitch_name_with_case(&self) -> String {
let name = self.tonic.name();
if self.mode.is_minor_like() {
name.to_lowercase()
} else {
name
}
}
pub fn derive_by_degree(&self, degree: u8, pitch_ref: &Pitch) -> Option<Key> {
if degree == 0 || degree > 7 {
return None;
}
let intervals = Scale::intervals_for_mode(self.mode);
let offset = intervals[(degree - 1) as usize];
let tonic_pc = (pitch_ref.pitch_class() as i32 - offset).rem_euclid(12);
let tonic_pitch = Pitch::from_midi(60 + tonic_pc as u8);
Some(Key::new(tonic_pitch, self.mode))
}
}
impl Default for Key {
fn default() -> Self {
Self::major(Step::C)
}
}
impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct KeySignature {
sharps: i8,
minor: bool,
}
impl KeySignature {
pub fn new(sharps: i8, minor: bool) -> Self {
Self { sharps, minor }
}
pub fn from_sharps(sharps: i8) -> Self {
Self {
sharps,
minor: false,
}
}
pub fn from_flats(flats: u8) -> Self {
Self {
sharps: -(flats as i8),
minor: false,
}
}
pub fn c_major() -> Self {
Self::new(0, false)
}
pub fn a_minor() -> Self {
Self::new(0, true)
}
pub fn g_major() -> Self {
Self::new(1, false)
}
pub fn f_major() -> Self {
Self::new(-1, false)
}
pub fn sharps(&self) -> i8 {
self.sharps
}
pub fn flats(&self) -> u8 {
if self.sharps < 0 {
(-self.sharps) as u8
} else {
0
}
}
pub fn is_minor(&self) -> bool {
self.minor
}
pub fn is_major(&self) -> bool {
!self.minor
}
pub fn is_non_traditional(&self) -> bool {
!(-7..=7).contains(&self.sharps)
}
pub fn tonic(&self) -> Pitch {
sharps_to_pitch(self.sharps, self.minor)
}
pub fn altered_pitches(&self) -> Vec<Step> {
let sharp_order = [
Step::F,
Step::C,
Step::G,
Step::D,
Step::A,
Step::E,
Step::B,
];
let flat_order = [
Step::B,
Step::E,
Step::A,
Step::D,
Step::G,
Step::C,
Step::F,
];
if self.sharps > 0 {
sharp_order[..self.sharps as usize].to_vec()
} else if self.sharps < 0 {
flat_order[..(-self.sharps) as usize].to_vec()
} else {
vec![]
}
}
pub fn is_altered(&self, step: Step) -> bool {
self.altered_pitches().contains(&step)
}
pub fn accidental_for(&self, step: Step) -> Option<Accidental> {
if self.is_altered(step) {
if self.sharps > 0 {
Some(Accidental::Sharp)
} else {
Some(Accidental::Flat)
}
} else {
None
}
}
pub fn to_key(&self) -> Key {
let mode = if self.minor {
KeyMode::Minor
} else {
KeyMode::Major
};
Key::new(self.tonic(), mode)
}
pub fn as_key(&self, mode: KeyMode) -> Key {
match mode {
KeyMode::Major => Key::new(sharps_to_pitch(self.sharps, false), KeyMode::Major),
KeyMode::Minor => Key::new(sharps_to_pitch(self.sharps, true), KeyMode::Minor),
other => Key::new(sharps_to_pitch(self.sharps, false), other),
}
}
pub fn get_scale(&self, mode: KeyMode) -> Scale {
let key = self.as_key(mode);
Scale::new(key.tonic().clone(), mode)
}
pub fn transpose_pitch_from_c(&self, pitch: &Pitch) -> Pitch {
let major_tonic = sharps_to_pitch(self.sharps, false);
let c = Pitch::from_parts(Step::C, None, None);
let shift = (major_tonic.pitch_class() as i32 - c.pitch_class() as i32).rem_euclid(12);
pitch.transpose_semitones(shift)
}
}
impl Default for KeySignature {
fn default() -> Self {
Self::c_major()
}
}
impl fmt::Display for KeySignature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.sharps == 0 {
write!(f, "no sharps/flats")
} else if self.sharps > 0 {
write!(f, "{} sharp(s)", self.sharps)
} else {
write!(f, "{} flat(s)", -self.sharps)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_key_creation() {
let key = Key::major(Step::C);
assert_eq!(key.tonic().step(), Step::C);
assert_eq!(key.tonic().accidental(), None);
assert_eq!(key.mode(), KeyMode::Major);
}
#[test]
fn test_key_relative() {
let c_major = Key::major(Step::C);
let relative = c_major.relative();
assert_eq!(relative.tonic().step(), Step::A);
assert_eq!(relative.tonic().accidental(), None);
assert_eq!(relative.mode(), KeyMode::Minor);
}
#[test]
fn test_sharp_minor_key_is_correctly_spelled() {
let a_major = Key::major(Step::A);
let relative = a_major.relative();
assert_eq!(relative.tonic().step(), Step::F);
assert_eq!(relative.tonic().accidental(), Some(Accidental::Sharp));
assert_eq!(relative.name(), "F# minor");
let three_sharps_minor = KeySignature::new(3, true);
let tonic = three_sharps_minor.tonic();
assert_eq!(tonic.step(), Step::F);
assert_eq!(tonic.accidental(), Some(Accidental::Sharp));
}
#[test]
fn test_can_construct_previously_unrepresentable_keys() {
let f_sharp_minor = Key::new(
Pitch::from_parts(Step::F, None, Some(Accidental::Sharp)),
KeyMode::Minor,
);
assert_eq!(f_sharp_minor.name(), "F# minor");
let b_flat_major = Key::new(
Pitch::from_parts(Step::B, None, Some(Accidental::Flat)),
KeyMode::Major,
);
assert_eq!(b_flat_major.name(), "Bb major");
}
#[test]
fn test_sharps_to_pitch_and_back_roundtrip() {
for sharps in -7..=7i8 {
for minor in [false, true] {
let pitch = sharps_to_pitch(sharps, minor);
assert_eq!(pitch_to_sharps(&pitch, minor), Some(sharps));
}
}
}
#[test]
fn test_key_signature() {
let ks = KeySignature::g_major();
assert_eq!(ks.sharps(), 1);
assert!(!ks.is_minor());
assert_eq!(ks.tonic().step(), Step::G);
assert_eq!(ks.tonic().accidental(), None);
}
#[test]
fn test_key_signature_altered() {
let g_major = KeySignature::g_major();
assert!(g_major.is_altered(Step::F));
assert!(!g_major.is_altered(Step::C));
let f_major = KeySignature::f_major();
assert!(f_major.is_altered(Step::B));
}
#[test]
fn test_key_signature_as_key_and_non_traditional() {
let three_sharps = KeySignature::new(3, false);
assert!(!three_sharps.is_non_traditional());
assert_eq!(three_sharps.as_key(KeyMode::Minor).name(), "F# minor");
let out_of_range = KeySignature::new(10, false);
assert!(out_of_range.is_non_traditional());
}
#[test]
fn test_tonic_pitch_name_with_case() {
let f_sharp_minor = Key::new(
Pitch::from_parts(Step::F, None, Some(Accidental::Sharp)),
KeyMode::Minor,
);
assert_eq!(f_sharp_minor.tonic_pitch_name_with_case(), "f#");
let c_major = Key::major(Step::C);
assert_eq!(c_major.tonic_pitch_name_with_case(), "C");
}
#[test]
fn test_transpose_pitch_from_c() {
let d_major_sig = KeySignature::new(2, false);
let c = Pitch::from_parts(Step::C, None, None);
let transposed = d_major_sig.transpose_pitch_from_c(&c);
assert_eq!(transposed.step(), Step::D);
}
#[test]
fn test_key_signature_get_scale() {
let g_major_sig = KeySignature::g_major();
let scale = g_major_sig.get_scale(KeyMode::Major);
let names: Vec<String> = scale.pitches().iter().map(|p| p.name()).collect();
assert_eq!(names, vec!["G", "A", "B", "C", "D", "E", "F#"]);
}
#[test]
fn test_key_derive_by_degree() {
let major = Key::major(Step::C);
let d = Pitch::from_parts(Step::D, None, None);
let derived = major.derive_by_degree(5, &d).unwrap();
assert_eq!(derived.tonic().step(), Step::G);
assert_eq!(derived.mode(), KeyMode::Major);
assert!(major.derive_by_degree(0, &d).is_none());
assert!(major.derive_by_degree(8, &d).is_none());
}
}