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 {
26 semitones: b.to_midi() - a.to_midi(),
27 }
28 }
29
30 pub fn semitones(&self) -> i16 {
32 self.semitones
33 }
34
35 pub fn abs_semitones(&self) -> u16 {
37 self.semitones.unsigned_abs()
38 }
39
40 pub fn is_ascending(&self) -> bool {
42 self.semitones > 0
43 }
44
45 pub fn simple_number(&self) -> u8 {
49 const TABLE: [u8; 12] = [1, 2, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7];
50 TABLE[(self.semitones.unsigned_abs() as usize) % 12]
51 }
52
53 pub fn quality(&self) -> IntervalQuality {
57 match (self.semitones.unsigned_abs() as usize) % 12 {
58 0 => IntervalQuality::Perfect,
59 1 => IntervalQuality::Minor,
60 2 => IntervalQuality::Major,
61 3 => IntervalQuality::Minor,
62 4 => IntervalQuality::Major,
63 5 => IntervalQuality::Perfect,
64 6 => IntervalQuality::Augmented,
65 7 => IntervalQuality::Perfect,
66 8 => IntervalQuality::Minor,
67 9 => IntervalQuality::Major,
68 10 => IntervalQuality::Minor,
69 11 => IntervalQuality::Major,
70 _ => IntervalQuality::Perfect,
71 }
72 }
73
74 pub fn display(&self) -> String {
78 let abs = self.semitones.unsigned_abs() as usize;
79 let q = match self.quality() {
80 IntervalQuality::Perfect => "P",
81 IntervalQuality::Major => "M",
82 IntervalQuality::Minor => "m",
83 IntervalQuality::Augmented => "A",
84 IntervalQuality::Diminished => "d",
85 };
86 let n = if abs.is_multiple_of(12) && abs >= 12 {
87 8
88 } else {
89 self.simple_number()
90 };
91 format!("{}{}", q, n)
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::{Pitch, Step};
99
100 #[test]
101 fn interval_unison_p1() {
102 let c4 = Pitch::new(Step::C, 4);
103 let iv = Interval::between(&c4, &c4);
104 assert_eq!(iv.semitones(), 0);
105 assert_eq!(iv.display(), "P1");
106 }
107
108 #[test]
109 fn interval_major_third_c4_e4() {
110 let a = Pitch::new(Step::C, 4);
111 let b = Pitch::new(Step::E, 4);
112 let iv = Interval::between(&a, &b);
113 assert_eq!(iv.semitones(), 4);
114 assert_eq!(iv.quality(), IntervalQuality::Major);
115 assert_eq!(iv.display(), "M3");
116 assert!(iv.is_ascending());
117 }
118
119 #[test]
120 fn interval_perfect_fifth_c4_g4() {
121 let a = Pitch::new(Step::C, 4);
122 let b = Pitch::new(Step::G, 4);
123 let iv = Interval::between(&a, &b);
124 assert_eq!(iv.semitones(), 7);
125 assert_eq!(iv.display(), "P5");
126 }
127
128 #[test]
129 fn interval_minor_seventh_descending() {
130 let a = Pitch::new(Step::A, 4);
132 let b = Pitch::new(Step::B, 3);
133 let iv = Interval::between(&a, &b);
134 assert_eq!(iv.semitones(), -10);
135 assert_eq!(iv.display(), "m7");
136 assert!(!iv.is_ascending());
137 }
138
139 #[test]
140 fn interval_tritone() {
141 let a = Pitch::new(Step::C, 4);
142 let b = Pitch::with_alter(Step::F, 4, 1); let iv = Interval::between(&a, &b);
144 assert_eq!(iv.semitones(), 6);
145 assert_eq!(iv.quality(), IntervalQuality::Augmented);
146 assert_eq!(iv.display(), "A4");
147 }
148
149 #[test]
150 fn interval_octave() {
151 let a = Pitch::new(Step::C, 4);
152 let b = Pitch::new(Step::C, 5);
153 let iv = Interval::between(&a, &b);
154 assert_eq!(iv.semitones(), 12);
155 assert_eq!(iv.display(), "P8");
156 }
157
158 #[test]
159 fn interval_minor_second() {
160 let a = Pitch::new(Step::E, 4);
161 let b = Pitch::new(Step::F, 4);
162 let iv = Interval::between(&a, &b);
163 assert_eq!(iv.semitones(), 1);
164 assert_eq!(iv.display(), "m2");
165 }
166}