1use serde::{Deserialize, Serialize};
2
3use super::pitch::Pitch;
4
5#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
6pub enum IntervalQuality {
7 Perfect,
8 Major,
9 Minor,
10 Augmented,
11 Diminished,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
18pub struct Interval {
19 semitones: i16,
20}
21
22impl Interval {
23 pub fn between(a: &Pitch, b: &Pitch) -> Self {
25 Self { semitones: b.to_midi() - a.to_midi() }
26 }
27
28 pub fn semitones(&self) -> i16 { self.semitones }
30
31 pub fn abs_semitones(&self) -> u16 { self.semitones.unsigned_abs() }
33
34 pub fn is_ascending(&self) -> bool { self.semitones > 0 }
36
37 pub fn simple_number(&self) -> u8 {
41 const TABLE: [u8; 12] = [1, 2, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7];
42 TABLE[(self.semitones.unsigned_abs() as usize) % 12]
43 }
44
45 pub fn quality(&self) -> IntervalQuality {
49 match (self.semitones.unsigned_abs() as usize) % 12 {
50 0 => IntervalQuality::Perfect,
51 1 => IntervalQuality::Minor,
52 2 => IntervalQuality::Major,
53 3 => IntervalQuality::Minor,
54 4 => IntervalQuality::Major,
55 5 => IntervalQuality::Perfect,
56 6 => IntervalQuality::Augmented,
57 7 => IntervalQuality::Perfect,
58 8 => IntervalQuality::Minor,
59 9 => IntervalQuality::Major,
60 10 => IntervalQuality::Minor,
61 11 => IntervalQuality::Major,
62 _ => IntervalQuality::Perfect,
63 }
64 }
65
66 pub fn display(&self) -> String {
70 let abs = self.semitones.unsigned_abs() as usize;
71 let q = match self.quality() {
72 IntervalQuality::Perfect => "P",
73 IntervalQuality::Major => "M",
74 IntervalQuality::Minor => "m",
75 IntervalQuality::Augmented => "A",
76 IntervalQuality::Diminished => "d",
77 };
78 let n = if abs.is_multiple_of(12) && abs >= 12 { 8 } else { self.simple_number() };
79 format!("{}{}", q, n)
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::{Pitch, Step};
87
88 #[test]
89 fn interval_unison_p1() {
90 let c4 = Pitch::new(Step::C, 4);
91 let iv = Interval::between(&c4, &c4);
92 assert_eq!(iv.semitones(), 0);
93 assert_eq!(iv.display(), "P1");
94 }
95
96 #[test]
97 fn interval_major_third_c4_e4() {
98 let a = Pitch::new(Step::C, 4);
99 let b = Pitch::new(Step::E, 4);
100 let iv = Interval::between(&a, &b);
101 assert_eq!(iv.semitones(), 4);
102 assert_eq!(iv.quality(), IntervalQuality::Major);
103 assert_eq!(iv.display(), "M3");
104 assert!(iv.is_ascending());
105 }
106
107 #[test]
108 fn interval_perfect_fifth_c4_g4() {
109 let a = Pitch::new(Step::C, 4);
110 let b = Pitch::new(Step::G, 4);
111 let iv = Interval::between(&a, &b);
112 assert_eq!(iv.semitones(), 7);
113 assert_eq!(iv.display(), "P5");
114 }
115
116 #[test]
117 fn interval_minor_seventh_descending() {
118 let a = Pitch::new(Step::A, 4);
120 let b = Pitch::new(Step::B, 3);
121 let iv = Interval::between(&a, &b);
122 assert_eq!(iv.semitones(), -10);
123 assert_eq!(iv.display(), "m7");
124 assert!(!iv.is_ascending());
125 }
126
127 #[test]
128 fn interval_tritone() {
129 let a = Pitch::new(Step::C, 4);
130 let b = Pitch::with_alter(Step::F, 4, 1); let iv = Interval::between(&a, &b);
132 assert_eq!(iv.semitones(), 6);
133 assert_eq!(iv.quality(), IntervalQuality::Augmented);
134 assert_eq!(iv.display(), "A4");
135 }
136
137 #[test]
138 fn interval_octave() {
139 let a = Pitch::new(Step::C, 4);
140 let b = Pitch::new(Step::C, 5);
141 let iv = Interval::between(&a, &b);
142 assert_eq!(iv.semitones(), 12);
143 assert_eq!(iv.display(), "P8");
144 }
145
146 #[test]
147 fn interval_minor_second() {
148 let a = Pitch::new(Step::E, 4);
149 let b = Pitch::new(Step::F, 4);
150 let iv = Interval::between(&a, &b);
151 assert_eq!(iv.semitones(), 1);
152 assert_eq!(iv.display(), "m2");
153 }
154}