1use crate::error::Result;
17use crate::pitch::Pitch;
18
19use super::stepscale::StepScale;
20
21#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub enum BluesForm {
25 Pentatonic,
27 Hexatonic,
29}
30
31impl BluesForm {
32 pub const ALL: [BluesForm; 2] = [Self::Pentatonic, Self::Hexatonic];
34
35 fn steps(self) -> &'static [&'static str] {
37 match self {
38 Self::Pentatonic => &["m3", "M2", "M2", "m3", "M2"],
40 Self::Hexatonic => &["m3", "M2", "a1", "m2", "m3", "M2"],
42 }
43 }
44}
45
46#[derive(Clone, Debug, PartialEq)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64#[must_use]
65pub struct WeightedHexatonicBlues {
66 tonic: Pitch,
67}
68
69impl WeightedHexatonicBlues {
70 pub fn new(tonic: Pitch) -> Self {
72 Self { tonic }
73 }
74
75 pub fn tonic(&self) -> &Pitch {
77 &self.tonic
78 }
79
80 pub fn pitches(&self, form: BluesForm) -> Result<Vec<Pitch>> {
82 self.scale(form)?.pitches()
83 }
84
85 pub fn scale(&self, form: BluesForm) -> Result<StepScale> {
87 StepScale::cyclical(self.tonic.clone(), form.steps())
88 }
89
90 pub fn form_for_seed(seed: u64) -> BluesForm {
96 if split_mix_64(seed) & 1 == 0 {
97 BluesForm::Pentatonic
98 } else {
99 BluesForm::Hexatonic
100 }
101 }
102
103 pub fn sample(&self, seed: u64) -> Result<Vec<Pitch>> {
107 self.pitches(Self::form_for_seed(seed))
108 }
109}
110
111fn split_mix_64(seed: u64) -> u64 {
116 let mut z = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
117 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
118 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
119 z ^ (z >> 31)
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 fn names(tonic: &str, form: BluesForm) -> Vec<String> {
127 WeightedHexatonicBlues::new(Pitch::from_name(tonic).expect("valid tonic"))
128 .pitches(form)
129 .expect("scale realizes")
130 .iter()
131 .map(|pitch| pitch.name())
132 .collect()
133 }
134
135 #[test]
136 fn both_forms_match_music21() {
137 assert_eq!(
139 names("C4", BluesForm::Hexatonic),
140 ["C", "E-", "F", "F#", "G", "B-", "C"]
141 );
142 assert_eq!(
143 names("C4", BluesForm::Pentatonic),
144 ["C", "E-", "F", "G", "B-", "C"]
145 );
146 assert_eq!(
147 names("G4", BluesForm::Hexatonic),
148 ["G", "B-", "C", "C#", "D", "F", "G"]
149 );
150 assert_eq!(
151 names("E-4", BluesForm::Hexatonic),
152 ["E-", "G-", "A-", "A", "B-", "D-", "E-"]
153 );
154 assert_eq!(
155 names("F#4", BluesForm::Hexatonic),
156 ["F#", "A", "B", "B#", "C#", "E", "F#"]
157 );
158 }
159
160 #[test]
161 fn the_blue_note_is_the_only_difference() {
162 let hexatonic = names("C4", BluesForm::Hexatonic);
163 let pentatonic = names("C4", BluesForm::Pentatonic);
164 assert_eq!(hexatonic.len(), pentatonic.len() + 1);
165
166 let without_blue_note: Vec<&String> =
167 hexatonic.iter().filter(|name| *name != "F#").collect();
168 assert_eq!(without_blue_note, pentatonic.iter().collect::<Vec<_>>());
169 }
170
171 #[test]
172 fn sampling_is_reproducible_and_reaches_both_forms() {
173 let blues = WeightedHexatonicBlues::new(Pitch::from_name("C4").unwrap());
174 for seed in [0, 1, 7, 42, u64::MAX] {
175 assert_eq!(
176 blues.sample(seed).unwrap(),
177 blues.sample(seed).unwrap(),
178 "seed {seed} should be reproducible"
179 );
180 }
181
182 let forms: Vec<BluesForm> = (0..64).map(WeightedHexatonicBlues::form_for_seed).collect();
183 assert!(forms.contains(&BluesForm::Pentatonic));
184 assert!(forms.contains(&BluesForm::Hexatonic));
185 }
186
187 #[test]
188 fn every_form_realizes_on_every_common_tonic() {
189 for form in BluesForm::ALL {
190 for tonic in [
191 "C4", "G4", "D4", "A4", "E4", "B4", "F#4", "F4", "B-4", "E-4",
192 ] {
193 let pitches = WeightedHexatonicBlues::new(Pitch::from_name(tonic).unwrap())
194 .pitches(form)
195 .expect("scale realizes");
196 assert_eq!(pitches.len(), form.steps().len() + 1, "{form:?} on {tonic}");
197 }
198 }
199 }
200}