1use crate::defaults::{FloatType, IntegerType};
19use crate::error::Result;
20use crate::interval::{ChromaticInterval, Interval};
21use crate::pitch::Pitch;
22use crate::scale::Scale;
23use crate::scale::scaletype::{close_on_tonic, cycle_within_octave};
24use crate::sieve::Sieve;
25
26use std::sync::LazyLock;
27
28static DEFAULT_STEP: LazyLock<Interval> =
30 LazyLock::new(|| Interval::from_name("m2").expect("m2 is a valid interval name"));
31
32#[derive(Clone, Debug)]
50#[must_use]
51pub struct StepScale {
52 tonic: Pitch,
53 steps: Vec<Interval>,
54 closed: bool,
57}
58
59impl StepScale {
60 pub fn cyclical(tonic: Pitch, steps: &[&str]) -> Result<Self> {
69 Self::cyclical_of(tonic, parse_steps(steps)?)
70 }
71
72 pub fn cyclical_of(tonic: Pitch, steps: Vec<Interval>) -> Result<Self> {
75 let steps = or_default(steps);
76 Ok(match cycle_within_octave(&steps)? {
77 Some(filled) => Self {
78 tonic,
79 steps: filled,
80 closed: true,
81 },
82 None => Self {
83 tonic,
84 steps,
85 closed: false,
86 },
87 })
88 }
89
90 pub fn octave_repeating(tonic: Pitch, steps: &[&str]) -> Result<Self> {
109 Self::octave_repeating_of(tonic, parse_steps(steps)?)
110 }
111
112 pub fn octave_repeating_of(tonic: Pitch, steps: Vec<Interval>) -> Result<Self> {
114 let mut steps = or_default(steps);
115 let risen: FloatType = steps.iter().map(Interval::semitones).sum();
119 if risen == 12.0 {
120 if !interval_sum(&tonic, &steps).is_ok_and(|sum| sum.short_name() == "P8") {
121 steps = close_on_tonic(&steps)?;
122 }
123 return Ok(Self {
124 tonic,
125 steps,
126 closed: true,
127 });
128 }
129 let closing = match interval_sum(&tonic, &steps).and_then(|sum| sum.inversion()) {
130 Ok(closing) => closing,
131 Err(_) => {
135 let risen: FloatType = steps.iter().map(Interval::semitones).sum();
136 let octaves = (risen / 12.0).floor() + 1.0;
137 Interval::from_semitones((octaves * 12.0 - risen).round() as IntegerType)?
138 }
139 };
140 if closing.short_name() != "P1" {
142 steps.push(closing);
143 }
144 Ok(Self {
145 tonic,
146 steps,
147 closed: true,
148 })
149 }
150
151 pub fn sieve(tonic: Pitch, expression: &str) -> Result<Self> {
160 Self::sieve_by(tonic, expression, 1.0)
161 }
162
163 pub fn sieve_by(tonic: Pitch, expression: &str, eld: FloatType) -> Result<Self> {
168 let widths = Sieve::parse(expression)?.interval_widths()?;
169 let steps = widths
170 .into_iter()
171 .map(|width| {
172 Interval::from_chromatic(ChromaticInterval::new(FloatType::from(width) * eld)?)
173 })
174 .collect::<Result<Vec<_>>>()?;
175 Ok(match cycle_within_octave(&steps)? {
176 Some(filled) => Self {
177 tonic,
178 steps: filled,
179 closed: true,
180 },
181 None => Self {
182 tonic,
183 steps,
184 closed: false,
185 },
186 })
187 }
188
189 pub fn tonic(&self) -> &Pitch {
191 &self.tonic
192 }
193
194 pub fn steps(&self) -> &[Interval] {
196 &self.steps
197 }
198
199 pub fn degree_count(&self) -> usize {
201 self.steps.len()
202 }
203
204 pub fn scale(&self) -> Result<Scale> {
208 if self.closed {
209 Scale::from_steps(self.tonic.clone(), self.steps.clone())
210 } else {
211 Scale::from_cycle(self.tonic.clone(), self.steps.clone())
212 }
213 }
214
215 pub fn pitches(&self) -> Result<Vec<Pitch>> {
221 let mut pitches = self.scale()?.pitches()?;
222 if self.tonic.octave().is_none() {
223 for pitch in &mut pitches {
224 pitch.octave_setter(None);
225 }
226 }
227 Ok(pitches)
228 }
229}
230
231fn interval_sum(reference: &Pitch, steps: &[Interval]) -> Result<Interval> {
237 let mut current = reference.clone();
238 for step in steps {
239 current = step.transpose_pitch_with_options(¤t, false, None)?;
240 }
241 Interval::between_pitches(reference, ¤t)
242}
243
244fn or_default(steps: Vec<Interval>) -> Vec<Interval> {
246 if steps.is_empty() {
247 vec![DEFAULT_STEP.clone()]
248 } else {
249 steps
250 }
251}
252
253fn parse_steps(steps: &[&str]) -> Result<Vec<Interval>> {
254 steps
255 .iter()
256 .map(|name| Interval::from_name(*name))
257 .collect()
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn a_step_scale_reports_its_tonic_and_steps() {
266 let tonic = Pitch::from_name("C4").unwrap();
267 let scale = StepScale::cyclical(tonic.clone(), &["P5", "M2"]).unwrap();
268 assert_eq!(scale.tonic(), &tonic);
269 assert_eq!(scale.steps().len(), 2);
270 assert_eq!(scale.degree_count(), 2);
271 }
272
273 #[test]
274 fn a_cycle_that_comes_back_within_an_octave_fills_it() {
275 let scale = StepScale::cyclical(tonic("C4"), &["M2", "M2"]).unwrap();
278 assert_eq!(scale.degree_count(), 6);
279 let scale = StepScale::cyclical(tonic("C4"), &["M3"]).unwrap();
280 assert_eq!(names(&scale), ["C", "E", "G#", "C"]);
281 assert_eq!(scale.steps()[2].short_name(), "d4");
282 }
283
284 #[test]
285 fn octave_repeating_steps_close_on_the_tonic() {
286 let scale = StepScale::octave_repeating(tonic("C4"), &["m3", "m3", "m3", "m3"]).unwrap();
290 assert_eq!(names(&scale), ["C", "Eb", "Gb", "A", "C"]);
291 let scale = StepScale::octave_repeating(tonic("F#4"), &["M3", "M3", "M3"]).unwrap();
292 let pitches = scale.pitches().unwrap();
293 assert_eq!(pitches.last().unwrap().name_with_octave(), "F#5");
294 }
295
296 #[test]
297 fn steps_reaching_the_octave_close_with_no_unison() {
298 let scale = StepScale::octave_repeating(
299 Pitch::from_name("C4").unwrap(),
300 &["m3", "M2", "m3", "M2", "M2"],
301 )
302 .unwrap();
303 assert_eq!(names(&scale), ["C", "Eb", "F", "Ab", "Bb", "C"]);
304 }
305
306 fn names(scale: &StepScale) -> Vec<String> {
307 scale
308 .pitches()
309 .expect("scale realizes")
310 .iter()
311 .map(|pitch| pitch.name())
312 .collect()
313 }
314
315 fn tonic(name: &str) -> Pitch {
316 Pitch::from_name(name).expect("valid tonic")
317 }
318
319 #[test]
320 fn a_step_scale_answers_as_a_scale() {
321 let scale = StepScale::octave_repeating(tonic("C4"), &["m3", "M3"])
325 .unwrap()
326 .scale()
327 .unwrap();
328 let spelled = |pitches: Vec<Pitch>| -> Vec<String> {
329 pitches.iter().map(Pitch::name_with_octave).collect()
330 };
331 assert_eq!(
332 spelled(scale.pitches_between(&tonic("G2"), &tonic("G4")).unwrap()),
333 ["G2", "C3", "Eb3", "G3", "C4", "Eb4", "G4"]
334 );
335 assert_eq!(scale.degree_count(), 3);
337 assert_eq!(scale.degree_of(&tonic("C4")).unwrap(), Some(1));
338 assert_eq!(scale.degree_of(&tonic("E-")).unwrap(), Some(2));
339 assert_eq!(scale.degree_of(&tonic("G5")).unwrap(), Some(3));
340 assert_eq!(scale.degree_of(&tonic("D4")).unwrap(), None);
341 let next = |origin: &str, steps: usize| {
342 scale
343 .next_pitch_above(&tonic(origin), steps)
344 .unwrap()
345 .name_with_octave()
346 };
347 assert_eq!(next("C4", 1), "Eb4");
348 assert_eq!(next("G3", 2), "Eb4");
349 assert_eq!(
350 scale
351 .next_pitch_below(&tonic("Eb5"), 1)
352 .unwrap()
353 .name_with_octave(),
354 "C5"
355 );
356 }
357
358 #[test]
359 fn a_respelling_scale_comes_down_by_its_own_walk() {
360 let scale = StepScale::octave_repeating(tonic("D"), &["m2", "m2", "m2"])
364 .unwrap()
365 .scale()
366 .unwrap();
367 let spelled = |pitches: Vec<Pitch>| -> Vec<String> {
368 pitches.iter().map(Pitch::name_with_octave).collect()
369 };
370 assert_eq!(
371 spelled(scale.pitches_between(&tonic("D4"), &tonic("D5")).unwrap()),
372 ["D4", "Eb4", "Fb4", "F4", "D5"]
373 );
374 assert_eq!(
375 spelled(
376 scale
377 .pitches_between_descending(&tonic("D4"), &tonic("D5"))
378 .unwrap()
379 ),
380 ["D5", "F4", "E4", "D#4", "D4"]
381 );
382 }
383
384 #[test]
385 fn a_cycle_that_does_not_close_at_the_octave_is_walked_both_ways() {
386 let spelled = |pitches: Vec<Pitch>| -> Vec<String> {
388 pitches.iter().map(ToString::to_string).collect()
389 };
390 let fifths = StepScale::cyclical(tonic("C4"), &["P5"])
391 .unwrap()
392 .scale()
393 .unwrap();
394 assert_eq!(spelled(fifths.pitches().unwrap()), ["C4", "G4"]);
395 assert_eq!(
396 spelled(fifths.pitches_between(&tonic("G2"), &tonic("G6")).unwrap()),
397 ["Bb2", "F3", "C4", "G4", "D5", "A5", "E6"]
398 );
399 assert_eq!(fifths.degree_of(&tonic("G4")).unwrap(), Some(1));
401 assert_eq!(fifths.degree_of(&tonic("Bb2")).unwrap(), Some(1));
402 assert_eq!(fifths.degree_of(&tonic("F#3")).unwrap(), None);
405 assert_eq!(fifths.degree_of(&tonic("F3")).unwrap(), Some(1));
406
407 let mixed = StepScale::cyclical(tonic("C4"), &["M2", "m3"])
410 .unwrap()
411 .scale()
412 .unwrap();
413 assert_eq!(
414 mixed
415 .degrees_of_by(&tonic("G4"), crate::scale::DegreeComparison::Name)
416 .unwrap(),
417 [1, 2]
418 );
419 assert_eq!(
420 fifths
421 .next_pitch_above(&tonic("G4"), 2)
422 .unwrap()
423 .to_string(),
424 "A5"
425 );
426
427 let seconds = StepScale::cyclical(tonic("C4"), &["m2", "m2"])
428 .unwrap()
429 .scale()
430 .unwrap();
431 assert_eq!(
432 spelled(seconds.pitches_between(&tonic("G4"), &tonic("G5")).unwrap()),
433 [
434 "G4", "Ab4", "A4", "Bb4", "Cb5", "C5", "Db5", "D5", "Eb5", "Fb5", "F5", "Gb5", "G5"
435 ]
436 );
437
438 let whole = StepScale::sieve_by(tonic("D4"), "1@0", 2.0)
441 .unwrap()
442 .scale()
443 .unwrap();
444 assert_eq!(
445 spelled(whole.pitches_between(&tonic("C2"), &tonic("C3")).unwrap()),
446 ["C2", "D2", "E2", "F#2", "G#2", "Bb2", "C3"]
447 );
448 let quarter = StepScale::sieve_by(tonic("D4"), "1@0", 0.5)
449 .unwrap()
450 .scale()
451 .unwrap();
452 assert_eq!(
453 spelled(quarter.pitches_between(&tonic("C2"), &tonic("D2")).unwrap()),
454 ["C2", "C~2", "Db2", "D`2", "D2"]
455 );
456 }
457
458 #[test]
459 fn a_cycle_whose_sum_cannot_be_spelled_still_closes() {
460 let steps = ["m2"; 11];
461 let scale = StepScale::octave_repeating(tonic("C4"), &steps).unwrap();
462 let pitches = scale.pitches().unwrap();
463 assert_eq!(pitches.len(), 13);
464 assert_eq!(pitches.last().unwrap().ps(), 72.0);
465 }
466
467 #[test]
468 fn cyclical_walks_the_cycle_once() {
469 let scale = StepScale::cyclical(tonic("C4"), &["P5"]).unwrap();
470 assert_eq!(names(&scale), ["C", "G"]);
471
472 let scale = StepScale::cyclical(tonic("C4"), &["m3", "M3"]).unwrap();
473 assert_eq!(names(&scale), ["C", "Eb", "G"]);
474 }
475
476 #[test]
477 fn octave_repeating_closes_on_the_octave() {
478 let scale = StepScale::octave_repeating(tonic("C4"), &["m3", "M3"]).unwrap();
479 assert_eq!(names(&scale), ["C", "Eb", "G", "C"]);
480 let pitches = scale.pitches().unwrap();
481 assert_eq!(pitches.first().unwrap().octave(), Some(4));
482 assert_eq!(pitches.last().unwrap().octave(), Some(5));
483 }
484
485 #[test]
486 fn an_empty_interval_list_defaults_to_a_minor_second() {
487 assert_eq!(
490 names(&StepScale::cyclical(tonic("C4"), &[]).unwrap()),
491 [
492 "C", "Db", "D", "Eb", "Fb", "F", "Gb", "G", "Ab", "A", "Bb", "Cb", "C"
493 ]
494 );
495 assert_eq!(
496 names(&StepScale::octave_repeating(tonic("C4"), &[]).unwrap()),
497 ["C", "Db", "C"]
498 );
499 }
500
501 #[test]
502 fn the_closing_interval_comes_from_the_interval_sum_not_the_pitches() {
503 let scale = StepScale::octave_repeating(tonic("C4"), &["m2", "m2", "m2"]).unwrap();
509 assert_eq!(scale.steps()[3].short_name(), "AA5");
510 assert_eq!(names(&scale), ["C", "Db", "D", "Eb", "C"]);
511
512 let scale = StepScale::octave_repeating(tonic("C4"), &["M2", "M2", "m2"]).unwrap();
515 assert_eq!(names(&scale), ["C", "D", "E", "F", "C"]);
516 let scale = StepScale::octave_repeating(tonic("F#4"), &["M2", "M2", "m2"]).unwrap();
517 assert_eq!(names(&scale), ["F#", "G#", "A#", "B", "F#"]);
518 }
519
520 #[test]
521 fn a_cycle_wider_than_an_octave_closes_above_it() {
522 let scale = StepScale::octave_repeating(tonic("C4"), &["P5", "P5"]).unwrap();
525 assert_eq!(names(&scale), ["C", "G", "D", "C"]);
526 let pitches = scale.pitches().unwrap();
527 assert_eq!(pitches.first().unwrap().octave(), Some(4));
528 assert_eq!(pitches.last().unwrap().octave(), Some(6));
529 }
530
531 #[test]
532 fn sieve_scales_match_music21() {
533 let cases: [(&str, &str, &[&str]); 6] = [
534 ("C4", "3@0", &["C", "Eb", "F#", "A", "C"]),
535 ("D4", "3@0", &["D", "F", "G#", "B", "D"]),
536 ("E-4", "2@0", &["Eb", "F", "G", "A", "B", "C#", "Eb"]),
537 (
538 "C2",
539 "(-3@2 & 4) | (-3@1 & 4@1) | (3@2 & 4@2) | (-3 & 4@3)",
540 &["C", "D", "E", "F", "G", "A", "B", "C"],
541 ),
542 (
543 "C4",
544 "3@0|7@0",
545 &["C", "Eb", "F#", "G", "A", "C", "D", "Eb", "F#", "A"],
546 ),
547 ("C4", "{3@0|4@0}", &["C", "Eb", "E", "F#", "G#", "A", "C"]),
548 ];
549
550 for (tonic_name, expression, expected) in cases {
551 let scale = StepScale::sieve(tonic(tonic_name), expression).expect("sieve realizes");
552 assert_eq!(names(&scale), expected, "{tonic_name} {expression}");
553 }
554 }
555
556 #[test]
557 fn a_sieve_with_no_intervals_errors() {
558 assert!(StepScale::sieve(tonic("C4"), "3@1").is_err());
559 assert!(StepScale::sieve(tonic("C4"), "not a sieve").is_err());
560 }
561
562 #[test]
563 fn malformed_interval_names_error_instead_of_panicking() {
564 assert!(StepScale::cyclical(tonic("C4"), &["nonsense"]).is_err());
565 }
566}