1use std::fmt;
5
6use crate::{
7 common::numbertools::MUSICAL_ORDINAL_STRINGS,
8 error::{Error, Result},
9 key::KeySignature,
10 pitch::{Accidental, Pitch},
11};
12
13use super::{
14 IntegerType, diatonicinterval::DiatonicInterval, direction::Direction, specifier::Specifier,
15};
16
17#[derive(Clone, Debug, PartialEq, Eq, Hash)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21#[must_use]
22pub struct GenericInterval {
23 value: IntegerType,
24}
25
26impl GenericInterval {
27 pub fn new(value: IntegerType) -> Result<Self> {
30 Self::from_int(value)
31 }
32
33 pub fn from_name(name: &str) -> Result<Self> {
37 let trimmed = name.trim();
38 let (body, scale) = if let Some(rest) = strip_word_prefix(trimmed, "descending") {
39 (rest, -1)
40 } else if let Some(rest) = strip_word_prefix(trimmed, "ascending") {
41 (rest, 1)
42 } else {
43 (trimmed, 1)
44 };
45 let body = body.trim();
46 let lower = body.to_ascii_lowercase();
47 let musical = MUSICAL_ORDINAL_STRINGS
48 .iter()
49 .position(|ordinal| ordinal.to_ascii_lowercase() == lower);
50 let plain = PLAIN_ORDINALS.iter().position(|ordinal| *ordinal == lower);
51 match musical.or(plain).or_else(|| numbered_ordinal(&lower)) {
52 Some(index) if index > 0 => Self::from_int(index as IntegerType * scale),
53 _ => Err(Error::Interval(format!(
54 "Cannot convert '{name}' to an interval."
55 ))),
56 }
57 }
58
59 pub fn value(&self) -> IntegerType {
61 self.value
62 }
63
64 pub fn directed(&self) -> IntegerType {
66 self.value()
67 }
68
69 pub fn undirected(&self) -> IntegerType {
71 self.value().abs()
72 }
73
74 pub fn direction(&self) -> Direction {
76 let directed = self.directed();
77 if directed == 1 {
78 Direction::Oblique
79 } else if directed < 0 {
80 Direction::Descending
81 } else {
82 Direction::Ascending
83 }
84 }
85
86 pub fn simple_directed(&self) -> IntegerType {
89 let simple_undirected = self.simple_undirected();
90 if self.direction() == Direction::Descending && simple_undirected > 1 {
91 -simple_undirected
92 } else {
93 simple_undirected
94 }
95 }
96
97 pub fn simple_undirected(&self) -> IntegerType {
99 self.simple_steps_and_octaves().0
100 }
101
102 pub fn semi_simple_directed(&self) -> IntegerType {
105 let semi_simple_undirected = self.semi_simple_undirected();
106 if self.direction() == Direction::Descending && semi_simple_undirected > 1 {
107 -semi_simple_undirected
108 } else {
109 semi_simple_undirected
110 }
111 }
112
113 pub fn semi_simple_undirected(&self) -> IntegerType {
115 let simple_undirected = self.simple_undirected();
116 if self.simple_steps_and_octaves().1 >= 1 && simple_undirected == 1 {
117 8
118 } else {
119 simple_undirected
120 }
121 }
122
123 pub fn undirected_octaves(&self) -> IntegerType {
125 self.simple_steps_and_octaves().1
126 }
127
128 pub fn octaves(&self) -> IntegerType {
130 if self.direction() == Direction::Descending {
131 -self.undirected_octaves()
132 } else {
133 self.undirected_octaves()
134 }
135 }
136
137 pub fn staff_distance(&self) -> IntegerType {
140 let directed = self.directed();
141 if directed > 0 {
142 directed - 1
143 } else {
144 directed + 1
145 }
146 }
147
148 pub fn mod7(&self) -> IntegerType {
150 if self.direction() == Direction::Descending {
151 self.mod7_inversion()
152 } else {
153 self.simple_undirected()
154 }
155 }
156
157 pub fn mod7_inversion(&self) -> IntegerType {
159 9 - self.semi_simple_undirected()
160 }
161
162 pub fn is_perfectable(&self) -> bool {
165 matches!(self.simple_undirected(), 1 | 4 | 5)
166 }
167
168 pub fn is_step(&self) -> bool {
170 self.undirected() == 2
171 }
172
173 pub fn is_diatonic_step(&self) -> bool {
176 self.is_step()
177 }
178
179 pub fn is_skip(&self) -> bool {
181 self.undirected() > 2
182 }
183
184 pub fn is_unison(&self) -> bool {
186 self.undirected() == 1
187 }
188
189 pub fn nice_name(&self) -> String {
191 name_from_interval_number(self.undirected())
192 }
193
194 pub fn simple_nice_name(&self) -> String {
196 name_from_interval_number(self.simple_undirected())
197 }
198
199 pub fn semi_simple_nice_name(&self) -> String {
201 name_from_interval_number(self.semi_simple_undirected())
202 }
203
204 pub fn complement(&self) -> Self {
207 Self {
208 value: self.mod7_inversion(),
209 }
210 }
211
212 pub fn reverse(&self) -> Self {
214 if self.undirected() == 1 {
215 Self { value: 1 }
216 } else {
217 Self {
218 value: self.undirected() * -self.direction().as_int(),
219 }
220 }
221 }
222
223 pub fn get_diatonic(&self, spec: Specifier) -> DiatonicInterval {
225 DiatonicInterval::new(spec, self)
226 }
227
228 pub fn transpose_pitch(&self, pitch: &Pitch) -> Result<Pitch> {
232 self.transpose_pitch_key_aware(pitch, None)
233 }
234
235 pub fn transpose_pitch_key_aware(
238 &self,
239 pitch: &Pitch,
240 key_signature: Option<&KeySignature>,
241 ) -> Result<Pitch> {
242 let mut out = pitch.clone();
243 let had_octave = pitch.octave().is_some();
244 let dnn = pitch.diatonic_note_number();
245 out.set_diatonic_note_number(dnn + self.staff_distance())?;
246 if let Some(key_signature) = key_signature {
247 let step_alter = key_signature
248 .accidental_by_step(pitch.step().as_char())?
249 .map_or(0.0, |accidental| accidental.alter());
250 let offset_from_key = pitch.accidental().alter() - step_alter;
251 let new_step_alter = key_signature
252 .accidental_by_step(out.step().as_char())?
253 .map_or(0.0, |accidental| accidental.alter());
254 let alter = new_step_alter + offset_from_key;
255 let accidental = if alter == 0.0 {
256 None
257 } else {
258 Some(Accidental::new(alter)?)
259 };
260 out.set_accidental_or_natural(accidental);
261 }
262 if !had_octave {
263 out.octave_setter(None);
264 }
265 Ok(out)
266 }
267
268 pub(crate) fn from_int(value: IntegerType) -> Result<Self> {
269 let mut slf = Self { value: 1 };
270 slf.value_setter(convert_generic(value))?;
271 Ok(slf)
272 }
273
274 fn value_setter(&mut self, value: IntegerType) -> Result<()> {
275 if value == 0 {
276 return Err(Error::Interval("The Zeroth is not an interval".to_owned()));
277 }
278 self.value = value;
279 Ok(())
280 }
281
282 pub(crate) fn simple_steps_and_octaves(&self) -> (IntegerType, IntegerType) {
283 let undirected = self.undirected();
284 let mut octaves = undirected / 7;
285 let mut steps = undirected % 7;
286 if steps == 0 {
287 octaves -= 1;
288 steps = 7;
289 }
290 (steps, octaves)
291 }
292}
293
294impl fmt::Display for GenericInterval {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296 write!(f, "{}", self.value)
297 }
298}
299
300const PLAIN_ORDINALS: [&str; 23] = [
301 "zeroth",
302 "first",
303 "second",
304 "third",
305 "fourth",
306 "fifth",
307 "sixth",
308 "seventh",
309 "eighth",
310 "ninth",
311 "tenth",
312 "eleventh",
313 "twelfth",
314 "thirteenth",
315 "fourteenth",
316 "fifteenth",
317 "sixteenth",
318 "seventeenth",
319 "eighteenth",
320 "nineteenth",
321 "twentieth",
322 "twenty-first",
323 "twenty-second",
324];
325
326fn numbered_ordinal(value: &str) -> Option<usize> {
329 let digits: String = value.chars().take_while(char::is_ascii_digit).collect();
330 if digits.is_empty() {
331 return None;
332 }
333 let number: usize = digits.parse().ok()?;
334 let expected = match number % 100 {
335 11..=13 => "th",
336 _ => match number % 10 {
337 1 => "st",
338 2 => "nd",
339 3 => "rd",
340 _ => "th",
341 },
342 };
343 (&value[digits.len()..] == expected).then_some(number)
344}
345
346fn strip_word_prefix<'a>(value: &'a str, word: &str) -> Option<&'a str> {
347 let head = value.get(..word.len())?;
348 if head.eq_ignore_ascii_case(word) {
349 Some(&value[word.len()..])
350 } else {
351 None
352 }
353}
354
355fn name_from_interval_number(value: IntegerType) -> String {
356 let value = value.unsigned_abs() as usize;
357 if let Some(name) = MUSICAL_ORDINAL_STRINGS.get(value) {
358 return name.clone();
359 }
360 let suffix = match value % 100 {
361 11..=13 => "th",
362 _ => match value % 10 {
363 1 => "st",
364 2 => "nd",
365 3 => "rd",
366 _ => "th",
367 },
368 };
369 format!("{value}{suffix}")
370}
371
372pub fn convert_generic(value: IntegerType) -> IntegerType {
375 value
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381
382 #[test]
383 fn a_zeroth_is_not_an_interval() {
384 assert!(GenericInterval::new(0).is_err());
385 assert_eq!(GenericInterval::new(-3).unwrap().value(), -3);
386 }
387
388 #[test]
389 fn generic_interval_direction_and_simple_values() {
390 let descending_ninth = GenericInterval::from_int(-9).unwrap();
391 assert_eq!(descending_ninth.simple_undirected(), 2);
392 assert_eq!(descending_ninth.simple_directed(), -2);
393 assert!(matches!(
394 descending_ninth.direction(),
395 Direction::Descending
396 ));
397 }
398
399 #[test]
400 fn generic_interval_octave_and_unison_edges() {
401 let octave = GenericInterval::from_int(8).unwrap();
402 assert_eq!(octave.simple_undirected(), 1);
403 assert_eq!(octave.semi_simple_undirected(), 8);
404 assert_eq!(octave.semi_simple_directed(), 8);
405 assert_eq!(octave.undirected_octaves(), 1);
406 assert_eq!(GenericInterval::from_int(-15).unwrap().octaves(), -2);
407 assert_eq!(
408 GenericInterval::from_int(-9)
409 .unwrap()
410 .semi_simple_directed(),
411 -2
412 );
413 let unison = GenericInterval::from_int(1).unwrap();
414 assert!(unison.is_unison());
415 assert_eq!(unison.direction(), Direction::Oblique);
416 assert_eq!(unison.staff_distance(), 0);
417 assert!(GenericInterval::from_int(0).is_err());
418 }
419
420 #[test]
421 fn generic_interval_mod7_and_complement() {
422 let descending_third = GenericInterval::from_int(-3).unwrap();
423 assert_eq!(descending_third.mod7(), 6);
424 assert_eq!(descending_third.mod7_inversion(), 6);
425 assert_eq!(descending_third.complement().value(), 6);
426 assert_eq!(descending_third.reverse().value(), 3);
427 assert_eq!(GenericInterval::from_int(1).unwrap().reverse().value(), 1);
428 assert!(GenericInterval::from_int(2).unwrap().is_step());
429 assert!(GenericInterval::from_int(-2).unwrap().is_diatonic_step());
430 assert!(GenericInterval::from_int(4).unwrap().is_skip());
431 assert!(GenericInterval::from_int(5).unwrap().is_perfectable());
432 assert!(!GenericInterval::from_int(10).unwrap().is_perfectable());
433 }
434
435 #[test]
436 fn generic_interval_names() {
437 assert_eq!(GenericInterval::from_int(3).unwrap().nice_name(), "Third");
438 assert_eq!(
439 GenericInterval::from_int(10).unwrap().simple_nice_name(),
440 "Third"
441 );
442 assert_eq!(
443 GenericInterval::from_int(15)
444 .unwrap()
445 .semi_simple_nice_name(),
446 "Octave"
447 );
448 assert_eq!(GenericInterval::from_int(23).unwrap().nice_name(), "23rd");
449 assert_eq!(GenericInterval::from_int(-3).unwrap().to_string(), "-3");
450 }
451
452 #[test]
453 fn generic_interval_from_name() {
454 assert_eq!(GenericInterval::from_name("Third").unwrap().value(), 3);
455 assert_eq!(
456 GenericInterval::from_name("descending fifth")
457 .unwrap()
458 .value(),
459 -5
460 );
461 assert_eq!(GenericInterval::from_name("octave").unwrap().value(), 8);
462 assert_eq!(GenericInterval::from_name("3rd").unwrap().value(), 3);
463 assert_eq!(
464 GenericInterval::from_name("Descending 2nd")
465 .unwrap()
466 .value(),
467 -2
468 );
469 assert!(GenericInterval::from_name("blah").is_err());
470 assert!(GenericInterval::from_name("zeroth").is_err());
471 assert!(GenericInterval::from_name("1").is_err());
472 assert!(GenericInterval::from_name("3nd").is_err());
473 }
474
475 #[test]
476 fn generic_transposition_keeps_the_accidental() {
477 let c_sharp = Pitch::from_name("C#4").unwrap();
478 let third = GenericInterval::from_int(3).unwrap();
479 assert_eq!(
480 third.transpose_pitch(&c_sharp).unwrap().name_with_octave(),
481 "E#4"
482 );
483 let b_flat = Pitch::from_name("B-4").unwrap();
484 assert_eq!(
485 GenericInterval::from_int(-3)
486 .unwrap()
487 .transpose_pitch(&b_flat)
488 .unwrap()
489 .name_with_octave(),
490 "G-4"
491 );
492 let no_octave = Pitch::from_name("C").unwrap();
493 let up = third.transpose_pitch(&no_octave).unwrap();
494 assert_eq!(up.name(), "E");
495 assert!(up.octave().is_none());
496 }
497
498 #[test]
499 fn key_aware_generic_transposition_reads_the_signature() {
500 let a_major = KeySignature::new(3);
501 let a = Pitch::from_name("A4").unwrap();
502 let third = GenericInterval::from_int(3).unwrap();
503 assert_eq!(
504 third
505 .transpose_pitch_key_aware(&a, Some(&a_major))
506 .unwrap()
507 .name_with_octave(),
508 "C#5"
509 );
510 let c_sharp = Pitch::from_name("C#5").unwrap();
511 assert_eq!(
512 third
513 .transpose_pitch_key_aware(&c_sharp, Some(&a_major))
514 .unwrap()
515 .name_with_octave(),
516 "E5"
517 );
518 let g_major = KeySignature::new(1);
519 let step = GenericInterval::from_int(2).unwrap();
520 let f_natural = Pitch::from_name("F4").unwrap();
521 assert_eq!(
522 step.transpose_pitch_key_aware(&f_natural, Some(&g_major))
523 .unwrap()
524 .name_with_octave(),
525 "G-4"
526 );
527 let e = Pitch::from_name("E4").unwrap();
528 assert_eq!(
529 step.transpose_pitch_key_aware(&e, Some(&g_major))
530 .unwrap()
531 .name_with_octave(),
532 "F#4"
533 );
534 }
535}