use std::fmt;
use super::ParseError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Accidental {
TripleFlat,
DoubleFlat,
Flat,
Natural,
Sharp,
DoubleSharp,
TripleSharp,
QuarterFlat,
QuarterSharp,
ThreeQuarterFlat,
ThreeQuarterSharp,
}
impl Accidental {
pub fn alter(&self) -> f64 {
match self {
Accidental::TripleFlat => -3.0,
Accidental::DoubleFlat => -2.0,
Accidental::Flat => -1.0,
Accidental::Natural => 0.0,
Accidental::Sharp => 1.0,
Accidental::DoubleSharp => 2.0,
Accidental::TripleSharp => 3.0,
Accidental::QuarterFlat => -0.5,
Accidental::QuarterSharp => 0.5,
Accidental::ThreeQuarterFlat => -1.5,
Accidental::ThreeQuarterSharp => 1.5,
}
}
pub fn from_alter(alter: f64) -> Option<Accidental> {
match alter {
x if (x - -3.0).abs() < 0.01 => Some(Accidental::TripleFlat),
x if (x - -2.0).abs() < 0.01 => Some(Accidental::DoubleFlat),
x if (x - -1.5).abs() < 0.01 => Some(Accidental::ThreeQuarterFlat),
x if (x - -1.0).abs() < 0.01 => Some(Accidental::Flat),
x if (x - -0.5).abs() < 0.01 => Some(Accidental::QuarterFlat),
x if x.abs() < 0.01 => Some(Accidental::Natural),
x if (x - 0.5).abs() < 0.01 => Some(Accidental::QuarterSharp),
x if (x - 1.0).abs() < 0.01 => Some(Accidental::Sharp),
x if (x - 1.5).abs() < 0.01 => Some(Accidental::ThreeQuarterSharp),
x if (x - 2.0).abs() < 0.01 => Some(Accidental::DoubleSharp),
x if (x - 3.0).abs() < 0.01 => Some(Accidental::TripleSharp),
_ => None,
}
}
pub fn from_str(s: &str) -> Result<Accidental, ParseError> {
match s.to_lowercase().as_str() {
"bbb" | "triple-flat" => Ok(Accidental::TripleFlat),
"bb" | "--" | "double-flat" => Ok(Accidental::DoubleFlat),
"b" | "-" | "flat" => Ok(Accidental::Flat),
"" | "n" | "natural" => Ok(Accidental::Natural),
"#" | "sharp" => Ok(Accidental::Sharp),
"##" | "x" | "++" | "double-sharp" => Ok(Accidental::DoubleSharp),
"###" | "triple-sharp" => Ok(Accidental::TripleSharp),
"~" | "half-flat" | "quarter-flat" => Ok(Accidental::QuarterFlat),
"`" | "half-sharp" | "quarter-sharp" => Ok(Accidental::QuarterSharp),
_ => Err(ParseError::InvalidAccidental(s.to_string())),
}
}
pub fn unicode(&self) -> &'static str {
match self {
Accidental::TripleFlat => "\u{266D}\u{266D}\u{266D}",
Accidental::DoubleFlat => "\u{1D12B}",
Accidental::Flat => "\u{266D}",
Accidental::Natural => "\u{266E}",
Accidental::Sharp => "\u{266F}",
Accidental::DoubleSharp => "\u{1D12A}",
Accidental::TripleSharp => "\u{266F}\u{266F}\u{266F}",
Accidental::QuarterFlat => "\u{1D132}",
Accidental::QuarterSharp => "\u{1D132}",
Accidental::ThreeQuarterFlat => "\u{1D12B}\u{1D132}",
Accidental::ThreeQuarterSharp => "\u{1D12A}\u{1D132}",
}
}
pub fn ascii(&self) -> &'static str {
match self {
Accidental::TripleFlat => "bbb",
Accidental::DoubleFlat => "bb",
Accidental::Flat => "b",
Accidental::Natural => "",
Accidental::Sharp => "#",
Accidental::DoubleSharp => "##",
Accidental::TripleSharp => "###",
Accidental::QuarterFlat => "~",
Accidental::QuarterSharp => "`",
Accidental::ThreeQuarterFlat => "b~",
Accidental::ThreeQuarterSharp => "#`",
}
}
pub fn is_standard(&self) -> bool {
matches!(
self,
Accidental::TripleFlat
| Accidental::DoubleFlat
| Accidental::Flat
| Accidental::Natural
| Accidental::Sharp
| Accidental::DoubleSharp
| Accidental::TripleSharp
)
}
pub fn list_names() -> Vec<&'static str> {
vec![
"triple-flat",
"double-flat",
"flat",
"natural",
"sharp",
"double-sharp",
"triple-sharp",
"quarter-flat",
"quarter-sharp",
]
}
}
impl Default for Accidental {
fn default() -> Self {
Accidental::Natural
}
}
impl fmt::Display for Accidental {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.ascii())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AccidentalDisplayType {
#[default]
Normal,
Always,
Never,
IfNeeded,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AccidentalDisplay {
pub accidental: Accidental,
pub display_status: Option<bool>,
pub display_type: AccidentalDisplayType,
}
impl AccidentalDisplay {
pub fn new(accidental: Accidental) -> Self {
Self {
accidental,
display_status: None,
display_type: AccidentalDisplayType::Normal,
}
}
pub fn set_attribute_independently(
&mut self,
accidental: Option<Accidental>,
display_type: Option<AccidentalDisplayType>,
) {
if let Some(a) = accidental {
self.accidental = a;
}
if let Some(t) = display_type {
self.display_type = t;
}
}
pub fn inherit_display(&mut self, other: &AccidentalDisplay) {
self.display_status = other.display_status;
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Microtone {
cents: f64,
}
impl Microtone {
pub fn new(cents: f64) -> Self {
Self { cents }
}
pub fn cents(&self) -> f64 {
self.cents
}
pub fn alter(&self) -> f64 {
self.cents / 100.0
}
pub fn from_alter(alter: f64) -> Self {
Self {
cents: alter * 100.0,
}
}
}
impl Default for Microtone {
fn default() -> Self {
Self { cents: 0.0 }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_accidental_alter() {
assert_eq!(Accidental::Sharp.alter(), 1.0);
assert_eq!(Accidental::Flat.alter(), -1.0);
assert_eq!(Accidental::DoubleSharp.alter(), 2.0);
assert_eq!(Accidental::Natural.alter(), 0.0);
}
#[test]
fn test_accidental_from_alter() {
assert_eq!(Accidental::from_alter(1.0), Some(Accidental::Sharp));
assert_eq!(Accidental::from_alter(-1.0), Some(Accidental::Flat));
assert_eq!(Accidental::from_alter(0.5), Some(Accidental::QuarterSharp));
}
#[test]
fn test_accidental_parse() {
assert_eq!(Accidental::from_str("#").unwrap(), Accidental::Sharp);
assert_eq!(Accidental::from_str("b").unwrap(), Accidental::Flat);
assert_eq!(Accidental::from_str("bb").unwrap(), Accidental::DoubleFlat);
}
#[test]
fn test_microtone() {
let m = Microtone::new(50.0);
assert_eq!(m.cents(), 50.0);
assert_eq!(m.alter(), 0.5);
}
#[test]
fn test_accidental_list_names() {
let names = Accidental::list_names();
assert!(names.contains(&"sharp"));
assert!(names.contains(&"flat"));
assert_eq!(names.len(), 9);
}
#[test]
fn test_accidental_display() {
let mut display = AccidentalDisplay::new(Accidental::Sharp);
assert_eq!(display.display_status, None);
assert_eq!(display.display_type, AccidentalDisplayType::Normal);
display.set_attribute_independently(None, Some(AccidentalDisplayType::Always));
assert_eq!(display.accidental, Accidental::Sharp); assert_eq!(display.display_type, AccidentalDisplayType::Always);
let mut other = AccidentalDisplay::new(Accidental::Flat);
other.display_status = Some(false);
display.inherit_display(&other);
assert_eq!(display.display_status, Some(false));
}
}