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