resonata 0.3.0

A music theory library for Rust.
Documentation
use std::{fmt::{self, Display, Formatter, Debug}, str::FromStr, ops::{Add, Sub, AddAssign, SubAssign}};
use regex::Regex;
use crate::{err, Interval};
use lazy_static::lazy_static;

use super::*;

impl From<u8> for Note {
    fn from(value: u8) -> Self {
        let number = value % 12;
        let name = NoteName::from_chromatic_scale_degree(number);
        let accidental = Accidental::from_chromatic_scale_degree(number);
        Self { name, accidental }
    }
}

impl From<Note> for u8 {
    fn from(note: Note) -> u8 {
        let number = u8::from(note.name) as i8 + i8::from(note.accidental);
        number.abs() as u8
    }
}

impl Add<u8> for Note {
    type Output = Self;
    fn add(self, semitones: u8) -> Self::Output {
        let number = std::cmp::min(u8::from(self) as u16 + semitones as u16, 127) as u8;
        Self::from(number)
    }
}

impl AddAssign<u8> for Note {
    fn add_assign(&mut self, semitones: u8) {
        *self = *self + semitones;
    }
}

impl Sub<u8> for Note {
    type Output = Self;
    fn sub(self, semitones: u8) -> Self::Output {
        let number = std::cmp::max(u8::from(self) as i8 - semitones as i8, 0) as u8;
        Self::from(number)
    }
}

impl SubAssign<u8> for Note {
    fn sub_assign(&mut self, semitones: u8) {
        *self = *self - semitones;
    }
}

impl Sub for Note {
    type Output = crate::Interval;
    fn sub(self, other: Self) -> Self::Output {
        crate::Interval::from(u8::from(self) as i8 - u8::from(other) as i8)
    }
}

impl Add<Interval> for Note {
    type Output = Option<Self>;
    fn add(self, interval: Interval) -> Self::Output {
        let number = u8::from(self) + u8::from(interval);
        if number > 127 {
            None
        } else {
            Some(Self::from(number))
        }
    }
}

impl AddAssign<Interval> for Note {
    fn add_assign(&mut self, interval: Interval) {
        *self = (*self + interval).unwrap();
    }
}

impl Sub<Interval> for Note {
    type Output = Option<Self>;
    fn sub(self, interval: Interval) -> Self::Output {
        let number = u8::from(self) as i8 - i8::from(interval);
        if number < 0 {
            None
        } else {
            Some(Self::from(number as u8))
        }
    }
}

impl SubAssign<Interval> for Note {
    fn sub_assign(&mut self, interval: Interval) {
        *self = (*self - interval).unwrap();
    }
}

lazy_static! {
    static ref NOTE_RE: Regex = Regex::new("^([A-Ga-g])([#x𝄪b♯♯♭♭♮]*)$").unwrap();
}

impl FromStr for Note {
    type Err = ResonataError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match NOTE_RE.captures(s) {
            Some(cap) => {
                let name = NoteName::from_str(&cap[1])?;
                let acc_str = &cap[2];
                let accidental = if acc_str.is_empty() {
                    Accidental::Natural
                } else {
                    Accidental::from_str(&acc_str)?
                };

                Ok(Self { name, accidental })
            }
            None => err!(InvalidNoteName)
        }
    }
}

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

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

impl From<u8> for PitchedNote {
    fn from(value: u8) -> Self {
        let note = Note::from(value);
        let octave = (value / 12) as i8 - 1;
        Self { note, octave }
    }
}

impl From<PitchedNote> for u8 {
    fn from(note: PitchedNote) -> Self {
        u8::from(note.note) + ((note.octave() + 1) * 12) as u8
    }
}

impl Add<u8> for PitchedNote {
    type Output = Self;
    fn add(self, semitones: u8) -> Self::Output {
        let number = std::cmp::min(u8::from(self) as u16 + semitones as u16, 127) as u8;
        Self::from(number)
    }
}

impl AddAssign<u8> for PitchedNote {
    fn add_assign(&mut self, semitones: u8) {
        *self = *self + semitones;
    }
}

impl Sub<u8> for PitchedNote {
    type Output = Self;
    fn sub(self, semitones: u8) -> Self::Output {
        let number = std::cmp::max(u8::from(self) as i8 - semitones as i8, 0) as u8;
        Self::from(number)
    }
}

impl SubAssign<u8> for PitchedNote {
    fn sub_assign(&mut self, semitones: u8) {
        *self = *self - semitones;
    }
}

impl Sub for PitchedNote {
    type Output = crate::Interval;
    fn sub(self, other: Self) -> Self::Output {
        crate::Interval::from(u8::from(self) as i8 - u8::from(other) as i8)
    }
}

impl Add<Interval> for PitchedNote {
    type Output = Option<Self>;
    fn add(self, interval: Interval) -> Self::Output {
        let number = u8::from(self) + u8::from(interval);
        if number > 127 {
            None
        } else {
            Some(Self::from(number))
        }
    }
}

impl AddAssign<Interval> for PitchedNote {
    fn add_assign(&mut self, interval: Interval) {
        *self = (*self + interval).unwrap();
    }
}

impl Sub<Interval> for PitchedNote {
    type Output = Option<Self>;
    fn sub(self, interval: Interval) -> Self::Output {
        let number = u8::from(self) as i8 - i8::from(interval);
        if number < 0 {
            None
        } else {
            Some(Self::from(number as u8))
        }
    }
}

impl SubAssign<Interval> for PitchedNote {
    fn sub_assign(&mut self, interval: Interval) {
        *self = (*self - interval).unwrap();
    }
}

lazy_static! {
    static ref PITCHED_NOTE_RE: Regex = Regex::new("^([A-Ga-g][#x𝄪b♯♯♭♭♮]*)(-?[0-9]*)$").unwrap();
}

impl FromStr for PitchedNote {
    type Err = ResonataError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match PITCHED_NOTE_RE.captures(s) {
            Some(cap) => {
                let note = Note::from_str(&cap[1])?;
                let octave: i8 = cap[2].parse().unwrap_or(4);
        
                if octave < -1 || octave > 9 {
                    nope!(InvalidOctave);
                }
        
                Ok(Self { note, octave })
            }
            None => nope!(InvalidNoteName)
        }
    }
}

impl Display for PitchedNote {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let note_name = self.name().to_string();
        let accidental = self.accidental().to_string();
        let octave = self.octave.to_string();
        write!(f, "{}{}{}", note_name, accidental, octave)
    }
}

impl Debug for PitchedNote {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.to_string())
    }
}