notation_core/
scale.rs

1use serde::{Deserialize, Serialize};
2use std::fmt::Display;
3
4use crate::prelude::{Chord, Key, Note, Octave, Pitch, Semitones, Syllable};
5use crate::tone::Tone;
6
7// https://hellomusictheory.com/learn/music-scales-beginners-guide/
8#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
9pub enum Scale {
10    Ionian,
11    Dorian,
12    Phrygian,
13    Lydian,
14    Mixolydian,
15    Aeolian,
16    Locrian,
17}
18impl Display for Scale {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        write!(f, "{:?}", self)
21    }
22}
23impl Default for Scale {
24    fn default() -> Self {
25        Self::Ionian
26    }
27}
28impl Scale {
29    #[allow(non_upper_case_globals)]
30    pub const Major: Scale = Scale::Ionian;
31    #[allow(non_upper_case_globals)]
32    pub const Minor: Scale = Scale::Aeolian;
33    pub const ALL: [ Scale; 7 ] = [
34        Scale::Ionian, Scale::Dorian, Scale::Phrygian, Scale::Lydian, Scale::Mixolydian, Scale::Aeolian, Scale::Locrian,
35    ];
36
37    pub fn to_ident(&self) -> String {
38        format!("{}", self)
39    }
40    pub fn from_ident(ident: &str) -> Self {
41        match ident {
42            "Major" => Self::Major,
43            "Minor" => Self::Minor,
44            "Ionian" => Self::Ionian,
45            "Dorian" => Self::Dorian,
46            "Phrygian" => Self::Phrygian,
47            "Lydian" => Self::Lydian,
48            "Mixolydian" => Self::Mixolydian,
49            "Aeolian" => Self::Aeolian,
50            "Locrian" => Self::Locrian,
51            _ => Self::default(),
52        }
53    }
54    pub fn get_syllables(&self) -> Vec<Syllable> {
55        match self {
56            Scale::Ionian => vec![
57                Syllable::Do,
58                Syllable::Re,
59                Syllable::Mi,
60                Syllable::Fa,
61                Syllable::So,
62                Syllable::La,
63                Syllable::Ti,
64            ],
65            Scale::Dorian => vec![
66                Syllable::Re,
67                Syllable::Mi,
68                Syllable::Fa,
69                Syllable::So,
70                Syllable::La,
71                Syllable::Ti,
72                Syllable::Do,
73            ],
74            Scale::Phrygian => vec![
75                Syllable::Mi,
76                Syllable::Fa,
77                Syllable::So,
78                Syllable::La,
79                Syllable::Ti,
80                Syllable::Do,
81                Syllable::Re,
82            ],
83            Scale::Lydian => vec![
84                Syllable::Fa,
85                Syllable::So,
86                Syllable::La,
87                Syllable::Ti,
88                Syllable::Do,
89                Syllable::Re,
90                Syllable::Mi,
91            ],
92            Scale::Mixolydian => vec![
93                Syllable::So,
94                Syllable::La,
95                Syllable::Ti,
96                Syllable::Do,
97                Syllable::Re,
98                Syllable::Mi,
99                Syllable::Fa,
100            ],
101            Scale::Aeolian => vec![
102                Syllable::La,
103                Syllable::Ti,
104                Syllable::Do,
105                Syllable::Re,
106                Syllable::Mi,
107                Syllable::Fa,
108                Syllable::So,
109            ],
110            Scale::Locrian => vec![
111                Syllable::Ti,
112                Syllable::Do,
113                Syllable::Re,
114                Syllable::Mi,
115                Syllable::Fa,
116                Syllable::So,
117                Syllable::La,
118            ],
119        }
120    }
121    pub fn calc_key_index(&self, key: Key) -> usize {
122        let offset = Semitones::from(key) - Semitones::from(self.get_keys()[0]);
123        let offset_val = if offset.0 >= 0 { offset.0 % 12 } else { offset.0 % 12 + 12 };
124        match offset_val {
125            0 => 0,
126            1 => 7,
127            2 => 2,
128            3 => 9,
129            4 => 4,
130            5 => 11,
131            6 => 6,
132            7 => 1,
133            8 => 8,
134            9 => 3,
135            10 => 10,
136            11 => 5,
137            _ => 0,
138        }
139    }
140    //https://www.hooktheory.com/cheat-sheet
141    pub fn get_keys(&self) -> [Key; 12] {
142        match self {
143            Scale::Ionian => [
144                Key::C,
145                Key::G,
146                Key::D,
147                Key::A,
148                Key::E,
149                Key::B,
150                Key::F_SHARP,
151                Key::D_FLAT,
152                Key::A_FLAT,
153                Key::E_FLAT,
154                Key::B_FLAT,
155                Key::F,
156            ],
157            Scale::Dorian => [
158                Key::D,
159                Key::A,
160                Key::E,
161                Key::B,
162                Key::F_SHARP,
163                Key::C_SHARP,
164                Key::G_SHARP,
165                Key::E_FLAT,
166                Key::B_FLAT,
167                Key::F,
168                Key::C,
169                Key::G,
170            ],
171            Scale::Phrygian => [
172                Key::E,
173                Key::B,
174                Key::F_SHARP,
175                Key::C_SHARP,
176                Key::G_SHARP,
177                Key::D_SHARP,
178                Key::A_SHARP,
179                Key::F,
180                Key::C,
181                Key::G,
182                Key::D,
183                Key::A,
184            ],
185            Scale::Lydian => [
186                Key::F,
187                Key::C,
188                Key::G,
189                Key::D,
190                Key::A,
191                Key::E,
192                Key::B,
193                Key::G_FLAT,
194                Key::D_FLAT,
195                Key::A_FLAT,
196                Key::E_FLAT,
197                Key::B_FLAT,
198            ],
199            Scale::Mixolydian => [
200                Key::G,
201                Key::D,
202                Key::A,
203                Key::E,
204                Key::B,
205                Key::F_SHARP,
206                Key::C_SHARP,
207                Key::A_FLAT,
208                Key::E_FLAT,
209                Key::B_FLAT,
210                Key::F,
211                Key::C,
212            ],
213            Scale::Aeolian => [
214                Key::A,
215                Key::E,
216                Key::B,
217                Key::F_SHARP,
218                Key::C_SHARP,
219                Key::G_SHARP,
220                Key::D_SHARP,
221                Key::B_FLAT,
222                Key::F,
223                Key::C,
224                Key::G,
225                Key::D,
226            ],
227            Scale::Locrian => [
228                Key::B,
229                Key::F_SHARP,
230                Key::C_SHARP,
231                Key::G_SHARP,
232                Key::D_SHARP,
233                Key::A_SHARP,
234                Key::E_SHARP,
235                Key::C,
236                Key::G,
237                Key::D,
238                Key::A,
239                Key::E,
240            ],
241        }
242    }
243}
244
245impl Scale {
246    pub fn calc_do_offset(&self) -> i8 {
247        match self {
248            Scale::Ionian => 0,
249            Scale::Dorian => -2,
250            Scale::Phrygian => -4,
251            Scale::Lydian => -5,
252            Scale::Mixolydian => 5,
253            Scale::Aeolian => 3,
254            Scale::Locrian => 1,
255        }
256    }
257    pub fn calc_do_semitones(&self, key: &Key) -> Semitones {
258        let semitones = Semitones::from(*key).0 + self.calc_do_offset();
259        Semitones(semitones)
260    }
261    pub fn calc_root_syllable(&self) -> Syllable {
262        Semitones(0 - self.calc_do_offset()).into()
263    }
264    pub fn calc_syllable_for_sort(&self, syllable: &Syllable) -> Syllable {
265        let semitones = Semitones::from(*syllable).0 + self.calc_do_offset();
266        Semitones(semitones).into()
267    }
268    pub fn calc_chord_for_sort(&self, chord: &Chord) -> Chord {
269        if *self == Scale::Ionian {
270            return chord.clone();
271        }
272        let root = self.calc_syllable_for_sort(&chord.root);
273        Chord {
274            root,
275            intervals: chord.intervals.clone(),
276            bass: chord.bass.clone(),
277        }
278    }
279    pub fn calc_syllable(&self, key: &Key, pitch: &Pitch) -> Syllable {
280        (Semitones::from(*pitch) - self.calc_do_semitones(key)).into()
281    }
282    pub fn calc_pitch(&self, key: &Key, syllable: &Syllable) -> Pitch {
283        let key_index = self.calc_key_index(key.clone());
284        if let Some(keys) = match syllable {
285            Syllable::Do => Some(Scale::Ionian.get_keys()),
286            Syllable::Re => Some(Scale::Dorian.get_keys()),
287            Syllable::Mi => Some(Scale::Phrygian.get_keys()),
288            Syllable::Fa => Some(Scale::Lydian.get_keys()),
289            Syllable::So => Some(Scale::Mixolydian.get_keys()),
290            Syllable::La => Some(Scale::Aeolian.get_keys()),
291            Syllable::Ti => Some(Scale::Locrian.get_keys()),
292            _ => None,
293        } {
294            keys[key_index].into()
295        } else {
296            (Semitones::from(*syllable) + self.calc_do_semitones(key)).into()
297        }
298    }
299    pub fn calc_note_from_pitch(&self, key: &Key, pitch: &Pitch, octave: &Octave) -> Note {
300        let syllable = self.calc_syllable(key, pitch);
301        Note::new(*octave, *pitch, syllable)
302    }
303    pub fn calc_note_from_syllable(&self, key: &Key, syllable: &Syllable, octave: &Octave) -> Note {
304        let semitones = Semitones::from(*octave) + self.calc_do_semitones(key) + Semitones::from(*syllable);
305        let pitch = self.calc_pitch(key, syllable);
306        Note::new(Octave::from(semitones), pitch, *syllable)
307    }
308    pub fn calc_note_from_semitones(&self, key: &Key, semitones: Semitones) -> Note {
309        let (pitch, octave) = semitones.as_pitch_octave();
310        self.calc_note_from_pitch(key, &pitch, &octave)
311    }
312    pub fn calc_click_note(&self, key: &Key, octave: &Octave, syllable: &Syllable) -> Note {
313        let pitch = self.calc_pitch(key, syllable);
314        Note::new(*octave, pitch, *syllable)
315    }
316    pub fn calc_click_tone(&self, key: &Key, octave: &Octave, syllable: &Syllable) -> Tone {
317        Tone::Single(self.calc_click_note(key, octave, syllable))
318    }
319}