rust_music_theory/interval/
interval.rs

1use crate::interval::errors::IntervalError;
2use crate::note::{Note, PitchClass};
3use strum_macros::Display;
4
5/// The quality of an interval; major, minor, etc.
6#[derive(Display, Debug, Copy, Clone)]
7pub enum Quality {
8    /// A perfect interval; unisons, fourths, fifths, and octaves.
9    Perfect,
10    Major,
11    Minor,
12    Augmented,
13    Diminished,
14}
15
16/// The number of an interval.
17#[derive(Display, Debug, Copy, Clone)]
18pub enum Number {
19    Unison,
20    Second,
21    Third,
22    Fourth,
23    Fifth,
24    Sixth,
25    Seventh,
26    Octave,
27}
28
29/// A step between notes.
30#[derive(Display, Debug, Copy, Clone)]
31pub enum Step {
32    /// A semitone step.
33    Half,
34    /// A tone step.
35    Whole,
36    /// A tritone step.
37    Tritone,
38}
39
40/// An interval between two notes.
41#[derive(Debug, Copy, Clone)]
42pub struct Interval {
43    /// The number of semitones between the notes.
44    pub semitone_count: u8,
45    /// The quality of the interval.
46    pub quality: Quality,
47    /// The number of the interval.
48    pub number: Number,
49    /// The step of the interval.
50    pub step: Option<Step>,
51}
52
53impl Interval {
54    /// Create a new interval.
55    pub fn new(semitone_count: u8, quality: Quality, number: Number, step: Option<Step>) -> Self {
56        Interval {
57            semitone_count,
58            quality,
59            number,
60            step,
61        }
62    }
63
64    /// Creates multiple intervals each based on the number of semitones from the root.
65    ///
66    /// # Errors
67    ///
68    /// Fails if `sc` is greater than 12.
69    pub fn from_semitones(semi_tones: &[u8]) -> Result<Vec<Self>, IntervalError> {
70        let mut intervals: Vec<Interval> = vec![];
71
72        if semi_tones.is_empty() {
73            return Err(IntervalError::InvalidInterval);
74        }
75
76        for i in semi_tones {
77            let interval = Self::from_semitone(*i)?;
78            intervals.push(interval);
79        }
80
81        Ok(intervals)
82    }
83
84    /// Create an interval based on the number of semitones from the root.
85    ///
86    /// # Errors
87    ///
88    /// Fails if `sc` is greater than 12.
89    pub fn from_semitone(sc: u8) -> Result<Self, IntervalError> {
90        let (number, quality, mut step): (Number, Quality, Option<Step>);
91        step = None;
92
93        match sc {
94            0 => {
95                number = Number::Unison;
96                quality = Quality::Perfect;
97            }
98            1 => {
99                number = Number::Second;
100                quality = Quality::Minor;
101                step = Some(Step::Half);
102            }
103            2 => {
104                number = Number::Second;
105                quality = Quality::Major;
106                step = Some(Step::Whole);
107            }
108            3 => {
109                number = Number::Third;
110                quality = Quality::Minor;
111            }
112            4 => {
113                number = Number::Third;
114                quality = Quality::Major;
115            }
116            5 => {
117                number = Number::Fourth;
118                quality = Quality::Perfect;
119            }
120            6 => {
121                number = Number::Fifth;
122                quality = Quality::Diminished;
123                step = Some(Step::Tritone);
124            }
125            7 => {
126                number = Number::Fifth;
127                quality = Quality::Perfect;
128            }
129            8 => {
130                number = Number::Sixth;
131                quality = Quality::Minor;
132            }
133            9 => {
134                number = Number::Sixth;
135                quality = Quality::Major;
136            }
137            10 => {
138                number = Number::Seventh;
139                quality = Quality::Minor;
140            }
141            11 => {
142                number = Number::Seventh;
143                quality = Quality::Major;
144            }
145            12 => {
146                number = Number::Octave;
147                quality = Quality::Perfect;
148            }
149            _ => {
150                return Err(IntervalError::InvalidInterval);
151            }
152        };
153
154        Ok(Interval {
155            semitone_count: sc,
156            number,
157            quality,
158            step,
159        })
160    }
161
162    /// Creates an interval by inverting the given interval
163    /// e.g. Perfect fifth (C to G) becomes a perfect fourth (G to C)
164    pub fn invert(interval: &Self) -> Result<Self, IntervalError> {
165        if interval.semitone_count == 12 {
166            Self::from_semitone(12)
167        } else {
168            let adjusted = (12 + (12i16 - interval.semitone_count as i16)) % 12;
169            Self::from_semitone(adjusted as u8)
170        }
171    }
172
173    /// Move the given note up by this interval.
174    pub fn second_note_from(self, first_note: Note) -> Note {
175        let pitch_class = PitchClass::from_interval(first_note.pitch_class, self);
176        let octave = first_note.octave;
177        let excess_octave = (first_note.pitch_class as u8 + self.semitone_count) / 12;
178
179        Note {
180            octave: octave + excess_octave,
181            pitch_class,
182        }
183    }
184
185    /// Move the given note down by this interval.
186    pub fn second_note_down_from(self, first_note: Note) -> Note {
187        let pitch_class = PitchClass::from_interval_down(first_note.pitch_class, self);
188        let octave = first_note.octave;
189        let raw_diff = first_note.pitch_class as i16 - self.semitone_count as i16;
190        let excess_octave = (raw_diff / -12) + if raw_diff < 0 { 1 } else { 0 };
191
192        Note {
193            octave: octave - excess_octave as u8,
194            pitch_class,
195        }
196    }
197
198    /// Produce the list of notes that have had each interval applied in order.
199    pub fn to_notes(root: Note, intervals: impl IntoIterator<Item = Interval>) -> Vec<Note> {
200        let mut notes = vec![root];
201
202        for interval in intervals {
203            let last_note = notes.last().unwrap();
204            let interval_first_note = Note::new(last_note.pitch_class, last_note.octave);
205            let interval_second_note = interval.second_note_from(interval_first_note);
206            notes.push(interval_second_note);
207        }
208
209        notes
210    }
211
212    /// Produce the list of notes that have had each interval applied in order.
213    pub fn to_notes_reverse(
214        root: Note,
215        intervals: impl IntoIterator<Item = Interval>,
216    ) -> Vec<Note> {
217        let mut notes = vec![root];
218
219        let reversed = intervals
220            .into_iter()
221            .collect::<Vec<Interval>>()
222            .into_iter()
223            .rev();
224        for interval in reversed {
225            let last_note = notes.last().unwrap();
226            let interval_first_note = Note::new(last_note.pitch_class, last_note.octave);
227            let interval_second_note = interval.second_note_down_from(interval_first_note);
228            notes.push(interval_second_note);
229        }
230
231        notes
232    }
233}
234
235impl Default for Interval {
236    fn default() -> Self {
237        Interval {
238            semitone_count: 0,
239            quality: Quality::Major,
240            number: Number::Unison,
241            step: None,
242        }
243    }
244}