Skip to main content

klib/core/
scale_kind.rs

1//! A module for working with scale kinds.
2
3use crate::core::{
4    base::{HasDescription, HasName, HasStaticName},
5    interval::{HasIntervals, Interval},
6};
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11// Enum.
12
13/// An enum representing a scale kind (type of scale).
14///
15/// Each scale kind has an explicit list of intervals that define the scale.
16#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug, Ord, PartialOrd)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18#[repr(u8)]
19pub enum ScaleKind {
20    /// A major scale (Ionian mode root scale).
21    Major,
22    /// A natural minor scale (Aeolian mode root scale).
23    NaturalMinor,
24    /// A harmonic minor scale.
25    HarmonicMinor,
26    /// A melodic minor scale (ascending).
27    MelodicMinor,
28    /// A whole tone scale.
29    WholeTone,
30    /// A chromatic scale (all 12 semitones).
31    Chromatic,
32    /// A diminished (whole-half) scale.
33    DiminishedWholeHalf,
34    /// A diminished (half-whole) scale.
35    DiminishedHalfWhole,
36    /// A major pentatonic scale.
37    MajorPentatonic,
38    /// A minor pentatonic scale.
39    MinorPentatonic,
40    /// A blues scale.
41    Blues,
42}
43
44// Impls.
45
46impl HasIntervals for ScaleKind {
47    fn intervals(&self) -> &'static [Interval] {
48        match self {
49            ScaleKind::Major => &[
50                Interval::PerfectUnison,
51                Interval::MajorSecond,
52                Interval::MajorThird,
53                Interval::PerfectFourth,
54                Interval::PerfectFifth,
55                Interval::MajorSixth,
56                Interval::MajorSeventh,
57            ],
58            ScaleKind::NaturalMinor => &[
59                Interval::PerfectUnison,
60                Interval::MajorSecond,
61                Interval::MinorThird,
62                Interval::PerfectFourth,
63                Interval::PerfectFifth,
64                Interval::MinorSixth,
65                Interval::MinorSeventh,
66            ],
67            ScaleKind::HarmonicMinor => &[
68                Interval::PerfectUnison,
69                Interval::MajorSecond,
70                Interval::MinorThird,
71                Interval::PerfectFourth,
72                Interval::PerfectFifth,
73                Interval::MinorSixth,
74                Interval::MajorSeventh,
75            ],
76            ScaleKind::MelodicMinor => &[
77                Interval::PerfectUnison,
78                Interval::MajorSecond,
79                Interval::MinorThird,
80                Interval::PerfectFourth,
81                Interval::PerfectFifth,
82                Interval::MajorSixth,
83                Interval::MajorSeventh,
84            ],
85            ScaleKind::WholeTone => &[
86                Interval::PerfectUnison,
87                Interval::MajorSecond,
88                Interval::MajorThird,
89                Interval::AugmentedFourth,
90                Interval::AugmentedFifth,
91                Interval::AugmentedSixth,
92            ],
93            ScaleKind::Chromatic => &[
94                Interval::PerfectUnison,
95                Interval::MinorSecond,
96                Interval::MajorSecond,
97                Interval::MinorThird,
98                Interval::MajorThird,
99                Interval::PerfectFourth,
100                Interval::AugmentedFourth,
101                Interval::PerfectFifth,
102                Interval::MinorSixth,
103                Interval::MajorSixth,
104                Interval::MinorSeventh,
105                Interval::MajorSeventh,
106            ],
107            ScaleKind::DiminishedWholeHalf => &[
108                Interval::PerfectUnison,
109                Interval::MajorSecond,
110                Interval::MinorThird,
111                Interval::PerfectFourth,
112                Interval::DiminishedFifth,
113                Interval::MinorSixth,
114                Interval::DiminishedSeventh,
115                Interval::MajorSeventh,
116            ],
117            ScaleKind::DiminishedHalfWhole => &[
118                Interval::PerfectUnison,
119                Interval::MinorSecond,
120                Interval::MinorThird,
121                Interval::MajorThird,
122                Interval::AugmentedFourth,
123                Interval::PerfectFifth,
124                Interval::MajorSixth,
125                Interval::MinorSeventh,
126            ],
127            // Major Pentatonic: 1, 2, 3, 5, 6 (no 4th or 7th)
128            ScaleKind::MajorPentatonic => &[Interval::PerfectUnison, Interval::MajorSecond, Interval::MajorThird, Interval::PerfectFifth, Interval::MajorSixth],
129            // Minor Pentatonic: 1, ♭3, 4, 5, ♭7 (no 2nd or 6th)
130            ScaleKind::MinorPentatonic => &[Interval::PerfectUnison, Interval::MinorThird, Interval::PerfectFourth, Interval::PerfectFifth, Interval::MinorSeventh],
131            // Blues: 1, ♭3, 4, ♯4, 5, ♭7 (minor pentatonic + ♯4)
132            ScaleKind::Blues => &[
133                Interval::PerfectUnison,
134                Interval::MinorThird,
135                Interval::PerfectFourth,
136                Interval::AugmentedFourth, // #4 blue note – chromatic passing tone between the 4th and 5th
137                Interval::PerfectFifth,
138                Interval::MinorSeventh,
139            ],
140        }
141    }
142}
143
144impl HasDescription for ScaleKind {
145    fn description(&self) -> &'static str {
146        match self {
147            ScaleKind::Major => "major scale, ionian mode parent",
148            ScaleKind::NaturalMinor => "natural minor scale, aeolian mode parent",
149            ScaleKind::HarmonicMinor => "harmonic minor scale, raised seventh degree",
150            ScaleKind::MelodicMinor => "melodic minor scale, raised sixth and seventh degrees",
151            ScaleKind::WholeTone => "whole tone scale, all whole steps",
152            ScaleKind::Chromatic => "chromatic scale, all twelve semitones",
153            ScaleKind::DiminishedWholeHalf => "diminished scale, whole-half (W-H) pattern, fully diminished 7th chord parent",
154            ScaleKind::DiminishedHalfWhole => "diminished scale, half-whole (H-W) pattern, dominant 7♭9 (flat 9) chord parent",
155            ScaleKind::MajorPentatonic => "major pentatonic scale, five-note major scale without 4th and 7th",
156            ScaleKind::MinorPentatonic => "minor pentatonic scale, five-note minor scale without 2nd and 6th",
157            ScaleKind::Blues => "blues scale, minor pentatonic with added ♯4 (blue note)",
158        }
159    }
160}
161
162impl HasStaticName for ScaleKind {
163    fn static_name(&self) -> &'static str {
164        match self {
165            ScaleKind::Major => "major",
166            ScaleKind::NaturalMinor => "natural minor",
167            ScaleKind::HarmonicMinor => "harmonic minor",
168            ScaleKind::MelodicMinor => "melodic minor",
169            ScaleKind::WholeTone => "whole tone",
170            ScaleKind::Chromatic => "chromatic",
171            ScaleKind::DiminishedWholeHalf => "diminished (whole-half)",
172            ScaleKind::DiminishedHalfWhole => "diminished (half-whole)",
173            ScaleKind::MajorPentatonic => "major pentatonic",
174            ScaleKind::MinorPentatonic => "minor pentatonic",
175            ScaleKind::Blues => "blues",
176        }
177    }
178}
179
180impl HasName for ScaleKind {
181    fn name(&self) -> String {
182        self.static_name().to_owned()
183    }
184}
185
186impl ScaleKind {
187    /// Returns the ASCII name of the scale (using 'b', '#', 'nat' instead of Unicode symbols).
188    pub fn ascii_name(&self) -> &'static str {
189        match self {
190            ScaleKind::Major => "major",
191            ScaleKind::NaturalMinor => "natural minor",
192            ScaleKind::HarmonicMinor => "harmonic minor",
193            ScaleKind::MelodicMinor => "melodic minor",
194            ScaleKind::WholeTone => "whole tone",
195            ScaleKind::Chromatic => "chromatic",
196            ScaleKind::DiminishedWholeHalf => "diminished whole-half",
197            ScaleKind::DiminishedHalfWhole => "diminished half-whole",
198            ScaleKind::MajorPentatonic => "major pentatonic",
199            ScaleKind::MinorPentatonic => "minor pentatonic",
200            ScaleKind::Blues => "blues",
201        }
202    }
203}
204
205// Tests.
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use pretty_assertions::assert_eq;
211
212    #[test]
213    fn test_scale_intervals() {
214        // Major scale: W-W-H-W-W-W-H
215        assert_eq!(ScaleKind::Major.intervals().len(), 7);
216        assert_eq!(
217            ScaleKind::Major.intervals(),
218            &[
219                Interval::PerfectUnison,
220                Interval::MajorSecond,
221                Interval::MajorThird,
222                Interval::PerfectFourth,
223                Interval::PerfectFifth,
224                Interval::MajorSixth,
225                Interval::MajorSeventh,
226            ]
227        );
228
229        // Natural minor: W-H-W-W-H-W-W
230        assert_eq!(ScaleKind::NaturalMinor.intervals().len(), 7);
231        assert_eq!(
232            ScaleKind::NaturalMinor.intervals(),
233            &[
234                Interval::PerfectUnison,
235                Interval::MajorSecond,
236                Interval::MinorThird,
237                Interval::PerfectFourth,
238                Interval::PerfectFifth,
239                Interval::MinorSixth,
240                Interval::MinorSeventh,
241            ]
242        );
243
244        // Harmonic minor: W-H-W-W-H-W+H-H
245        assert_eq!(ScaleKind::HarmonicMinor.intervals().len(), 7);
246
247        // Whole tone: W-W-W-W-W-W
248        assert_eq!(ScaleKind::WholeTone.intervals().len(), 6);
249
250        // Chromatic: all 12 semitones
251        assert_eq!(ScaleKind::Chromatic.intervals().len(), 12);
252
253        // Diminished scales: 8 notes
254        assert_eq!(ScaleKind::DiminishedWholeHalf.intervals().len(), 8);
255        assert_eq!(ScaleKind::DiminishedHalfWhole.intervals().len(), 8);
256
257        // Pentatonic scales: 5 notes
258        assert_eq!(ScaleKind::MajorPentatonic.intervals().len(), 5);
259        assert_eq!(
260            ScaleKind::MajorPentatonic.intervals(),
261            &[Interval::PerfectUnison, Interval::MajorSecond, Interval::MajorThird, Interval::PerfectFifth, Interval::MajorSixth,]
262        );
263
264        assert_eq!(ScaleKind::MinorPentatonic.intervals().len(), 5);
265        assert_eq!(
266            ScaleKind::MinorPentatonic.intervals(),
267            &[Interval::PerfectUnison, Interval::MinorThird, Interval::PerfectFourth, Interval::PerfectFifth, Interval::MinorSeventh,]
268        );
269
270        // Blues scale: 6 notes (minor pentatonic + ♯4)
271        assert_eq!(ScaleKind::Blues.intervals().len(), 6);
272        assert_eq!(
273            ScaleKind::Blues.intervals(),
274            &[
275                Interval::PerfectUnison,
276                Interval::MinorThird,
277                Interval::PerfectFourth,
278                Interval::AugmentedFourth,
279                Interval::PerfectFifth,
280                Interval::MinorSeventh,
281            ]
282        );
283    }
284
285    #[test]
286    fn test_scale_names() {
287        assert_eq!(ScaleKind::Major.static_name(), "major");
288        assert_eq!(ScaleKind::NaturalMinor.static_name(), "natural minor");
289        assert_eq!(ScaleKind::HarmonicMinor.static_name(), "harmonic minor");
290        assert_eq!(ScaleKind::MelodicMinor.static_name(), "melodic minor");
291        assert_eq!(ScaleKind::WholeTone.static_name(), "whole tone");
292        assert_eq!(ScaleKind::MajorPentatonic.static_name(), "major pentatonic");
293        assert_eq!(ScaleKind::MinorPentatonic.static_name(), "minor pentatonic");
294        assert_eq!(ScaleKind::Blues.static_name(), "blues");
295    }
296
297    #[test]
298    fn test_scale_descriptions() {
299        assert_eq!(ScaleKind::Major.description(), "major scale, ionian mode parent");
300        assert_eq!(ScaleKind::NaturalMinor.description(), "natural minor scale, aeolian mode parent");
301        assert_eq!(ScaleKind::MajorPentatonic.description(), "major pentatonic scale, five-note major scale without 4th and 7th");
302        assert_eq!(ScaleKind::MinorPentatonic.description(), "minor pentatonic scale, five-note minor scale without 2nd and 6th");
303        assert_eq!(ScaleKind::Blues.description(), "blues scale, minor pentatonic with added ♯4 (blue note)");
304    }
305}