Skip to main content

music21/scale/
stepscale.rs

1//! Scales built from a caller-supplied cycle of intervals.
2//!
3//! [`ScaleType`](super::ScaleType) covers music21's *named* `ConcreteScale`
4//! subclasses, which are fixed tables and so can be an enum. `CyclicalScale`
5//! and `OctaveRepeatingScale` are not: they take an arbitrary interval list at
6//! construction, so they are a runtime type rather than a variant.
7//!
8//! The two differ only in how they close:
9//!
10//! - [`StepScale::cyclical`] walks the intervals once and stops, so the scale
11//!   need not span or repeat at an octave — `["P5"]` from C is just `C G`.
12//!   A cycle that comes back to its tonic within an octave fills the octave
13//!   instead: `["M3"]` from C is `C E G# C`.
14//! - [`StepScale::octave_repeating`] appends whatever interval is needed to
15//!   reach the octave above the tonic, so `["m3", "M3"]` from C becomes
16//!   `C E- G C`.
17
18use 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
28/// music21 defaults an absent interval list to a single minor second.
29static DEFAULT_STEP: LazyLock<Interval> =
30    LazyLock::new(|| Interval::from_name("m2").expect("m2 is a valid interval name"));
31
32/// A scale built by walking a cycle of intervals from a tonic.
33///
34/// ```
35/// use music21::{Pitch, StepScale};
36///
37/// let tonic = Pitch::from_name("C4")?;
38/// let cyclical = StepScale::cyclical(tonic.clone(), &["m3", "M3"])?;
39/// let names: Vec<String> = cyclical.pitches()?.iter().map(|p| p.name()).collect();
40/// assert_eq!(names, ["C", "Eb", "G"]);
41///
42/// let repeating = StepScale::octave_repeating(tonic, &["m3", "M3"])?;
43/// let names: Vec<String> = repeating.pitches()?.iter().map(|p| p.name()).collect();
44/// assert_eq!(names, ["C", "Eb", "G", "C"]);
45/// # Ok::<(), music21::Error>(())
46/// ```
47/// `Interval` implements neither `PartialEq` nor `Hash`, so neither is derived
48/// here; compare realized [`pitches`](StepScale::pitches) instead.
49#[derive(Clone, Debug)]
50#[must_use]
51pub struct StepScale {
52    tonic: Pitch,
53    steps: Vec<Interval>,
54    /// Whether the cycle was closed at the octave, which makes it a scale
55    /// that repeats there rather than a cycle walked from its tonic.
56    closed: bool,
57}
58
59impl StepScale {
60    /// Builds music21's `CyclicalScale`: the intervals walked once, no closing.
61    ///
62    /// A cycle of whole semitones adding up to 1, 2, 3, 4, 6 or 12 semitones
63    /// comes back to its tonic within an octave, and is repeated to fill the
64    /// octave with its last step spelled to land on the tonic, so it repeats
65    /// there: `["M3"]` is `M3 M3 d4`, and `["m2"]` is twelve steps.
66    ///
67    /// An empty list defaults to a single `m2`, as music21 does.
68    pub fn cyclical(tonic: Pitch, steps: &[&str]) -> Result<Self> {
69        Self::cyclical_of(tonic, parse_steps(steps)?)
70    }
71
72    /// The same from intervals already in hand, which is the only way to
73    /// give a step that has no name: one measured in cents.
74    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    /// Builds music21's `OctaveRepeatingScale`: the intervals plus a closing
91    /// interval that completes the octave, where they do not reach it
92    /// already.
93    ///
94    /// An empty list defaults to a single `m2`, as music21 does.
95    ///
96    /// Steps that come to twelve semitones close on the tonic: the last one
97    /// is respelled to land on its octave, so `M3 M3 M3` is `M3 M3 d4`.
98    /// Otherwise the closing interval is the *complement of the interval
99    /// sum*, which is how music21 derives it — not the interval between the
100    /// last realized pitch and the octave. The two differ in spelling
101    /// whenever realization respells a degree: three `m2` steps from C sum to
102    /// `dd4`, whose complement is `AA5`, not the major sixth from the `E-`
103    /// the walk reaches. A note the walk reaches on the tonic's place is
104    /// spelled as the tonic, so that scale still ends on `C`.
105    ///
106    /// A cycle wider than an octave is closed at the octave above its last
107    /// pitch: `["P5", "P5"]` is `C4 G4 D5 C6`.
108    pub fn octave_repeating(tonic: Pitch, steps: &[&str]) -> Result<Self> {
109        Self::octave_repeating_of(tonic, parse_steps(steps)?)
110    }
111
112    /// The same from intervals already in hand.
113    pub fn octave_repeating_of(tonic: Pitch, steps: Vec<Interval>) -> Result<Self> {
114        let mut steps = or_default(steps);
115        // Steps a whole octave wide spelled as something else -- three major
116        // thirds come to `B#` -- have their last step respelled to land on
117        // the tonic.
118        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            // A sum no accidental can spell -- eleven minor seconds come to a
132            // twelfth diminished ten times over -- still has a size, and the
133            // cycle closes by what is left of the octave above it.
134            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        // Steps that already reach the octave need no closing step.
141        if closing.short_name() != "P1" {
142            steps.push(closing);
143        }
144        Ok(Self {
145            tonic,
146            steps,
147            closed: true,
148        })
149    }
150
151    /// Builds music21's `SieveScale` from a Xenakis sieve expression.
152    ///
153    /// The sieve's interval widths become the cycle, which music21 then treats
154    /// as a `CyclicalScale` — so `"3@0"` from C is `C E- F# A C`, and the major-scale
155    /// sieve gives a major scale.
156    ///
157    /// The widths are counted in semitones; [`Self::sieve_by`] counts them in
158    /// anything else.
159    pub fn sieve(tonic: Pitch, expression: &str) -> Result<Self> {
160        Self::sieve_by(tonic, expression, 1.0)
161    }
162
163    /// The same with the sieve's unit said: music21's `eld`, the elementary
164    /// displacement, in semitones. A sieve of every integer counted in twos
165    /// is a whole-tone scale, and counted in halves is a scale of quarter
166    /// tones.
167    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    /// Returns the tonic pitch.
190    pub fn tonic(&self) -> &Pitch {
191        &self.tonic
192    }
193
194    /// Returns the step intervals, including any closing interval.
195    pub fn steps(&self) -> &[Interval] {
196        &self.steps
197    }
198
199    /// Returns the number of steps, which is one fewer than the pitch count.
200    pub fn degree_count(&self) -> usize {
201        self.steps.len()
202    }
203
204    /// The same cycle as a [`Scale`], which is what answers everything a
205    /// scale is asked past its own notes: a range of them, the degree a note
206    /// stands on, the note beside another.
207    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    /// Returns the pitches of one pass through the cycle, starting at the
216    /// tonic. Each step is spelled with at most one accidental, as music21's
217    /// interval network spells it, and a scale that repeats at the octave
218    /// ends on its tonic's name. A tonic with no octave gives pitches with
219    /// none.
220    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
231/// Returns the sum of `steps`, spelled exactly.
232///
233/// Computed by transposing a reference pitch with simplification switched off
234/// and measuring the result, so the sum keeps the accidentals the arithmetic
235/// actually produces rather than the ones a realized scale would show.
236fn 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(&current, false, None)?;
240    }
241    Interval::between_pitches(reference, &current)
242}
243
244/// music21 reads no steps at all as a single minor second.
245fn 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        // music21: CyclicalScale('c4', ['M2', 'M2']) has six degrees, and
276        // CyclicalScale('c4', ['M3']) is [C4, E4, G#4, C5].
277        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        // music21: OctaveRepeatingScale('c4', ['m3'] * 4) is
287        // [C4, E-4, G-4, A4, C5], and on F# three major thirds come back to
288        // F#5.
289        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        // Read off music21 11.0.0b9: `OctaveRepeatingScale('c4', ['m3', 'M3'])`
322        // asked for `getPitches('g2', 'g4')`, the degree of three notes, and
323        // `nextPitch` from `c4`, down from `e-5`, and two steps up from `g3`.
324        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        // The closing octave is the tonic again, not a fourth degree.
336        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        // music21: `OctaveRepeatingScale('d', ['m2', 'm2', 'm2'])`, going up
361        // and coming down. Going up the second step is an `F-`; coming down
362        // from `F` the same sound is an `E`, and the one below it a `D#`.
363        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        // music21's own `testCyclicalScales` and `testSieveScaleA`.
387        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        // A cycle of one step has one degree, and every note of it is that.
400        assert_eq!(fifths.degree_of(&tonic("G4")).unwrap(), Some(1));
401        assert_eq!(fifths.degree_of(&tonic("Bb2")).unwrap(), Some(1));
402        // A degree is looked for an octave either side of the note, which is
403        // where music21 looks: the cycle's F# is six fifths up, far past F#3.
404        assert_eq!(fifths.degree_of(&tonic("F#3")).unwrap(), None);
405        assert_eq!(fifths.degree_of(&tonic("F3")).unwrap(), Some(1));
406
407        // A cycle of two steps may stand one name on both of them: G3 is its
408        // first degree and G4 its second, and both lie within an octave of G4.
409        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        // A sieve of every integer, counted in whole tones and then in
439        // quarter tones, each walked down from D4.
440        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        // music21: CyclicalScale() fills the octave with twelve minor
488        // seconds; OctaveRepeatingScale() is [C4, D-4, C5].
489        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        // Three m2 steps sum to dd4, whose complement is AA5. Realization
504        // respells the third degree from E-double-flat to D, so reading the
505        // closing interval off the pitches would give a major sixth to C
506        // instead. The AA5 walks to B#, which is spelled as the tonic it
507        // comes back to.
508        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        // Where realization changes nothing, the two agree: M2+M2+m2 is P4 and
513        // its complement P5 closes on the octave.
514        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        // Two fifths sum to M9, whose complement is m7, so the cycle closes an
523        // octave higher rather than folding back, as music21 closes it.
524        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}