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 let lower = name.to_ascii_lowercase();
103 if let Some(found) = Self::ALL.into_iter().find(|specifier| {
104 specifier.prefix().to_ascii_lowercase() == lower
105 || specifier.nice_name().to_ascii_lowercase() == lower
106 }) {
107 return Ok(found);
108 }
109 match lower.as_str() {
110 "double augmented" => Ok(Specifier::DoubleAugmented),
111 "double diminished" => Ok(Specifier::DoubleDiminished),
112 "triple augmented" => Ok(Specifier::TripleAugmented),
113 "triple diminished" => Ok(Specifier::TripleDiminished),
114 "quadruple augmented" => Ok(Specifier::QuadrupleAugmented),
115 "quadruple diminished" => Ok(Specifier::QuadrupleDiminished),
116 _ => Err(Error::Interval(format!(
117 "Cannot find a match for value: '{name}'"
118 ))),
119 }
120 }
121
122 pub(crate) fn parse(remain: &str) -> Result<Self> {
123 Self::from_name(remain)
124 }
125
126 pub fn prefix(self) -> &'static str {
129 match self {
130 Specifier::Perfect => "P",
131 Specifier::Major => "M",
132 Specifier::Minor => "m",
133 Specifier::Augmented => "A",
134 Specifier::Diminished => "d",
135 Specifier::DoubleAugmented => "AA",
136 Specifier::DoubleDiminished => "dd",
137 Specifier::TripleAugmented => "AAA",
138 Specifier::TripleDiminished => "ddd",
139 Specifier::QuadrupleAugmented => "AAAA",
140 Specifier::QuadrupleDiminished => "dddd",
141 }
142 }
143
144 pub fn inversion(&self) -> Self {
147 match self {
148 Specifier::Perfect => Specifier::Perfect,
149 Specifier::Major => Specifier::Minor,
150 Specifier::Minor => Specifier::Major,
151 Specifier::Augmented => Specifier::Diminished,
152 Specifier::Diminished => Specifier::Augmented,
153 Specifier::DoubleAugmented => Specifier::DoubleDiminished,
154 Specifier::DoubleDiminished => Specifier::DoubleAugmented,
155 Specifier::TripleAugmented => Specifier::TripleDiminished,
156 Specifier::TripleDiminished => Specifier::TripleAugmented,
157 Specifier::QuadrupleAugmented => Specifier::QuadrupleDiminished,
158 Specifier::QuadrupleDiminished => Specifier::QuadrupleAugmented,
159 }
160 }
161
162 pub fn semitones_above_perfect(&self) -> Result<IntegerType> {
165 match self {
166 Specifier::Perfect => Ok(0),
167 Specifier::Augmented => Ok(1),
168 Specifier::DoubleAugmented => Ok(2),
169 Specifier::TripleAugmented => Ok(3),
170 Specifier::QuadrupleAugmented => Ok(4),
171 Specifier::Diminished => Ok(-1),
172 Specifier::DoubleDiminished => Ok(-2),
173 Specifier::TripleDiminished => Ok(-3),
174 Specifier::QuadrupleDiminished => Ok(-4),
175 _ => Err(Error::Interval(format!(
176 "<Specifier.{}> cannot be compared to Perfect",
177 self.python_name()
178 ))),
179 }
180 }
181
182 pub fn semitones_above_major(&self) -> Result<IntegerType> {
185 match self {
186 Specifier::Major => Ok(0),
187 Specifier::Minor => Ok(-1),
188 Specifier::Augmented => Ok(1),
189 Specifier::DoubleAugmented => Ok(2),
190 Specifier::TripleAugmented => Ok(3),
191 Specifier::QuadrupleAugmented => Ok(4),
192 Specifier::Diminished => Ok(-2),
193 Specifier::DoubleDiminished => Ok(-3),
194 Specifier::TripleDiminished => Ok(-4),
195 Specifier::QuadrupleDiminished => Ok(-5),
196 _ => Err(Error::Interval(format!(
197 "<Specifier.{}> cannot be compared to Major",
198 self.python_name()
199 ))),
200 }
201 }
202
203 pub fn python_name(self) -> &'static str {
205 match self {
206 Specifier::Perfect => "PERFECT",
207 Specifier::Major => "MAJOR",
208 Specifier::Minor => "MINOR",
209 Specifier::Augmented => "AUGMENTED",
210 Specifier::Diminished => "DIMINISHED",
211 Specifier::DoubleAugmented => "DBLAUG",
212 Specifier::DoubleDiminished => "DBLDIM",
213 Specifier::TripleAugmented => "TRPAUG",
214 Specifier::TripleDiminished => "TRPDIM",
215 Specifier::QuadrupleAugmented => "QUADAUG",
216 Specifier::QuadrupleDiminished => "QUADDIM",
217 }
218 }
219}
220
221impl fmt::Display for Specifier {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 f.write_str(self.prefix())
225 }
226}
227
228impl FromStr for Specifier {
229 type Err = Error;
230
231 fn from_str(name: &str) -> Result<Self> {
232 Self::from_name(name)
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn test_specifier_nice_name() {
242 assert_eq!(Specifier::Perfect.nice_name(), "Perfect");
243 assert_eq!(Specifier::Major.nice_name(), "Major");
244 assert_eq!(Specifier::Minor.nice_name(), "Minor");
245 assert_eq!(Specifier::Augmented.nice_name(), "Augmented");
246 assert_eq!(Specifier::Diminished.nice_name(), "Diminished");
247 assert_eq!(Specifier::DoubleAugmented.nice_name(), "Doubly-Augmented");
248 assert_eq!(Specifier::DoubleDiminished.nice_name(), "Doubly-Diminished");
249 assert_eq!(Specifier::TripleAugmented.nice_name(), "Triply-Augmented");
250 assert_eq!(Specifier::TripleDiminished.nice_name(), "Triply-Diminished");
251 assert_eq!(
252 Specifier::QuadrupleAugmented.nice_name(),
253 "Quadruply-Augmented"
254 );
255 assert_eq!(
256 Specifier::QuadrupleDiminished.nice_name(),
257 "Quadruply-Diminished"
258 );
259 }
260
261 #[test]
262 fn test_specifier_semitones_above_perfect() {
263 assert_eq!(Specifier::Perfect.semitones_above_perfect().unwrap(), 0);
264 assert_eq!(Specifier::Augmented.semitones_above_perfect().unwrap(), 1);
265 assert_eq!(
266 Specifier::DoubleDiminished
267 .semitones_above_perfect()
268 .unwrap(),
269 -2
270 );
271 assert!(Specifier::Major.semitones_above_perfect().is_err());
272 }
273
274 #[test]
275 fn test_specifier_semitones_above_major() {
276 assert_eq!(Specifier::Major.semitones_above_major().unwrap(), 0);
277 assert_eq!(Specifier::Minor.semitones_above_major().unwrap(), -1);
278 assert_eq!(Specifier::Diminished.semitones_above_major().unwrap(), -2);
279 assert!(Specifier::Perfect.semitones_above_major().is_err());
280 }
281
282 #[test]
283 fn names_numbers_and_prefixes_round_trip() {
284 for specifier in Specifier::ALL {
285 assert_eq!(Specifier::from_value(specifier.value()).unwrap(), specifier);
286 assert_eq!(Specifier::from_name(specifier.prefix()).unwrap(), specifier);
287 assert_eq!(
288 Specifier::from_name(&specifier.nice_name()).unwrap(),
289 specifier
290 );
291 assert_eq!(specifier.to_string(), specifier.prefix());
292 }
293 assert_eq!(Specifier::from_name("perfect").unwrap(), Specifier::Perfect);
294 assert_eq!(
295 Specifier::from_name("dd").unwrap(),
296 Specifier::DoubleDiminished
297 );
298 assert_eq!("M".parse::<Specifier>().unwrap(), Specifier::Major);
299 assert!(Specifier::from_name("xyz").is_err());
300 assert!(Specifier::from_value(12).is_err());
301 }
302}