Skip to main content

klib/core/
mode_kind.rs

1//! A module for working with mode kinds.
2
3use crate::core::{
4    base::{HasDescription, HasName, HasStaticName},
5    interval::{HasIntervals, Interval},
6    scale_kind::ScaleKind,
7};
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12// Enum.
13
14/// An enum representing a mode kind (type of mode).
15///
16/// Each mode kind has an **explicit** list of intervals that define the mode.
17/// These intervals are NOT derived by rotation - they are the authoritative definition.
18/// Parent scale information is included for documentation purposes only.
19#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug, Ord, PartialOrd)]
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21#[repr(u8)]
22pub enum ModeKind {
23    // Major scale modes (diatonic)
24    /// Ionian mode (1st mode of major scale).
25    Ionian,
26    /// Dorian mode (2nd mode of major scale).
27    Dorian,
28    /// Phrygian mode (3rd mode of major scale).
29    Phrygian,
30    /// Lydian mode (4th mode of major scale).
31    Lydian,
32    /// Mixolydian mode (5th mode of major scale).
33    Mixolydian,
34    /// Aeolian mode (6th mode of major scale).
35    Aeolian,
36    /// Locrian mode (7th mode of major scale).
37    Locrian,
38
39    // Harmonic minor modes
40    /// Locrian ♮6 mode (2nd mode of harmonic minor).
41    LocrianNatural6,
42    /// Ionian ♯5 mode (3rd mode of harmonic minor).
43    IonianSharp5,
44    /// Dorian ♯4 mode (4th mode of harmonic minor).
45    DorianSharp4,
46    /// Phrygian Dominant mode (5th mode of harmonic minor).
47    PhrygianDominant,
48    /// Lydian ♯2 mode (6th mode of harmonic minor).
49    LydianSharp2,
50    /// Ultralocrian mode (7th mode of harmonic minor).
51    Ultralocrian,
52
53    // Melodic minor modes
54    /// Dorian ♭2 mode (2nd mode of melodic minor).
55    DorianFlat2,
56    /// Lydian Augmented mode (3rd mode of melodic minor).
57    LydianAugmented,
58    /// Lydian Dominant mode (4th mode of melodic minor).
59    LydianDominant,
60    /// Mixolydian ♭6 mode (5th mode of melodic minor).
61    MixolydianFlat6,
62    /// Locrian ♮2 mode (6th mode of melodic minor).
63    LocrianNatural2,
64    /// Altered / Super Locrian mode (7th mode of melodic minor).
65    Altered,
66}
67
68// Impls.
69
70impl ModeKind {
71    /// Returns the parent scale kind for this mode (for documentation only).
72    ///
73    /// This is metadata - the intervals are NOT derived from the parent.
74    pub fn parent_scale(&self) -> ScaleKind {
75        match self {
76            ModeKind::Ionian | ModeKind::Dorian | ModeKind::Phrygian | ModeKind::Lydian | ModeKind::Mixolydian | ModeKind::Aeolian | ModeKind::Locrian => ScaleKind::Major,
77
78            ModeKind::LocrianNatural6 | ModeKind::IonianSharp5 | ModeKind::DorianSharp4 | ModeKind::PhrygianDominant | ModeKind::LydianSharp2 | ModeKind::Ultralocrian => ScaleKind::HarmonicMinor,
79
80            ModeKind::DorianFlat2 | ModeKind::LydianAugmented | ModeKind::LydianDominant | ModeKind::MixolydianFlat6 | ModeKind::LocrianNatural2 | ModeKind::Altered => ScaleKind::MelodicMinor,
81        }
82    }
83
84    /// Returns the degree of the parent scale that this mode starts on (for documentation only).
85    ///
86    /// This is metadata - the intervals are NOT derived from the parent.
87    pub fn parent_degree(&self) -> u8 {
88        match self {
89            // Major scale modes
90            ModeKind::Ionian => 1,
91            ModeKind::Dorian => 2,
92            ModeKind::Phrygian => 3,
93            ModeKind::Lydian => 4,
94            ModeKind::Mixolydian => 5,
95            ModeKind::Aeolian => 6,
96            ModeKind::Locrian => 7,
97
98            // Harmonic minor modes
99            ModeKind::LocrianNatural6 => 2,
100            ModeKind::IonianSharp5 => 3,
101            ModeKind::DorianSharp4 => 4,
102            ModeKind::PhrygianDominant => 5,
103            ModeKind::LydianSharp2 => 6,
104            ModeKind::Ultralocrian => 7,
105
106            // Melodic minor modes
107            ModeKind::DorianFlat2 => 2,
108            ModeKind::LydianAugmented => 3,
109            ModeKind::LydianDominant => 4,
110            ModeKind::MixolydianFlat6 => 5,
111            ModeKind::LocrianNatural2 => 6,
112            ModeKind::Altered => 7,
113        }
114    }
115}
116
117impl HasIntervals for ModeKind {
118    fn intervals(&self) -> &'static [Interval] {
119        match self {
120            // MAJOR SCALE MODES
121
122            // Ionian: W-W-H-W-W-W-H (same as major scale)
123            ModeKind::Ionian => &[
124                Interval::PerfectUnison,
125                Interval::MajorSecond,
126                Interval::MajorThird,
127                Interval::PerfectFourth,
128                Interval::PerfectFifth,
129                Interval::MajorSixth,
130                Interval::MajorSeventh,
131            ],
132            // Dorian: W-H-W-W-W-H-W (minor scale with raised 6th)
133            ModeKind::Dorian => &[
134                Interval::PerfectUnison,
135                Interval::MajorSecond,
136                Interval::MinorThird,
137                Interval::PerfectFourth,
138                Interval::PerfectFifth,
139                Interval::MajorSixth,
140                Interval::MinorSeventh,
141            ],
142            // Phrygian: H-W-W-W-H-W-W (minor scale with lowered 2nd)
143            ModeKind::Phrygian => &[
144                Interval::PerfectUnison,
145                Interval::MinorSecond,
146                Interval::MinorThird,
147                Interval::PerfectFourth,
148                Interval::PerfectFifth,
149                Interval::MinorSixth,
150                Interval::MinorSeventh,
151            ],
152            // Lydian: W-W-W-H-W-W-H (major scale with raised 4th)
153            ModeKind::Lydian => &[
154                Interval::PerfectUnison,
155                Interval::MajorSecond,
156                Interval::MajorThird,
157                Interval::AugmentedFourth,
158                Interval::PerfectFifth,
159                Interval::MajorSixth,
160                Interval::MajorSeventh,
161            ],
162            // Mixolydian: W-W-H-W-W-H-W (major scale with lowered 7th)
163            ModeKind::Mixolydian => &[
164                Interval::PerfectUnison,
165                Interval::MajorSecond,
166                Interval::MajorThird,
167                Interval::PerfectFourth,
168                Interval::PerfectFifth,
169                Interval::MajorSixth,
170                Interval::MinorSeventh,
171            ],
172            // Aeolian: W-H-W-W-H-W-W (natural minor scale)
173            ModeKind::Aeolian => &[
174                Interval::PerfectUnison,
175                Interval::MajorSecond,
176                Interval::MinorThird,
177                Interval::PerfectFourth,
178                Interval::PerfectFifth,
179                Interval::MinorSixth,
180                Interval::MinorSeventh,
181            ],
182            // Locrian: H-W-W-H-W-W-W (diminished scale)
183            ModeKind::Locrian => &[
184                Interval::PerfectUnison,
185                Interval::MinorSecond,
186                Interval::MinorThird,
187                Interval::PerfectFourth,
188                Interval::DiminishedFifth,
189                Interval::MinorSixth,
190                Interval::MinorSeventh,
191            ],
192
193            // HARMONIC MINOR MODES
194
195            // Locrian ♮6: H-W-W-H-W+H-W (Locrian with natural 6th)
196            // Example: B Locrian ♮6 = B C D E F G♯ A
197            ModeKind::LocrianNatural6 => &[
198                Interval::PerfectUnison,
199                Interval::MinorSecond,
200                Interval::MinorThird,
201                Interval::PerfectFourth,
202                Interval::DiminishedFifth,
203                Interval::MajorSixth,
204                Interval::MinorSeventh,
205            ],
206            // Ionian ♯5: W-W-H-W+H-H-H (Major with augmented 5th)
207            // Example: C Ionian ♯5 = C D E F G♯ A B
208            ModeKind::IonianSharp5 => &[
209                Interval::PerfectUnison,
210                Interval::MajorSecond,
211                Interval::MajorThird,
212                Interval::PerfectFourth,
213                Interval::AugmentedFifth,
214                Interval::MajorSixth,
215                Interval::MajorSeventh,
216            ],
217            // Dorian ♯4: W-H-W+H-H-W-W (Dorian with augmented 4th)
218            // Example: D Dorian ♯4 = D E F G♯ A B C
219            ModeKind::DorianSharp4 => &[
220                Interval::PerfectUnison,
221                Interval::MajorSecond,
222                Interval::MinorThird,
223                Interval::AugmentedFourth,
224                Interval::PerfectFifth,
225                Interval::MajorSixth,
226                Interval::MinorSeventh,
227            ],
228            // Phrygian Dominant: H-W+H-H-W-H-W-W (Phrygian with major 3rd)
229            // Example: E Phrygian Dominant = E F G♯ A B C D
230            ModeKind::PhrygianDominant => &[
231                Interval::PerfectUnison,
232                Interval::MinorSecond,
233                Interval::MajorThird,
234                Interval::PerfectFourth,
235                Interval::PerfectFifth,
236                Interval::MinorSixth,
237                Interval::MinorSeventh,
238            ],
239            // Lydian ♯2: W+H-H-W-H-W-W-H (Lydian with augmented 2nd)
240            // Example: F Lydian ♯2 = F G♯ A B C D E
241            ModeKind::LydianSharp2 => &[
242                Interval::PerfectUnison,
243                Interval::AugmentedSecond,
244                Interval::MajorThird,
245                Interval::AugmentedFourth,
246                Interval::PerfectFifth,
247                Interval::MajorSixth,
248                Interval::MajorSeventh,
249            ],
250            // Ultralocrian: H-W-H-W-W-W-W (Locrian ♭♭7)
251            // Example: G♯ Ultralocrian = G♯ A B C D E F
252            ModeKind::Ultralocrian => &[
253                Interval::PerfectUnison,
254                Interval::MinorSecond,
255                Interval::MinorThird,
256                Interval::DiminishedFourth,
257                Interval::DiminishedFifth,
258                Interval::MinorSixth,
259                Interval::DiminishedSeventh,
260            ],
261
262            // MELODIC MINOR MODES
263
264            // Dorian ♭2 (Phrygian ♮6): H-W-W-W-W-W-H (Dorian with flat 2nd)
265            // Example: B Dorian ♭2 = B C D E F♯ G♯ A
266            ModeKind::DorianFlat2 => &[
267                Interval::PerfectUnison,
268                Interval::MinorSecond,
269                Interval::MinorThird,
270                Interval::PerfectFourth,
271                Interval::PerfectFifth,
272                Interval::MajorSixth,
273                Interval::MajorSeventh,
274            ],
275            // Lydian Augmented: W-W-W-W-H-W-H (Lydian with augmented 5th)
276            // Example: C Lydian Augmented = C D E F♯ G♯ A B
277            ModeKind::LydianAugmented => &[
278                Interval::PerfectUnison,
279                Interval::MajorSecond,
280                Interval::MajorThird,
281                Interval::AugmentedFourth,
282                Interval::AugmentedFifth,
283                Interval::MajorSixth,
284                Interval::MajorSeventh,
285            ],
286            // Lydian Dominant (Acoustic): W-W-W-H-W-H-W (Mixolydian with sharp 4th)
287            // Example: D Lydian Dominant = D E F♯ G♯ A B C
288            ModeKind::LydianDominant => &[
289                Interval::PerfectUnison,
290                Interval::MajorSecond,
291                Interval::MajorThird,
292                Interval::AugmentedFourth,
293                Interval::PerfectFifth,
294                Interval::MajorSixth,
295                Interval::MinorSeventh,
296            ],
297            // Mixolydian ♭6 (Aeolian Dominant): W-W-H-W-H-W-W (Mixolydian with flat 6th)
298            // Example: E Mixolydian ♭6 = E F♯ G♯ A B C D
299            ModeKind::MixolydianFlat6 => &[
300                Interval::PerfectUnison,
301                Interval::MajorSecond,
302                Interval::MajorThird,
303                Interval::PerfectFourth,
304                Interval::PerfectFifth,
305                Interval::MinorSixth,
306                Interval::MinorSeventh,
307            ],
308            // Locrian ♮2 (Half-diminished): W-H-W-H-W-W-W (Locrian with natural 2nd)
309            // Example: F♯ Locrian ♮2 = F♯ G♯ A B C D E
310            ModeKind::LocrianNatural2 => &[
311                Interval::PerfectUnison,
312                Interval::MajorSecond,
313                Interval::MinorThird,
314                Interval::PerfectFourth,
315                Interval::DiminishedFifth,
316                Interval::MinorSixth,
317                Interval::MinorSeventh,
318            ],
319            // Altered (Super Locrian): H-W-H-W-W-W-W (All alterations)
320            // Example: G♯ Altered = G♯ A B C D E F♯
321            //
322            // Theoretical interval spelling from the melodic minor parent would be:
323            //   1, m2, A2, M3, dim5, dim6, m7
324            //
325            // However, we intentionally use enharmonic equivalents here:
326            //   A2  → m3  (AugmentedSecond  → MinorThird)
327            //   M3  → dim4 (MajorThird      → DiminishedFourth)
328            //   dim6 → m6  (DiminishedSixth → MinorSixth)
329            //
330            // This keeps the internal interval representation free of double accidentals
331            // and avoids reusing the same letter name multiple times when combined with
332            // validate_spelling(), which checks for unique letter names. In other words,
333            // these spellings are chosen for practical spelling/validation reasons while
334            // remaining pitch‑equivalent to the theoretical Altered mode.
335            ModeKind::Altered => &[
336                Interval::PerfectUnison,
337                Interval::MinorSecond,
338                Interval::MinorThird,
339                Interval::DiminishedFourth,
340                Interval::DiminishedFifth,
341                Interval::MinorSixth,
342                Interval::MinorSeventh,
343            ],
344        }
345    }
346}
347
348impl HasDescription for ModeKind {
349    fn description(&self) -> &'static str {
350        match self {
351            // Major scale modes
352            ModeKind::Ionian => "ionian, 1st mode of major scale, major scale",
353            ModeKind::Dorian => "dorian, 2nd mode of major scale, minor with raised 6th",
354            ModeKind::Phrygian => "phrygian, 3rd mode of major scale, minor with lowered 2nd",
355            ModeKind::Lydian => "lydian, 4th mode of major scale, major with raised 4th",
356            ModeKind::Mixolydian => "mixolydian, 5th mode of major scale, major with lowered 7th",
357            ModeKind::Aeolian => "aeolian, 6th mode of major scale, natural minor",
358            ModeKind::Locrian => "locrian, 7th mode of major scale, diminished, half-diminished chord scale",
359
360            // Harmonic minor modes
361            ModeKind::LocrianNatural6 => "locrian ♮6, 2nd mode of harmonic minor, m7♭5(♮13) color",
362            ModeKind::IonianSharp5 => "ionian ♯5, 3rd mode of harmonic minor, augmented major",
363            ModeKind::DorianSharp4 => "dorian ♯4, 4th mode of harmonic minor, minor with lydian bite",
364            ModeKind::PhrygianDominant => "phrygian dominant, 5th mode of harmonic minor, spanish phrygian",
365            ModeKind::LydianSharp2 => "lydian ♯2, 6th mode of harmonic minor, bright + exotic",
366            ModeKind::Ultralocrian => "ultralocrian, 7th mode of harmonic minor, very unstable/dark",
367
368            // Melodic minor modes
369            ModeKind::DorianFlat2 => "dorian ♭2, 2nd mode of melodic minor, phrygian ♮6, minor with spicy ♭2",
370            ModeKind::LydianAugmented => "lydian augmented, 3rd mode of melodic minor, lydian ♯5",
371            ModeKind::LydianDominant => "lydian dominant, 4th mode of melodic minor, acoustic scale, dominant with ♯11",
372            ModeKind::MixolydianFlat6 => "mixolydian ♭6, 5th mode of melodic minor, aeolian dominant, dominant with ♭13",
373            ModeKind::LocrianNatural2 => "locrian ♮2, 6th mode of melodic minor, half-diminished ♮2",
374            ModeKind::Altered => "altered, 7th mode of melodic minor, super locrian, V7alt scale",
375        }
376    }
377}
378
379impl HasStaticName for ModeKind {
380    fn static_name(&self) -> &'static str {
381        match self {
382            // Major scale modes
383            ModeKind::Ionian => "ionian",
384            ModeKind::Dorian => "dorian",
385            ModeKind::Phrygian => "phrygian",
386            ModeKind::Lydian => "lydian",
387            ModeKind::Mixolydian => "mixolydian",
388            ModeKind::Aeolian => "aeolian",
389            ModeKind::Locrian => "locrian",
390
391            // Harmonic minor modes
392            ModeKind::LocrianNatural6 => "locrian ♮6",
393            ModeKind::IonianSharp5 => "ionian ♯5",
394            ModeKind::DorianSharp4 => "dorian ♯4",
395            ModeKind::PhrygianDominant => "phrygian dominant",
396            ModeKind::LydianSharp2 => "lydian ♯2",
397            ModeKind::Ultralocrian => "ultralocrian",
398
399            // Melodic minor modes
400            ModeKind::DorianFlat2 => "dorian ♭2",
401            ModeKind::LydianAugmented => "lydian augmented",
402            ModeKind::LydianDominant => "lydian dominant",
403            ModeKind::MixolydianFlat6 => "mixolydian ♭6",
404            ModeKind::LocrianNatural2 => "locrian ♮2",
405            ModeKind::Altered => "altered",
406        }
407    }
408}
409
410impl HasName for ModeKind {
411    fn name(&self) -> String {
412        self.static_name().to_owned()
413    }
414}
415
416impl ModeKind {
417    /// Returns the ASCII name of the mode (using 'b', '#', 'nat' instead of Unicode symbols).
418    pub fn ascii_name(&self) -> &'static str {
419        match self {
420            // Major scale modes
421            ModeKind::Ionian => "ionian",
422            ModeKind::Dorian => "dorian",
423            ModeKind::Phrygian => "phrygian",
424            ModeKind::Lydian => "lydian",
425            ModeKind::Mixolydian => "mixolydian",
426            ModeKind::Aeolian => "aeolian",
427            ModeKind::Locrian => "locrian",
428
429            // Harmonic minor modes
430            ModeKind::LocrianNatural6 => "locrian nat6",
431            ModeKind::IonianSharp5 => "ionian #5",
432            ModeKind::DorianSharp4 => "dorian #4",
433            ModeKind::PhrygianDominant => "phrygian dominant",
434            ModeKind::LydianSharp2 => "lydian #2",
435            ModeKind::Ultralocrian => "ultralocrian",
436
437            // Melodic minor modes
438            ModeKind::DorianFlat2 => "dorian b2",
439            ModeKind::LydianAugmented => "lydian augmented",
440            ModeKind::LydianDominant => "lydian dominant",
441            ModeKind::MixolydianFlat6 => "mixolydian b6",
442            ModeKind::LocrianNatural2 => "locrian nat2",
443            ModeKind::Altered => "altered",
444        }
445    }
446}
447
448// Tests.
449
450#[cfg(test)]
451mod tests {
452    use super::*;
453    use pretty_assertions::assert_eq;
454
455    #[test]
456    fn test_mode_intervals_explicit() {
457        // All modes should have 7 notes
458        assert_eq!(ModeKind::Ionian.intervals().len(), 7);
459        assert_eq!(ModeKind::Dorian.intervals().len(), 7);
460        assert_eq!(ModeKind::Phrygian.intervals().len(), 7);
461        assert_eq!(ModeKind::Lydian.intervals().len(), 7);
462        assert_eq!(ModeKind::Mixolydian.intervals().len(), 7);
463        assert_eq!(ModeKind::Aeolian.intervals().len(), 7);
464        assert_eq!(ModeKind::Locrian.intervals().len(), 7);
465
466        // Ionian = Major
467        assert_eq!(
468            ModeKind::Ionian.intervals(),
469            &[
470                Interval::PerfectUnison,
471                Interval::MajorSecond,
472                Interval::MajorThird,
473                Interval::PerfectFourth,
474                Interval::PerfectFifth,
475                Interval::MajorSixth,
476                Interval::MajorSeventh,
477            ]
478        );
479
480        // Dorian has characteristic major 6th in minor context
481        assert_eq!(
482            ModeKind::Dorian.intervals(),
483            &[
484                Interval::PerfectUnison,
485                Interval::MajorSecond,
486                Interval::MinorThird,
487                Interval::PerfectFourth,
488                Interval::PerfectFifth,
489                Interval::MajorSixth, // Characteristic raised 6th
490                Interval::MinorSeventh,
491            ]
492        );
493
494        // Phrygian has characteristic minor 2nd
495        assert_eq!(
496            ModeKind::Phrygian.intervals(),
497            &[
498                Interval::PerfectUnison,
499                Interval::MinorSecond, // Characteristic lowered 2nd
500                Interval::MinorThird,
501                Interval::PerfectFourth,
502                Interval::PerfectFifth,
503                Interval::MinorSixth,
504                Interval::MinorSeventh,
505            ]
506        );
507
508        // Lydian has characteristic augmented 4th
509        assert_eq!(
510            ModeKind::Lydian.intervals(),
511            &[
512                Interval::PerfectUnison,
513                Interval::MajorSecond,
514                Interval::MajorThird,
515                Interval::AugmentedFourth, // Characteristic raised 4th
516                Interval::PerfectFifth,
517                Interval::MajorSixth,
518                Interval::MajorSeventh,
519            ]
520        );
521
522        // Locrian has characteristic diminished 5th
523        assert_eq!(
524            ModeKind::Locrian.intervals(),
525            &[
526                Interval::PerfectUnison,
527                Interval::MinorSecond,
528                Interval::MinorThird,
529                Interval::PerfectFourth,
530                Interval::DiminishedFifth, // Characteristic diminished 5th
531                Interval::MinorSixth,
532                Interval::MinorSeventh,
533            ]
534        );
535    }
536
537    #[test]
538    fn test_mode_names() {
539        assert_eq!(ModeKind::Ionian.static_name(), "ionian");
540        assert_eq!(ModeKind::Dorian.static_name(), "dorian");
541        assert_eq!(ModeKind::Phrygian.static_name(), "phrygian");
542        assert_eq!(ModeKind::Lydian.static_name(), "lydian");
543        assert_eq!(ModeKind::Mixolydian.static_name(), "mixolydian");
544        assert_eq!(ModeKind::Aeolian.static_name(), "aeolian");
545        assert_eq!(ModeKind::Locrian.static_name(), "locrian");
546    }
547
548    #[test]
549    fn test_mode_parent_metadata() {
550        // All major scale modes have Major as parent
551        assert_eq!(ModeKind::Ionian.parent_scale(), ScaleKind::Major);
552        assert_eq!(ModeKind::Dorian.parent_scale(), ScaleKind::Major);
553        assert_eq!(ModeKind::Locrian.parent_scale(), ScaleKind::Major);
554
555        // Degrees should be 1-7
556        assert_eq!(ModeKind::Ionian.parent_degree(), 1);
557        assert_eq!(ModeKind::Dorian.parent_degree(), 2);
558        assert_eq!(ModeKind::Phrygian.parent_degree(), 3);
559        assert_eq!(ModeKind::Lydian.parent_degree(), 4);
560        assert_eq!(ModeKind::Mixolydian.parent_degree(), 5);
561        assert_eq!(ModeKind::Aeolian.parent_degree(), 6);
562        assert_eq!(ModeKind::Locrian.parent_degree(), 7);
563    }
564
565    #[test]
566    fn test_mode_descriptions() {
567        assert_eq!(ModeKind::Ionian.description(), "ionian, 1st mode of major scale, major scale");
568        assert_eq!(ModeKind::Dorian.description(), "dorian, 2nd mode of major scale, minor with raised 6th");
569        assert_eq!(ModeKind::Lydian.description(), "lydian, 4th mode of major scale, major with raised 4th");
570    }
571}