#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::Automation;
pub type Octave = i8;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[allow(missing_docs)]
pub enum Letter {
C,
Db,
D,
Eb,
E,
F,
Gb,
G,
Ab,
A,
Bb,
B,
}
impl Letter {
pub const fn oct(self, octave: Octave) -> Pitch {
Pitch::new(self, octave)
}
pub fn frequency(self, octave: Octave) -> f64 {
440.0 * 2f64.powf(((octave as f64 - 4.0) * 12.0 + (self as i8 as f64 - 9.0)) / 12.0)
}
pub const fn half_steps(self, octave: Octave) -> i16 {
(octave as i16 * 12) + (self as i16)
}
#[allow(missing_docs, non_upper_case_globals)]
pub const Csh: Self = Self::Db;
#[allow(missing_docs, non_upper_case_globals)]
pub const Dsh: Self = Self::Eb;
#[allow(missing_docs, non_upper_case_globals)]
pub const Fsh: Self = Self::Gb;
#[allow(missing_docs, non_upper_case_globals)]
pub const Gsh: Self = Self::Ab;
#[allow(missing_docs, non_upper_case_globals)]
pub const Ash: Self = Self::Bb;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Pitch {
pub octave: Octave,
pub letter: Letter,
}
impl Pitch {
pub const fn new(letter: Letter, octave: Octave) -> Self {
Self { letter, octave }
}
pub fn frequency(&self) -> f64 {
self.letter.frequency(self.octave)
}
pub const fn from_half_steps(half_steps: i16) -> Self {
let octave = (half_steps / 12) as i8;
let letter = match half_steps % 12 {
0 => Letter::C,
1 => Letter::Db,
2 => Letter::D,
3 => Letter::Eb,
4 => Letter::E,
5 => Letter::F,
6 => Letter::Gb,
7 => Letter::G,
8 => Letter::Ab,
9 => Letter::A,
10 => Letter::Bb,
11 => Letter::B,
_ => unreachable!(),
};
Self { letter, octave }
}
pub const fn to_half_steps(&self) -> i16 {
self.letter.half_steps(self.octave)
}
}
impl Automation for Pitch {
fn next_value(&mut self, _sample_rate: f64) -> Option<f64> {
Some(self.frequency())
}
}
impl Automation for (Letter, Octave) {
fn next_value(&mut self, _sample_rate: f64) -> Option<f64> {
Some(self.0.frequency(self.1))
}
}
impl From<(Letter, Octave)> for Pitch {
fn from((letter, octave): (Letter, Octave)) -> Self {
Self { letter, octave }
}
}
impl From<i16> for Pitch {
fn from(half_steps: i16) -> Self {
Self::from_half_steps(half_steps)
}
}
impl PartialEq<(Letter, Octave)> for Pitch {
fn eq(&self, other: &(Letter, Octave)) -> bool {
self.letter == other.0 && self.octave == other.1
}
}
impl PartialEq<Pitch> for (Letter, Octave) {
fn eq(&self, other: &Pitch) -> bool {
self.0 == other.letter && self.1 == other.octave
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Letter {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = <&str>::deserialize(deserializer)?;
match s.to_lowercase().as_str() {
"c" => Ok(Letter::C),
"db" | "c#" | "csh" => Ok(Letter::Db),
"d" => Ok(Letter::D),
"eb" | "d#" | "dsh" => Ok(Letter::Eb),
"e" => Ok(Letter::E),
"f" => Ok(Letter::F),
"gb" | "f#" | "fsh" => Ok(Letter::Gb),
"g" => Ok(Letter::G),
"ab" | "g#" | "gsh" => Ok(Letter::Ab),
"a" => Ok(Letter::A),
"bb" | "a#" | "ash" => Ok(Letter::Bb),
"b" => Ok(Letter::B),
_ => Err(serde::de::Error::custom(format!(
"Invalid note letter: {s:?}"
))),
}
}
}
#[cfg(feature = "serde")]
mod pitch_ser {
use super::*;
impl Serialize for Pitch {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
(self.letter, self.octave).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Pitch {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let (letter, octave) = Deserialize::deserialize(deserializer)?;
Ok(Self { letter, octave })
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(missing_docs)]
pub enum Mode {
Major,
Minor,
Dorian,
Phrygian,
Lydian,
Mixolydian,
Aeolian,
Locrian,
HarmonicMinor,
MelodicMinor,
WholeTone,
Diminished,
}
const MAJOR_SCALE: [i16; 7] = [0, 2, 4, 5, 7, 9, 11];
const MINOR_SCALE: [i16; 7] = [0, 2, 3, 5, 7, 8, 10];
const DORIAN_SCALE: [i16; 7] = [0, 2, 3, 5, 7, 9, 10];
const PHRYGIAN_SCALE: [i16; 7] = [0, 1, 3, 5, 7, 8, 10];
const LYDIAN_SCALE: [i16; 7] = [0, 2, 4, 6, 7, 9, 11];
const MIXOLYDIAN_SCALE: [i16; 7] = [0, 2, 4, 5, 7, 9, 10];
const AEOLIAN_SCALE: [i16; 7] = [0, 2, 3, 5, 7, 8, 10];
const LOCRIAN_SCALE: [i16; 7] = [0, 1, 3, 5, 6, 8, 10];
const HARMONIC_MINOR_SCALE: [i16; 7] = [0, 2, 3, 5, 7, 8, 11];
const MELODIC_MINOR_SCALE: [i16; 7] = [0, 2, 3, 5, 7, 9, 11];
const WHOLE_TONE_SCALE: [i16; 7] = [0, 2, 4, 6, 8, 10, 12];
const DIMINISHED_SCALE: [i16; 7] = [0, 2, 3, 5, 6, 8, 9];
impl Mode {
pub fn scale(&self) -> [i16; 7] {
match self {
Mode::Major => MAJOR_SCALE,
Mode::Minor => MINOR_SCALE,
Mode::Dorian => DORIAN_SCALE,
Mode::Phrygian => PHRYGIAN_SCALE,
Mode::Lydian => LYDIAN_SCALE,
Mode::Mixolydian => MIXOLYDIAN_SCALE,
Mode::Aeolian => AEOLIAN_SCALE,
Mode::Locrian => LOCRIAN_SCALE,
Mode::HarmonicMinor => HARMONIC_MINOR_SCALE,
Mode::MelodicMinor => MELODIC_MINOR_SCALE,
Mode::WholeTone => WHOLE_TONE_SCALE,
Mode::Diminished => DIMINISHED_SCALE,
}
}
pub fn note(&self, base: impl Into<Pitch>, steps: i16) -> Pitch {
let i = base.into().to_half_steps() * 7 / 12 + steps;
let octave = i / 7;
let half_step = self.scale()[(i % 7) as usize];
let half_steps = (octave * 12) + (half_step);
Pitch::from_half_steps(half_steps)
}
pub fn round(&self, base: impl Into<Pitch>, pitch: impl Into<Pitch>) -> Pitch {
let base = base.into();
let pitch = pitch.into();
let steps =
((pitch.to_half_steps() - base.to_half_steps()) as f64 / 12.0 * 7.0).round() as i16;
self.note(base, steps)
}
}