1use crate::num::u7;
2
3mod intervals {
4 pub static IONIAN: [u8; 6] = [2, 4, 5, 7, 9, 11];
5 pub static AEOLIAN: [u8; 6] = [2, 3, 5, 7, 8, 10];
6 pub static DORIAN: [u8; 6] = [2, 3, 5, 7, 9, 10];
7 pub static LYDIAN: [u8; 6] = [2, 4, 6, 7, 9, 11];
8 pub static MIXOLYDIAN: [u8; 6] = [2, 4, 5, 7, 9, 10];
9 pub static PHRYGIAN: [u8; 6] = [1, 3, 5, 7, 8, 10];
10 pub static LOCRIAN: [u8; 6] = [1, 3, 5, 6, 8, 10];
11 pub static HARMONIC_MINOR: [u8; 6] = [2, 3, 5, 7, 8, 11];
12 pub static MELODIC_MINOR: [u8; 6] = [2, 3, 5, 7, 9, 11];
13}
14
15#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum ScaleMode {
18 #[default]
19 Ionian, Aeolian, Dorian,
22 Lydian,
23 Mixolydian,
24 Phrygian,
25 Locrian,
26 HarmonicMinor,
27 MelodicMinor,
28}
29
30impl ScaleMode {
31 pub const MAJOR: Self = Self::Ionian;
32 pub const NATURAL_MINOR: Self = Self::Aeolian;
33
34 pub fn intervals(&self) -> &'static [u8] {
36 match *self {
37 Self::Ionian => &intervals::IONIAN,
38 Self::Aeolian => &intervals::AEOLIAN,
39 Self::Dorian => &intervals::DORIAN,
40 Self::Lydian => &intervals::LYDIAN,
41 Self::Mixolydian => &intervals::MIXOLYDIAN,
42 Self::Phrygian => &intervals::PHRYGIAN,
43 Self::Locrian => &intervals::LOCRIAN,
44 Self::HarmonicMinor => &intervals::HARMONIC_MINOR,
45 Self::MelodicMinor => &intervals::MELODIC_MINOR,
46 }
47 }
48}
49
50#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
52pub struct Scale {
53 tonic_pitch: u7,
54 scale_mode: ScaleMode,
55}
56
57impl Scale {
58 pub fn new(tonic_pitch: u7, scale_mode: ScaleMode) -> Self {
60 Self {
61 tonic_pitch,
62 scale_mode,
63 }
64 }
65
66 pub fn pitches(&self) -> ScalePitchesIterator<'static> {
68 let intervals = self.scale_mode.intervals();
69 ScalePitchesIterator::new(self.tonic_pitch, intervals, intervals.len() + 1)
70 }
71
72 pub fn n_pitches(&self, num_pitches: usize) -> ScalePitchesIterator<'static> {
76 ScalePitchesIterator::new(self.tonic_pitch, self.scale_mode.intervals(), num_pitches)
77 }
78}
79
80#[derive(Debug, Clone)]
88pub struct ScalePitchesIterator<'a> {
89 intervals: &'a [u8],
90 iteration: usize,
91 length: usize,
92 tonic: u7,
93}
94
95impl<'a> ScalePitchesIterator<'a> {
96 pub fn new(tonic: u7, intervals: &'a [u8], length: usize) -> Self {
104 Self {
105 intervals,
106 iteration: 0,
107 length,
108 tonic,
109 }
110 }
111}
112
113impl Iterator for ScalePitchesIterator<'_> {
114 type Item = u7;
115 fn next(&mut self) -> Option<Self::Item> {
116 if self.iteration >= self.length {
117 return None;
118 }
119
120 let pos = self.iteration % (self.intervals.len() + 1);
121 self.iteration += 1;
122
123 if self.iteration == 1 {
124 return Some(self.tonic);
125 }
126 if pos == 0 {
127 if u7::max_value().as_int() - 12 < self.tonic {
128 return None;
129 }
130 self.tonic += 12.into();
131 return Some(self.tonic);
132 }
133 let interval = self.intervals[pos - 1];
134 if u7::max_value().as_int() - interval < self.tonic {
135 return None;
136 }
137 Some(self.tonic + u7::new(interval))
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::ScalePitchesIterator;
144 use crate::*;
145
146 #[test]
147 fn scale_pitches_iterator() -> Result<()> {
148 let pitches = vec![3, 5, 7];
149 let iter = ScalePitchesIterator::new(5.into(), &pitches, 6);
150
151 let s =
152 Note::new_sequence(rhythm::CROTCHET, dynamic::MF, iter).collect::<Result<Vec<_>>>()?;
153 let expected = vec![
154 Note::new(5.into(), rhythm::CROTCHET, dynamic::MF)?,
155 Note::new(8.into(), rhythm::CROTCHET, dynamic::MF)?,
156 Note::new(10.into(), rhythm::CROTCHET, dynamic::MF)?,
157 Note::new(12.into(), rhythm::CROTCHET, dynamic::MF)?,
158 Note::new(17.into(), rhythm::CROTCHET, dynamic::MF)?,
159 Note::new(20.into(), rhythm::CROTCHET, dynamic::MF)?,
160 ];
161 assert_eq!(expected, s);
162 Ok(())
163 }
164}