Skip to main content

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