1use serde::{Deserialize, Serialize};
2use super::pitch::Pitch;
3use super::notation::KeySignature;
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6pub enum ScaleKind {
7 Major,
8 NaturalMinor,
9 HarmonicMinor,
10 MelodicMinor,
11 Dorian,
12 Phrygian,
13 Lydian,
14 Mixolydian,
15 Aeolian,
16 Locrian,
17 MajorPentatonic,
18 MinorPentatonic,
19 Blues,
20 WholeTone,
21 Chromatic,
22}
23
24impl ScaleKind {
25 pub fn intervals(&self) -> &'static [u8] {
27 match self {
28 ScaleKind::Major => &[0, 2, 4, 5, 7, 9, 11],
29 ScaleKind::NaturalMinor => &[0, 2, 3, 5, 7, 8, 10],
30 ScaleKind::HarmonicMinor => &[0, 2, 3, 5, 7, 8, 11],
31 ScaleKind::MelodicMinor => &[0, 2, 3, 5, 7, 9, 11],
32 ScaleKind::Dorian => &[0, 2, 3, 5, 7, 9, 10],
33 ScaleKind::Phrygian => &[0, 1, 3, 5, 7, 8, 10],
34 ScaleKind::Lydian => &[0, 2, 4, 6, 7, 9, 11],
35 ScaleKind::Mixolydian => &[0, 2, 4, 5, 7, 9, 10],
36 ScaleKind::Aeolian => &[0, 2, 3, 5, 7, 8, 10],
37 ScaleKind::Locrian => &[0, 1, 3, 5, 6, 8, 10],
38 ScaleKind::MajorPentatonic => &[0, 2, 4, 7, 9],
39 ScaleKind::MinorPentatonic => &[0, 3, 5, 7, 10],
40 ScaleKind::Blues => &[0, 3, 5, 6, 7, 10],
41 ScaleKind::WholeTone => &[0, 2, 4, 6, 8, 10],
42 ScaleKind::Chromatic => &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
43 }
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
48pub struct Scale {
49 pub root: Pitch,
50 pub kind: ScaleKind,
51}
52
53impl Scale {
54 pub fn new(root: Pitch, kind: ScaleKind) -> Self {
55 Self { root, kind }
56 }
57
58 pub fn from_key(key: &KeySignature) -> Self {
60 let (step, alter) = key.tonic();
61 let root = Pitch::with_alter(step, 4, alter);
62 let kind = if key.mode == "minor" { ScaleKind::NaturalMinor } else { ScaleKind::Major };
63 Self { root, kind }
64 }
65
66 pub fn pitches(&self) -> Vec<Pitch> {
68 let root_midi = self.root.to_midi() as i32;
69 self.kind.intervals().iter().map(|&interval| {
70 let midi = (root_midi + interval as i32).clamp(0, 127) as u8;
71 Pitch::from_midi(midi, self.root.alter < 0)
72 }).collect()
73 }
74
75 pub fn contains(&self, pitch: &Pitch) -> bool {
77 let root_class = self.root.to_midi() % 12;
78 let pitch_class = pitch.to_midi() % 12;
79 let interval = (pitch_class as i32 - root_class as i32).rem_euclid(12) as u8;
80 self.kind.intervals().contains(&interval)
81 }
82
83 pub fn degree(&self, pitch: &Pitch) -> Option<usize> {
85 let root_class = self.root.to_midi() % 12;
86 let pitch_class = pitch.to_midi() % 12;
87 let interval = (pitch_class as i32 - root_class as i32).rem_euclid(12) as u8;
88 self.kind.intervals().iter().position(|&i| i == interval).map(|pos| pos + 1)
89 }
90
91 pub fn transpose(&self, semitones: i8) -> Scale {
93 let new_midi = (self.root.to_midi() as i32 + semitones as i32).clamp(0, 127) as u8;
94 Scale {
95 root: Pitch::from_midi(new_midi, self.root.alter < 0),
96 kind: self.kind.clone(),
97 }
98 }
99
100 pub fn best_fit(pitches: &[Pitch]) -> Option<Scale> {
107 if pitches.is_empty() {
108 return None;
109 }
110
111 const CANDIDATES: &[ScaleKind] = &[
112 ScaleKind::Major,
113 ScaleKind::NaturalMinor,
114 ScaleKind::HarmonicMinor,
115 ScaleKind::MelodicMinor,
116 ScaleKind::Dorian,
117 ScaleKind::Phrygian,
118 ScaleKind::Lydian,
119 ScaleKind::Mixolydian,
120 ScaleKind::Aeolian,
121 ScaleKind::Locrian,
122 ScaleKind::MajorPentatonic,
123 ScaleKind::MinorPentatonic,
124 ScaleKind::Blues,
125 ScaleKind::WholeTone,
126 ];
127
128 let mut best_scale: Option<Scale> = None;
129 let mut best_covered: usize = 0;
130 let mut best_degrees: usize = 0;
131
132 for root_pc in 0u8..12 {
133 let root = Pitch::from_midi(60 + root_pc, false);
134 for kind in CANDIDATES {
135 let scale = Scale::new(root.clone(), kind.clone());
136 let covered = pitches.iter().filter(|p| scale.contains(p)).count();
137 let degrees = kind.intervals().len();
138 if covered > best_covered
139 || (covered == best_covered && degrees > best_degrees)
140 {
141 best_covered = covered;
142 best_degrees = degrees;
143 best_scale = Some(scale);
144 }
145 }
146 }
147
148 best_scale
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use super::super::pitch::Step;
156
157 fn c4() -> Pitch { Pitch::new(Step::C, 4) }
158 fn d4() -> Pitch { Pitch::new(Step::D, 4) }
159 fn g4() -> Pitch { Pitch::new(Step::G, 4) }
160
161 #[test]
162 fn c_major_pitches() {
163 let scale = Scale::new(c4(), ScaleKind::Major);
164 let pitches = scale.pitches();
165 assert_eq!(pitches.len(), 7);
166 assert_eq!(pitches[0].step, Step::C);
167 assert_eq!(pitches[2].step, Step::E);
168 assert_eq!(pitches[4].step, Step::G);
169 assert_eq!(pitches[6].step, Step::B);
170 }
171
172 #[test]
173 fn c_major_contains() {
174 let scale = Scale::new(c4(), ScaleKind::Major);
175 assert!(scale.contains(&c4()));
176 assert!(scale.contains(&g4()));
177 assert!(!scale.contains(&Pitch::with_alter(Step::F, 4, 1))); }
179
180 #[test]
181 fn c_major_degree() {
182 let scale = Scale::new(c4(), ScaleKind::Major);
183 assert_eq!(scale.degree(&c4()), Some(1));
184 assert_eq!(scale.degree(&d4()), Some(2));
185 assert_eq!(scale.degree(&g4()), Some(5));
186 assert_eq!(scale.degree(&Pitch::with_alter(Step::F, 4, 1)), None);
187 }
188
189 #[test]
190 fn c_major_transpose_up_fifth() {
191 let scale = Scale::new(c4(), ScaleKind::Major);
192 let g_major = scale.transpose(7);
193 assert_eq!(g_major.root.step, Step::G);
194 assert_eq!(g_major.kind, ScaleKind::Major);
195 }
196
197 #[test]
198 fn a_natural_minor_pitches() {
199 let a4 = Pitch::new(Step::A, 4);
200 let scale = Scale::new(a4, ScaleKind::NaturalMinor);
201 let pitches = scale.pitches();
202 assert_eq!(pitches.len(), 7);
203 assert_eq!(pitches[0].step, Step::A);
204 assert_eq!(pitches[2].step, Step::C);
205 }
206
207 #[test]
208 fn blues_scale_has_six_degrees() {
209 let scale = Scale::new(c4(), ScaleKind::Blues);
210 assert_eq!(scale.pitches().len(), 6);
211 }
212
213 #[test]
214 fn chromatic_has_twelve_degrees() {
215 let scale = Scale::new(c4(), ScaleKind::Chromatic);
216 assert_eq!(scale.pitches().len(), 12);
217 }
218
219 #[test]
220 fn from_key_g_major() {
221 let key = KeySignature { fifths: 1, mode: "major".to_string() };
222 let scale = Scale::from_key(&key);
223 assert_eq!(scale.root.step, Step::G);
224 assert_eq!(scale.kind, ScaleKind::Major);
225 }
226
227 #[test]
228 fn from_key_a_minor() {
229 let key = KeySignature { fifths: 0, mode: "minor".to_string() };
230 let scale = Scale::from_key(&key);
231 assert_eq!(scale.root.step, Step::A);
232 assert_eq!(scale.kind, ScaleKind::NaturalMinor);
233 }
234
235 #[test]
236 fn best_fit_c_major() {
237 let pitches = [
238 Pitch::new(Step::C, 4), Pitch::new(Step::D, 4), Pitch::new(Step::E, 4),
239 Pitch::new(Step::F, 4), Pitch::new(Step::G, 4), Pitch::new(Step::A, 4),
240 Pitch::new(Step::B, 4),
241 ];
242 let scale = Scale::best_fit(&pitches).unwrap();
243 assert_eq!(scale.root.step, Step::C);
244 assert_eq!(scale.kind, ScaleKind::Major);
245 }
246
247 #[test]
248 fn best_fit_c_blues() {
249 let pitches = [
251 Pitch::new(Step::C, 4),
252 Pitch::with_alter(Step::E, 4, -1), Pitch::new(Step::F, 4),
254 Pitch::with_alter(Step::G, 4, -1), Pitch::new(Step::G, 4),
256 Pitch::with_alter(Step::B, 4, -1), ];
258 let scale = Scale::best_fit(&pitches).unwrap();
259 assert_eq!(scale.root.step, Step::C);
260 assert_eq!(scale.kind, ScaleKind::Blues);
261 }
262
263 #[test]
264 fn best_fit_empty_returns_none() {
265 assert!(Scale::best_fit(&[]).is_none());
266 }
267}