1use std::fmt;
4use std::str::FromStr;
5
6use crate::{
7 defaults::IntegerType,
8 error::{Error, Result},
9};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[must_use]
15pub enum Specifier {
16 Perfect = 1,
18 Major = 2,
20 Minor = 3,
22 Augmented = 4,
24 Diminished = 5,
26 DoubleAugmented = 6,
28 DoubleDiminished = 7,
30 TripleAugmented = 8,
32 TripleDiminished = 9,
34 QuadrupleAugmented = 10,
36 QuadrupleDiminished = 11,
38}
39
40impl Specifier {
41 pub const ALL: [Specifier; 11] = [
43 Specifier::Perfect,
44 Specifier::Major,
45 Specifier::Minor,
46 Specifier::Augmented,
47 Specifier::Diminished,
48 Specifier::DoubleAugmented,
49 Specifier::DoubleDiminished,
50 Specifier::TripleAugmented,
51 Specifier::TripleDiminished,
52 Specifier::QuadrupleAugmented,
53 Specifier::QuadrupleDiminished,
54 ];
55
56 pub fn value(self) -> IntegerType {
58 self as IntegerType
59 }
60
61 pub fn from_value(value: IntegerType) -> Result<Self> {
63 Self::ALL
64 .into_iter()
65 .find(|specifier| specifier.value() == value)
66 .ok_or_else(|| Error::Interval(format!("{value} is not a valid Specifier")))
67 }
68
69 pub fn nice_name(&self) -> String {
71 match self {
72 Specifier::Perfect => "Perfect".to_string(),
73 Specifier::Major => "Major".to_string(),
74 Specifier::Minor => "Minor".to_string(),
75 Specifier::Augmented => "Augmented".to_string(),
76 Specifier::Diminished => "Diminished".to_string(),
77 Specifier::DoubleAugmented => "Doubly-Augmented".to_string(),
78 Specifier::DoubleDiminished => "Doubly-Diminished".to_string(),
79 Specifier::TripleAugmented => "Triply-Augmented".to_string(),
80 Specifier::TripleDiminished => "Triply-Diminished".to_string(),
81 Specifier::QuadrupleAugmented => "Quadruply-Augmented".to_string(),
82 Specifier::QuadrupleDiminished => "Quadruply-Diminished".to_string(),
83 }
84 }
85
86 pub fn from_name(name: &str) -> Result<Self> {
97 match name {
98 "M" => return Ok(Specifier::Major),
99 "m" => return Ok(Specifier::Minor),
100 _ => {}
101 }
102 if let Some(found) = Self::ALL.into_iter().find(|specifier| {
107 specifier.prefix().eq_ignore_ascii_case(name)
108 || specifier.nice_name().eq_ignore_ascii_case(name)
109 }) {
110 return Ok(found);
111 }
112 match name.to_ascii_lowercase().as_str() {
113 "double augmented" => Ok(Specifier::DoubleAugmented),
114 "double diminished" => Ok(Specifier::DoubleDiminished),
115 "triple augmented" => Ok(Specifier::TripleAugmented),
116 "triple diminished" => Ok(Specifier::TripleDiminished),
117 "quadruple augmented" => Ok(Specifier::QuadrupleAugmented),
118 "quadruple diminished" => Ok(Specifier::QuadrupleDiminished),
119 _ => Err(Error::Interval(format!(
120 "Cannot find a match for value: '{name}'"
121 ))),
122 }
123 }
124
125 pub(crate) fn parse(remain: &str) -> Result<Self> {
126 Self::from_name(remain)
127 }
128
129 pub fn prefix(self) -> &'static str {
132 match self {
133 Specifier::Perfect => "P",
134 Specifier::Major => "M",
135 Specifier::Minor => "m",
136 Specifier::Augmented => "A",
137 Specifier::Diminished => "d",
138 Specifier::DoubleAugmented => "AA",
139 Specifier::DoubleDiminished => "dd",
140 Specifier::TripleAugmented => "AAA",
141 Specifier::TripleDiminished => "ddd",
142 Specifier::QuadrupleAugmented => "AAAA",
143 Specifier::QuadrupleDiminished => "dddd",
144 }
145 }
146
147 pub fn inversion(&self) -> Self {
150 match self {
151 Specifier::Perfect => Specifier::Perfect,
152 Specifier::Major => Specifier::Minor,
153 Specifier::Minor => Specifier::Major,
154 Specifier::Augmented => Specifier::Diminished,
155 Specifier::Diminished => Specifier::Augmented,
156 Specifier::DoubleAugmented => Specifier::DoubleDiminished,
157 Specifier::DoubleDiminished => Specifier::DoubleAugmented,
158 Specifier::TripleAugmented => Specifier::TripleDiminished,
159 Specifier::TripleDiminished => Specifier::TripleAugmented,
160 Specifier::QuadrupleAugmented => Specifier::QuadrupleDiminished,
161 Specifier::QuadrupleDiminished => Specifier::QuadrupleAugmented,
162 }
163 }
164
165 pub fn semitones_above_perfect(&self) -> Result<IntegerType> {
168 match self {
169 Specifier::Perfect => Ok(0),
170 Specifier::Augmented => Ok(1),
171 Specifier::DoubleAugmented => Ok(2),
172 Specifier::TripleAugmented => Ok(3),
173 Specifier::QuadrupleAugmented => Ok(4),
174 Specifier::Diminished => Ok(-1),
175 Specifier::DoubleDiminished => Ok(-2),
176 Specifier::TripleDiminished => Ok(-3),
177 Specifier::QuadrupleDiminished => Ok(-4),
178 _ => Err(Error::Interval(format!(
179 "<Specifier.{}> cannot be compared to Perfect",
180 self.python_name()
181 ))),
182 }
183 }
184
185 pub fn semitones_above_major(&self) -> Result<IntegerType> {
188 match self {
189 Specifier::Major => Ok(0),
190 Specifier::Minor => Ok(-1),
191 Specifier::Augmented => Ok(1),
192 Specifier::DoubleAugmented => Ok(2),
193 Specifier::TripleAugmented => Ok(3),
194 Specifier::QuadrupleAugmented => Ok(4),
195 Specifier::Diminished => Ok(-2),
196 Specifier::DoubleDiminished => Ok(-3),
197 Specifier::TripleDiminished => Ok(-4),
198 Specifier::QuadrupleDiminished => Ok(-5),
199 _ => Err(Error::Interval(format!(
200 "<Specifier.{}> cannot be compared to Major",
201 self.python_name()
202 ))),
203 }
204 }
205
206 pub fn python_name(self) -> &'static str {
208 match self {
209 Specifier::Perfect => "PERFECT",
210 Specifier::Major => "MAJOR",
211 Specifier::Minor => "MINOR",
212 Specifier::Augmented => "AUGMENTED",
213 Specifier::Diminished => "DIMINISHED",
214 Specifier::DoubleAugmented => "DBLAUG",
215 Specifier::DoubleDiminished => "DBLDIM",
216 Specifier::TripleAugmented => "TRPAUG",
217 Specifier::TripleDiminished => "TRPDIM",
218 Specifier::QuadrupleAugmented => "QUADAUG",
219 Specifier::QuadrupleDiminished => "QUADDIM",
220 }
221 }
222}
223
224impl fmt::Display for Specifier {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 f.write_str(self.prefix())
228 }
229}
230
231impl FromStr for Specifier {
232 type Err = Error;
233
234 fn from_str(name: &str) -> Result<Self> {
235 Self::from_name(name)
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn test_specifier_nice_name() {
245 assert_eq!(Specifier::Perfect.nice_name(), "Perfect");
246 assert_eq!(Specifier::Major.nice_name(), "Major");
247 assert_eq!(Specifier::Minor.nice_name(), "Minor");
248 assert_eq!(Specifier::Augmented.nice_name(), "Augmented");
249 assert_eq!(Specifier::Diminished.nice_name(), "Diminished");
250 assert_eq!(Specifier::DoubleAugmented.nice_name(), "Doubly-Augmented");
251 assert_eq!(Specifier::DoubleDiminished.nice_name(), "Doubly-Diminished");
252 assert_eq!(Specifier::TripleAugmented.nice_name(), "Triply-Augmented");
253 assert_eq!(Specifier::TripleDiminished.nice_name(), "Triply-Diminished");
254 assert_eq!(
255 Specifier::QuadrupleAugmented.nice_name(),
256 "Quadruply-Augmented"
257 );
258 assert_eq!(
259 Specifier::QuadrupleDiminished.nice_name(),
260 "Quadruply-Diminished"
261 );
262 }
263
264 #[test]
265 fn test_specifier_semitones_above_perfect() {
266 assert_eq!(Specifier::Perfect.semitones_above_perfect().unwrap(), 0);
267 assert_eq!(Specifier::Augmented.semitones_above_perfect().unwrap(), 1);
268 assert_eq!(
269 Specifier::DoubleDiminished
270 .semitones_above_perfect()
271 .unwrap(),
272 -2
273 );
274 assert!(Specifier::Major.semitones_above_perfect().is_err());
275 }
276
277 #[test]
278 fn test_specifier_semitones_above_major() {
279 assert_eq!(Specifier::Major.semitones_above_major().unwrap(), 0);
280 assert_eq!(Specifier::Minor.semitones_above_major().unwrap(), -1);
281 assert_eq!(Specifier::Diminished.semitones_above_major().unwrap(), -2);
282 assert!(Specifier::Perfect.semitones_above_major().is_err());
283 }
284
285 #[test]
286 fn names_numbers_and_prefixes_round_trip() {
287 for specifier in Specifier::ALL {
288 assert_eq!(Specifier::from_value(specifier.value()).unwrap(), specifier);
289 assert_eq!(Specifier::from_name(specifier.prefix()).unwrap(), specifier);
290 assert_eq!(
291 Specifier::from_name(&specifier.nice_name()).unwrap(),
292 specifier
293 );
294 assert_eq!(specifier.to_string(), specifier.prefix());
295 }
296 assert_eq!(Specifier::from_name("perfect").unwrap(), Specifier::Perfect);
297 assert_eq!(
298 Specifier::from_name("dd").unwrap(),
299 Specifier::DoubleDiminished
300 );
301 assert_eq!("M".parse::<Specifier>().unwrap(), Specifier::Major);
302 assert!(Specifier::from_name("xyz").is_err());
303 assert!(Specifier::from_value(12).is_err());
304 }
305}