Skip to main content

music21_rs/scale/
realized.rs

1//! A scale realized from a tonic: music21's `ConcreteScale`, the pitches
2//! a pattern of steps gives on a note, and every question asked of them —
3//! degrees, neighbours, matching, derivation and tuning.
4
5use super::scaletype::{
6    DegreeComparison, HUMDRUM_SOLFEG_SYLLABLES, MAX_RANGE_OCTAVES, SOLFEG_SYLLABLES, ScaleType,
7    Simplification, SolfegVariant, advance, step_interval,
8};
9use crate::chord::{Chord, root};
10use crate::defaults::{FloatType, IntegerType};
11use crate::error::{Error, Result};
12use crate::interval::Interval;
13use crate::key::Key;
14use crate::pitch::Pitch;
15use crate::roman::{Minor67Default, RomanNumeral, degree_to_roman};
16use crate::tuningsystem::scala::{ScalaDegree, ScalaScale};
17
18/// A named scale realized from a tonic pitch.
19///
20/// ```
21/// use music21_rs::{Pitch, Scale, ScaleType};
22///
23/// let scale = Scale::new(ScaleType::Octatonic, Pitch::from_name("C4")?);
24/// let names: Vec<String> = scale.pitches()?.iter().map(|p| p.name()).collect();
25///
26/// assert_eq!(names, ["C", "D", "Eb", "F", "Gb", "Ab", "A", "B", "C"]);
27/// # Ok::<(), music21_rs::Error>(())
28/// ```
29#[derive(Clone, Debug, PartialEq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31#[must_use]
32pub struct Scale {
33    scale_type: ScaleType,
34    tonic: Pitch,
35    /// The steps of a scale nobody has a name for.
36    ///
37    /// music21's `ConcreteScale(pitches=[...])` is a scale given by its
38    /// notes rather than by a name, and it behaves as any other scale does —
39    /// it realizes, it has degrees, it can be matched against. `None` is the
40    /// ordinary case, where the steps come from the named type.
41    ///
42    /// They are intervals and not names: a step between microtonal pitches
43    /// has no name to be written and read back through.
44    #[cfg_attr(feature = "serde", serde(default))]
45    custom_steps: Option<Vec<Interval>>,
46    /// How a scale given by its steps spells what it realizes, where that is
47    /// not how the named type would. `None` leaves it to the type.
48    #[cfg_attr(feature = "serde", serde(default))]
49    custom_simplification: Option<Simplification>,
50    /// Whether the scale is a cycle, realized by walking out from its tonic
51    /// both ways rather than once and moved by octaves: music21's
52    /// `AbstractCyclicalScale`, whose network does not duplicate at the
53    /// octave even where its steps happen to add up to one.
54    #[cfg_attr(feature = "serde", serde(default))]
55    cyclic: bool,
56}
57
58impl Scale {
59    /// The roman numeral on a degree of this scale: music21's `romanNumeral`,
60    /// the triad that degree carries read against the major key of the
61    /// tonic, whatever its case would say.
62    pub fn roman_numeral(&self, degree: u8) -> Result<RomanNumeral> {
63        if !(1..=7).contains(&degree) {
64            return Err(Error::Scale(format!(
65                "a roman numeral stands on a degree from 1 to 7, not {degree}"
66            )));
67        }
68        let key = Key::from_tonic_mode(&self.tonic.name(), "major")?;
69        RomanNumeral::over_scale(
70            degree_to_roman(degree),
71            key,
72            Some(self.clone()),
73            Minor67Default::default(),
74            Minor67Default::default(),
75            false,
76        )
77    }
78
79    /// Moves every note and chord of a stream onto this scale: music21's
80    /// `tune`. A pitch whose name, or any enharmonic of it within two
81    /// accidentals, is a name of the scale's octave from the tonic becomes
82    /// that scale pitch in its own octave, spelled as it was where the scale
83    /// pitch has such a spelling; a pitch the scale has no name for is left
84    /// alone. Nested streams are tuned too.
85    pub fn tune(&self, stream: &mut crate::Stream) -> Result<()> {
86        let scale_pitches = self.pitches()?;
87        let names: Vec<String> = scale_pitches.iter().map(Pitch::name).collect();
88        let tuned = |pitch: &Pitch| -> Result<Option<Pitch>> {
89            let mut candidates = pitch.all_common_enharmonics(2);
90            candidates.push(pitch.clone());
91            for candidate in candidates {
92                let Some(index) = names.iter().position(|name| *name == candidate.name()) else {
93                    continue;
94                };
95                let mut target = scale_pitches[index].clone();
96                target.set_octave(candidate.octave());
97                let spelled = target
98                    .all_common_enharmonics(2)
99                    .into_iter()
100                    .find(|spelling| spelling.name() == pitch.name());
101                return Ok(Some(spelled.unwrap_or(target)));
102            }
103            Ok(None)
104        };
105        for event in stream.events_mut() {
106            match event.element_mut() {
107                crate::stream::StreamElement::Note(note) => {
108                    if let Some(pitch) = tuned(note.pitch())? {
109                        note.set_pitch(pitch);
110                    }
111                }
112                crate::stream::StreamElement::Chord(chord) => {
113                    for note in chord.notes_mut() {
114                        if let Some(pitch) = tuned(note.pitch())? {
115                            note.set_pitch(pitch);
116                        }
117                    }
118                }
119                crate::stream::StreamElement::Stream(inner) => self.tune(inner)?,
120                _ => {}
121            }
122        }
123        Ok(())
124    }
125
126    /// This scale written as a Scala file writes one: music21's
127    /// `getScalaData`, each degree as the cents above the tonic, with the
128    /// interval the scale closes on as the period.
129    pub fn scala_data(&self) -> Result<ScalaScale> {
130        let pitches = self.pitches()?;
131        let (Some(tonic), Some(closing)) = (pitches.first(), pitches.last()) else {
132            return Err(Error::Scale(
133                "a scale with no pitches has no degrees".to_string(),
134            ));
135        };
136        let cents = |pitch: &Pitch| ScalaDegree::Cents((pitch.ps() - tonic.ps()) * 100.0);
137        let mut degrees = vec![ScalaDegree::Ratio(crate::tuningsystem::Ratio::new(1, 1))];
138        degrees.extend(pitches[1..pitches.len() - 1].iter().map(cents));
139        Ok(ScalaScale::new(
140            format!(
141                "{} {}",
142                self.tonic.name(),
143                self.scale_type.music21_descriptive_name()
144            ),
145            degrees,
146            cents(closing),
147        ))
148    }
149
150    /// Builds a scale of the given type on a tonic.
151    pub fn new(scale_type: ScaleType, tonic: Pitch) -> Self {
152        Self {
153            scale_type,
154            tonic,
155            custom_steps: None,
156            custom_simplification: None,
157            cyclic: scale_type.is_cycle(),
158        }
159    }
160
161    /// A scale given by the notes of one octave of it rather than by a name:
162    /// music21's `ConcreteScale(pitches=[...])`.
163    ///
164    /// The first pitch is the tonic, the steps are the intervals between
165    /// neighbours, and the scale closes back on the octave, so the notes
166    /// repeat an octave higher as any scale's do.
167    pub fn from_pitches(pitches: &[Pitch]) -> Result<Self> {
168        let pitches = fix_default_octave_for_pitch_list(pitches);
169        let pitches = pitches.as_slice();
170        let Some(tonic) = pitches.first() else {
171            return Err(crate::error::Error::Scale(
172                "a scale needs at least one pitch".to_string(),
173            ));
174        };
175        let mut steps = Vec::with_capacity(pitches.len());
176        for pair in pitches.windows(2) {
177            steps.push(Interval::between_pitches(&pair[0], &pair[1])?);
178        }
179        // The closing step back to the tonic, so the collection repeats.
180        // Notes that already end on the tonic's name need none -- and they
181        // may end two octaves up rather than one, which is a pattern two
182        // octaves long and not a scale that folds back on itself. Otherwise
183        // the tonic is carried the way the notes went, up or down, to the
184        // first octave of it at or past the last note.
185        let last = pitches.last().unwrap_or(tonic);
186        if last.name() != tonic.name() {
187            let mut closing = tonic.clone();
188            let mut octave = tonic
189                .octave()
190                .unwrap_or(crate::defaults::PITCH_OCTAVE as IntegerType);
191            closing.octave_setter(Some(octave));
192            if last.ps() > tonic.ps() {
193                while closing.ps() < last.ps() {
194                    octave += 1;
195                    closing.octave_setter(Some(octave));
196                }
197            } else {
198                while closing.ps() > last.ps() {
199                    octave -= 1;
200                    closing.octave_setter(Some(octave));
201                }
202            }
203            steps.push(Interval::between_pitches(last, &closing)?);
204        }
205        Ok(Self {
206            scale_type: ScaleType::Major,
207            tonic: tonic.clone(),
208            custom_steps: Some(steps),
209            custom_simplification: None,
210            cyclic: false,
211        })
212    }
213
214    /// A scale given by the steps between its notes rather than by a name or
215    /// by the notes themselves: the pattern music21's `OctaveRepeatingScale`
216    /// and `CyclicalScale` hand their interval network.
217    ///
218    /// The steps are walked in order from the tonic and then again from
219    /// wherever they leave off, so they are one period of the scale. A
220    /// pattern with no steps in it is no scale at all.
221    pub fn from_steps(tonic: Pitch, steps: Vec<Interval>) -> Result<Self> {
222        if steps.is_empty() {
223            return Err(crate::error::Error::Scale(
224                "a scale needs at least one step".to_string(),
225            ));
226        }
227        Ok(Self {
228            scale_type: ScaleType::Major,
229            tonic,
230            custom_steps: Some(steps),
231            // music21's interval network spells with at most one accidental
232            // unless it is told otherwise, which is what turns the third
233            // minor second above C from E double flat into D.
234            custom_simplification: Some(Simplification::MaxAccidental),
235            cyclic: false,
236        })
237    }
238
239    /// A scale that is a cycle of steps from its tonic: music21's
240    /// `CyclicalScale`, `SieveScale` and `ScalaScale`, all of which stand on
241    /// an `AbstractCyclicalScale`.
242    ///
243    /// It sounds the same notes [`Self::from_steps`] would where the steps
244    /// add up to an octave, but it reaches each one by walking to it from
245    /// the tonic, so a note below the tonic is spelled for the step it was
246    /// reached by coming down. With microtones that is a different spelling
247    /// -- slendro on C has `Bb3(-40c)` below its tonic and `A~4(+10c)` above.
248    pub fn from_cycle(tonic: Pitch, steps: Vec<Interval>) -> Result<Self> {
249        let mut scale = Self::from_steps(tonic, steps)?;
250        scale.cyclic = true;
251        Ok(scale)
252    }
253
254    /// The same scale spelling what it realizes another way: music21's
255    /// `pitchSimplification` set on the network underneath. It changes how
256    /// the notes are written and nothing about where they sound.
257    pub fn with_simplification(mut self, simplification: Simplification) -> Self {
258        self.custom_simplification = Some(simplification);
259        self
260    }
261
262    /// A scale read out of a Scala file: music21's `ScalaScale`, the file's
263    /// steps walked from a tonic, each note written in its most common
264    /// spelling as music21 writes them.
265    ///
266    /// The steps are the file's own, in cents, and the cycle closes on the
267    /// file's last degree, so a scale that does not repeat at the octave does
268    /// not here either.
269    pub fn from_scala(tonic: Pitch, scala: &ScalaScale) -> Result<Self> {
270        Ok(Self::from_cycle(tonic, scala.interval_sequence()?)?
271            .with_simplification(Simplification::MostCommon))
272    }
273
274    /// This scale as it sounds coming down, which for most is itself.
275    pub fn descending(&self) -> Scale {
276        if self.custom_steps.is_some() {
277            return self.clone();
278        }
279        if let Some(scale_type) = self.scale_type.descending_form() {
280            // Rag Asawari comes down through the minor scale's steps but
281            // still spells them its own way.
282            let descending = Scale::new(scale_type, self.tonic.clone());
283            return if scale_type.simplification() == self.simplification() {
284                descending
285            } else {
286                descending.with_simplification(self.simplification())
287            };
288        }
289        // A pattern no other named scale spells, walked as its own list of
290        // steps: Rag Marwa comes down through the flat second above its
291        // octave and no scale here rises that way.
292        if let Some(steps) = self.scale_type.descending_steps() {
293            let walked: Result<Vec<Interval>> = steps.iter().copied().map(step_interval).collect();
294            if let Ok(walked) = walked {
295                return Self {
296                    scale_type: self.scale_type,
297                    tonic: self.tonic.clone(),
298                    custom_steps: Some(walked),
299                    custom_simplification: None,
300                    cyclic: false,
301                };
302            }
303        }
304        self.clone()
305    }
306
307    /// The scale coming down: highest note first, through the collection it
308    /// uses descending.
309    pub fn pitches_descending(&self) -> Result<Vec<Pitch>> {
310        // Walked down from the top, as music21 walks it: a scale that
311        // respells as it goes spells its notes coming down differently from
312        // going up.
313        // Over the span the scale sounds going up, which for Rag Marwa
314        // reaches the flat second above its octave that its descent turns
315        // through.
316        let ascending = self.pitches()?;
317        let bottom = ascending.iter().min_by(|a, b| a.ps().total_cmp(&b.ps()));
318        let top = ascending.iter().max_by(|a, b| a.ps().total_cmp(&b.ps()));
319        let (Some(bottom), Some(top)) = (bottom, top) else {
320            return Ok(ascending);
321        };
322        self.pitches_between_descending(bottom, top)
323    }
324
325    /// A range of the scale coming down, highest note first.
326    pub fn pitches_between_descending(
327        &self,
328        minimum: &Pitch,
329        maximum: &Pitch,
330    ) -> Result<Vec<Pitch>> {
331        self.descending().walked_down_between(minimum, maximum)
332    }
333
334    /// A range of a scale given by its steps, walked downward from the tonic
335    /// above it.
336    ///
337    /// Which way a scale is walked only matters where it respells as it
338    /// goes, and then it matters: three minor seconds up from D are `E-`,
339    /// `F-` and `F`, and the same three coming down from F are `E`, `D#` and
340    /// `D`. music21 walks its network in the direction it is asked for, so
341    /// the notes coming down are not the notes going up read backwards.
342    fn walked_down_between(&self, minimum: &Pitch, maximum: &Pitch) -> Result<Vec<Pitch>> {
343        let (lowest, highest) = (
344            minimum.ps().min(maximum.ps()),
345            minimum.ps().max(maximum.ps()),
346        );
347        if !self.repeats_at_the_octave() || self.cyclic {
348            let mut pitches: Vec<Pitch> = self
349                .cycle_between(lowest, highest)?
350                .into_iter()
351                .map(|(pitch, _)| pitch)
352                .collect();
353            pitches.reverse();
354            return Ok(pitches);
355        }
356        let simplification = self.simplification();
357        let steps = self.walk()?;
358        let period = self.period_in_octaves(&steps);
359        let mut current = self.realization_start()?;
360        shift_by_periods(&mut current, period, highest, false);
361        let mut pitches = Vec::new();
362        let limit = steps.len() * (MAX_RANGE_OCTAVES + 2) + 1;
363        for index in 0..limit {
364            let sounding = current.ps();
365            if within(sounding, lowest, highest) {
366                pitches.push(current.clone());
367            }
368            if sounding < lowest + SLACK {
369                break;
370            }
371            let step = &steps[steps.len() - 1 - index % steps.len()];
372            current = advance(&current, &step.reversed()?, simplification)?;
373        }
374        Ok(pitches)
375    }
376
377    /// The steps of a scale with no name, in the order they are walked: a
378    /// cycle a caller gave, or the steps between the notes a caller gave.
379    /// `None` for a named scale, whose steps are its type's.
380    pub fn custom_steps(&self) -> Option<&[Interval]> {
381        self.custom_steps.as_deref()
382    }
383
384    /// Whether this scale is a cycle walked from its tonic: see
385    /// [`Self::from_cycle`].
386    pub fn is_cyclic(&self) -> bool {
387        self.cyclic
388    }
389
390    /// Whether this scale was given by its notes rather than by a name.
391    pub fn is_custom(&self) -> bool {
392        self.custom_steps.is_some()
393    }
394
395    /// Moves the scale to a new tonic, keeping its pattern of steps.
396    pub fn set_tonic(&mut self, tonic: Pitch) {
397        self.tonic = tonic;
398    }
399
400    /// The scales of *this* pattern that contain the most of `pitches`, best
401    /// first: music21's `deriveRanked` on the scale rather than on the type,
402    /// which is what a scale given by its notes has to use.
403    pub fn derive_ranked_by(
404        &self,
405        pitches: &[Pitch],
406        limit: Option<usize>,
407        comparison: DegreeComparison,
408    ) -> Result<Vec<(usize, Scale)>> {
409        if !self.is_custom() {
410            return self.scale_type.derive_ranked_by(pitches, limit, comparison);
411        }
412        super::scaletype::rank_on_every_start(pitches, limit, comparison, |tonic| {
413            let mut candidate = self.clone();
414            candidate.set_tonic(tonic);
415            candidate
416        })
417    }
418
419    /// How the scale spells what it realizes: music21's
420    /// `pitchSimplification` on the network underneath.
421    pub fn simplification(&self) -> Simplification {
422        self.custom_simplification
423            .unwrap_or_else(|| self.scale_type.simplification())
424    }
425
426    /// The steps walked from where the scale is realized.
427    fn walk(&self) -> Result<Vec<Interval>> {
428        match &self.custom_steps {
429            Some(steps) => Ok(steps.clone()),
430            None => self
431                .scale_type
432                .realization_steps()
433                .into_iter()
434                .map(step_interval)
435                .collect(),
436        }
437    }
438
439    /// Returns the number of distinct degrees: music21's `getDegreeMaxUnique`,
440    /// seven for a major scale and twelve for the chromatic.
441    pub fn degree_count(&self) -> usize {
442        match &self.custom_steps {
443            Some(steps) => steps.len(),
444            None => self.scale_type.degree_count(),
445        }
446    }
447
448    /// Returns the scale type.
449    pub fn scale_type(&self) -> ScaleType {
450        self.scale_type
451    }
452
453    /// Returns the tonic pitch.
454    pub fn tonic(&self) -> &Pitch {
455        &self.tonic
456    }
457
458    /// Returns the pitches of one octave, from the tonic through its octave.
459    ///
460    /// The result has `degree_count() + 1` entries, since the closing octave is
461    /// included the way music21's `getPitches` includes it.
462    ///
463    /// A tonic with no octave is realized in octave 4, which is what music21
464    /// does: the scale on a bare `G` runs `G4 A4 Bb4 C5 …`. The tonic itself
465    /// keeps its own spelling — `Scale::tonic` still has no octave — because
466    /// the octave belongs to the realization and not to the scale.
467    pub fn pitches(&self) -> Result<Vec<Pitch>> {
468        let simplification = self.simplification();
469        let start = self.realization_start()?;
470        let mut pitches = Vec::with_capacity(self.scale_type.degree_count() + 1);
471        pitches.push(start.clone());
472
473        let mut current = start;
474        for step in self.walk()? {
475            current = advance(&current, &step, simplification)?;
476            pitches.push(current.clone());
477        }
478        if self.custom_steps.is_none()
479            && let Some(beyond) = self.scale_type.beyond_terminus()
480        {
481            pitches.push(advance(&current, &step_interval(beyond)?, simplification)?);
482        }
483        Ok(pitches)
484    }
485
486    /// The pitch the scale is realized from, which is its final except in a
487    /// plagal mode, where the range starts below it.
488    ///
489    /// How far below is the scale's own business rather than a flat fourth:
490    /// the walk goes back down the last steps of the collection, so the
491    /// hypolocrian on C starts on `G-` and not on `G`, its fifth degree
492    /// being diminished.
493    fn realization_start(&self) -> Result<Pitch> {
494        let mut start = self.realized_tonic();
495        if self.custom_steps.is_some() {
496            return Ok(start);
497        }
498        let steps = self.scale_type.steps();
499        for step in steps
500            .iter()
501            .rev()
502            .take(self.scale_type.tonic_degree().saturating_sub(1))
503        {
504            start = start.transpose(&step_interval(step)?.reversed()?)?;
505        }
506        Ok(start)
507    }
508
509    /// The tonic as the scale sounds it: music21's `getTonic`, which is the
510    /// tonic in octave 4 when it was given without one.
511    pub fn realized_tonic(&self) -> Pitch {
512        let mut tonic = self.tonic.clone();
513        if tonic.octave().is_none() {
514            tonic.octave_setter(Some(crate::defaults::PITCH_OCTAVE as IntegerType));
515        }
516        tonic
517    }
518
519    /// The major scale written with the same key signature as this one:
520    /// music21's `getRelativeMajor`.
521    ///
522    /// A mode is written with the signature of the major scale it is a
523    /// rotation of, so D dorian is C major and E minor is G major. Only the
524    /// seven-note modes have one.
525    pub fn relative_major(&self) -> Result<Scale> {
526        self.relative(ScaleType::Major)
527    }
528
529    /// The minor scale written with the same key signature: music21's
530    /// `getRelativeMinor`.
531    pub fn relative_minor(&self) -> Result<Scale> {
532        self.relative(ScaleType::Minor)
533    }
534
535    /// The major scale on the same tonic: music21's `getParallelMajor`.
536    pub fn parallel_major(&self) -> Scale {
537        Scale::new(ScaleType::Major, self.tonic.clone())
538    }
539
540    /// The minor scale on the same tonic: music21's `getParallelMinor`.
541    pub fn parallel_minor(&self) -> Scale {
542        Scale::new(ScaleType::Minor, self.tonic.clone())
543    }
544
545    /// The scale of the wanted type carrying this one's key signature.
546    ///
547    /// It stands at or above this scale, in the same octave where that is
548    /// possible: the relative major of A minor on `A4` is C major on `C5`,
549    /// because a `C4` would sound below the scale it came from.
550    fn relative(&self, wanted: ScaleType) -> Result<Scale> {
551        let mode = self.scale_type.music21_descriptive_name();
552        let sharps = crate::key::pitch_to_sharps(&self.tonic, Some(mode))?;
553        let key = crate::key::KeySignature::new(sharps)
554            .try_as_key(Some(wanted.music21_descriptive_name()), None)?;
555        let here = self.realized_tonic();
556        let mut tonic = key.tonic();
557        tonic.set_octave(here.octave());
558        if tonic.ps() < here.ps() {
559            tonic.set_octave(tonic.octave().map(|octave| octave + 1));
560        }
561        Ok(Scale::new(wanted, tonic))
562    }
563
564    /// The degree each of this scale's notes stands on, in order from the
565    /// tonic, where those are not simply the notes counted off.
566    ///
567    /// Only a named pattern can say so — a collection given by its notes is
568    /// counted — and only one of them does: see
569    /// [`ScaleType::ascending_degrees`].
570    pub fn named_degrees(&self) -> Option<Vec<IntegerType>> {
571        if self.custom_steps.is_some() {
572            return None;
573        }
574        Some(
575            self.scale_type
576                .ascending_degrees()?
577                .iter()
578                .map(|&degree| IntegerType::from(degree))
579                .collect(),
580        )
581    }
582
583    /// The degree the note at this position stands on, counting from one.
584    fn degree_at_position(&self, position: usize) -> usize {
585        self.named_degrees()
586            .and_then(|degrees| degrees.get(position).copied())
587            .map_or(position + 1, |degree| degree as usize)
588    }
589
590    /// Returns the pitch standing on a one-based scale degree, or nothing
591    /// where the scale has no such degree.
592    ///
593    /// Degree 1 is the tonic. Every other degree is read within the one
594    /// octave the scale is realized in, so the eighth degree is the tonic
595    /// again and not the octave above it, and the zeroth and the negative
596    /// degrees count back round from the top. That is music21's
597    /// `pitchFromDegree`, which asks its interval network for the node the
598    /// degree names and gets one of the nodes it has.
599    ///
600    /// A scale that names its degrees has only the ones it names: Rag
601    /// Asawari's ascent has no third, and answers nothing when asked for
602    /// one rather than handing back the note that would be third in line.
603    pub fn pitch_on_degree(&self, degree: IntegerType) -> Result<Option<Pitch>> {
604        let position = match self.named_degrees() {
605            Some(degrees) => match degrees.iter().position(|&named| named == degree) {
606                Some(position) => position,
607                None => return Ok(None),
608            },
609            None => {
610                let count = self.degree_count().max(1) as IntegerType;
611                (degree - 1).rem_euclid(count) as usize
612            }
613        };
614        let simplification = self.simplification();
615        let steps = self.walk()?;
616        let mut current = self.realization_start()?;
617        for index in 0..position {
618            current = advance(&current, &steps[index % steps.len()], simplification)?;
619        }
620        Ok(Some(current))
621    }
622
623    /// The pitch standing on a one-based scale degree, which the scale is
624    /// expected to have: [`Self::pitch_on_degree`] is the one that says when
625    /// it does not.
626    pub fn pitch_at_degree(&self, degree: IntegerType) -> Result<Pitch> {
627        self.pitch_on_degree(degree)?.ok_or_else(|| {
628            Error::Scale(format!(
629                "{} has no degree {degree}",
630                self.scale_type.music21_descriptive_name()
631            ))
632        })
633    }
634    /// Returns every pitch of the scale from `minimum` up to `maximum`,
635    /// inclusive: music21's `getPitches` given a range.
636    ///
637    /// The scale is realized from the tonic in whatever octave puts it at or
638    /// below the bottom of the range, then walked upward, so asking a C major
639    /// scale for `Eb5` to `Gb7` starts at `E5` — the first scale pitch that
640    /// is not below the bottom — and not at a respelled `Eb5`.
641    pub fn pitches_between(&self, minimum: &Pitch, maximum: &Pitch) -> Result<Vec<Pitch>> {
642        // Asked the other way round, music21 walks down instead: the same
643        // pitches, highest first.
644        if maximum.ps() < minimum.ps() {
645            let mut descending = self.pitches_between(maximum, minimum)?;
646            descending.reverse();
647            return Ok(descending);
648        }
649        Ok(self
650            .placed_between(minimum.ps(), maximum.ps())?
651            .into_iter()
652            .map(|(pitch, _)| pitch)
653            .collect())
654    }
655
656    /// The pitch standing on a degree within a range: the first one the
657    /// scale comes to walking up from the bottom of it, or nothing where no
658    /// note of that degree falls inside. music21's `pitchFromDegree` given a
659    /// `minPitch` and a `maxPitch`, which realizes the range and reads the
660    /// degree off it — so a cycle of minor seconds on C4 asked for its first
661    /// degree between C2 and C3 answers the `B#1` it spells there.
662    pub fn pitch_on_degree_between(
663        &self,
664        degree: IntegerType,
665        minimum: &Pitch,
666        maximum: &Pitch,
667    ) -> Result<Option<Pitch>> {
668        let (low, high) = if maximum.ps() < minimum.ps() {
669            (maximum, minimum)
670        } else {
671            (minimum, maximum)
672        };
673        let count = self.degree_count().max(1);
674        let wanted = |place: usize| match self.named_degrees() {
675            Some(_) => self.degree_at_position(place) as IntegerType == degree,
676            None => place % count == (degree - 1).rem_euclid(count as IntegerType) as usize,
677        };
678        Ok(self
679            .placed_between(low.ps(), high.ps())?
680            .into_iter()
681            .find(|(_, place)| wanted(*place))
682            .map(|(pitch, _)| pitch))
683    }
684
685    /// Every note of the scale sounding between two pitch-space values, in
686    /// the order the walk comes to them, each with its place in the pattern.
687    fn placed_between(&self, lowest: FloatType, highest: FloatType) -> Result<Vec<(Pitch, usize)>> {
688        if !self.repeats_at_the_octave() || self.cyclic {
689            return self.cycle_between(lowest, highest);
690        }
691        let simplification = self.simplification();
692        let steps = self.walk()?;
693        // Down whole periods until the start is at or below the range. A
694        // period is usually the octave, but a scale given by its notes may
695        // take two to come back to where it began, and dropping by one would
696        // start the pattern halfway through itself.
697        let period = self.period_in_octaves(&steps);
698        let mut current = self.realization_start()?;
699        shift_by_periods(&mut current, period, lowest, true);
700        let mut pitches = Vec::new();
701        // Two octaves of headroom past the range, so a scale whose degrees
702        // are not evenly spaced still reaches the top of it.
703        let limit = steps.len() * (MAX_RANGE_OCTAVES + 2) + 1;
704        for index in 0..limit {
705            let sounding = current.ps();
706            if within(sounding, lowest, highest) {
707                pitches.push((current.clone(), index % steps.len()));
708            }
709            if sounding > highest - SLACK {
710                break;
711            }
712            current = advance(&current, &steps[index % steps.len()], simplification)?;
713        }
714        Ok(pitches)
715    }
716
717    /// Whether the pattern comes back to its tonic a whole number of octaves
718    /// up, as every named scale does. A cycle of fifths does not: it comes
719    /// back a fifth up, and again a fifth above that, and never on an octave
720    /// of where it began until it has been all the way round.
721    fn repeats_at_the_octave(&self) -> bool {
722        match &self.custom_steps {
723            Some(steps) => {
724                let risen: FloatType = steps.iter().map(Interval::semitones).sum();
725                (risen / 12.0 - (risen / 12.0).round()).abs() < 1e-9
726            }
727            None => true,
728        }
729    }
730
731    /// The notes of a cycle sounding between two pitch-space values, lowest
732    /// first, each with its place in the cycle.
733    ///
734    /// A scale that repeats at the octave can be realized once and moved by
735    /// octaves; one that does not has to be walked, up from the tonic for
736    /// what lies above it and down from the tonic for what lies below, which
737    /// is also how each note comes by its spelling.
738    fn cycle_between(&self, lowest: FloatType, highest: FloatType) -> Result<Vec<(Pitch, usize)>> {
739        let steps = self.walk()?;
740        let period: FloatType = steps.iter().map(Interval::semitones).sum();
741        if period <= 0.0 {
742            return Err(Error::Scale(
743                "a cycle that does not rise cannot be walked over a range".to_string(),
744            ));
745        }
746        let simplification = self.simplification();
747        let start = self.realization_start()?;
748        let limit = steps.len() * 512;
749        let mut found = Vec::new();
750
751        // Each walk stops at the first note past its end of the range, as
752        // music21's does. Going further is not only wasted: a Scala file's
753        // steps respell as they go, and a walk carried a cycle too far can
754        // land on a note no accidental spells, which music21 never reaches.
755        let mut current = start.clone();
756        for index in 0..limit {
757            let sounding = current.ps();
758            if within(sounding, lowest, highest) {
759                found.push((current.clone(), index % steps.len()));
760            }
761            if sounding > highest - SLACK {
762                break;
763            }
764            current = advance(&current, &steps[index % steps.len()], simplification)?;
765        }
766
767        let mut current = start;
768        for back in 1..=limit {
769            let place = (steps.len() - back % steps.len()) % steps.len();
770            current = advance(&current, &steps[place].reversed()?, simplification)?;
771            let sounding = current.ps();
772            if within(sounding, lowest, highest) {
773                found.push((current.clone(), place));
774            }
775            if sounding < lowest + SLACK {
776                break;
777            }
778        }
779        found.sort_by(|left, right| left.0.ps().total_cmp(&right.0.ps()));
780        Ok(found)
781    }
782
783    /// Every place a pitch stands on in a cycle that does not repeat at the
784    /// octave, lowest first.
785    ///
786    /// Such a cycle need not have a note in every register — a cycle of
787    /// fifths on C has an F#, but not the one below middle C — so music21
788    /// realizes the cycle an octave either side of the pitch and takes every
789    /// note there that matches it. A pitch with no octave is heard in the
790    /// fourth. Two matches may stand on different places, and every one is
791    /// answered here.
792    fn cycle_places_of(&self, pitch: &Pitch, comparison: DegreeComparison) -> Result<Vec<usize>> {
793        let sounding = Self::sounding(pitch).ps();
794        let wanted = comparison.key(pitch);
795        let mut places = Vec::new();
796        for (candidate, place) in self.cycle_between(sounding - 12.0, sounding + 12.0)? {
797            if comparison.key(&candidate) == wanted && !places.contains(&place) {
798                places.push(place);
799            }
800        }
801        Ok(places)
802    }
803
804    /// The note of the cycle sounding exactly where a pitch does, which is
805    /// what it takes for the pitch to be on the cycle and not merely named
806    /// by it somewhere.
807    fn cycle_place_sounding(
808        &self,
809        pitch: &Pitch,
810        comparison: DegreeComparison,
811    ) -> Result<Option<(Pitch, usize)>> {
812        let sounding = Self::sounding(pitch).ps();
813        let wanted = comparison.key(pitch);
814        Ok(self
815            .cycle_between(sounding - 1e-9, sounding + 1e-9)?
816            .into_iter()
817            .find(|(candidate, _)| comparison.key(candidate) == wanted))
818    }
819
820    /// A pitch as a scale hears it, in octave four where it names none.
821    fn sounding(pitch: &Pitch) -> Pitch {
822        let mut heard = pitch.clone();
823        if heard.octave().is_none() {
824            heard.octave_setter(Some(crate::defaults::PITCH_OCTAVE as IntegerType));
825        }
826        heard
827    }
828
829    /// `steps` notes along a cycle from a pitch, which need not be on it: a
830    /// pitch beside the cycle first comes onto it, on the side the move is
831    /// going or the one named, and that counts as a step unless a side was
832    /// named.
833    fn cycle_steps_from(
834        &self,
835        origin: &Pitch,
836        steps: IntegerType,
837        neighbour_below: Option<bool>,
838    ) -> Result<Pitch> {
839        let walk = self.walk()?;
840        let period: FloatType = walk.iter().map(Interval::semitones).sum();
841        let simplification = self.simplification();
842        let at = Self::sounding(origin).ps();
843        let ascending = steps > 0;
844        let (mut current, mut place, mut remaining) =
845            // A pitch sounding where a note of the cycle does is on it,
846            // however it is spelled: music21 compares them by pitch space.
847            match self.cycle_place_sounding(origin, DegreeComparison::PitchClass)? {
848                Some((pitch, place)) => (pitch, place, steps),
849                None => {
850                    let take_below = neighbour_below.unwrap_or(!ascending);
851                    let beside = self.cycle_between(at - period, at + period)?;
852                    let neighbour = if take_below {
853                        beside.into_iter().rfind(|(pitch, _)| pitch.ps() < at)
854                    } else {
855                        beside.into_iter().find(|(pitch, _)| pitch.ps() > at)
856                    };
857                    let (pitch, place) = neighbour
858                        .ok_or_else(|| Error::Scale(format!("no scale pitch beside {origin}")))?;
859                    let remaining = if neighbour_below.is_some() {
860                        steps
861                    } else {
862                        steps - steps.signum()
863                    };
864                    (pitch, place, remaining)
865                }
866            };
867        while remaining > 0 {
868            current = advance(&current, &walk[place], simplification)?;
869            place = (place + 1) % walk.len();
870            remaining -= 1;
871        }
872        while remaining < 0 {
873            place = (place + walk.len() - 1) % walk.len();
874            current = advance(&current, &walk[place].reversed()?, simplification)?;
875            remaining += 1;
876        }
877        if origin.octave().is_none() {
878            current.octave_setter(None);
879        }
880        Ok(current)
881    }
882
883    /// Whether the pattern can be walked at all.
884    ///
885    /// A collection given by its notes may rise and fall back to where it
886    /// began — `A4 B4 C4 D4 E4 F4 G4 A4` does — and a pattern that goes
887    /// nowhere cannot be realized over a range, however many times it is
888    /// walked. music21 says so as well, out of the network it walks.
889    pub fn is_realizable(&self) -> bool {
890        match &self.custom_steps {
891            Some(steps) => steps.iter().map(Interval::semitones).sum::<FloatType>() > 0.0,
892            None => true,
893        }
894    }
895
896    /// Whether the pattern repeats at the octave: music21's
897    /// `octaveDuplicating`. Every named scale does, and one given by its
898    /// notes need not — a collection spanning two octaves before it comes
899    /// back to its tonic is a pattern two octaves long.
900    pub fn octave_duplicating(&self) -> bool {
901        if self.cyclic {
902            return false;
903        }
904        match &self.custom_steps {
905            Some(steps) => self.period_in_octaves(steps) == 1,
906            None => true,
907        }
908    }
909
910    /// How many octaves the pattern takes to come back to where it began,
911    /// which is one for every scale that has a name and may be more for one
912    /// given by its notes.
913    fn period_in_octaves(&self, steps: &[Interval]) -> IntegerType {
914        let semitones: FloatType = steps.iter().map(Interval::semitones).sum();
915        ((semitones / 12.0).round() as IntegerType).max(1)
916    }
917
918    /// The note the scale comes to rest on, as it sounds: music21's
919    /// `getTonic`, which is the fourth degree of a plagal mode.
920    pub fn final_pitch(&self) -> Result<Pitch> {
921        self.pitch_at_degree(self.scale_type.tonic_degree() as IntegerType)
922    }
923
924    /// The reciting tone: music21's `getDominant`.
925    pub fn dominant(&self) -> Result<Pitch> {
926        self.pitch_at_degree(self.scale_type.dominant_degree() as IntegerType)
927    }
928
929    /// The seventh degree raised or lowered to sit a semitone below the
930    /// final: music21's `getLeadingTone`, which in a minor scale is not the
931    /// seventh degree the scale itself has.
932    pub fn leading_tone(&self) -> Result<Pitch> {
933        let seventh = self.pitch_at_degree(7)?;
934        let tonic = self.final_pitch()?;
935        let distance = seventh.midi() - tonic.midi();
936        if distance == 11 {
937            return Ok(seventh);
938        }
939        let alter = seventh.accidental().alter() + FloatType::from(11 - distance);
940        let mut raised = seventh.clone();
941        raised.set_accidental(crate::pitch::Accidental::new(alter)?);
942        Ok(raised)
943    }
944
945    /// Returns the scale of the same type on which `pitch` is the given degree:
946    /// music21's `deriveByDegree`, so the major scale with `E` as its fifth is
947    /// A major. The pitch keeps its spelling; a pitch without an octave is
948    /// read in octave 4, as music21 reads it, so the new tonic has one.
949    pub fn derive_by_degree(&self, degree: usize, pitch: &Pitch) -> Result<Scale> {
950        let implicit_octave = Some(crate::defaults::PITCH_OCTAVE as IntegerType);
951        let mut tonic = self.tonic.clone();
952        if tonic.octave().is_none() {
953            tonic.octave_setter(implicit_octave);
954        }
955        let degree_pitch =
956            Scale::new(self.scale_type, tonic.clone()).pitch_at_degree(degree as IntegerType)?;
957        let up_to_degree = Interval::between_pitches(&tonic, &degree_pitch)?;
958        let mut reference = pitch.clone();
959        if reference.octave().is_none() {
960            reference.octave_setter(implicit_octave);
961        }
962        let new_tonic = reference.transpose(&up_to_degree.reversed()?)?;
963        Ok(Scale::new(self.scale_type, new_tonic))
964    }
965
966    /// Returns the same scale type on the tonic transposed by `interval`.
967    pub fn transpose(&self, interval: &Interval) -> Result<Scale> {
968        Ok(Scale::new(self.scale_type, self.tonic.transpose(interval)?))
969    }
970
971    /// Returns one octave of the scale as a chord, tonic through octave:
972    /// music21's `getChord`.
973    pub fn chord(&self) -> Result<Chord> {
974        Chord::new(self.pitches()?.as_slice())
975    }
976
977    /// Returns the pitches at the given degrees within one octave of the
978    /// tonic: music21's `pitchesFromScaleDegrees`, which realizes tonic
979    /// through octave once and so silently drops a degree beyond the octave.
980    pub fn pitches_from_scale_degrees(&self, degrees: &[usize]) -> Result<Vec<Pitch>> {
981        let octave = self.pitches()?;
982        // The realization closes on the tonic an octave up, and that closing
983        // pitch is degree one again — music21 asks the whole realization
984        // which of its pitches stand on the degrees wanted, so the first
985        // degree of A minor answers with both `A3` and `A4`.
986        let count = octave.len().saturating_sub(1).max(1);
987        Ok(octave
988            .into_iter()
989            .enumerate()
990            .filter(|(index, _)| degrees.contains(&(index % count + 1)))
991            .map(|(_, pitch)| pitch)
992            .collect())
993    }
994
995    /// Every pitch of the named degrees between two pitches: music21's
996    /// `pitchesFromScaleDegrees` given a range, so the third and seventh of
997    /// C major from `c2` to `c6` are `D2 G2 D3 G3 D4 G4 D5 G5`.
998    pub fn pitches_from_scale_degrees_between(
999        &self,
1000        degrees: &[usize],
1001        minimum: &Pitch,
1002        maximum: &Pitch,
1003    ) -> Result<Vec<Pitch>> {
1004        let wanted: Vec<String> = self
1005            .pitches_from_scale_degrees(degrees)?
1006            .iter()
1007            .map(Pitch::name)
1008            .collect();
1009        Ok(self
1010            .pitches_between(minimum, maximum)?
1011            .into_iter()
1012            .filter(|pitch| wanted.contains(&pitch.name()))
1013            .collect())
1014    }
1015
1016    /// Returns the interval from one degree to another, both folded into the
1017    /// first octave the way music21's `pitchFromDegree` folds them, so degree
1018    /// 9 of a seven-note scale is degree 2 and the interval from 2 to 9 is a
1019    /// unison.
1020    pub fn interval_between_degrees(&self, start: usize, end: usize) -> Result<Interval> {
1021        Interval::between_pitches(
1022            &self.pitch_at_degree(start as IntegerType)?,
1023            &self.pitch_at_degree(end as IntegerType)?,
1024        )
1025    }
1026
1027    /// Returns whether `other` is the scale pitch `steps` degrees above
1028    /// `origin`, compared by name so the octave does not matter: music21's
1029    /// `isNext`.
1030    pub fn is_next(&self, other: &Pitch, origin: &Pitch, steps: usize) -> Result<bool> {
1031        Ok(self.next_pitch_above(origin, steps)?.name() == other.name())
1032    }
1033
1034    /// Splits pitches into those whose names the scale contains and those it
1035    /// does not: music21's `match`. The matched list carries the scale's own
1036    /// pitches, realized from the tonic in octave 4 when it has none, and
1037    /// the unmatched list carries the pitches as given.
1038    pub fn match_pitches(&self, pitches: &[Pitch]) -> Result<(Vec<Pitch>, Vec<Pitch>)> {
1039        self.match_pitches_by(pitches, DegreeComparison::Name)
1040    }
1041
1042    /// The same, saying how a pitch is matched against a degree.
1043    ///
1044    /// Both lists hold the pitches as given rather than the scale's own —
1045    /// music21 hands its targets straight back — except that one with no
1046    /// octave is heard in octave 4, since that is where the scale sounds.
1047    pub fn match_pitches_by(
1048        &self,
1049        pitches: &[Pitch],
1050        comparison: DegreeComparison,
1051    ) -> Result<(Vec<Pitch>, Vec<Pitch>)> {
1052        let realized = self.realized_in_implicit_octave()?;
1053        let degrees: Vec<String> = realized.iter().map(|p| comparison.key(p)).collect();
1054        let mut matched = Vec::new();
1055        let mut unmatched = Vec::new();
1056        for pitch in pitches {
1057            let mut heard = pitch.clone();
1058            if heard.octave().is_none() {
1059                heard.octave_setter(Some(crate::defaults::PITCH_OCTAVE as IntegerType));
1060            }
1061            if degrees.contains(&comparison.key(&heard)) {
1062                matched.push(heard);
1063            } else {
1064                unmatched.push(heard);
1065            }
1066        }
1067        Ok((matched, unmatched))
1068    }
1069
1070    /// Returns the scale pitches, tonic through octave, whose pitch classes
1071    /// none of `pitches` has: music21's `findMissing`, so C major against
1072    /// `C E G` is `D4 F4 A4 B4`.
1073    pub fn find_missing(&self, pitches: &[Pitch]) -> Result<Vec<Pitch>> {
1074        let present: Vec<u8> = pitches.iter().map(root::pitch_class).collect();
1075        Ok(self
1076            .realized_in_implicit_octave()?
1077            .into_iter()
1078            .filter(|candidate| !present.contains(&root::pitch_class(candidate)))
1079            .collect())
1080    }
1081
1082    /// Returns the solfège syllable for a pitch, `do` through `ti` with the
1083    /// chromatic inflections (`di`, `ra`, …): music21's `solfeg`. Without
1084    /// `chromatic` the plain syllable of the degree is returned whatever the
1085    /// accidental. Errors for degrees past seven and alterations past a
1086    /// double sharp or flat.
1087    pub fn solfeg(&self, pitch: &Pitch, variant: SolfegVariant, chromatic: bool) -> Result<String> {
1088        let (degree, accidental) = self.degree_and_accidental_of(pitch)?;
1089        if degree > 7 {
1090            return Err(crate::error::Error::Scale(
1091                "Cannot call solfeg on non-7-degree scales".to_string(),
1092            ));
1093        }
1094        let table = match variant {
1095            SolfegVariant::Music21 => &SOLFEG_SYLLABLES,
1096            SolfegVariant::Humdrum => &HUMDRUM_SOLFEG_SYLLABLES,
1097        };
1098        let alter = if chromatic {
1099            accidental.map_or(0, |accidental| accidental.alter() as IntegerType)
1100        } else {
1101            0
1102        };
1103        let column = usize::try_from(alter + 2)
1104            .ok()
1105            .filter(|column| *column < 5)
1106            .ok_or_else(|| {
1107                crate::error::Error::Scale(format!(
1108                    "no solfeg syllable for an alteration of {alter}"
1109                ))
1110            })?;
1111        Ok(table[degree - 1][column].to_string())
1112    }
1113
1114    fn realized_in_implicit_octave(&self) -> Result<Vec<Pitch>> {
1115        self.pitches()
1116    }
1117
1118    /// Returns the one-based degree matching a pitch under the given
1119    /// comparison, or `None` when the scale does not have it.
1120    pub fn degree_of_by(
1121        &self,
1122        pitch: &Pitch,
1123        comparison: DegreeComparison,
1124    ) -> Result<Option<usize>> {
1125        if !self.repeats_at_the_octave() {
1126            return Ok(self
1127                .cycle_places_of(pitch, comparison)?
1128                .first()
1129                .map(|place| self.degree_at_position(*place)));
1130        }
1131        let wanted = comparison.key(pitch);
1132        Ok(self
1133            .scale_pitches()?
1134            .iter()
1135            .position(|candidate| comparison.key(candidate) == wanted)
1136            .map(|index| self.degree_at_position(index)))
1137    }
1138
1139    /// Every one-based degree the pitch stands on.
1140    ///
1141    /// A scale may name the same note twice — Rag Marwa's A is both its
1142    /// fifth degree and its seventh, since the pattern dips before it closes
1143    /// — and this gives every one. [`Self::degree_of_by`] gives the first
1144    /// the scale reaches.
1145    pub fn degrees_of_by(&self, pitch: &Pitch, comparison: DegreeComparison) -> Result<Vec<usize>> {
1146        if !self.repeats_at_the_octave() {
1147            return Ok(self
1148                .cycle_places_of(pitch, comparison)?
1149                .into_iter()
1150                .map(|place| self.degree_at_position(place))
1151                .collect());
1152        }
1153        let wanted = comparison.key(pitch);
1154        Ok(self
1155            .scale_pitches()?
1156            .iter()
1157            .enumerate()
1158            .filter(|(_, candidate)| comparison.key(candidate) == wanted)
1159            .map(|(index, _)| self.degree_at_position(index))
1160            .collect())
1161    }
1162
1163    /// Returns the one-based degree whose pitch name matches, ignoring octave,
1164    /// or `None` when the pitch is not in the scale.
1165    pub fn degree_of(&self, pitch: &Pitch) -> Result<Option<usize>> {
1166        if !self.repeats_at_the_octave() {
1167            return self.degree_of_by(pitch, DegreeComparison::Name);
1168        }
1169        let name = pitch.name();
1170        Ok(self
1171            .scale_pitches()?
1172            .iter()
1173            .position(|candidate| candidate.name() == name)
1174            .map(|index| self.degree_at_position(index)))
1175    }
1176
1177    /// Returns the one-based degree whose pitch class matches, so `F-` finds
1178    /// the `E` of C major.
1179    pub fn degree_of_pitch_class(&self, pitch: &Pitch) -> Result<Option<usize>> {
1180        if !self.repeats_at_the_octave() {
1181            return self.degree_of_by(pitch, DegreeComparison::PitchClass);
1182        }
1183        let pitch_class = pitch.pitch_class().number();
1184        Ok(self
1185            .scale_pitches()?
1186            .iter()
1187            .position(|candidate| candidate.pitch_class().number() == pitch_class)
1188            .map(|index| self.degree_at_position(index)))
1189    }
1190
1191    /// Returns the scale pitch `steps` degrees above `origin`. A pitch outside
1192    /// the scale first moves to the nearest scale pitch above it.
1193    pub fn next_pitch_above(&self, origin: &Pitch, steps: usize) -> Result<Pitch> {
1194        self.pitch_steps_from(origin, steps as IntegerType, None)
1195    }
1196
1197    /// The same, told which side of a pitch outside the scale to start from:
1198    /// music21's `getNeighbor`, where naming a side means stepping the whole
1199    /// way from the neighbour on it rather than counting the move onto the
1200    /// scale as one of the steps.
1201    pub fn next_pitch_beside(
1202        &self,
1203        origin: &Pitch,
1204        steps: IntegerType,
1205        below: bool,
1206    ) -> Result<Pitch> {
1207        self.pitch_steps_from(origin, steps, Some(below))
1208    }
1209
1210    /// Returns the scale pitch `steps` degrees below `origin`. A pitch outside
1211    /// the scale first moves to the nearest scale pitch below it.
1212    pub fn next_pitch_below(&self, origin: &Pitch, steps: usize) -> Result<Pitch> {
1213        self.pitch_steps_from(origin, -(steps as IntegerType), None)
1214    }
1215
1216    /// Returns the degree a pitch sits on together with the accidental that
1217    /// separates it from the scale's own spelling of that degree, so `E-` in
1218    /// C major is degree three with a flat. Errors when no degree shares the
1219    /// pitch's letter.
1220    pub fn degree_and_accidental_of(
1221        &self,
1222        pitch: &Pitch,
1223    ) -> Result<(usize, Option<crate::pitch::Accidental>)> {
1224        if let Some(degree) = self.degree_of(pitch)? {
1225            return Ok((degree, None));
1226        }
1227        let pitches = self.scale_pitches()?;
1228        let index = pitches
1229            .iter()
1230            .position(|candidate| candidate.step() == pitch.step())
1231            .ok_or_else(|| {
1232                crate::error::Error::Scale(format!(
1233                    "cannot get any scale degree for {pitch} in {self:?}"
1234                ))
1235            })?;
1236        let difference = pitch.accidental().alter() - pitches[index].accidental().alter();
1237        let accidental = if difference == 0.0 {
1238            None
1239        } else {
1240            Some(crate::pitch::Accidental::new(difference)?)
1241        };
1242        Ok((index + 1, accidental))
1243    }
1244
1245    fn scale_pitches(&self) -> Result<Vec<Pitch>> {
1246        let mut pitches = self.pitches()?;
1247        pitches.truncate(self.degree_count());
1248        Ok(pitches)
1249    }
1250
1251    fn pitch_steps_from(
1252        &self,
1253        origin: &Pitch,
1254        steps: IntegerType,
1255        neighbour_below: Option<bool>,
1256    ) -> Result<Pitch> {
1257        self.pitch_steps_from_place(origin, steps, neighbour_below, 0)
1258    }
1259
1260    /// How many places in this scale's realization stand on `origin`.
1261    ///
1262    /// A scale may name the same note twice: Rag Marwa's `D-` is both the
1263    /// note above its tonic and the one it passes through coming down from
1264    /// the octave, and where the next note is depends on which of them is
1265    /// meant. [`Self::next_pitch_below_from`] and
1266    /// [`Self::next_pitch_above_from`] take the place to read it as. The
1267    /// places are numbered by the degree each stands on, lowest first, so
1268    /// place `0` is the lowest degree; that is the one
1269    /// [`Self::next_pitch_below`] and [`Self::next_pitch_above`] read a note
1270    /// as.
1271    pub fn places_of(&self, origin: &Pitch) -> Result<usize> {
1272        Ok(self.places_on(origin)?.len())
1273    }
1274
1275    /// The places of the realization, and the octave shift each stands at,
1276    /// that sound `origin`, lowest degree first. A place sounds a pitch
1277    /// however it is spelled, as music21 finds where to step from by pitch
1278    /// space: `E#4` in C major stands on the place of `F4`.
1279    fn places_on(&self, origin: &Pitch) -> Result<Vec<(usize, IntegerType)>> {
1280        if !self.repeats_at_the_octave() {
1281            return Ok(self
1282                .cycle_place_sounding(origin, DegreeComparison::PitchClass)?
1283                .map(|(_, place)| (place, 0))
1284                .into_iter()
1285                .collect());
1286        }
1287        let pitches = self.scale_pitches()?;
1288        let origin_ps = origin.ps();
1289        let base_shift = ((origin_ps - self.tonic.ps()) / 12.0).floor() as IntegerType;
1290        let mut places = (base_shift - 1..=base_shift + 1)
1291            .flat_map(|shift| {
1292                pitches.iter().enumerate().map(move |(index, pitch)| {
1293                    (pitch.ps() + 12.0 * shift as FloatType, index, shift)
1294                })
1295            })
1296            .filter(|(ps, _, _)| (ps - origin_ps).abs() < 1e-9)
1297            .map(|(_, index, shift)| (index, shift))
1298            .collect::<Vec<_>>();
1299        places.sort_by_key(|&(index, shift)| (self.degree_at_position(index), index, shift));
1300        Ok(places)
1301    }
1302
1303    /// The note `steps` below `origin`, read as the `place`-th of the places
1304    /// this scale stands that note on. See [`Self::places_of`].
1305    pub fn next_pitch_below_from(
1306        &self,
1307        origin: &Pitch,
1308        steps: usize,
1309        place: usize,
1310    ) -> Result<Pitch> {
1311        self.pitch_steps_from_place(origin, -(steps as IntegerType), None, place)
1312    }
1313
1314    /// The note `steps` above `origin`, read the same way.
1315    pub fn next_pitch_above_from(
1316        &self,
1317        origin: &Pitch,
1318        steps: usize,
1319        place: usize,
1320    ) -> Result<Pitch> {
1321        self.pitch_steps_from_place(origin, steps as IntegerType, None, place)
1322    }
1323
1324    fn pitch_steps_from_place(
1325        &self,
1326        origin: &Pitch,
1327        steps: IntegerType,
1328        neighbour_below: Option<bool>,
1329        place: usize,
1330    ) -> Result<Pitch> {
1331        if steps == 0 {
1332            return Err(crate::error::Error::Scale(
1333                "step size must be at least 1".to_string(),
1334            ));
1335        }
1336        if !self.repeats_at_the_octave() {
1337            return self.cycle_steps_from(origin, steps, neighbour_below);
1338        }
1339        let pitches = self.scale_pitches()?;
1340        let count = pitches.len() as IntegerType;
1341        let origin_ps = origin.ps();
1342        let base_shift = ((origin_ps - self.tonic.ps()) / 12.0).floor() as IntegerType;
1343        let candidates = (base_shift - 1..=base_shift + 1)
1344            .flat_map(|shift| {
1345                pitches.iter().enumerate().map(move |(index, pitch)| {
1346                    (pitch.ps() + 12.0 * shift as FloatType, index, shift)
1347                })
1348            })
1349            .collect::<Vec<_>>();
1350        let ascending = steps > 0;
1351        let standing = self.places_on(origin)?;
1352        let (index, shift, remaining) = match standing.get(place % standing.len().max(1)).copied() {
1353            Some((index, shift)) => (index, shift, steps),
1354            None => {
1355                // Which side of the pitch to come onto the scale at: the way
1356                // the move is going unless a caller has said otherwise, and
1357                // then the move onto the scale counts as one of the steps.
1358                let take_below = neighbour_below.unwrap_or(!ascending);
1359                let neighbour = if take_below {
1360                    candidates
1361                        .iter()
1362                        .filter(|(ps, _, _)| *ps < origin_ps)
1363                        .max_by(|left, right| left.0.total_cmp(&right.0))
1364                } else {
1365                    candidates
1366                        .iter()
1367                        .filter(|(ps, _, _)| *ps > origin_ps)
1368                        .min_by(|left, right| left.0.total_cmp(&right.0))
1369                };
1370                let &(_, index, shift) = neighbour.ok_or_else(|| {
1371                    crate::error::Error::Scale(format!("no scale pitch beside {origin}"))
1372                })?;
1373                let remaining = if neighbour_below.is_some() {
1374                    steps
1375                } else {
1376                    steps - steps.signum()
1377                };
1378                (index, shift, remaining)
1379            }
1380        };
1381        let total = index as IntegerType + remaining;
1382        let mut pitch = pitches[total.rem_euclid(count) as usize].clone();
1383        let octave_shift = shift + total.div_euclid(count);
1384        let octave = origin.octave().map(|_| {
1385            pitch
1386                .octave()
1387                .unwrap_or(crate::defaults::PITCH_OCTAVE as IntegerType)
1388                + octave_shift
1389        });
1390        pitch.octave_setter(octave);
1391        Ok(pitch)
1392    }
1393}
1394
1395/// Whether a note sounds inside a range, allowing it the hundred-thousandth
1396/// of a semitone music21 allows: a scale read from a file in cents lands its
1397/// octave a hair past where the range ends, and still closes on it.
1398fn within(sounding: FloatType, lowest: FloatType, highest: FloatType) -> bool {
1399    sounding > lowest - SLACK && sounding < highest + SLACK
1400}
1401
1402/// The slack [`within`] allows at either end of a range, in semitones.
1403const SLACK: FloatType = 1e-5;
1404
1405/// Moves a pitch by whole periods to where music21 starts walking a range:
1406/// as close to `target` as it can be while at or below it, or at or above it,
1407/// which is `transposeBelowTarget` and `transposeAboveTarget` with
1408/// `minimize`. A period is usually the octave, but a scale given by its notes
1409/// may take two to come back to where it began, and moving by one would start
1410/// the pattern halfway through itself.
1411fn shift_by_periods(pitch: &mut Pitch, period: IntegerType, target: FloatType, below: bool) {
1412    let semitones = 12.0 * FloatType::from(period);
1413    let shift = |pitch: &mut Pitch, by: IntegerType| {
1414        let octave = pitch.octave().unwrap_or(0);
1415        pitch.octave_setter(Some(octave + by));
1416    };
1417    // Pitch space runs to a few hundred semitones either way, so no walk
1418    // here needs more than a few dozen periods.
1419    for _ in 0..64 {
1420        let ps = pitch.ps();
1421        let too_high = if below {
1422            ps > target + SLACK
1423        } else {
1424            ps - semitones >= target - SLACK
1425        };
1426        let too_low = if below {
1427            ps + semitones <= target + SLACK
1428        } else {
1429            ps < target - SLACK
1430        };
1431        if too_high {
1432            shift(pitch, -period);
1433        } else if too_low {
1434            shift(pitch, period);
1435        } else {
1436            break;
1437        }
1438    }
1439}
1440
1441/// A collection's notes with the octaves a caller left out filled in so that
1442/// the collection rises: music21's `fixDefaultOctaveForPitchList`.
1443///
1444/// `A B C D E F G# A` is a scale on A, not a collection that climbs a tone
1445/// and then falls a seventh, and a caller who names no octaves means the
1446/// first. Notes that carry an octave are left exactly as they are.
1447pub fn fix_default_octave_for_pitch_list(pitches: &[Pitch]) -> Vec<Pitch> {
1448    let mut risen: Vec<Pitch> = Vec::with_capacity(pitches.len());
1449    let mut last_ps = 0.0;
1450    let mut last_octave =
1451        pitches
1452            .first()
1453            .map_or(crate::defaults::PITCH_OCTAVE as IntegerType, |pitch| {
1454                pitch
1455                    .octave()
1456                    .unwrap_or(crate::defaults::PITCH_OCTAVE as IntegerType)
1457            });
1458    for pitch in pitches {
1459        let mut pitch = pitch.clone();
1460        if pitch.octave().is_none() {
1461            if last_ps > pitch.ps() {
1462                pitch.octave_setter(Some(last_octave));
1463            }
1464            while last_ps > pitch.ps() {
1465                last_octave += 1;
1466                pitch.octave_setter(Some(last_octave));
1467            }
1468        }
1469        last_ps = pitch.ps();
1470        last_octave = pitch
1471            .octave()
1472            .unwrap_or(crate::defaults::PITCH_OCTAVE as IntegerType);
1473        risen.push(pitch);
1474    }
1475    risen
1476}
1477
1478#[cfg(test)]
1479mod tests {
1480    use super::*;
1481
1482    /// Each answer is music21's `fixDefaultOctaveForPitchList`.
1483    #[test]
1484    fn octaves_left_out_are_filled_in_as_music21_fills_them() {
1485        let cases = [
1486            ("C5 A F3 G B", "C5 A5 F3 G B"),
1487            ("C3 D6 E", "C3 D6 E6"),
1488            ("A B C D E F G A", "A B C5 D5 E5 F5 G5 A5"),
1489            ("C6 B A", "C6 B6 A7"),
1490            ("B#3 C D", "B#3 C D"),
1491        ];
1492        for (given, expected) in cases {
1493            let pitches: Vec<Pitch> = given
1494                .split(' ')
1495                .map(|name| Pitch::from_name(name).unwrap())
1496                .collect();
1497            let risen: Vec<String> = fix_default_octave_for_pitch_list(&pitches)
1498                .iter()
1499                .map(Pitch::name_with_octave)
1500                .collect();
1501            assert_eq!(risen.join(" "), expected, "{given}");
1502        }
1503    }
1504}