staff 0.11.0

Music theory library with midi, notes, chords, scales, and more
Documentation
//! Formatted notes

use crate::Natural;
use core::{
    fmt::{self, Write},
    str::FromStr,
};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Accidental {
    Natural,
    Flat,
    DoubleFlat,
    Sharp,
    DoubleSharp,
}

impl Accidental {
    pub fn is_natural(self) -> bool {
        match self {
            Self::Natural => true,
            _ => false,
        }
    }
}

impl fmt::Display for Accidental {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Accidental::Natural => Ok(()),
            Accidental::Flat => f.write_char('b'),
            Accidental::DoubleFlat => f.write_str("bb"),
            Accidental::Sharp => f.write_char('#'),
            Accidental::DoubleSharp => f.write_str("##"),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Note {
    pub natural: Natural,
    pub accidental: Accidental,
}

impl Note {
    pub fn new(natural: Natural, accidental: Accidental) -> Self {
        Self {
            natural,
            accidental,
        }
    }

    pub fn flat(natural: Natural) -> Self {
        Self::new(natural, Accidental::Flat)
    }

    pub fn double_flat(natural: Natural) -> Self {
        Self::new(natural, Accidental::DoubleFlat)
    }

    pub fn sharp(natural: Natural) -> Self {
        Self::new(natural, Accidental::Sharp)
    }

    pub fn double_sharp(natural: Natural) -> Self {
        Self::new(natural, Accidental::DoubleSharp)
    }
}

impl From<Natural> for Note {
    fn from(natural: Natural) -> Self {
        Self::new(natural, Accidental::Natural)
    }
}

impl fmt::Display for Note {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}{}", self.natural, self.accidental)
    }
}

impl FromStr for Note {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut chars = s.chars();
        let natural: Natural = if let Some(c) = chars.next() {
            c.try_into().unwrap()
        } else {
            return Err(Error::Empty);
        };

        let accidental = match chars.next() {
            Some('b') => match chars.next() {
                Some('b') => Accidental::DoubleFlat,
                Some(c) => return Err(c.into()),
                None => Accidental::Flat,
            },
            Some('#') => match chars.next() {
                Some('#') => Accidental::DoubleSharp,
                Some(c) => return Err(c.into()),
                None => Accidental::Sharp,
            },
            Some(c) => return Err(c.into()),
            None => Accidental::Natural,
        };

        Ok(Self::new(natural, accidental))
    }
}

#[derive(Debug)]
pub enum Error {
    Empty,
    Invalid(char),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Empty => f.write_str("Empty note input"),
            Self::Invalid(c) => write!(f, "Invalid character `{}`", c),
        }
    }
}

impl From<char> for Error {
    fn from(c: char) -> Self {
        Self::Invalid(c)
    }
}