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_written_accidental(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 if value == IntegerType::MIN {
280 return Err(Error::Interval(format!(
281 "a generic interval of {value} is too large"
282 )));
283 }
284 self.value = value;
285 Ok(())
286 }
287
288 pub(crate) fn simple_steps_and_octaves(&self) -> (IntegerType, IntegerType) {
289 let undirected = self.undirected();
290 let mut octaves = undirected / 7;
291 let mut steps = undirected % 7;
292 if steps == 0 {
293 octaves -= 1;
294 steps = 7;
295 }
296 (steps, octaves)
297 }
298}
299
300impl fmt::Display for GenericInterval {
301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302 write!(f, "{}", self.value)
303 }
304}
305
306const PLAIN_ORDINALS: [&str; 23] = [
307 "zeroth",
308 "first",
309 "second",
310 "third",
311 "fourth",
312 "fifth",
313 "sixth",
314 "seventh",
315 "eighth",
316 "ninth",
317 "tenth",
318 "eleventh",
319 "twelfth",
320 "thirteenth",
321 "fourteenth",
322 "fifteenth",
323 "sixteenth",
324 "seventeenth",
325 "eighteenth",
326 "nineteenth",
327 "twentieth",
328 "twenty-first",
329 "twenty-second",
330];
331
332fn numbered_ordinal(value: &str) -> Option<usize> {
335 let digits: String = value.chars().take_while(char::is_ascii_digit).collect();
336 if digits.is_empty() {
337 return None;
338 }
339 let number: usize = digits.parse().ok()?;
340 let expected = match number % 100 {
341 11..=13 => "th",
342 _ => match number % 10 {
343 1 => "st",
344 2 => "nd",
345 3 => "rd",
346 _ => "th",
347 },
348 };
349 (&value[digits.len()..] == expected).then_some(number)
350}
351
352fn strip_word_prefix<'a>(value: &'a str, word: &str) -> Option<&'a str> {
353 let head = value.get(..word.len())?;
354 if head.eq_ignore_ascii_case(word) {
355 Some(&value[word.len()..])
356 } else {
357 None
358 }
359}
360
361fn name_from_interval_number(value: IntegerType) -> String {
362 let value = value.unsigned_abs() as usize;
363 if let Some(name) = MUSICAL_ORDINAL_STRINGS.get(value) {
364 return name.clone();
365 }
366 let suffix = match value % 100 {
367 11..=13 => "th",
368 _ => match value % 10 {
369 1 => "st",
370 2 => "nd",
371 3 => "rd",
372 _ => "th",
373 },
374 };
375 format!("{value}{suffix}")
376}
377
378pub fn convert_generic(value: IntegerType) -> IntegerType {
381 value
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387
388 #[test]
389 fn a_zeroth_is_not_an_interval() {
390 assert!(GenericInterval::new(0).is_err());
391 assert_eq!(GenericInterval::new(-3).unwrap().value(), -3);
392 }
393
394 #[test]
395 fn a_size_with_no_undirected_size_is_refused() {
396 assert!(GenericInterval::new(IntegerType::MIN).is_err());
397 assert!(GenericInterval::new(IntegerType::MIN + 1).is_ok());
398 }
399
400 #[test]
401 fn generic_interval_direction_and_simple_values() {
402 let descending_ninth = GenericInterval::from_int(-9).unwrap();
403 assert_eq!(descending_ninth.simple_undirected(), 2);
404 assert_eq!(descending_ninth.simple_directed(), -2);
405 assert!(matches!(
406 descending_ninth.direction(),
407 Direction::Descending
408 ));
409 }
410
411 #[test]
412 fn generic_interval_octave_and_unison_edges() {
413 let octave = GenericInterval::from_int(8).unwrap();
414 assert_eq!(octave.simple_undirected(), 1);
415 assert_eq!(octave.semi_simple_undirected(), 8);
416 assert_eq!(octave.semi_simple_directed(), 8);
417 assert_eq!(octave.undirected_octaves(), 1);
418 assert_eq!(GenericInterval::from_int(-15).unwrap().octaves(), -2);
419 assert_eq!(
420 GenericInterval::from_int(-9)
421 .unwrap()
422 .semi_simple_directed(),
423 -2
424 );
425 let unison = GenericInterval::from_int(1).unwrap();
426 assert!(unison.is_unison());
427 assert_eq!(unison.direction(), Direction::Oblique);
428 assert_eq!(unison.staff_distance(), 0);
429 assert!(GenericInterval::from_int(0).is_err());
430 }
431
432 #[test]
433 fn generic_interval_mod7_and_complement() {
434 let descending_third = GenericInterval::from_int(-3).unwrap();
435 assert_eq!(descending_third.mod7(), 6);
436 assert_eq!(descending_third.mod7_inversion(), 6);
437 assert_eq!(descending_third.complement().value(), 6);
438 assert_eq!(descending_third.reverse().value(), 3);
439 assert_eq!(GenericInterval::from_int(1).unwrap().reverse().value(), 1);
440 assert!(GenericInterval::from_int(2).unwrap().is_step());
441 assert!(GenericInterval::from_int(-2).unwrap().is_diatonic_step());
442 assert!(GenericInterval::from_int(4).unwrap().is_skip());
443 assert!(GenericInterval::from_int(5).unwrap().is_perfectable());
444 assert!(!GenericInterval::from_int(10).unwrap().is_perfectable());
445 }
446
447 #[test]
448 fn generic_interval_names() {
449 assert_eq!(GenericInterval::from_int(3).unwrap().nice_name(), "Third");
450 assert_eq!(
451 GenericInterval::from_int(10).unwrap().simple_nice_name(),
452 "Third"
453 );
454 assert_eq!(
455 GenericInterval::from_int(15)
456 .unwrap()
457 .semi_simple_nice_name(),
458 "Octave"
459 );
460 assert_eq!(GenericInterval::from_int(23).unwrap().nice_name(), "23rd");
461 assert_eq!(GenericInterval::from_int(-3).unwrap().to_string(), "-3");
462 }
463
464 #[test]
465 fn generic_interval_from_name() {
466 assert_eq!(GenericInterval::from_name("Third").unwrap().value(), 3);
467 assert_eq!(
468 GenericInterval::from_name("descending fifth")
469 .unwrap()
470 .value(),
471 -5
472 );
473 assert_eq!(GenericInterval::from_name("octave").unwrap().value(), 8);
474 assert_eq!(GenericInterval::from_name("3rd").unwrap().value(), 3);
475 assert_eq!(
476 GenericInterval::from_name("Descending 2nd")
477 .unwrap()
478 .value(),
479 -2
480 );
481 assert!(GenericInterval::from_name("blah").is_err());
482 assert!(GenericInterval::from_name("zeroth").is_err());
483 assert!(GenericInterval::from_name("1").is_err());
484 assert!(GenericInterval::from_name("3nd").is_err());
485 }
486
487 #[test]
488 fn generic_transposition_keeps_the_accidental() {
489 let c_sharp = Pitch::from_name("C#4").unwrap();
490 let third = GenericInterval::from_int(3).unwrap();
491 assert_eq!(
492 third.transpose_pitch(&c_sharp).unwrap().name_with_octave(),
493 "E#4"
494 );
495 let b_flat = Pitch::from_name("B-4").unwrap();
496 assert_eq!(
497 GenericInterval::from_int(-3)
498 .unwrap()
499 .transpose_pitch(&b_flat)
500 .unwrap()
501 .name_with_octave(),
502 "Gb4"
503 );
504 let no_octave = Pitch::from_name("C").unwrap();
505 let up = third.transpose_pitch(&no_octave).unwrap();
506 assert_eq!(up.name(), "E");
507 assert!(up.octave().is_none());
508 }
509
510 #[test]
511 fn key_aware_generic_transposition_reads_the_signature() {
512 let a_major = KeySignature::new(3);
513 let a = Pitch::from_name("A4").unwrap();
514 let third = GenericInterval::from_int(3).unwrap();
515 assert_eq!(
516 third
517 .transpose_pitch_key_aware(&a, Some(&a_major))
518 .unwrap()
519 .name_with_octave(),
520 "C#5"
521 );
522 let c_sharp = Pitch::from_name("C#5").unwrap();
523 assert_eq!(
524 third
525 .transpose_pitch_key_aware(&c_sharp, Some(&a_major))
526 .unwrap()
527 .name_with_octave(),
528 "E5"
529 );
530 let g_major = KeySignature::new(1);
531 let step = GenericInterval::from_int(2).unwrap();
532 let f_natural = Pitch::from_name("F4").unwrap();
533 assert_eq!(
534 step.transpose_pitch_key_aware(&f_natural, Some(&g_major))
535 .unwrap()
536 .name_with_octave(),
537 "Gb4"
538 );
539 let e = Pitch::from_name("E4").unwrap();
540 assert_eq!(
541 step.transpose_pitch_key_aware(&e, Some(&g_major))
542 .unwrap()
543 .name_with_octave(),
544 "F#4"
545 );
546 }
547}