Skip to main content

music21_rs/figuredbass/
segment.rs

1//! One bass note and its figures, and every way of voicing them: music21's
2//! `figuredBass.segment`.
3//!
4//! A [`Segment`] knows the notes its bass and figures stand for, and every
5//! pitch at or above the bass that spells one of them. A possibility is one
6//! pitch from those for each upper part and the bass itself for the lowest;
7//! the segment lists every possibility, keeps those its rules accept, and --
8//! given the segment that follows -- every pair of possibilities that may
9//! move from one to the other, resolving a dominant seventh, a diminished
10//! seventh or an augmented sixth by the rules those chords resolve by.
11
12use crate::chord::Chord;
13use crate::defaults::FloatType;
14use crate::error::{Error, Result};
15use crate::figuredbass::Notation;
16use crate::figuredbass::possibility::{self, ItalianSixth};
17use crate::figuredbass::resolution::{self, AugmentedSixth, ChordInfo};
18use crate::figuredbass::rules::Rules;
19use crate::figuredbass::scale::FiguredBassScale;
20use crate::interval::Interval;
21use crate::pitch::Pitch;
22use crate::scale::{Scale, ScaleType};
23
24/// A voicing: a pitch for each part, highest part first, the bass last.
25pub type Possibility = Vec<Pitch>;
26
27/// A voicing in one segment beside a voicing in the next.
28pub type PossibilityPair = (Possibility, Possibility);
29
30/// The pairs of voicings two segments may move between, and why they were
31/// found as they were.
32#[derive(Clone, Debug, PartialEq)]
33pub struct Consecutive {
34    /// Every pair, in the order music21 lists them.
35    pub pairs: Vec<PossibilityPair>,
36    /// Where a chord that resolves by a rule of its own found no rule to
37    /// resolve to the next chord by, and the pairs were found as between any
38    /// two chords: music21's warning, word for word.
39    pub fallback: Option<String>,
40}
41
42impl Consecutive {
43    fn resolved(pairs: Vec<PossibilityPair>) -> Self {
44        Self {
45            pairs,
46            fallback: None,
47        }
48    }
49}
50
51/// A bass note, its figures, and the voicings they allow.
52#[derive(Clone, Debug)]
53#[must_use]
54pub struct Segment {
55    bass: Pitch,
56    quarter_length: FloatType,
57    num_parts: usize,
58    max_pitch: Pitch,
59    pitch_names: Vec<String>,
60    pitches_above_bass: Vec<Pitch>,
61    chord: Chord,
62    overlaid: bool,
63    /// The rules its voicings are held to.
64    pub rules: Rules,
65}
66
67impl Segment {
68    /// The segment of `bass` under `notation`, read in `scale`, voiced in
69    /// `num_parts` parts no higher than `max_pitch`: music21's `Segment`.
70    ///
71    /// # Errors
72    ///
73    /// A bass or figures the scale cannot read, or a maximum pitch with no
74    /// octave.
75    pub fn new(
76        bass: Pitch,
77        quarter_length: FloatType,
78        notation: &Notation,
79        scale: &FiguredBassScale,
80        rules: Rules,
81        num_parts: usize,
82        max_pitch: Pitch,
83    ) -> Result<Self> {
84        let names = scale.pitch_names(&bass, notation)?;
85        Self::from_pitch_names(bass, quarter_length, names, rules, num_parts, max_pitch)
86    }
87
88    /// The segment of `bass` spelling the notes named `pitch_names`, as
89    /// music21 builds one given `listOfPitches` rather than figures.
90    ///
91    /// # Errors
92    ///
93    /// A pitch name that is none, or a maximum pitch with no octave.
94    pub fn from_pitch_names(
95        bass: Pitch,
96        quarter_length: FloatType,
97        pitch_names: Vec<String>,
98        rules: Rules,
99        num_parts: usize,
100        max_pitch: Pitch,
101    ) -> Result<Self> {
102        let pitches_above_bass = pitches(&pitch_names, &bass, &max_pitch)?;
103        let chord = Chord::new(pitches_above_bass.as_slice())?;
104        Ok(Self {
105            bass,
106            quarter_length,
107            num_parts,
108            max_pitch,
109            pitch_names,
110            pitches_above_bass,
111            chord,
112            overlaid: false,
113            rules,
114        })
115    }
116
117    /// The same segment, with the parts its rules hold to a pitch sung at
118    /// that pitch in every voicing: music21's `OverlaidSegment`.
119    pub fn overlaid(mut self) -> Self {
120        self.overlaid = true;
121        self
122    }
123
124    /// The bass.
125    pub fn bass(&self) -> &Pitch {
126        &self.bass
127    }
128
129    /// How long the bass lasts, in quarter notes.
130    pub fn quarter_length(&self) -> FloatType {
131        self.quarter_length
132    }
133
134    /// How many parts sing it, the bass among them.
135    pub fn num_parts(&self) -> usize {
136        self.num_parts
137    }
138
139    /// The highest pitch any part may sing.
140    pub fn max_pitch(&self) -> &Pitch {
141        &self.max_pitch
142    }
143
144    /// The names of the notes the bass and figures stand for, the bass's
145    /// first: music21's `pitchNamesInChord`.
146    pub fn pitch_names(&self) -> &[String] {
147        &self.pitch_names
148    }
149
150    /// Every pitch from the bass up to the highest allowed that spells one
151    /// of those notes, lowest first: music21's `allPitchesAboveBass`.
152    pub fn pitches_above_bass(&self) -> &[Pitch] {
153        &self.pitches_above_bass
154    }
155
156    /// Those pitches as one chord: music21's `segmentChord`.
157    pub fn chord(&self) -> &Chord {
158        &self.chord
159    }
160
161    /// The pitches each part may sing, highest part first: the pitches
162    /// above the bass for each upper part, and the bass for the lowest, with
163    /// the parts an overlaid segment holds to a pitch held to it.
164    fn choices(&self) -> Result<Vec<Vec<Pitch>>> {
165        let upper = self.num_parts.saturating_sub(1);
166        let mut choices: Vec<Vec<Pitch>> = vec![self.pitches_above_bass.clone(); upper];
167        choices.push(vec![Pitch::from_name(self.bass.name_with_octave())?]);
168        if self.overlaid {
169            for (part, pitch) in &self.rules.part_pitch_limits {
170                let slot = part.checked_sub(1).and_then(|index| choices.get_mut(index));
171                let Some(slot) = slot else {
172                    return Err(Error::FiguredBass(format!(
173                        "a segment of {} parts has no part {part}",
174                        self.num_parts
175                    )));
176                };
177                *slot = vec![Pitch::from_name(pitch.name_with_octave())?];
178            }
179        }
180        Ok(choices)
181    }
182
183    /// Every voicing, acceptable or not, in music21's order: the highest
184    /// part changes slowest. music21's `allSinglePossibilities`.
185    pub fn all_single_possibilities(&self) -> Result<Vec<Possibility>> {
186        Ok(product(&self.choices()?))
187    }
188
189    /// The voicings its rules accept: music21's
190    /// `allCorrectSinglePossibilities`.
191    ///
192    /// The voicings are walked as indices into what each part may sing, and
193    /// held to the rules over each pitch's place and name worked out once, so
194    /// only the voicings kept are ever built: a seventh chord has thousands
195    /// to look at and a handful to keep.
196    pub fn all_correct_single_possibilities(&self) -> Result<Vec<Possibility>> {
197        let choices = self.choices()?;
198        if choices.iter().any(Vec::is_empty) {
199            return Ok(Vec::new());
200        }
201        let rules = &self.rules;
202        // Which of the chord's notes each pitch spells, as bits: a voicing is
203        // complete when its pitches' bits cover every note.
204        let wanted: Option<u64> = if rules.forbid_incomplete_possibilities {
205            if self.pitch_names.len() > 64 {
206                return Ok(product(&choices)
207                    .into_iter()
208                    .filter(|voicing| self.is_correct_single(voicing))
209                    .collect());
210            }
211            Some(
212                self.pitch_names
213                    .iter()
214                    .enumerate()
215                    .fold(0, |bits, (index, _)| bits | 1 << index),
216            )
217        } else {
218            None
219        };
220        let spaces: Vec<Vec<FloatType>> = choices
221            .iter()
222            .map(|part| part.iter().map(Pitch::ps).collect())
223            .collect();
224        let spelled: Vec<Vec<u64>> = choices
225            .iter()
226            .map(|part| {
227                part.iter()
228                    .map(|pitch| {
229                        self.pitch_names
230                            .iter()
231                            .enumerate()
232                            .filter(|(_, name)| pitch.is_named(name))
233                            .fold(0, |bits, (index, _)| bits | 1 << index)
234                    })
235                    .collect()
236            })
237            .collect();
238        let limit = rules
239            .upper_parts_max_semitone_separation
240            .map(FloatType::from);
241        let upper = choices.len() - 1;
242        let mut indices = vec![0; choices.len()];
243        let mut places = vec![0.0; choices.len()];
244        let mut kept = Vec::new();
245        loop {
246            for (part, &index) in indices.iter().enumerate() {
247                places[part] = spaces[part][index];
248            }
249            let complete = wanted.is_none_or(|wanted| {
250                indices
251                    .iter()
252                    .enumerate()
253                    .fold(0, |bits, (part, &index)| bits | spelled[part][index])
254                    & wanted
255                    == wanted
256            });
257            let within = limit.is_none_or(|limit| {
258                (0..upper).all(|high| {
259                    (high + 1..upper).all(|low| (places[high] - places[low]).abs() <= limit)
260                })
261            });
262            let crossing = rules.forbid_voice_crossing
263                && (0..places.len())
264                    .any(|high| (high + 1..places.len()).any(|low| places[high] < places[low]));
265            if complete && within && !crossing {
266                kept.push(
267                    indices
268                        .iter()
269                        .enumerate()
270                        .map(|(part, &index)| choices[part][index].clone())
271                        .collect(),
272                );
273            }
274            let mut position = choices.len();
275            loop {
276                if position == 0 {
277                    return Ok(kept);
278                }
279                position -= 1;
280                indices[position] += 1;
281                if indices[position] < choices[position].len() {
282                    break;
283                }
284                indices[position] = 0;
285            }
286        }
287    }
288
289    /// Whether the rules accept `voicing` on its own.
290    fn is_correct_single(&self, voicing: &[Pitch]) -> bool {
291        let rules = &self.rules;
292        !(rules.forbid_incomplete_possibilities
293            && possibility::is_incomplete(voicing, &self.pitch_names))
294            && possibility::upper_parts_within_limit(
295                voicing,
296                rules.upper_parts_max_semitone_separation,
297            )
298            && !(rules.forbid_voice_crossing && possibility::voice_crossing(voicing))
299    }
300
301    /// Whether the rules accept moving from `from` to `to`, a voicing of the
302    /// segment after this one.
303    fn is_correct_consecutive(
304        &self,
305        from: &[Pitch],
306        to: &[Pitch],
307        italian: Option<&ItalianSixth>,
308    ) -> Result<bool> {
309        let rules = &self.rules;
310        Ok(
311            possibility::parts_same(from, to, Some(&rules.parts_to_check))?
312                && (!rules.upper_parts_remain_same || possibility::upper_parts_same(from, to)?)
313                && !(rules.forbid_voice_overlap && possibility::voice_overlap(from, to)?)
314                && possibility::part_movements_within_limits(
315                    from,
316                    to,
317                    &rules.part_movement_limits,
318                )?
319                && !(rules.forbid_parallel_fifths && possibility::parallel_fifths(from, to)?)
320                && !(rules.forbid_parallel_octaves && possibility::parallel_octaves(from, to)?)
321                && !(rules.forbid_hidden_fifths && possibility::hidden_fifths(from, to)?)
322                && !(rules.forbid_hidden_octaves && possibility::hidden_octaves(from, to)?)
323                && match italian {
324                    Some(sixth) => possibility::could_be_italian_a6_resolution(
325                        from,
326                        to,
327                        Some(sixth),
328                        rules.restrict_doublings_in_italian_a6_resolution,
329                    )?,
330                    None => true,
331                },
332        )
333    }
334
335    /// The Italian sixth an Italian-sixth resolution is judged by, where the
336    /// rules resolve augmented sixths and this chord is one.
337    fn italian_sixth(&self) -> Result<Option<ItalianSixth>> {
338        if self.rules.resolve_augmented_sixth_properly
339            && self.chord.is_italian_augmented_sixth(false, false)
340        {
341            Ok(Some(ItalianSixth::from_chord(&self.chord)?))
342        } else {
343            Ok(None)
344        }
345    }
346
347    /// Every pair of voicings `next` may follow this segment by, resolving
348    /// this chord by its own rule where the rules say to and it is a chord
349    /// with one: music21's `allCorrectConsecutivePossibilities`.
350    ///
351    /// `next` is taken mutably because music21 lets a dominant seventh
352    /// resolving to its tonic leave that tonic incomplete, which it does by
353    /// switching the tonic segment's rule off.
354    ///
355    /// # Errors
356    ///
357    /// Two segments of different numbers of parts or different highest
358    /// pitches, or a resolution that fails.
359    pub fn all_correct_consecutive_possibilities(&self, next: &mut Segment) -> Result<Consecutive> {
360        if self.num_parts != next.num_parts {
361            return Err(Error::FiguredBass(
362                "Two segments with unequal numParts cannot be compared.".to_string(),
363            ));
364        }
365        if self.max_pitch != next.max_pitch {
366            return Err(Error::FiguredBass(
367                "Two segments with unequal maxPitch cannot be compared.".to_string(),
368            ));
369        }
370        let rules = &self.rules;
371        if rules.resolve_dominant_seventh_properly && self.chord.is_dominant_seventh() {
372            return self.resolve_dominant_seventh_segment(next);
373        }
374        if rules.resolve_diminished_seventh_properly && self.chord.is_diminished_seventh() {
375            return self.resolve_diminished_seventh_segment(next, rules.doubled_root_in_dim7);
376        }
377        if rules.resolve_augmented_sixth_properly && self.chord.is_augmented_sixth(false) {
378            return self.resolve_augmented_sixth_segment(next);
379        }
380        Ok(Consecutive::resolved(self.resolve_ordinary(next)?))
381    }
382
383    /// The pairs between two segments where no chord needs a resolution of
384    /// its own: every acceptable voicing of this beside every acceptable
385    /// voicing of `next`, kept where the rules accept the move.
386    fn resolve_ordinary(&self, next: &Segment) -> Result<Vec<PossibilityPair>> {
387        let italian = self.italian_sixth()?;
388        let from = self.all_correct_single_possibilities()?;
389        let to = next.all_correct_single_possibilities()?;
390        let mut pairs = Vec::new();
391        for a in &from {
392            for b in &to {
393                if self.is_correct_consecutive(a, b, italian.as_ref())? {
394                    pairs.push((a.clone(), b.clone()));
395                }
396            }
397        }
398        Ok(pairs)
399    }
400
401    /// Resolves each acceptable voicing by `resolve`, keeping the
402    /// resolutions no part of which sings above `next`'s highest pitch, and
403    /// holding them to the rules the rules say to.
404    fn resolve_special(
405        &self,
406        next: &Segment,
407        resolve: impl Fn(&[Pitch]) -> Result<Vec<Pitch>>,
408    ) -> Result<Vec<PossibilityPair>> {
409        let italian = self.italian_sixth()?;
410        let mut pairs = Vec::new();
411        for voicing in self.all_correct_single_possibilities()? {
412            let resolved = resolve(&voicing)?;
413            if !possibility::pitches_within_limit(&resolved, &next.max_pitch) {
414                continue;
415            }
416            if self.rules.apply_consecutive_possib_rules_to_resolution
417                && !self.is_correct_consecutive(&voicing, &resolved, italian.as_ref())?
418            {
419                continue;
420            }
421            if self.rules.apply_single_possib_rules_to_resolution
422                && !next.is_correct_single(&resolved)
423            {
424                continue;
425            }
426            pairs.push((voicing, resolved));
427        }
428        Ok(pairs)
429    }
430
431    /// The first special resolution whose condition holds, or the ordinary
432    /// one with `warning` where none does.
433    fn resolve_first(
434        &self,
435        next: &Segment,
436        candidates: Vec<(bool, SpecialResolution<'_>)>,
437        warning: &str,
438    ) -> Result<Consecutive> {
439        match candidates.into_iter().find(|(applies, _)| *applies) {
440            Some((_, resolve)) => Ok(Consecutive::resolved(self.resolve_special(next, resolve)?)),
441            None => Ok(Consecutive {
442                pairs: self.resolve_ordinary(next)?,
443                fallback: Some(warning.to_string()),
444            }),
445        }
446    }
447
448    /// Resolves a dominant seventh to `next`: to the tonic, major or minor,
449    /// and from root position also to either submediant or subdominant.
450    /// music21's `resolveDominantSeventhSegment`.
451    ///
452    /// # Errors
453    ///
454    /// A segment whose chord is no dominant seventh.
455    pub fn resolve_dominant_seventh_segment(&self, next: &mut Segment) -> Result<Consecutive> {
456        let dominant = &self.chord;
457        if !dominant.is_dominant_seventh() {
458            return Err(Error::FiguredBass(
459                "Dominant seventh resolution: Not a dominant seventh Segment.".to_string(),
460            ));
461        }
462        let info = ChordInfo::of(dominant);
463        let dominant_scale = ScaleType::Major.derive(dominant.pitches().as_slice())?;
464        let minor_scale = dominant_scale.parallel_minor();
465        let tonic = dominant_scale.tonic().name();
466        let subdominant = dominant_scale.pitch_at_degree(4)?.name();
467        let major_submediant = dominant_scale.pitch_at_degree(6)?.name();
468        let minor_submediant = minor_scale.pitch_at_degree(6)?.name();
469
470        let resolving = next.chord.clone();
471        let second_inversion = dominant.inversion() == Some(2);
472        let v43_to_i6 = second_inversion && resolving.inversion() == Some(1);
473        let root = resolving.root().map(Pitch::name).unwrap_or_default();
474        let major = resolving.is_major_triad();
475        let minor = resolving.is_minor_triad();
476        if dominant.inversion() == Some(0) && root == tonic && (major || minor) {
477            next.rules.forbid_incomplete_possibilities = false;
478        }
479        let info = &info;
480        let candidates: Vec<(bool, SpecialResolution<'_>)> = vec![
481            (
482                root == tonic && major,
483                Box::new(move |voicing: &[Pitch]| {
484                    resolution::dominant_seventh_to_major_tonic(voicing, v43_to_i6, Some(info))
485                }),
486            ),
487            (
488                root == tonic && minor,
489                Box::new(move |voicing: &[Pitch]| {
490                    resolution::dominant_seventh_to_minor_tonic(voicing, v43_to_i6, Some(info))
491                }),
492            ),
493            (
494                root == major_submediant && minor && !second_inversion,
495                Box::new(move |voicing: &[Pitch]| {
496                    resolution::dominant_seventh_to_minor_submediant(voicing, Some(info))
497                }),
498            ),
499            (
500                root == minor_submediant && major && !second_inversion,
501                Box::new(move |voicing: &[Pitch]| {
502                    resolution::dominant_seventh_to_major_submediant(voicing, Some(info))
503                }),
504            ),
505            (
506                root == subdominant && major && !second_inversion,
507                Box::new(move |voicing: &[Pitch]| {
508                    resolution::dominant_seventh_to_major_subdominant(voicing, Some(info))
509                }),
510            ),
511            (
512                root == subdominant && minor && !second_inversion,
513                Box::new(move |voicing: &[Pitch]| {
514                    resolution::dominant_seventh_to_minor_subdominant(voicing, Some(info))
515                }),
516            ),
517        ];
518        self.resolve_first(
519            next,
520            candidates,
521            "Dominant seventh resolution: No proper resolution available. Executing ordinary resolution.",
522        )
523    }
524
525    /// Resolves a diminished seventh to `next`: to the tonic, major or minor,
526    /// or to the subdominant. With `doubled_root`, a resolution to the tonic
527    /// doubles its root, unless the inversions decide it.
528    /// music21's `resolveDiminishedSeventhSegment`.
529    ///
530    /// # Errors
531    ///
532    /// A segment whose chord is no fully diminished seventh.
533    pub fn resolve_diminished_seventh_segment(
534        &self,
535        next: &Segment,
536        doubled_root: bool,
537    ) -> Result<Consecutive> {
538        let diminished = &self.chord;
539        if !diminished.is_diminished_seventh() {
540            return Err(Error::FiguredBass(
541                "Diminished seventh resolution: Not a diminished seventh Segment.".to_string(),
542            ));
543        }
544        let info = ChordInfo::of(diminished);
545        let root = diminished
546            .root()
547            .ok_or_else(|| Error::FiguredBass("a diminished seventh with no root".to_string()))?;
548        let placeholder = Pitch::from_name("C")?;
549        let scale = Scale::new(ScaleType::HarmonicMinor, placeholder).derive_by_degree(7, root)?;
550        let tonic = scale.tonic().name();
551        let subdominant = scale.pitch_at_degree(4)?.name();
552
553        let resolving = &next.chord;
554        let mut doubled_root = doubled_root;
555        if diminished.inversion() == Some(1) {
556            match resolving.inversion() {
557                Some(0) => doubled_root = true,
558                Some(1) => doubled_root = false,
559                _ => {}
560            }
561        }
562        let resolving_root = resolving.root().map(Pitch::name).unwrap_or_default();
563        let major = resolving.is_major_triad();
564        let minor = resolving.is_minor_triad();
565        let info = &info;
566        let candidates: Vec<(bool, SpecialResolution<'_>)> = vec![
567            (
568                resolving_root == tonic && major,
569                Box::new(move |voicing: &[Pitch]| {
570                    resolution::diminished_seventh_to_major_tonic(voicing, doubled_root, Some(info))
571                }),
572            ),
573            (
574                resolving_root == tonic && minor,
575                Box::new(move |voicing: &[Pitch]| {
576                    resolution::diminished_seventh_to_minor_tonic(voicing, doubled_root, Some(info))
577                }),
578            ),
579            (
580                resolving_root == subdominant && major,
581                Box::new(move |voicing: &[Pitch]| {
582                    resolution::diminished_seventh_to_major_subdominant(voicing, Some(info))
583                }),
584            ),
585            (
586                resolving_root == subdominant && minor,
587                Box::new(move |voicing: &[Pitch]| {
588                    resolution::diminished_seventh_to_minor_subdominant(voicing, Some(info))
589                }),
590            ),
591        ];
592        self.resolve_first(
593            next,
594            candidates,
595            "Diminished seventh resolution: No proper resolution available. Executing ordinary resolution.",
596        )
597    }
598
599    /// Resolves an augmented sixth to `next`: to the tonic in second
600    /// inversion, major or minor, or to the dominant. An Italian sixth, and
601    /// a kind music21 has no rule for, resolve as any chord does.
602    /// music21's `resolveAugmentedSixthSegment`.
603    ///
604    /// # Errors
605    ///
606    /// A segment whose chord is no augmented sixth.
607    pub fn resolve_augmented_sixth_segment(&self, next: &Segment) -> Result<Consecutive> {
608        let sixth = &self.chord;
609        if !sixth.is_augmented_sixth(false) {
610            return Err(Error::FiguredBass(
611                "Augmented sixth resolution: Not an augmented sixth Segment.".to_string(),
612            ));
613        }
614        if sixth.is_italian_augmented_sixth(false, false) {
615            return Ok(Consecutive::resolved(self.resolve_ordinary(next)?));
616        }
617        let kind = if sixth.is_french_augmented_sixth(false) {
618            AugmentedSixth::French
619        } else if sixth.is_german_augmented_sixth(false) {
620            AugmentedSixth::German
621        } else if sixth.is_swiss_augmented_sixth(false) {
622            AugmentedSixth::Swiss
623        } else {
624            return Ok(Consecutive {
625                pairs: self.resolve_ordinary(next)?,
626                fallback: Some(
627                    "Augmented sixth resolution: Augmented sixth type not supported. Executing ordinary resolution."
628                        .to_string(),
629                ),
630            });
631        };
632        let bass = sixth
633            .bass()
634            .ok_or_else(|| Error::FiguredBass("an augmented sixth with no bass".to_string()))?;
635        let tonic = bass.transpose(&Interval::from_name("M3")?)?;
636        let dominant = Scale::new(ScaleType::Major, tonic.clone())
637            .pitch_at_degree(5)?
638            .name();
639        let tonic = tonic.name();
640        let info = ChordInfo::of(sixth);
641
642        let resolving = &next.chord;
643        let root = resolving.root().map(Pitch::name).unwrap_or_default();
644        let resolving_bass = resolving.bass().map(Pitch::name).unwrap_or_default();
645        let second_inversion = resolving.inversion() == Some(2);
646        let major = resolving.is_major_triad();
647        let minor = resolving.is_minor_triad();
648        let info = &info;
649        let candidates: Vec<(bool, SpecialResolution<'_>)> = vec![
650            (
651                second_inversion && root == tonic && major,
652                Box::new(move |voicing: &[Pitch]| {
653                    resolution::augmented_sixth_to_major_tonic(voicing, Some(kind), Some(info))
654                }),
655            ),
656            (
657                second_inversion && root == tonic && minor,
658                Box::new(move |voicing: &[Pitch]| {
659                    resolution::augmented_sixth_to_minor_tonic(voicing, Some(kind), Some(info))
660                }),
661            ),
662            (
663                dominant == resolving_bass && major,
664                Box::new(move |voicing: &[Pitch]| {
665                    resolution::augmented_sixth_to_dominant(voicing, Some(kind), Some(info))
666                }),
667            ),
668        ];
669        self.resolve_first(
670            next,
671            candidates,
672            "Augmented sixth resolution: No proper resolution available. Executing ordinary resolution.",
673        )
674    }
675}
676
677/// One way of resolving a voicing.
678type SpecialResolution<'a> = Box<dyn Fn(&[Pitch]) -> Result<Vec<Pitch>> + 'a>;
679
680/// Every choice of one item from each list, the first list changing
681/// slowest, as `itertools.product` gives them.
682fn product(choices: &[Vec<Pitch>]) -> Vec<Possibility> {
683    if choices.iter().any(Vec::is_empty) {
684        return Vec::new();
685    }
686    let mut indices = vec![0; choices.len()];
687    let mut all = Vec::new();
688    loop {
689        all.push(
690            indices
691                .iter()
692                .zip(choices)
693                .map(|(&index, choice)| choice[index].clone())
694                .collect(),
695        );
696        let mut position = choices.len();
697        loop {
698            if position == 0 {
699                return all;
700            }
701            position -= 1;
702            indices[position] += 1;
703            if indices[position] < choices[position].len() {
704                break;
705            }
706            indices[position] = 0;
707        }
708    }
709}
710
711/// Every pitch named in `pitch_names`, in every octave from `bass` up to
712/// `max_pitch`, both included, lowest first: music21's `segment.getPitches`.
713///
714/// # Errors
715///
716/// A maximum pitch with no octave, or a name that is no pitch.
717pub fn pitches<S: AsRef<str>>(
718    pitch_names: &[S],
719    bass: &Pitch,
720    max_pitch: &Pitch,
721) -> Result<Vec<Pitch>> {
722    let Some(top_octave) = max_pitch.octave() else {
723        return Err(Error::Value(
724            "maxPitch must not have an implicit octave".to_string(),
725        ));
726    };
727    let mut found = Vec::new();
728    for name in pitch_names {
729        for octave in 0..=top_octave {
730            let pitch = Pitch::from_name(format!("{}{octave}", name.as_ref()))?;
731            if pitch.ps() >= bass.ps() && pitch.ps() <= max_pitch.ps() {
732                found.push(pitch);
733            }
734        }
735    }
736    found.sort_by(|a, b| a.ps().total_cmp(&b.ps()));
737    Ok(found)
738}