rstmt_core/intervals/
kinds.rs

1/*
2    Appellation: kinds <module>
3    Contrib: FL03 <jo3mccain@icloud.com>
4*/
5use crate::{IntoPitch, Pitch};
6
7/// [Intervals] enumerates the various intervals used within music theory.
8/// The system considers a semitone to be the smallest interval, while the octave
9/// describe the maximum distance between any two pitches.
10#[derive(
11    Clone,
12    Copy,
13    Debug,
14    Default,
15    Eq,
16    Hash,
17    Ord,
18    PartialEq,
19    PartialOrd,
20    strum::AsRefStr,
21    strum::Display,
22    strum::EnumIs,
23)]
24#[cfg_attr(
25    feature = "serde",
26    derive(serde::Deserialize, serde::Serialize),
27    serde(rename_all = "lowercase")
28)]
29#[repr(u8)]
30#[strum(serialize_all = "lowercase")]
31pub enum Intervals {
32    #[default]
33    Semitone = 1,
34    Tone = 2,
35    Thirds(Third),
36    Fourths(Fourth),
37    Fifths(Fifth),
38    Sevenths(Seventh),
39    Octave = 12,
40}
41
42impl Intervals {
43    pub fn dist(a: impl IntoPitch, b: impl IntoPitch) -> Self {
44        Self::new(a.into_pitch().absmod(), b.into_pitch().absmod())
45    }
46    pub fn new<A, B, C>(lhs: A, rhs: B) -> Self
47    where
48        A: core::ops::Sub<B, Output = C>,
49        C: Into<Intervals>,
50    {
51        (lhs - rhs).into()
52    }
53    /// Use the difference between two pitches to determine the interval.
54    pub fn from_value(value: impl IntoPitch) -> Self {
55        use Intervals::*;
56        let Pitch(pitch) = value.into_pitch();
57        match pitch.abs() % 12 {
58            0 => Octave,
59            1 => Semitone,
60            2 => Tone,
61            3 => Thirds(Third::Minor),
62            4 => Thirds(Third::Major),
63            5 => Fourths(Fourth::Perfect),
64            6 => Fifths(Fifth::Diminished),
65            7 => Fifths(Fifth::Perfect),
66            8 => Fifths(Fifth::Augmented),
67            9 => Sevenths(Seventh::Diminished),
68            10 => Sevenths(Seventh::Minor),
69            11 => Sevenths(Seventh::Major),
70            _ => panic!("Invalid interval value: {}", pitch),
71        }
72    }
73    /// A convenience method for constructing a new instance of the [Octave](Intervals::Octave) variant.
74    pub fn octave() -> Self {
75        Intervals::Octave
76    }
77    /// A convenience method for constructing a new instance of the [Semitone](Intervals::Semitone) variant.
78    pub fn semitone() -> Self {
79        Intervals::Semitone
80    }
81    /// A convenience method for constructing a new instance of the [Tone](Intervals::Tone) variant.
82    pub fn tone() -> Self {
83        Intervals::Tone
84    }
85    /// A convenience method for constructing a new variant, [`Thirds`](Intervals::Thirds).
86    pub fn third(third: Third) -> Self {
87        Intervals::Thirds(third)
88    }
89
90    pub fn major_third() -> Self {
91        Intervals::Thirds(Third::Major)
92    }
93
94    pub fn minor_third() -> Self {
95        Intervals::Thirds(Third::Minor)
96    }
97
98    /// A convenience method for constructing a new variant, [`Fourths`](Intervals::Fourths).
99    pub fn fourth(fourth: Fourth) -> Self {
100        Intervals::Fourths(fourth)
101    }
102
103    pub fn perfect_fourth() -> Self {
104        Intervals::Fourths(Fourth::Perfect)
105    }
106    /// A convenience method for constructing a new variant, [`Fifths`](Intervals::Fifths).
107    pub fn fifth(fifth: Fifth) -> Self {
108        Intervals::Fifths(fifth)
109    }
110    pub fn augmented_fifth() -> Self {
111        Intervals::Fifths(Fifth::Augmented)
112    }
113
114    pub fn diminished_fifth() -> Self {
115        Intervals::Fifths(Fifth::Diminished)
116    }
117
118    pub fn perfect_fifth() -> Self {
119        Intervals::Fifths(Fifth::Perfect)
120    }
121    /// A convenience method for constructing a new variant, [`Sevenths`](Intervals::Sevenths).
122    pub fn seventh(seventh: Seventh) -> Self {
123        Intervals::Sevenths(seventh)
124    }
125
126    pub fn augmented_seventh() -> Self {
127        Intervals::Sevenths(Seventh::Augmented)
128    }
129
130    pub fn diminished_seventh() -> Self {
131        Intervals::Sevenths(Seventh::Diminished)
132    }
133
134    pub fn major_seventh() -> Self {
135        Intervals::Sevenths(Seventh::Major)
136    }
137
138    pub fn minor_seventh() -> Self {
139        Intervals::Sevenths(Seventh::Minor)
140    }
141    /// Interpret the current interval as a pitch.
142    pub fn as_pitch(&self) -> Pitch {
143        Pitch::from(self.value())
144    }
145    /// Returns the name of the selected interval.
146    pub fn name(&self) -> &str {
147        self.as_ref()
148    }
149    /// Returns the value of the selected interval.
150    pub fn value(&self) -> i8 {
151        match *self {
152            Intervals::Semitone => 1,
153            Intervals::Tone => 2,
154            Intervals::Thirds(third) => third as i8,
155            Intervals::Fourths(fourth) => fourth as i8,
156            Intervals::Fifths(fifth) => fifth as i8,
157            Intervals::Sevenths(seventh) => seventh as i8,
158            Intervals::Octave => 12,
159        }
160    }
161}
162
163macro_rules! impl_from_value {
164    (@impl $name:ident::$variant:ident($T:ty)) => {
165        impl From<$T> for $name {
166            fn from(value: $T) -> Self {
167                $name::$variant(value)
168            }
169        }
170    };
171    ($($name:ident::$variant:ident($T:ty)),* $(,)?) => {
172        $(
173            impl_from_value!(@impl $name::$variant($T));
174        )*
175    };
176}
177
178impl<P> From<P> for Intervals
179where
180    P: IntoPitch,
181{
182    fn from(value: P) -> Self {
183        Intervals::from_value(value)
184    }
185}
186
187impl_from_value! {
188    Intervals::Thirds(Third),
189    Intervals::Fourths(Fourth),
190    Intervals::Fifths(Fifth),
191    Intervals::Sevenths(Seventh),
192}
193
194interval! {
195    default: Major;
196    pub enum Third {
197        Minor = 3,
198        Major = 4,
199    }
200}
201
202interval! {
203    default: Perfect;
204    pub enum Fourth {
205        Perfect = 5,
206    }
207}
208
209interval! {
210    default: Perfect;
211    pub enum Fifth {
212        Diminished = 6,
213        Perfect = 7,
214        Augmented = 8,
215    }
216}
217
218interval! {
219    default: Diminished;
220    pub enum Seventh {
221        Diminished = 9,
222        Minor = 10,
223        Major = 11,
224        Augmented = 12,
225    }
226}
227
228impl Fifth {
229    pub fn from_thirds(lhs: Third, rhs: Third) -> Self {
230        let value = lhs as i8 + rhs as i8;
231        match value {
232            6 => Fifth::Diminished,
233            7 => Fifth::Perfect,
234            8 => Fifth::Augmented,
235            _ => panic!("Invalid fifth value: {}", value),
236        }
237    }
238}