1use std::fmt;
4
5use crate::{
6 defaults::{FloatType, IntegerType, UnsignedIntegerType},
7 error::{Error, Result},
8 pitch::Pitch,
9};
10
11use super::{
12 GenericInterval, chromaticinterval::ChromaticInterval, direction::Direction,
13 specifier::Specifier,
14};
15
16#[derive(Clone, Debug)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[must_use]
20pub struct DiatonicInterval {
21 pub(crate) generic: GenericInterval,
22 pub(crate) specifier: Specifier,
23}
24
25impl PartialEq for DiatonicInterval {
26 fn eq(&self, other: &Self) -> bool {
29 self.generic.value() == other.generic.value()
30 && self.specifier == other.specifier
31 && self.direction() == other.direction()
32 }
33}
34
35impl Eq for DiatonicInterval {}
36
37impl DiatonicInterval {
38 pub fn new(specifier: Specifier, generic: &GenericInterval) -> Self {
41 Self {
42 generic: generic.clone(),
43 specifier,
44 }
45 }
46
47 pub fn try_new(specifier: Specifier, generic: GenericInterval) -> Result<Self> {
51 let perfectable = generic.is_perfectable();
52 let mismatched = match specifier {
53 Specifier::Major | Specifier::Minor => perfectable,
54 Specifier::Perfect => !perfectable,
55 _ => false,
56 };
57 if mismatched {
58 return Err(Error::Interval(format!(
59 "Cannot create a '{} {}'",
60 specifier.nice_name(),
61 generic.nice_name()
62 )));
63 }
64 if generic.value() == -1 && specifier == Specifier::Perfect {
65 return Err(Error::Interval(
66 "There is no such thing as a descending Perfect Unison".to_owned(),
67 ));
68 }
69 Ok(Self { generic, specifier })
70 }
71
72 pub fn from_name(name: &str) -> Result<Self> {
75 let (diatonic, _, _) = super::parse_interval_name(name.to_string())?;
76 Self::try_new(diatonic.specifier, diatonic.generic)
77 }
78
79 pub fn specifier_abbreviation(&self) -> &'static str {
81 self.specifier.prefix()
82 }
83
84 pub fn specifier(&self) -> Specifier {
86 self.specifier
87 }
88
89 pub fn generic(&self) -> &GenericInterval {
91 &self.generic
92 }
93
94 pub fn name(&self) -> String {
96 format!("{}{}", self.specifier.prefix(), self.generic.undirected())
97 }
98
99 pub fn nice_name(&self) -> String {
101 format!(
102 "{} {}",
103 self.specifier.nice_name(),
104 self.generic.nice_name()
105 )
106 }
107
108 pub fn directed_name(&self) -> String {
111 format!("{}{}", self.specifier.prefix(), self.generic.directed())
112 }
113
114 pub fn directed_nice_name(&self) -> String {
116 format!("{}{}", self.direction_prefix(), self.nice_name())
117 }
118
119 pub fn simple_name(&self) -> String {
121 format!(
122 "{}{}",
123 self.specifier.prefix(),
124 self.generic.simple_undirected()
125 )
126 }
127
128 pub fn semi_simple_name(&self) -> String {
130 format!(
131 "{}{}",
132 self.specifier.prefix(),
133 self.generic.semi_simple_undirected()
134 )
135 }
136
137 pub fn simple_nice_name(&self) -> String {
139 format!(
140 "{} {}",
141 self.specifier.nice_name(),
142 self.generic.simple_nice_name()
143 )
144 }
145
146 pub fn semi_simple_nice_name(&self) -> String {
148 format!(
149 "{} {}",
150 self.specifier.nice_name(),
151 self.generic.semi_simple_nice_name()
152 )
153 }
154
155 pub fn directed_simple_name(&self) -> String {
158 format!(
159 "{}{}",
160 self.specifier.prefix(),
161 self.generic.simple_directed()
162 )
163 }
164
165 pub fn directed_semi_simple_name(&self) -> String {
167 format!(
168 "{}{}",
169 self.specifier.prefix(),
170 self.generic.semi_simple_directed()
171 )
172 }
173
174 pub fn directed_simple_nice_name(&self) -> String {
176 format!("{}{}", self.direction_prefix(), self.simple_nice_name())
177 }
178
179 pub fn directed_semi_simple_nice_name(&self) -> String {
181 format!(
182 "{}{}",
183 self.direction_prefix(),
184 self.semi_simple_nice_name()
185 )
186 }
187
188 pub fn specific_name(&self) -> String {
190 self.specifier.nice_name()
191 }
192
193 pub fn direction(&self) -> Direction {
197 if self.generic.undirected() != 1 {
198 return self.generic.direction();
199 }
200 match self.specifier {
201 Specifier::Diminished
202 | Specifier::DoubleDiminished
203 | Specifier::TripleDiminished
204 | Specifier::QuadrupleDiminished => Direction::Descending,
205 Specifier::Augmented
206 | Specifier::DoubleAugmented
207 | Specifier::TripleAugmented
208 | Specifier::QuadrupleAugmented => Direction::Ascending,
209 Specifier::Perfect | Specifier::Major | Specifier::Minor => Direction::Oblique,
210 }
211 }
212
213 pub fn cents(&self) -> Result<FloatType> {
215 Ok(self.get_chromatic()?.cents())
216 }
217
218 pub fn mod7(&self) -> String {
221 if self.direction() == Direction::Descending {
222 self.mod7_inversion()
223 } else {
224 self.simple_name()
225 }
226 }
227
228 pub fn mod7_inversion(&self) -> String {
230 format!(
231 "{}{}",
232 self.specifier.inversion().prefix(),
233 self.generic.mod7_inversion()
234 )
235 }
236
237 pub fn is_step(&self) -> bool {
239 self.generic.is_step()
240 }
241
242 pub fn is_diatonic_step(&self) -> bool {
244 self.generic.is_diatonic_step()
245 }
246
247 pub fn is_skip(&self) -> bool {
249 self.generic.is_skip()
250 }
251
252 pub fn is_perfectable(&self) -> bool {
254 self.generic.is_perfectable()
255 }
256
257 pub fn get_chromatic(&self) -> Result<ChromaticInterval> {
259 let octave_offset = (self.generic.staff_distance().abs() / 7) as UnsignedIntegerType;
260 let semitones_start =
261 semitones_generic(self.generic.simple_undirected() as UnsignedIntegerType)?;
262 let semitones_adjust = if self.generic.is_perfectable() {
263 self.specifier.semitones_above_perfect()?
264 } else {
265 self.specifier.semitones_above_major()?
266 };
267 let mut semitones: IntegerType =
268 ((octave_offset * 12 + semitones_start) as IntegerType) + semitones_adjust;
269 if self.generic.direction() == Direction::Descending {
270 semitones *= -1;
271 }
272 Ok(ChromaticInterval::from_int(semitones))
273 }
274
275 pub fn reverse(&self) -> Self {
279 if self.generic.undirected() == 1 {
280 Self::new(self.specifier.inversion(), &self.generic)
281 } else {
282 Self::new(self.specifier, &self.generic.reverse())
283 }
284 }
285
286 pub fn transpose_pitch(&self, pitch: &Pitch) -> Result<Pitch> {
288 let interval =
289 super::Interval::from_diatonic_and_chromatic(self.clone(), self.get_chromatic()?)?;
290 interval.transpose_pitch_with_options(pitch, false, Some(4))
291 }
292
293 fn direction_prefix(&self) -> &'static str {
294 match self.direction() {
295 Direction::Descending => "Descending ",
296 Direction::Ascending => "Ascending ",
297 Direction::Oblique => "",
298 }
299 }
300}
301
302impl fmt::Display for DiatonicInterval {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 f.write_str(&self.name())
305 }
306}
307
308fn semitones_generic(r#in: UnsignedIntegerType) -> Result<UnsignedIntegerType> {
309 match r#in {
310 1 => Ok(0),
311 2 => Ok(2),
312 3 => Ok(4),
313 4 => Ok(5),
314 5 => Ok(7),
315 6 => Ok(9),
316 7 => Ok(11),
317 _ => Err(Error::Interval(format!("Invalid diatonic interval: {in}"))),
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn a_diatonic_interval_classifies_itself_and_transposes() {
327 use crate::pitch::Pitch;
328
329 let third = DiatonicInterval::from_name("M3").unwrap();
330 assert_eq!(third.specifier(), Specifier::Major);
331 assert_eq!(third.generic().value(), 3);
332 assert!(!third.is_perfectable());
333 assert!(!third.is_step());
334 assert!(!third.is_diatonic_step());
335 assert!(third.is_skip());
336 assert_eq!(
337 third
338 .transpose_pitch(&Pitch::from_name("C4").unwrap())
339 .unwrap()
340 .name_with_octave(),
341 "E4"
342 );
343 let second = DiatonicInterval::from_name("M2").unwrap();
344 assert!(second.is_step());
345 assert!(second.is_diatonic_step());
346 assert!(!second.is_skip());
347 assert!(DiatonicInterval::from_name("P5").unwrap().is_perfectable());
348 }
349
350 #[test]
351 fn the_specifier_abbreviation_is_the_quality_prefix() {
352 assert_eq!(
353 DiatonicInterval::from_name("M-10")
354 .unwrap()
355 .specifier_abbreviation(),
356 "M"
357 );
358 assert_eq!(
359 DiatonicInterval::from_name("P5")
360 .unwrap()
361 .specifier_abbreviation(),
362 "P"
363 );
364 assert_eq!(
365 DiatonicInterval::from_name("d4")
366 .unwrap()
367 .specifier_abbreviation(),
368 "d"
369 );
370 }
371
372 #[test]
373 fn diatonic_get_chromatic_major_third() {
374 let generic = GenericInterval::from_int(3).unwrap();
375 let diatonic = DiatonicInterval::new(Specifier::Major, &generic);
376 assert_eq!(diatonic.get_chromatic().unwrap().semitones, 4.0);
377 }
378
379 #[test]
380 fn diatonic_reverse_unison_inverts_specifier() {
381 let generic = GenericInterval::from_int(1).unwrap();
382 let diatonic = DiatonicInterval::new(Specifier::Augmented, &generic);
383 let reversed = diatonic.reverse();
384 assert_eq!(reversed.get_chromatic().unwrap().semitones, -1.0);
385 }
386
387 #[test]
388 fn diatonic_names_match_music21() {
389 let descending_tenth =
390 DiatonicInterval::new(Specifier::Major, &GenericInterval::from_int(-10).unwrap());
391 assert_eq!(descending_tenth.name(), "M10");
392 assert_eq!(descending_tenth.directed_name(), "M-10");
393 assert_eq!(descending_tenth.nice_name(), "Major Tenth");
394 assert_eq!(
395 descending_tenth.directed_nice_name(),
396 "Descending Major Tenth"
397 );
398 assert_eq!(descending_tenth.simple_name(), "M3");
399 assert_eq!(descending_tenth.directed_simple_name(), "M-3");
400 assert_eq!(descending_tenth.simple_nice_name(), "Major Third");
401 assert_eq!(
402 descending_tenth.directed_simple_nice_name(),
403 "Descending Major Third"
404 );
405 assert_eq!(descending_tenth.mod7(), "m6");
406 assert_eq!(descending_tenth.mod7_inversion(), "m6");
407 assert_eq!(descending_tenth.specific_name(), "Major");
408 assert_eq!(descending_tenth.cents().unwrap(), -1600.0);
409 assert_eq!(descending_tenth.to_string(), "M10");
410 let fifteenth =
411 DiatonicInterval::new(Specifier::Perfect, &GenericInterval::from_int(15).unwrap());
412 assert_eq!(fifteenth.semi_simple_name(), "P8");
413 assert_eq!(fifteenth.directed_semi_simple_name(), "P8");
414 assert_eq!(fifteenth.semi_simple_nice_name(), "Perfect Octave");
415 assert_eq!(
416 fifteenth.directed_semi_simple_nice_name(),
417 "Ascending Perfect Octave"
418 );
419 }
420
421 #[test]
422 fn diatonic_unison_direction_comes_from_the_quality() {
423 let unison = GenericInterval::from_int(1).unwrap();
424 assert_eq!(
425 DiatonicInterval::new(Specifier::Augmented, &unison).direction(),
426 Direction::Ascending
427 );
428 assert_eq!(
429 DiatonicInterval::new(Specifier::Diminished, &unison).direction(),
430 Direction::Descending
431 );
432 assert_eq!(
433 DiatonicInterval::new(Specifier::Perfect, &unison).direction(),
434 Direction::Oblique
435 );
436 assert_eq!(
437 DiatonicInterval::new(Specifier::Diminished, &unison).directed_nice_name(),
438 "Descending Diminished Unison"
439 );
440 }
441
442 #[test]
443 fn diatonic_try_new_rejects_impossible_pairs() {
444 let fifth = GenericInterval::from_int(5).unwrap();
445 let err = DiatonicInterval::try_new(Specifier::Major, fifth).unwrap_err();
446 assert_eq!(
447 err.to_string(),
448 "Interval error: Cannot create a 'Major Fifth'"
449 );
450 let third = GenericInterval::from_int(3).unwrap();
451 assert!(DiatonicInterval::try_new(Specifier::Perfect, third).is_err());
452 let down_unison = GenericInterval::from_int(-1).unwrap();
453 assert!(DiatonicInterval::try_new(Specifier::Perfect, down_unison).is_err());
454 assert!(
455 DiatonicInterval::try_new(Specifier::Augmented, GenericInterval::from_int(-1).unwrap())
456 .is_ok()
457 );
458 let from_name = DiatonicInterval::from_name("M-3").unwrap();
459 assert_eq!(from_name.directed_name(), "M-3");
460 assert_eq!(
461 DiatonicInterval::from_name("Major Third").unwrap(),
462 DiatonicInterval::from_name("M3").unwrap()
463 );
464 }
465}