1use super::notation::KeySignature;
2use super::pitch::Pitch;
3use serde::{Deserialize, Serialize};
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" {
63 ScaleKind::NaturalMinor
64 } else {
65 ScaleKind::Major
66 };
67 Self { root, kind }
68 }
69
70 pub fn pitches(&self) -> Vec<Pitch> {
72 let root_midi = self.root.to_midi() as i32;
73 self.kind
74 .intervals()
75 .iter()
76 .map(|&interval| {
77 let midi = (root_midi + interval as i32).clamp(0, 127) as u8;
78 Pitch::from_midi(midi, self.root.alter < 0)
79 })
80 .collect()
81 }
82
83 pub fn contains(&self, pitch: &Pitch) -> bool {
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().contains(&interval)
89 }
90
91 pub fn degree(&self, pitch: &Pitch) -> Option<usize> {
93 let root_class = self.root.to_midi() % 12;
94 let pitch_class = pitch.to_midi() % 12;
95 let interval = (pitch_class as i32 - root_class as i32).rem_euclid(12) as u8;
96 self.kind
97 .intervals()
98 .iter()
99 .position(|&i| i == interval)
100 .map(|pos| pos + 1)
101 }
102
103 pub fn transpose(&self, semitones: i8) -> Scale {
105 let new_midi = (self.root.to_midi() as i32 + semitones as i32).clamp(0, 127) as u8;
106 Scale {
107 root: Pitch::from_midi(new_midi, self.root.alter < 0),
108 kind: self.kind.clone(),
109 }
110 }
111
112 pub fn best_fit(pitches: &[Pitch]) -> Option<Scale> {
119 if pitches.is_empty() {
120 return None;
121 }
122
123 const CANDIDATES: &[ScaleKind] = &[
124 ScaleKind::Major,
125 ScaleKind::NaturalMinor,
126 ScaleKind::HarmonicMinor,
127 ScaleKind::MelodicMinor,
128 ScaleKind::Dorian,
129 ScaleKind::Phrygian,
130 ScaleKind::Lydian,
131 ScaleKind::Mixolydian,
132 ScaleKind::Aeolian,
133 ScaleKind::Locrian,
134 ScaleKind::MajorPentatonic,
135 ScaleKind::MinorPentatonic,
136 ScaleKind::Blues,
137 ScaleKind::WholeTone,
138 ];
139
140 let mut best_scale: Option<Scale> = None;
141 let mut best_covered: usize = 0;
142 let mut best_degrees: usize = 0;
143
144 for root_pc in 0u8..12 {
145 let root = Pitch::from_midi(60 + root_pc, false);
146 for kind in CANDIDATES {
147 let scale = Scale::new(root.clone(), kind.clone());
148 let covered = pitches.iter().filter(|p| scale.contains(p)).count();
149 let degrees = kind.intervals().len();
150 if covered > best_covered || (covered == best_covered && degrees > best_degrees) {
151 best_covered = covered;
152 best_degrees = degrees;
153 best_scale = Some(scale);
154 }
155 }
156 }
157
158 best_scale
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::super::pitch::Step;
165 use super::*;
166
167 fn c4() -> Pitch {
168 Pitch::new(Step::C, 4)
169 }
170 fn d4() -> Pitch {
171 Pitch::new(Step::D, 4)
172 }
173 fn g4() -> Pitch {
174 Pitch::new(Step::G, 4)
175 }
176
177 #[test]
178 fn c_major_pitches() {
179 let scale = Scale::new(c4(), ScaleKind::Major);
180 let pitches = scale.pitches();
181 assert_eq!(pitches.len(), 7);
182 assert_eq!(pitches[0].step, Step::C);
183 assert_eq!(pitches[2].step, Step::E);
184 assert_eq!(pitches[4].step, Step::G);
185 assert_eq!(pitches[6].step, Step::B);
186 }
187
188 #[test]
189 fn c_major_contains() {
190 let scale = Scale::new(c4(), ScaleKind::Major);
191 assert!(scale.contains(&c4()));
192 assert!(scale.contains(&g4()));
193 assert!(!scale.contains(&Pitch::with_alter(Step::F, 4, 1))); }
195
196 #[test]
197 fn c_major_degree() {
198 let scale = Scale::new(c4(), ScaleKind::Major);
199 assert_eq!(scale.degree(&c4()), Some(1));
200 assert_eq!(scale.degree(&d4()), Some(2));
201 assert_eq!(scale.degree(&g4()), Some(5));
202 assert_eq!(scale.degree(&Pitch::with_alter(Step::F, 4, 1)), None);
203 }
204
205 #[test]
206 fn c_major_transpose_up_fifth() {
207 let scale = Scale::new(c4(), ScaleKind::Major);
208 let g_major = scale.transpose(7);
209 assert_eq!(g_major.root.step, Step::G);
210 assert_eq!(g_major.kind, ScaleKind::Major);
211 }
212
213 #[test]
214 fn a_natural_minor_pitches() {
215 let a4 = Pitch::new(Step::A, 4);
216 let scale = Scale::new(a4, ScaleKind::NaturalMinor);
217 let pitches = scale.pitches();
218 assert_eq!(pitches.len(), 7);
219 assert_eq!(pitches[0].step, Step::A);
220 assert_eq!(pitches[2].step, Step::C);
221 }
222
223 #[test]
224 fn blues_scale_has_six_degrees() {
225 let scale = Scale::new(c4(), ScaleKind::Blues);
226 assert_eq!(scale.pitches().len(), 6);
227 }
228
229 #[test]
230 fn chromatic_has_twelve_degrees() {
231 let scale = Scale::new(c4(), ScaleKind::Chromatic);
232 assert_eq!(scale.pitches().len(), 12);
233 }
234
235 #[test]
236 fn from_key_g_major() {
237 let key = KeySignature {
238 fifths: 1,
239 mode: "major".to_string(),
240 };
241 let scale = Scale::from_key(&key);
242 assert_eq!(scale.root.step, Step::G);
243 assert_eq!(scale.kind, ScaleKind::Major);
244 }
245
246 #[test]
247 fn from_key_a_minor() {
248 let key = KeySignature {
249 fifths: 0,
250 mode: "minor".to_string(),
251 };
252 let scale = Scale::from_key(&key);
253 assert_eq!(scale.root.step, Step::A);
254 assert_eq!(scale.kind, ScaleKind::NaturalMinor);
255 }
256
257 #[test]
258 fn best_fit_c_major() {
259 let pitches = [
260 Pitch::new(Step::C, 4),
261 Pitch::new(Step::D, 4),
262 Pitch::new(Step::E, 4),
263 Pitch::new(Step::F, 4),
264 Pitch::new(Step::G, 4),
265 Pitch::new(Step::A, 4),
266 Pitch::new(Step::B, 4),
267 ];
268 let scale = Scale::best_fit(&pitches).unwrap();
269 assert_eq!(scale.root.step, Step::C);
270 assert_eq!(scale.kind, ScaleKind::Major);
271 }
272
273 #[test]
274 fn best_fit_c_blues() {
275 let pitches = [
277 Pitch::new(Step::C, 4),
278 Pitch::with_alter(Step::E, 4, -1), Pitch::new(Step::F, 4),
280 Pitch::with_alter(Step::G, 4, -1), Pitch::new(Step::G, 4),
282 Pitch::with_alter(Step::B, 4, -1), ];
284 let scale = Scale::best_fit(&pitches).unwrap();
285 assert_eq!(scale.root.step, Step::C);
286 assert_eq!(scale.kind, ScaleKind::Blues);
287 }
288
289 #[test]
290 fn best_fit_empty_returns_none() {
291 assert!(Scale::best_fit(&[]).is_none());
292 }
293}