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))]
64pub struct WeightedHexatonicBlues {
65 tonic: Pitch,
66}
67
68impl WeightedHexatonicBlues {
69 pub fn new(tonic: Pitch) -> Self {
71 Self { tonic }
72 }
73
74 pub fn tonic(&self) -> &Pitch {
76 &self.tonic
77 }
78
79 pub fn pitches(&self, form: BluesForm) -> Result<Vec<Pitch>> {
81 self.scale(form)?.pitches()
82 }
83
84 pub fn scale(&self, form: BluesForm) -> Result<StepScale> {
86 StepScale::cyclical(self.tonic.clone(), form.steps())
87 }
88
89 pub fn form_for_seed(seed: u64) -> BluesForm {
95 if split_mix_64(seed) & 1 == 0 {
96 BluesForm::Pentatonic
97 } else {
98 BluesForm::Hexatonic
99 }
100 }
101
102 pub fn sample(&self, seed: u64) -> Result<Vec<Pitch>> {
106 self.pitches(Self::form_for_seed(seed))
107 }
108}
109
110fn split_mix_64(seed: u64) -> u64 {
115 let mut z = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
116 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
117 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
118 z ^ (z >> 31)
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 fn names(tonic: &str, form: BluesForm) -> Vec<String> {
126 WeightedHexatonicBlues::new(Pitch::from_name(tonic).expect("valid tonic"))
127 .pitches(form)
128 .expect("scale realizes")
129 .iter()
130 .map(|pitch| pitch.name())
131 .collect()
132 }
133
134 #[test]
135 fn both_forms_match_music21() {
136 assert_eq!(
138 names("C4", BluesForm::Hexatonic),
139 ["C", "E-", "F", "F#", "G", "B-", "C"]
140 );
141 assert_eq!(
142 names("C4", BluesForm::Pentatonic),
143 ["C", "E-", "F", "G", "B-", "C"]
144 );
145 assert_eq!(
146 names("G4", BluesForm::Hexatonic),
147 ["G", "B-", "C", "C#", "D", "F", "G"]
148 );
149 assert_eq!(
150 names("E-4", BluesForm::Hexatonic),
151 ["E-", "G-", "A-", "A", "B-", "D-", "E-"]
152 );
153 assert_eq!(
154 names("F#4", BluesForm::Hexatonic),
155 ["F#", "A", "B", "B#", "C#", "E", "F#"]
156 );
157 }
158
159 #[test]
160 fn the_blue_note_is_the_only_difference() {
161 let hexatonic = names("C4", BluesForm::Hexatonic);
162 let pentatonic = names("C4", BluesForm::Pentatonic);
163 assert_eq!(hexatonic.len(), pentatonic.len() + 1);
164
165 let without_blue_note: Vec<&String> =
166 hexatonic.iter().filter(|name| *name != "F#").collect();
167 assert_eq!(without_blue_note, pentatonic.iter().collect::<Vec<_>>());
168 }
169
170 #[test]
171 fn sampling_is_reproducible_and_reaches_both_forms() {
172 let blues = WeightedHexatonicBlues::new(Pitch::from_name("C4").unwrap());
173 for seed in [0, 1, 7, 42, u64::MAX] {
174 assert_eq!(
175 blues.sample(seed).unwrap(),
176 blues.sample(seed).unwrap(),
177 "seed {seed} should be reproducible"
178 );
179 }
180
181 let forms: Vec<BluesForm> = (0..64).map(WeightedHexatonicBlues::form_for_seed).collect();
182 assert!(forms.contains(&BluesForm::Pentatonic));
183 assert!(forms.contains(&BluesForm::Hexatonic));
184 }
185
186 #[test]
187 fn every_form_realizes_on_every_common_tonic() {
188 for form in BluesForm::ALL {
189 for tonic in [
190 "C4", "G4", "D4", "A4", "E4", "B4", "F#4", "F4", "B-4", "E-4",
191 ] {
192 let pitches = WeightedHexatonicBlues::new(Pitch::from_name(tonic).unwrap())
193 .pitches(form)
194 .expect("scale realizes");
195 assert_eq!(pitches.len(), form.steps().len() + 1, "{form:?} on {tonic}");
196 }
197 }
198 }
199}