rstmt_core/pitch/traits/
accidental.rs

1/*
2    Appellation: pitch_type <module>
3    Created At: 2025.12.23:14:35:22
4    Contrib: @FL03
5*/
6
7/// [`Accidental`] is a sealed marker trait used to designate various _kinds_ of musical notes,
8/// i.e., sharp, flat, natural, etc.
9pub trait Accidental
10where
11    Self: 'static + AsRef<str> + Send + Sync + core::fmt::Debug + core::fmt::Display,
12{
13    private! {}
14
15    fn new() -> Self
16    where
17        Self: Sized;
18
19    #[allow(clippy::should_implement_trait)]
20    fn from_str(s: &str) -> Result<Self, crate::error::Error>
21    where
22        Self: Sized + core::str::FromStr<Err = crate::error::Error>,
23    {
24        s.parse::<Self>()
25    }
26
27    fn name(&self) -> &str;
28
29    fn symbol(&self) -> char;
30}
31
32/*
33 ************* Implementations *************
34*/
35macro_rules! accidental {
36    (@impl $(#[$meta:meta])* $vis:vis $type:ident $name:ident = $sym:literal $(;)?) => {
37        unit_type! {
38            $(#[$meta])*
39            #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
40            $vis $type $name
41        }
42
43        impl $name {
44            pub const NAME: &'static str = stringify!($name);
45            /// compares some static type `T` against `Self`
46            pub fn of<T>() -> bool
47            where
48                T: 'static,
49            {
50                ::core::any::TypeId::of::<T>() == ::core::any::TypeId::of::<Self>()
51            }
52            /// returns the symbol of the accidental
53            pub const fn symbol(&self) -> char {
54                $sym
55            }
56            /// returns the name of the accidental
57            pub fn name(&self) -> &str {
58                stringify!($name)
59            }
60        }
61
62        impl $crate::pitch::Accidental for $name {
63            seal! {}
64
65            fn new() -> Self {
66                Self
67            }
68
69            fn name(&self) -> &str {
70                self.name()
71            }
72
73            fn symbol(&self) -> char {
74                self.symbol()
75            }
76        }
77
78        impl AsRef<str> for $name {
79            fn as_ref(&self) -> &str {
80                stringify!($name)
81            }
82        }
83
84        impl ::core::fmt::Debug for $name {
85            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
86                write!(f, "{}", self.symbol())
87            }
88        }
89
90        impl ::core::fmt::Display for $name {
91            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
92                write!(f, "{}", self.symbol())
93            }
94        }
95    };
96    ($($vis:vis $type:ident $name:ident $(= $sym:literal)?);* $(;)?) => {
97        $(accidental! { @impl $vis $type $name $(= $sym)? })*
98    };
99}
100
101accidental! {
102    pub struct Flat = '♭';
103    pub struct Sharp = '♯';
104}
105
106#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
107#[cfg_attr(
108    feature = "serde",
109    derive(serde::Deserialize, serde::Serialize),
110    serde(rename_all = "snake_case")
111)]
112#[repr(transparent)]
113pub struct Natural;
114
115impl Natural {
116    pub const fn new() -> Self {
117        Self
118    }
119    /// compares some static type `T` against `Self`
120    pub fn of<T>() -> bool
121    where
122        T: 'static,
123    {
124        ::core::any::TypeId::of::<T>() == ::core::any::TypeId::of::<Self>()
125    }
126
127    pub const fn name(&self) -> &str {
128        "Natural"
129    }
130
131    pub const fn symbol(&self) -> char {
132        '♮'
133    }
134}
135
136impl Accidental for Natural {
137    seal! {}
138
139    fn new() -> Self {
140        Self::new()
141    }
142
143    fn name(&self) -> &str {
144        self.name()
145    }
146
147    fn symbol(&self) -> char {
148        self.symbol()
149    }
150}
151
152impl AsRef<str> for Natural {
153    fn as_ref(&self) -> &str {
154        "Natural"
155    }
156}
157
158impl AsRef<char> for Natural {
159    fn as_ref(&self) -> &char {
160        &'♮'
161    }
162}
163
164impl core::borrow::Borrow<str> for Natural {
165    fn borrow(&self) -> &str {
166        self.as_ref()
167    }
168}
169
170impl core::borrow::Borrow<char> for Natural {
171    fn borrow(&self) -> &char {
172        self.as_ref()
173    }
174}
175
176impl core::ops::Deref for Natural {
177    type Target = str;
178
179    fn deref(&self) -> &Self::Target {
180        self.as_ref()
181    }
182}
183
184impl core::str::FromStr for Natural {
185    type Err = crate::error::Error;
186
187    fn from_str(s: &str) -> Result<Self, Self::Err> {
188        if s.is_empty() || s.to_lowercase() == "natural" || s == "♮" {
189            Ok(Natural)
190        } else {
191            Err(anyhow::anyhow!("Unable to parse a natural note : {}", s).into())
192        }
193    }
194}
195
196impl core::fmt::Debug for Natural {
197    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
198        f.write_str(self.name())
199    }
200}
201
202impl core::fmt::Display for Natural {
203    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
204        write!(f, "{}", self.symbol())
205    }
206}
207
208#[cfg(feature = "alloc")]
209impl ::core::str::FromStr for Flat {
210    type Err = crate::error::Error;
211
212    fn from_str(s: &str) -> Result<Self, Self::Err> {
213        if s.to_lowercase() == "flat" || s == "♭" || s == "b" {
214            Ok(Self::default())
215        } else {
216            Err(anyhow::anyhow!("Invalid accidental string: {}", s).into())
217        }
218    }
219}
220
221#[cfg(feature = "alloc")]
222impl ::core::str::FromStr for Sharp {
223    type Err = crate::error::Error;
224
225    fn from_str(s: &str) -> Result<Self, Self::Err> {
226        if s.to_lowercase() == "sharp" || s == "♯" || s == "#" {
227            Ok(Self::default())
228        } else {
229            Err(anyhow::anyhow!("Invalid accidental string: {}", s).into())
230        }
231    }
232}