#![allow(clippy::suspicious_arithmetic_impl)]
use std::ops::{Add, Sub};
use crate::{Semitones, PITCH_CLASS_COUNT};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum PitchClass {
C,
CSharp,
D,
DSharp,
E,
F,
FSharp,
G,
GSharp,
A,
ASharp,
B,
}
impl From<Semitones> for PitchClass {
fn from(n: Semitones) -> Self {
use PitchClass::*;
let v = n % PITCH_CLASS_COUNT;
match v {
0 => C,
1 => CSharp,
2 => D,
3 => DSharp,
4 => E,
5 => F,
6 => FSharp,
7 => G,
8 => GSharp,
9 => A,
10 => ASharp,
11 => B,
_ => unreachable!(),
}
}
}
impl Add<Semitones> for PitchClass {
type Output = Self;
fn add(self, n: Semitones) -> Self {
let v = self as Semitones + n;
Self::from(v)
}
}
impl Sub for PitchClass {
type Output = Semitones;
fn sub(self, other: Self) -> Semitones {
let d = self as i8 - other as i8;
let diff = match d {
d if d >= 0 => d,
_ => d + PITCH_CLASS_COUNT as i8,
};
diff as Semitones
}
}
impl Sub<Semitones> for PitchClass {
type Output = Self;
fn sub(self, n: Semitones) -> Self {
Self::from(self - Self::from(n))
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use PitchClass::*;
use super::*;
#[rstest(
n,
pitch_class,
case(0, C),
case(1, CSharp),
case(2, D),
case(3, DSharp),
case(4, E),
case(5, F),
case(6, FSharp),
case(7, G),
case(8, GSharp),
case(9, A),
case(10, ASharp),
case(11, B),
case(12, C),
case(13, CSharp),
case(24, C),
case(127, G),
case(255, DSharp)
)]
fn test_from_int(n: Semitones, pitch_class: PitchClass) {
assert_eq!(PitchClass::from(n), pitch_class);
}
#[rstest(
pitch_class,
n,
result,
case(C, 0, C),
case(C, 1, CSharp),
case(C, 10, ASharp),
case(C, 12, C),
case(C, 13, CSharp),
case(C, 24, C)
)]
fn test_add_int(pitch_class: PitchClass, n: Semitones, result: PitchClass) {
assert_eq!(pitch_class + n, result);
}
#[rstest(
pc1,
pc2,
n,
case(C, C, 0),
case(D, C, 2),
case(D, A, 5),
case(C, CSharp, 11)
)]
fn test_sub_self(pc1: PitchClass, pc2: PitchClass, n: Semitones) {
assert_eq!(pc1 - pc2, n);
}
#[rstest(
pc1,
n,
pc2,
case(C, 0, C),
case(D, 2, C),
case(D, 5, A),
case(C, 11, CSharp),
case(C, 12, C),
case(C, 13, B)
)]
fn test_sub_int(pc1: PitchClass, n: Semitones, pc2: PitchClass) {
assert_eq!(pc1 - n, pc2);
}
}