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, SCALE_STARTS, SOLFEG_SYLLABLES,
7    ScaleType, 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", "E-", "F", "G-", "A-", "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}
47
48impl Scale {
49    /// The roman numeral on a degree of this scale: music21's `romanNumeral`,
50    /// the triad that degree carries read against the major key of the
51    /// tonic, whatever its case would say.
52    pub fn roman_numeral(&self, degree: u8) -> Result<RomanNumeral> {
53        if !(1..=7).contains(&degree) {
54            return Err(Error::Scale(format!(
55                "a roman numeral stands on a degree from 1 to 7, not {degree}"
56            )));
57        }
58        let key = Key::from_tonic_mode(&self.tonic.name(), "major")?;
59        RomanNumeral::over_scale(
60            degree_to_roman(degree),
61            key,
62            Some(self.clone()),
63            Minor67Default::default(),
64            Minor67Default::default(),
65            false,
66        )
67    }
68
69    /// Moves every note and chord of a stream onto this scale: music21's
70    /// `tune`. A pitch whose name, or any enharmonic of it within two
71    /// accidentals, is a name of the scale's octave from the tonic becomes
72    /// that scale pitch in its own octave, spelled as it was where the scale
73    /// pitch has such a spelling; a pitch the scale has no name for is left
74    /// alone. Nested streams are tuned too.
75    pub fn tune(&self, stream: &mut crate::Stream) -> Result<()> {
76        let scale_pitches = self.pitches()?;
77        let names: Vec<String> = scale_pitches.iter().map(Pitch::name).collect();
78        let tuned = |pitch: &Pitch| -> Result<Option<Pitch>> {
79            let mut candidates = pitch.all_common_enharmonics(2);
80            candidates.push(pitch.clone());
81            for candidate in candidates {
82                let Some(index) = names.iter().position(|name| *name == candidate.name()) else {
83                    continue;
84                };
85                let mut target = scale_pitches[index].clone();
86                target.set_octave(candidate.octave());
87                let spelled = target
88                    .all_common_enharmonics(2)
89                    .into_iter()
90                    .find(|spelling| spelling.name() == pitch.name());
91                return Ok(Some(spelled.unwrap_or(target)));
92            }
93            Ok(None)
94        };
95        for event in stream.events_mut() {
96            match event.element_mut() {
97                crate::stream::StreamElement::Note(note) => {
98                    if let Some(pitch) = tuned(note.pitch())? {
99                        note.set_pitch(pitch);
100                    }
101                }
102                crate::stream::StreamElement::Chord(chord) => {
103                    for note in chord.notes_mut() {
104                        if let Some(pitch) = tuned(note.pitch())? {
105                            note.set_pitch(pitch);
106                        }
107                    }
108                }
109                crate::stream::StreamElement::Stream(inner) => self.tune(inner)?,
110                _ => {}
111            }
112        }
113        Ok(())
114    }
115
116    /// This scale written as a Scala file writes one: music21's
117    /// `getScalaData`, each degree as the cents above the tonic, with the
118    /// interval the scale closes on as the period.
119    pub fn scala_data(&self) -> Result<ScalaScale> {
120        let pitches = self.pitches()?;
121        let (Some(tonic), Some(closing)) = (pitches.first(), pitches.last()) else {
122            return Err(Error::Scale(
123                "a scale with no pitches has no degrees".to_string(),
124            ));
125        };
126        let cents = |pitch: &Pitch| ScalaDegree::Cents((pitch.ps() - tonic.ps()) * 100.0);
127        let mut degrees = vec![ScalaDegree::Ratio(crate::tuningsystem::Fraction::new(1, 1))];
128        degrees.extend(pitches[1..pitches.len() - 1].iter().map(cents));
129        Ok(ScalaScale::new(
130            format!(
131                "{} {}",
132                self.tonic.name(),
133                self.scale_type.music21_descriptive_name()
134            ),
135            degrees,
136            cents(closing),
137        ))
138    }
139
140    /// Builds a scale of the given type on a tonic.
141    pub fn new(scale_type: ScaleType, tonic: Pitch) -> Self {
142        Self {
143            scale_type,
144            tonic,
145            custom_steps: None,
146        }
147    }
148
149    /// A scale given by the notes of one octave of it rather than by a name:
150    /// music21's `ConcreteScale(pitches=[...])`.
151    ///
152    /// The first pitch is the tonic, the steps are the intervals between
153    /// neighbours, and the scale closes back on the octave, so the notes
154    /// repeat an octave higher as any scale's do.
155    pub fn from_pitches(pitches: &[Pitch]) -> Result<Self> {
156        let pitches = rising_octaves(pitches);
157        let pitches = pitches.as_slice();
158        let Some(tonic) = pitches.first() else {
159            return Err(crate::error::Error::Scale(
160                "a scale needs at least one pitch".to_string(),
161            ));
162        };
163        let mut steps = Vec::with_capacity(pitches.len());
164        for pair in pitches.windows(2) {
165            steps.push(Interval::between_pitches(&pair[0], &pair[1])?);
166        }
167        // The closing step back to the tonic, so the collection repeats.
168        // Notes that already close on it need none — and they may close two
169        // octaves up rather than one, which is a pattern two octaves long and
170        // not a scale that folds back on itself.
171        let last = pitches.last().unwrap_or(tonic);
172        let span = last.ps() - tonic.ps();
173        if span.rem_euclid(12.0) != 0.0 {
174            let octaves = (span / 12.0).floor() + 1.0;
175            let closing = tonic.transpose(&Interval::from_semitones(
176                (octaves * 12.0) as crate::defaults::IntegerType,
177            )?)?;
178            steps.push(Interval::between_pitches(last, &closing)?);
179        }
180        Ok(Self {
181            scale_type: ScaleType::Major,
182            tonic: tonic.clone(),
183            custom_steps: Some(steps),
184        })
185    }
186
187    /// This scale as it sounds coming down, which for most is itself.
188    pub fn descending(&self) -> Scale {
189        if self.custom_steps.is_some() {
190            return self.clone();
191        }
192        if let Some(scale_type) = self.scale_type.descending_form() {
193            return Scale::new(scale_type, self.tonic.clone());
194        }
195        // A pattern no other named scale spells, walked as its own list of
196        // steps: Rag Marwa comes down through the flat second above its
197        // octave and no scale here rises that way.
198        if let Some(steps) = self.scale_type.descending_steps() {
199            let walked: Result<Vec<Interval>> = steps.iter().copied().map(step_interval).collect();
200            if let Ok(walked) = walked {
201                return Self {
202                    scale_type: self.scale_type,
203                    tonic: self.tonic.clone(),
204                    custom_steps: Some(walked),
205                };
206            }
207        }
208        self.clone()
209    }
210
211    /// The scale coming down: highest note first, through the collection it
212    /// uses descending.
213    pub fn pitches_descending(&self) -> Result<Vec<Pitch>> {
214        let mut pitches = self.descending().pitches()?;
215        pitches.reverse();
216        Ok(pitches)
217    }
218
219    /// A range of the scale coming down, highest note first.
220    pub fn pitches_between_descending(
221        &self,
222        minimum: &Pitch,
223        maximum: &Pitch,
224    ) -> Result<Vec<Pitch>> {
225        let mut pitches = self.descending().pitches_between(minimum, maximum)?;
226        pitches.reverse();
227        Ok(pitches)
228    }
229
230    /// Whether this scale was given by its notes rather than by a name.
231    pub fn is_custom(&self) -> bool {
232        self.custom_steps.is_some()
233    }
234
235    /// Moves the scale to a new tonic, keeping its pattern of steps.
236    pub fn set_tonic(&mut self, tonic: Pitch) {
237        self.tonic = tonic;
238    }
239
240    /// The scales of *this* pattern that contain the most of `pitches`, best
241    /// first: music21's `deriveRanked` on the scale rather than on the type,
242    /// which is what a scale given by its notes has to use.
243    pub fn derive_ranked_by(
244        &self,
245        pitches: &[Pitch],
246        limit: Option<usize>,
247        comparison: DegreeComparison,
248    ) -> Result<Vec<(usize, Scale)>> {
249        if !self.is_custom() {
250            return self.scale_type.derive_ranked_by(pitches, limit, comparison);
251        }
252        let targets: Vec<String> = pitches.iter().map(|p| comparison.key(p)).collect();
253        let mut ranked = Vec::with_capacity(SCALE_STARTS.len());
254        for start in SCALE_STARTS {
255            let mut candidate = self.clone();
256            candidate.set_tonic(Pitch::from_name(start)?);
257            let degrees: Vec<String> = candidate
258                .pitches()?
259                .iter()
260                .map(|p| comparison.key(p))
261                .collect();
262            let matched = targets
263                .iter()
264                .filter(|target| degrees.contains(target))
265                .count();
266            ranked.push((matched, candidate));
267        }
268        ranked.sort_by(|left, right| {
269            left.0
270                .cmp(&right.0)
271                .then_with(|| left.1.tonic().ps().total_cmp(&right.1.tonic().ps()))
272        });
273        ranked.reverse();
274        if let Some(limit) = limit {
275            ranked.truncate(limit);
276        }
277        Ok(ranked)
278    }
279
280    /// The steps walked from where the scale is realized.
281    fn walk(&self) -> Result<Vec<Interval>> {
282        match &self.custom_steps {
283            Some(steps) => Ok(steps.clone()),
284            None => self
285                .scale_type
286                .realization_steps()
287                .into_iter()
288                .map(step_interval)
289                .collect(),
290        }
291    }
292
293    /// Returns the number of distinct degrees: music21's `getDegreeMaxUnique`,
294    /// seven for a major scale and twelve for the chromatic.
295    pub fn degree_count(&self) -> usize {
296        match &self.custom_steps {
297            Some(steps) => steps.len(),
298            None => self.scale_type.degree_count(),
299        }
300    }
301
302    /// Returns the scale type.
303    pub fn scale_type(&self) -> ScaleType {
304        self.scale_type
305    }
306
307    /// Returns the tonic pitch.
308    pub fn tonic(&self) -> &Pitch {
309        &self.tonic
310    }
311
312    /// Returns the pitches of one octave, from the tonic through its octave.
313    ///
314    /// The result has `degree_count() + 1` entries, since the closing octave is
315    /// included the way music21's `getPitches` includes it.
316    ///
317    /// A tonic with no octave is realized in octave 4, which is what music21
318    /// does: the scale on a bare `G` runs `G4 A4 B-4 C5 …`. The tonic itself
319    /// keeps its own spelling — `Scale::tonic` still has no octave — because
320    /// the octave belongs to the realization and not to the scale.
321    pub fn pitches(&self) -> Result<Vec<Pitch>> {
322        let simplification = self.scale_type.simplification();
323        let start = self.realization_start()?;
324        let mut pitches = Vec::with_capacity(self.scale_type.degree_count() + 1);
325        pitches.push(start.clone());
326
327        let mut current = start;
328        for step in self.walk()? {
329            current = advance(&current, &step, simplification)?;
330            pitches.push(current.clone());
331        }
332        if self.custom_steps.is_none()
333            && let Some(beyond) = self.scale_type.beyond_terminus()
334        {
335            pitches.push(advance(&current, &step_interval(beyond)?, simplification)?);
336        }
337        Ok(pitches)
338    }
339
340    /// The pitch the scale is realized from, which is its final except in a
341    /// plagal mode, where the range starts below it.
342    ///
343    /// How far below is the scale's own business rather than a flat fourth:
344    /// the walk goes back down the last steps of the collection, so the
345    /// hypolocrian on C starts on `G-` and not on `G`, its fifth degree
346    /// being diminished.
347    fn realization_start(&self) -> Result<Pitch> {
348        let mut start = self.realized_tonic();
349        if self.custom_steps.is_some() {
350            return Ok(start);
351        }
352        let steps = self.scale_type.steps();
353        for step in steps
354            .iter()
355            .rev()
356            .take(self.scale_type.tonic_degree().saturating_sub(1))
357        {
358            start = start.transpose(&step_interval(step)?.reversed()?)?;
359        }
360        Ok(start)
361    }
362
363    /// The tonic as the scale sounds it: music21's `getTonic`, which is the
364    /// tonic in octave 4 when it was given without one.
365    pub fn realized_tonic(&self) -> Pitch {
366        let mut tonic = self.tonic.clone();
367        if tonic.octave().is_none() {
368            tonic.octave_setter(Some(crate::defaults::PITCH_OCTAVE as IntegerType));
369        }
370        tonic
371    }
372
373    /// The major scale written with the same key signature as this one:
374    /// music21's `getRelativeMajor`.
375    ///
376    /// A mode is written with the signature of the major scale it is a
377    /// rotation of, so D dorian is C major and E minor is G major. Only the
378    /// seven-note modes have one.
379    pub fn relative_major(&self) -> Result<Scale> {
380        self.relative(ScaleType::Major)
381    }
382
383    /// The minor scale written with the same key signature: music21's
384    /// `getRelativeMinor`.
385    pub fn relative_minor(&self) -> Result<Scale> {
386        self.relative(ScaleType::Minor)
387    }
388
389    /// The major scale on the same tonic: music21's `getParallelMajor`.
390    pub fn parallel_major(&self) -> Scale {
391        Scale::new(ScaleType::Major, self.tonic.clone())
392    }
393
394    /// The minor scale on the same tonic: music21's `getParallelMinor`.
395    pub fn parallel_minor(&self) -> Scale {
396        Scale::new(ScaleType::Minor, self.tonic.clone())
397    }
398
399    /// The scale of the wanted type carrying this one's key signature.
400    ///
401    /// It stands at or above this scale, in the same octave where that is
402    /// possible: the relative major of A minor on `A4` is C major on `C5`,
403    /// because a `C4` would sound below the scale it came from.
404    fn relative(&self, wanted: ScaleType) -> Result<Scale> {
405        let mode = self.scale_type.music21_descriptive_name();
406        let sharps = crate::key::pitch_to_sharps(&self.tonic, Some(mode))?;
407        let key = crate::key::KeySignature::new(sharps)
408            .try_as_key(Some(wanted.music21_descriptive_name()), None)?;
409        let here = self.realized_tonic();
410        let mut tonic = key.tonic();
411        tonic.set_octave(here.octave());
412        if tonic.ps() < here.ps() {
413            tonic.set_octave(tonic.octave().map(|octave| octave + 1));
414        }
415        Ok(Scale::new(wanted, tonic))
416    }
417
418    /// The degree each of this scale's notes stands on, in order from the
419    /// tonic, where those are not simply the notes counted off.
420    ///
421    /// Only a named pattern can say so — a collection given by its notes is
422    /// counted — and only one of them does: see
423    /// [`ScaleType::ascending_degrees`].
424    pub fn named_degrees(&self) -> Option<Vec<IntegerType>> {
425        if self.custom_steps.is_some() {
426            return None;
427        }
428        Some(
429            self.scale_type
430                .ascending_degrees()?
431                .iter()
432                .map(|&degree| IntegerType::from(degree))
433                .collect(),
434        )
435    }
436
437    /// The degree the note at this position stands on, counting from one.
438    fn degree_at_position(&self, position: usize) -> usize {
439        self.named_degrees()
440            .and_then(|degrees| degrees.get(position).copied())
441            .map_or(position + 1, |degree| degree as usize)
442    }
443
444    /// Returns the pitch standing on a one-based scale degree, or nothing
445    /// where the scale has no such degree.
446    ///
447    /// Degree 1 is the tonic. Every other degree is read within the one
448    /// octave the scale is realized in, so the eighth degree is the tonic
449    /// again and not the octave above it, and the zeroth and the negative
450    /// degrees count back round from the top. That is music21's
451    /// `pitchFromDegree`, which asks its interval network for the node the
452    /// degree names and gets one of the nodes it has.
453    ///
454    /// A scale that names its degrees has only the ones it names: Rag
455    /// Asawari's ascent has no third, and answers nothing when asked for
456    /// one rather than handing back the note that would be third in line.
457    pub fn pitch_on_degree(&self, degree: IntegerType) -> Result<Option<Pitch>> {
458        let position = match self.named_degrees() {
459            Some(degrees) => match degrees.iter().position(|&named| named == degree) {
460                Some(position) => position,
461                None => return Ok(None),
462            },
463            None => {
464                let count = self.degree_count().max(1) as IntegerType;
465                (degree - 1).rem_euclid(count) as usize
466            }
467        };
468        let simplification = self.scale_type.simplification();
469        let steps = self.walk()?;
470        let mut current = self.realization_start()?;
471        for index in 0..position {
472            current = advance(&current, &steps[index % steps.len()], simplification)?;
473        }
474        Ok(Some(current))
475    }
476
477    /// The pitch standing on a one-based scale degree, which the scale is
478    /// expected to have: [`Self::pitch_on_degree`] is the one that says when
479    /// it does not.
480    pub fn pitch_at_degree(&self, degree: IntegerType) -> Result<Pitch> {
481        self.pitch_on_degree(degree)?.ok_or_else(|| {
482            Error::Scale(format!(
483                "{} has no degree {degree}",
484                self.scale_type.music21_descriptive_name()
485            ))
486        })
487    }
488    /// Returns every pitch of the scale from `minimum` up to `maximum`,
489    /// inclusive: music21's `getPitches` given a range.
490    ///
491    /// The scale is realized from the tonic in whatever octave puts it at or
492    /// below the bottom of the range, then walked upward, so asking a C major
493    /// scale for `E-5` to `G-7` starts at `E5` — the first scale pitch that
494    /// is not below the bottom — and not at a respelled `E-5`.
495    pub fn pitches_between(&self, minimum: &Pitch, maximum: &Pitch) -> Result<Vec<Pitch>> {
496        // Asked the other way round, music21 walks down instead: the same
497        // pitches, highest first.
498        if maximum.ps() < minimum.ps() {
499            let mut descending = self.pitches_between(maximum, minimum)?;
500            descending.reverse();
501            return Ok(descending);
502        }
503        let lowest = minimum.ps();
504        let highest = maximum.ps();
505        let simplification = self.scale_type.simplification();
506        let steps = self.walk()?;
507        // Down whole periods until the start is at or below the range. A
508        // period is usually the octave, but a scale given by its notes may
509        // take two to come back to where it began, and dropping by one would
510        // start the pattern halfway through itself.
511        let period = self.period_in_octaves(&steps);
512        let mut current = self.realization_start()?;
513        while current.ps() > lowest {
514            let octave = current.octave().unwrap_or(0);
515            current.octave_setter(Some(octave - period));
516        }
517        let mut pitches = Vec::new();
518        // Two octaves of headroom past the range, so a scale whose degrees
519        // are not evenly spaced still reaches the top of it.
520        let limit = steps.len() * (MAX_RANGE_OCTAVES + 2) + 1;
521        // A pattern may rise above the range and fall back into it — Rag
522        // Marwa's descending form goes up to the flat second above its
523        // octave and closes on the octave below that — so the walk carries
524        // on until it is clear of the range by a whole period.
525        let clear_of = highest + 12.0 * FloatType::from(period);
526        for index in 0..limit {
527            let sounding = current.ps();
528            if sounding > clear_of {
529                break;
530            }
531            if (lowest..=highest).contains(&sounding) {
532                pitches.push(current.clone());
533            }
534            current = advance(&current, &steps[index % steps.len()], simplification)?;
535        }
536        Ok(pitches)
537    }
538
539    /// Whether the pattern can be walked at all.
540    ///
541    /// A collection given by its notes may rise and fall back to where it
542    /// began — `A4 B4 C4 D4 E4 F4 G4 A4` does — and a pattern that goes
543    /// nowhere cannot be realized over a range, however many times it is
544    /// walked. music21 says so as well, out of the network it walks.
545    pub fn is_realizable(&self) -> bool {
546        match &self.custom_steps {
547            Some(steps) => steps.iter().map(Interval::semitones).sum::<FloatType>() > 0.0,
548            None => true,
549        }
550    }
551
552    /// Whether the pattern repeats at the octave: music21's
553    /// `octaveDuplicating`. Every named scale does, and one given by its
554    /// notes need not — a collection spanning two octaves before it comes
555    /// back to its tonic is a pattern two octaves long.
556    pub fn octave_duplicating(&self) -> bool {
557        match &self.custom_steps {
558            Some(steps) => self.period_in_octaves(steps) == 1,
559            None => true,
560        }
561    }
562
563    /// How many octaves the pattern takes to come back to where it began,
564    /// which is one for every scale that has a name and may be more for one
565    /// given by its notes.
566    fn period_in_octaves(&self, steps: &[Interval]) -> IntegerType {
567        let semitones: FloatType = steps.iter().map(Interval::semitones).sum();
568        ((semitones / 12.0).round() as IntegerType).max(1)
569    }
570
571    /// The note the scale comes to rest on, as it sounds: music21's
572    /// `getTonic`, which is the fourth degree of a plagal mode.
573    pub fn final_pitch(&self) -> Result<Pitch> {
574        self.pitch_at_degree(self.scale_type.tonic_degree() as IntegerType)
575    }
576
577    /// The reciting tone: music21's `getDominant`.
578    pub fn dominant(&self) -> Result<Pitch> {
579        self.pitch_at_degree(self.scale_type.dominant_degree() as IntegerType)
580    }
581
582    /// The seventh degree raised or lowered to sit a semitone below the
583    /// final: music21's `getLeadingTone`, which in a minor scale is not the
584    /// seventh degree the scale itself has.
585    pub fn leading_tone(&self) -> Result<Pitch> {
586        let seventh = self.pitch_at_degree(7)?;
587        let tonic = self.final_pitch()?;
588        let distance = seventh.midi() - tonic.midi();
589        if distance == 11 {
590            return Ok(seventh);
591        }
592        let alter = seventh.accidental().alter() + FloatType::from(11 - distance);
593        let mut raised = seventh.clone();
594        raised.set_accidental(Some(crate::pitch::Accidental::new(alter)?));
595        Ok(raised)
596    }
597
598    /// Returns the scale of the same type on which `pitch` is the given degree:
599    /// music21's `deriveByDegree`, so the major scale with `E` as its fifth is
600    /// A major. The pitch keeps its spelling; a pitch without an octave is
601    /// read in octave 4, as music21 reads it, so the new tonic has one.
602    pub fn derive_by_degree(&self, degree: usize, pitch: &Pitch) -> Result<Scale> {
603        let implicit_octave = Some(crate::defaults::PITCH_OCTAVE as IntegerType);
604        let mut tonic = self.tonic.clone();
605        if tonic.octave().is_none() {
606            tonic.octave_setter(implicit_octave);
607        }
608        let degree_pitch =
609            Scale::new(self.scale_type, tonic.clone()).pitch_at_degree(degree as IntegerType)?;
610        let up_to_degree = Interval::between_pitches(&tonic, &degree_pitch)?;
611        let mut reference = pitch.clone();
612        if reference.octave().is_none() {
613            reference.octave_setter(implicit_octave);
614        }
615        let new_tonic = reference.transpose(&up_to_degree.reversed()?)?;
616        Ok(Scale::new(self.scale_type, new_tonic))
617    }
618
619    /// Returns the same scale type on the tonic transposed by `interval`.
620    pub fn transpose(&self, interval: &Interval) -> Result<Scale> {
621        Ok(Scale::new(self.scale_type, self.tonic.transpose(interval)?))
622    }
623
624    /// Returns one octave of the scale as a chord, tonic through octave:
625    /// music21's `getChord`.
626    pub fn chord(&self) -> Result<Chord> {
627        Chord::new(self.pitches()?.as_slice())
628    }
629
630    /// Returns the pitches at the given degrees within one octave of the
631    /// tonic: music21's `pitchesFromScaleDegrees`, which realizes tonic
632    /// through octave once and so silently drops a degree beyond the octave.
633    pub fn pitches_from_scale_degrees(&self, degrees: &[usize]) -> Result<Vec<Pitch>> {
634        let octave = self.pitches()?;
635        // The realization closes on the tonic an octave up, and that closing
636        // pitch is degree one again — music21 asks the whole realization
637        // which of its pitches stand on the degrees wanted, so the first
638        // degree of A minor answers with both `A3` and `A4`.
639        let count = octave.len().saturating_sub(1).max(1);
640        Ok(octave
641            .into_iter()
642            .enumerate()
643            .filter(|(index, _)| degrees.contains(&(index % count + 1)))
644            .map(|(_, pitch)| pitch)
645            .collect())
646    }
647
648    /// Every pitch of the named degrees between two pitches: music21's
649    /// `pitchesFromScaleDegrees` given a range, so the third and seventh of
650    /// C major from `c2` to `c6` are `D2 G2 D3 G3 D4 G4 D5 G5`.
651    pub fn pitches_from_scale_degrees_between(
652        &self,
653        degrees: &[usize],
654        minimum: &Pitch,
655        maximum: &Pitch,
656    ) -> Result<Vec<Pitch>> {
657        let wanted: Vec<String> = self
658            .pitches_from_scale_degrees(degrees)?
659            .iter()
660            .map(Pitch::name)
661            .collect();
662        Ok(self
663            .pitches_between(minimum, maximum)?
664            .into_iter()
665            .filter(|pitch| wanted.contains(&pitch.name()))
666            .collect())
667    }
668
669    /// Returns the interval from one degree to another, both folded into the
670    /// first octave the way music21's `pitchFromDegree` folds them, so degree
671    /// 9 of a seven-note scale is degree 2 and the interval from 2 to 9 is a
672    /// unison.
673    pub fn interval_between_degrees(&self, start: usize, end: usize) -> Result<Interval> {
674        Interval::between_pitches(
675            &self.pitch_at_degree(start as IntegerType)?,
676            &self.pitch_at_degree(end as IntegerType)?,
677        )
678    }
679
680    /// Returns whether `other` is the scale pitch `steps` degrees above
681    /// `origin`, compared by name so the octave does not matter: music21's
682    /// `isNext`.
683    pub fn is_next(&self, other: &Pitch, origin: &Pitch, steps: usize) -> Result<bool> {
684        Ok(self.next_pitch_above(origin, steps)?.name() == other.name())
685    }
686
687    /// Splits pitches into those whose names the scale contains and those it
688    /// does not: music21's `match`. The matched list carries the scale's own
689    /// pitches, realized from the tonic in octave 4 when it has none, and
690    /// the unmatched list carries the pitches as given.
691    pub fn match_pitches(&self, pitches: &[Pitch]) -> Result<(Vec<Pitch>, Vec<Pitch>)> {
692        self.match_pitches_by(pitches, DegreeComparison::Name)
693    }
694
695    /// The same, saying how a pitch is matched against a degree.
696    ///
697    /// Both lists hold the pitches as given rather than the scale's own —
698    /// music21 hands its targets straight back — except that one with no
699    /// octave is heard in octave 4, since that is where the scale sounds.
700    pub fn match_pitches_by(
701        &self,
702        pitches: &[Pitch],
703        comparison: DegreeComparison,
704    ) -> Result<(Vec<Pitch>, Vec<Pitch>)> {
705        let realized = self.realized_in_implicit_octave()?;
706        let degrees: Vec<String> = realized.iter().map(|p| comparison.key(p)).collect();
707        let mut matched = Vec::new();
708        let mut unmatched = Vec::new();
709        for pitch in pitches {
710            let mut heard = pitch.clone();
711            if heard.octave().is_none() {
712                heard.octave_setter(Some(crate::defaults::PITCH_OCTAVE as IntegerType));
713            }
714            if degrees.contains(&comparison.key(&heard)) {
715                matched.push(heard);
716            } else {
717                unmatched.push(heard);
718            }
719        }
720        Ok((matched, unmatched))
721    }
722
723    /// Returns the scale pitches, tonic through octave, whose pitch classes
724    /// none of `pitches` has: music21's `findMissing`, so C major against
725    /// `C E G` is `D4 F4 A4 B4`.
726    pub fn find_missing(&self, pitches: &[Pitch]) -> Result<Vec<Pitch>> {
727        let present: Vec<u8> = pitches.iter().map(root::pitch_class).collect();
728        Ok(self
729            .realized_in_implicit_octave()?
730            .into_iter()
731            .filter(|candidate| !present.contains(&root::pitch_class(candidate)))
732            .collect())
733    }
734
735    /// Returns the solfège syllable for a pitch, `do` through `ti` with the
736    /// chromatic inflections (`di`, `ra`, …): music21's `solfeg`. Without
737    /// `chromatic` the plain syllable of the degree is returned whatever the
738    /// accidental. Errors for degrees past seven and alterations past a
739    /// double sharp or flat.
740    pub fn solfeg(&self, pitch: &Pitch, variant: SolfegVariant, chromatic: bool) -> Result<String> {
741        let (degree, accidental) = self.degree_and_accidental_of(pitch)?;
742        if degree > 7 {
743            return Err(crate::error::Error::Scale(
744                "Cannot call solfeg on non-7-degree scales".to_string(),
745            ));
746        }
747        let table = match variant {
748            SolfegVariant::Music21 => &SOLFEG_SYLLABLES,
749            SolfegVariant::Humdrum => &HUMDRUM_SOLFEG_SYLLABLES,
750        };
751        let alter = if chromatic {
752            accidental.map_or(0, |accidental| accidental.alter() as IntegerType)
753        } else {
754            0
755        };
756        let column = usize::try_from(alter + 2)
757            .ok()
758            .filter(|column| *column < 5)
759            .ok_or_else(|| {
760                crate::error::Error::Scale(format!(
761                    "no solfeg syllable for an alteration of {alter}"
762                ))
763            })?;
764        Ok(table[degree - 1][column].to_string())
765    }
766
767    fn realized_in_implicit_octave(&self) -> Result<Vec<Pitch>> {
768        self.pitches()
769    }
770
771    /// Returns the one-based degree matching a pitch under the given
772    /// comparison, or `None` when the scale does not have it.
773    pub fn degree_of_by(
774        &self,
775        pitch: &Pitch,
776        comparison: DegreeComparison,
777    ) -> Result<Option<usize>> {
778        let wanted = comparison.key(pitch);
779        Ok(self
780            .scale_pitches()?
781            .iter()
782            .position(|candidate| comparison.key(candidate) == wanted)
783            .map(|index| self.degree_at_position(index)))
784    }
785
786    /// Every one-based degree the pitch stands on.
787    ///
788    /// A scale may name the same note twice — Rag Marwa's A is both its
789    /// fifth degree and its seventh, since the pattern dips before it closes
790    /// — and music21 chooses between them at random. The choosing is left to
791    /// the caller; this says what there is to choose from.
792    pub fn degrees_of_by(&self, pitch: &Pitch, comparison: DegreeComparison) -> Result<Vec<usize>> {
793        let wanted = comparison.key(pitch);
794        Ok(self
795            .scale_pitches()?
796            .iter()
797            .enumerate()
798            .filter(|(_, candidate)| comparison.key(candidate) == wanted)
799            .map(|(index, _)| self.degree_at_position(index))
800            .collect())
801    }
802
803    /// Returns the one-based degree whose pitch name matches, ignoring octave,
804    /// or `None` when the pitch is not in the scale.
805    pub fn degree_of(&self, pitch: &Pitch) -> Result<Option<usize>> {
806        let name = pitch.name();
807        Ok(self
808            .scale_pitches()?
809            .iter()
810            .position(|candidate| candidate.name() == name)
811            .map(|index| self.degree_at_position(index)))
812    }
813
814    /// Returns the one-based degree whose pitch class matches, so `F-` finds
815    /// the `E` of C major.
816    pub fn degree_of_pitch_class(&self, pitch: &Pitch) -> Result<Option<usize>> {
817        let pitch_class = pitch.pitch_class().number();
818        Ok(self
819            .scale_pitches()?
820            .iter()
821            .position(|candidate| candidate.pitch_class().number() == pitch_class)
822            .map(|index| self.degree_at_position(index)))
823    }
824
825    /// Returns the scale pitch `steps` degrees above `origin`. A pitch outside
826    /// the scale first moves to the nearest scale pitch above it.
827    pub fn next_pitch_above(&self, origin: &Pitch, steps: usize) -> Result<Pitch> {
828        self.pitch_steps_from(origin, steps as IntegerType, None)
829    }
830
831    /// The same, told which side of a pitch outside the scale to start from:
832    /// music21's `getNeighbor`, where naming a side means stepping the whole
833    /// way from the neighbour on it rather than counting the move onto the
834    /// scale as one of the steps.
835    pub fn next_pitch_beside(
836        &self,
837        origin: &Pitch,
838        steps: IntegerType,
839        below: bool,
840    ) -> Result<Pitch> {
841        self.pitch_steps_from(origin, steps, Some(below))
842    }
843
844    /// Returns the scale pitch `steps` degrees below `origin`. A pitch outside
845    /// the scale first moves to the nearest scale pitch below it.
846    pub fn next_pitch_below(&self, origin: &Pitch, steps: usize) -> Result<Pitch> {
847        self.pitch_steps_from(origin, -(steps as IntegerType), None)
848    }
849
850    /// Returns the degree a pitch sits on together with the accidental that
851    /// separates it from the scale's own spelling of that degree, so `E-` in
852    /// C major is degree three with a flat. Errors when no degree shares the
853    /// pitch's letter.
854    pub fn degree_and_accidental_of(
855        &self,
856        pitch: &Pitch,
857    ) -> Result<(usize, Option<crate::pitch::Accidental>)> {
858        if let Some(degree) = self.degree_of(pitch)? {
859            return Ok((degree, None));
860        }
861        let pitches = self.scale_pitches()?;
862        let index = pitches
863            .iter()
864            .position(|candidate| candidate.step() == pitch.step())
865            .ok_or_else(|| {
866                crate::error::Error::Scale(format!(
867                    "cannot get any scale degree for {pitch} in {self:?}"
868                ))
869            })?;
870        let difference = pitch.accidental().alter() - pitches[index].accidental().alter();
871        let accidental = if difference == 0.0 {
872            None
873        } else {
874            Some(crate::pitch::Accidental::new(difference)?)
875        };
876        Ok((index + 1, accidental))
877    }
878
879    fn scale_pitches(&self) -> Result<Vec<Pitch>> {
880        let mut pitches = self.pitches()?;
881        pitches.truncate(self.scale_type.degree_count());
882        Ok(pitches)
883    }
884
885    fn pitch_steps_from(
886        &self,
887        origin: &Pitch,
888        steps: IntegerType,
889        neighbour_below: Option<bool>,
890    ) -> Result<Pitch> {
891        self.pitch_steps_from_place(origin, steps, neighbour_below, 0)
892    }
893
894    /// How many places in this scale's realization stand on `origin`.
895    ///
896    /// A scale may name the same note twice: Rag Marwa's `D-` is both the
897    /// note above its tonic and the one it passes through coming down from
898    /// the octave, and where the next note is depends on which of them is
899    /// meant. music21 chooses between them at random — the choosing is the
900    /// caller's, and [`Self::next_pitch_below_from`] takes it.
901    pub fn places_of(&self, origin: &Pitch) -> Result<usize> {
902        Ok(self.places_on(origin)?.len())
903    }
904
905    /// The places of the realization, and the octave shift each stands at,
906    /// that sound `origin`.
907    fn places_on(&self, origin: &Pitch) -> Result<Vec<(usize, IntegerType)>> {
908        let pitches = self.scale_pitches()?;
909        let origin_ps = origin.ps();
910        let name = origin.name();
911        let base_shift = ((origin_ps - self.tonic.ps()) / 12.0).floor() as IntegerType;
912        Ok((base_shift - 1..=base_shift + 1)
913            .flat_map(|shift| {
914                pitches.iter().enumerate().map(move |(index, pitch)| {
915                    (pitch.ps() + 12.0 * shift as FloatType, index, shift)
916                })
917            })
918            .filter(|(ps, index, _)| {
919                pitches[*index].name() == name && (ps - origin_ps).abs() < 1e-9
920            })
921            .map(|(_, index, shift)| (index, shift))
922            .collect())
923    }
924
925    /// The note `steps` below `origin`, read as the `place`-th of the places
926    /// this scale stands that note on. See [`Self::places_of`].
927    pub fn next_pitch_below_from(
928        &self,
929        origin: &Pitch,
930        steps: usize,
931        place: usize,
932    ) -> Result<Pitch> {
933        self.pitch_steps_from_place(origin, -(steps as IntegerType), None, place)
934    }
935
936    /// The note `steps` above `origin`, read the same way.
937    pub fn next_pitch_above_from(
938        &self,
939        origin: &Pitch,
940        steps: usize,
941        place: usize,
942    ) -> Result<Pitch> {
943        self.pitch_steps_from_place(origin, steps as IntegerType, None, place)
944    }
945
946    fn pitch_steps_from_place(
947        &self,
948        origin: &Pitch,
949        steps: IntegerType,
950        neighbour_below: Option<bool>,
951        place: usize,
952    ) -> Result<Pitch> {
953        if steps == 0 {
954            return Err(crate::error::Error::Scale(
955                "step size must be at least 1".to_string(),
956            ));
957        }
958        let pitches = self.scale_pitches()?;
959        let count = pitches.len() as IntegerType;
960        let origin_ps = origin.ps();
961        let base_shift = ((origin_ps - self.tonic.ps()) / 12.0).floor() as IntegerType;
962        let candidates = (base_shift - 1..=base_shift + 1)
963            .flat_map(|shift| {
964                pitches.iter().enumerate().map(move |(index, pitch)| {
965                    (pitch.ps() + 12.0 * shift as FloatType, index, shift)
966                })
967            })
968            .collect::<Vec<_>>();
969        let ascending = steps > 0;
970        let standing = self.places_on(origin)?;
971        let (index, shift, remaining) = match standing.get(place % standing.len().max(1)).copied() {
972            Some((index, shift)) => (index, shift, steps),
973            None => {
974                // Which side of the pitch to come onto the scale at: the way
975                // the move is going unless a caller has said otherwise, and
976                // then the move onto the scale counts as one of the steps.
977                let take_below = neighbour_below.unwrap_or(!ascending);
978                let neighbour = if take_below {
979                    candidates
980                        .iter()
981                        .filter(|(ps, _, _)| *ps < origin_ps)
982                        .max_by(|left, right| left.0.total_cmp(&right.0))
983                } else {
984                    candidates
985                        .iter()
986                        .filter(|(ps, _, _)| *ps > origin_ps)
987                        .min_by(|left, right| left.0.total_cmp(&right.0))
988                };
989                let &(_, index, shift) = neighbour.ok_or_else(|| {
990                    crate::error::Error::Scale(format!("no scale pitch beside {origin}"))
991                })?;
992                let remaining = if neighbour_below.is_some() {
993                    steps
994                } else {
995                    steps - steps.signum()
996                };
997                (index, shift, remaining)
998            }
999        };
1000        let total = index as IntegerType + remaining;
1001        let mut pitch = pitches[total.rem_euclid(count) as usize].clone();
1002        let octave_shift = shift + total.div_euclid(count);
1003        let octave = origin.octave().map(|_| {
1004            pitch
1005                .octave()
1006                .unwrap_or(crate::defaults::PITCH_OCTAVE as IntegerType)
1007                + octave_shift
1008        });
1009        pitch.octave_setter(octave);
1010        Ok(pitch)
1011    }
1012}
1013
1014/// A collection's notes with the octaves a caller left out filled in so that
1015/// the collection rises: music21's `fixDefaultOctaveForPitchList`.
1016///
1017/// `A B C D E F G# A` is a scale on A, not a collection that climbs a tone
1018/// and then falls a seventh, and a caller who names no octaves means the
1019/// first. Notes that carry an octave are left exactly as they are.
1020pub(super) fn rising_octaves(pitches: &[Pitch]) -> Vec<Pitch> {
1021    let mut risen: Vec<Pitch> = Vec::with_capacity(pitches.len());
1022    let mut last_ps = 0.0;
1023    let mut last_octave =
1024        pitches
1025            .first()
1026            .map_or(crate::defaults::PITCH_OCTAVE as IntegerType, |pitch| {
1027                pitch
1028                    .octave()
1029                    .unwrap_or(crate::defaults::PITCH_OCTAVE as IntegerType)
1030            });
1031    for pitch in pitches {
1032        let mut pitch = pitch.clone();
1033        if pitch.octave().is_none() {
1034            if last_ps > pitch.ps() {
1035                pitch.octave_setter(Some(last_octave));
1036            }
1037            while last_ps > pitch.ps() {
1038                last_octave += 1;
1039                pitch.octave_setter(Some(last_octave));
1040            }
1041        }
1042        last_ps = pitch.ps();
1043        risen.push(pitch);
1044    }
1045    risen
1046}