Skip to main content

music21/
voiceleading.rs

1//! Two-voice voice-leading checks, a port of music21's `VoiceLeadingQuartet`.
2//!
3//! A quartet is two consecutive notes in an upper voice and two in a lower
4//! voice. It classifies how the voices move between them and finds the
5//! parallel and hidden perfect intervals that common-practice counterpoint
6//! forbids.
7
8use crate::{
9    defaults::{FloatType, IntegerType},
10    error::{Error, Result},
11    interval::{Interval, IntervalDirection},
12    key::Key,
13    pitch::Pitch,
14    scale::{Scale, ScaleType},
15    stream::{Stream, StreamElement, StreamKind},
16};
17
18use crate::interval::constants::{
19    PERFECT_FIFTH_UP as PERFECT_FIFTH, PERFECT_OCTAVE, PERFECT_UNISON,
20};
21
22/// How two voices move relative to each other.
23#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub enum MotionType {
26    /// Contrary motion between two spellings of the same simple interval,
27    /// such as a fifth opening out to a twelfth. Only reported when asked for.
28    AntiParallel,
29    /// The voices move in opposite directions.
30    Contrary,
31    /// Neither voice moves.
32    NoMotion,
33    /// One voice holds while the other moves.
34    Oblique,
35    /// The voices move the same way and keep the same generic interval.
36    Parallel,
37    /// The voices move the same way through different intervals.
38    Similar,
39}
40
41impl MotionType {
42    /// Returns music21's label for the motion.
43    pub fn as_str(self) -> &'static str {
44        match self {
45            Self::AntiParallel => "Anti-Parallel",
46            Self::Contrary => "Contrary",
47            Self::NoMotion => "No Motion",
48            Self::Oblique => "Oblique",
49            Self::Parallel => "Parallel",
50            Self::Similar => "Similar",
51        }
52    }
53}
54
55impl std::fmt::Display for MotionType {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        f.write_str(self.as_str())
58    }
59}
60
61/// What a caller may ask of a parallel motion.
62///
63/// music21 takes either: a number is how wide the interval must be, whatever
64/// it is spelled — `3` for parallel thirds of any quality — and an interval
65/// is the interval itself, spelling and all.
66#[derive(Clone, Debug, PartialEq)]
67pub enum ParallelRequirement {
68    /// However many steps wide, counted inclusively.
69    Wide(IntegerType),
70    /// This interval exactly.
71    Named(Box<Interval>),
72}
73
74impl From<Interval> for ParallelRequirement {
75    fn from(interval: Interval) -> Self {
76        Self::Named(Box::new(interval))
77    }
78}
79
80impl From<IntegerType> for ParallelRequirement {
81    fn from(steps: IntegerType) -> Self {
82        Self::Wide(steps)
83    }
84}
85
86/// Two consecutive notes in each of two voices. Voice one is the upper voice.
87#[derive(Clone, Debug)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89#[must_use]
90pub struct VoiceLeadingQuartet {
91    v1n1: Pitch,
92    v1n2: Pitch,
93    v2n1: Pitch,
94    v2n2: Pitch,
95    vertical: [Interval; 2],
96    horizontal: [Interval; 2],
97    key: Option<Key>,
98}
99
100impl VoiceLeadingQuartet {
101    /// Builds a quartet from the first and second pitch of the upper voice,
102    /// then the first and second pitch of the lower voice.
103    pub fn new(v1n1: Pitch, v1n2: Pitch, v2n1: Pitch, v2n2: Pitch) -> Result<Self> {
104        let vertical = [
105            Interval::between_pitches(&v1n1, &v2n1)?,
106            Interval::between_pitches(&v1n2, &v2n2)?,
107        ];
108        let horizontal = [
109            Interval::between_pitches(&v1n1, &v1n2)?,
110            Interval::between_pitches(&v2n1, &v2n2)?,
111        ];
112        Ok(Self {
113            v1n1,
114            v1n2,
115            v2n1,
116            v2n2,
117            vertical,
118            horizontal,
119            key: None,
120        })
121    }
122
123    /// Builds a quartet from pitch names, in the same order as [`Self::new`].
124    pub fn from_names(v1n1: &str, v1n2: &str, v2n1: &str, v2n2: &str) -> Result<Self> {
125        Self::new(
126            Pitch::from_name(v1n1)?,
127            Pitch::from_name(v1n2)?,
128            Pitch::from_name(v2n1)?,
129            Pitch::from_name(v2n2)?,
130        )
131    }
132
133    /// Attaches the key the progression is heard in, which
134    /// [`Self::is_proper_resolution`] and [`Self::clausula_vera`] consult.
135    pub fn with_key(mut self, key: Key) -> Self {
136        self.key = Some(key);
137        self
138    }
139
140    /// The key the progression is heard in, if one was given.
141    pub fn key(&self) -> Option<&Key> {
142        self.key.as_ref()
143    }
144
145    /// Attaches a key, or takes one away.
146    pub fn set_key(&mut self, key: Option<Key>) {
147        self.key = key;
148    }
149
150    /// Moves or respells the four pitches and keeps the intervals as they
151    /// were: music21 works its intervals out when a note is set and reads
152    /// the notes as they stand everywhere else, so a note edited in place
153    /// crosses voices without changing the motion.
154    pub fn set_pitches(&mut self, v1n1: Pitch, v1n2: Pitch, v2n1: Pitch, v2n2: Pitch) {
155        self.v1n1 = v1n1;
156        self.v1n2 = v1n2;
157        self.v2n1 = v2n1;
158        self.v2n2 = v2n2;
159    }
160
161    /// Whether a dissonant first interval resolves the way voice-leading
162    /// rules expect: music21's `isProperResolution`. A fourth wants the upper
163    /// voice to fall or stay; a tritone or a minor seventh wants the
164    /// leading tone up, the seventh down and the right contrary motion — and
165    /// with a key set, only when the notes really are those scale degrees.
166    /// Anything else, and no motion at all, is proper.
167    pub fn is_proper_resolution(&self) -> Result<bool> {
168        if self.no_motion() {
169            return Ok(true);
170        }
171        let (lower_degree_before, lower_degree_after) = match &self.key {
172            Some(key) => {
173                // music21 counts in `key.getScale(key.mode)`, the scale the
174                // key's signature implies, which a tonic moved by hand
175                // leaves behind; it has no such scale for a church mode, for
176                // which the crate counts in the mode itself.
177                let scale = match key.mode() {
178                    "major" | "minor" => key.key_signature().scale(key.mode())?,
179                    _ => key.scale(),
180                };
181                let mut before = scale.degree_of(&self.v2n1)?;
182                let after = scale.degree_of(&self.v2n2)?;
183                if key.mode() == "minor" && before.is_none() {
184                    before =
185                        Scale::new(ScaleType::MelodicMinor, key.tonic()).degree_of(&self.v2n1)?;
186                }
187                (before, after)
188            }
189            None => (None, None),
190        };
191        let keyed = self.key.is_some();
192        let first = self.vertical[0].simple_name();
193        let second = self.vertical[1].generic().simple_undirected();
194        Ok(match first.as_str() {
195            "P4" => self.v1n1.ps() >= self.v1n2.ps(),
196            "A4" => {
197                if keyed && lower_degree_before != Some(4) {
198                    true
199                } else if keyed && lower_degree_after != Some(3) {
200                    false
201                } else {
202                    self.outward_contrary_motion() && second == 6
203                }
204            }
205            "d5" => {
206                if keyed && lower_degree_before != Some(7) {
207                    true
208                } else if keyed && lower_degree_after != Some(1) {
209                    false
210                } else {
211                    self.inward_contrary_motion() && second == 3
212                }
213            }
214            "m7" => {
215                if keyed && lower_degree_before != Some(5) {
216                    true
217                } else if keyed && lower_degree_after != Some(1) {
218                    false
219                } else {
220                    second == 3
221                }
222            }
223            _ => true,
224        })
225    }
226
227    /// Whether one voice leaps while the other neither steps nor holds:
228    /// music21's `leapNotSetWithStep`. Two thirds in contrary motion are let
229    /// through.
230    pub fn leap_not_set_with_step(&self) -> bool {
231        if self.no_motion() {
232            return false;
233        }
234        let [upper, lower] = &self.horizontal;
235        let steps_or_holds =
236            |interval: &Interval| interval.is_diatonic_step() || interval.is_unison();
237        if upper.generic().undirected() == 3
238            && lower.generic().undirected() == 3
239            && self.contrary_motion()
240        {
241            return false;
242        }
243        if upper.is_skip() {
244            !steps_or_holds(lower)
245        } else if lower.is_skip() {
246            !steps_or_holds(upper)
247        } else {
248            false
249        }
250    }
251
252    /// Whether the two voices open the way sixteenth-century counterpoint
253    /// opens: music21's `modalOpening`. Errors without a key.
254    ///
255    /// One of the two harmonic intervals must be a unison or a fifth — the
256    /// second may be, to allow for an anacrusis — and the pair must establish
257    /// the tonic or the dominant. Which of the two says so is whichever can
258    /// be read at all: music21 asks the first, and only falls to the second
259    /// when the first says nothing.
260    pub fn modal_opening(&self) -> Result<bool> {
261        let Some(key) = &self.key else {
262            return Err(Error::Analysis(
263                "modalOpening requires a key to be set on the VoiceLeadingQuartet".to_string(),
264            ));
265        };
266        let opening = ["P1", "P5"];
267        let sounds_open = opening.contains(&self.vertical[0].simple_name().as_str())
268            || opening.contains(&self.vertical[1].simple_name().as_str());
269        let function_of = |first: &Pitch, second: &Pitch| -> Result<Option<bool>> {
270            let chord = crate::chord::Chord::new([first.clone(), second.clone()].as_slice())?;
271            Ok(
272                crate::roman::identify_as_tonic_or_dominant(&chord, key)?.map(|figure| {
273                    figure
274                        .chars()
275                        .next()
276                        .is_some_and(|numeral| matches!(numeral.to_ascii_uppercase(), 'I' | 'V'))
277                }),
278            )
279        };
280        let established = match function_of(&self.v1n1, &self.v2n1)? {
281            Some(established) => established,
282            None => function_of(&self.v1n2, &self.v2n2)?.unwrap_or(false),
283        };
284        Ok(sounds_open && established)
285    }
286
287    /// The opposite of [`Self::modal_opening`]: music21's `opensIncorrectly`.
288    pub fn opens_incorrectly(&self) -> Result<bool> {
289        self.modal_opening().map(|opens| !opens)
290    }
291
292    /// The opposite of [`Self::clausula_vera`]: music21's `closesIncorrectly`.
293    pub fn closes_incorrectly(&self) -> Result<bool> {
294        self.clausula_vera().map(|closes| !closes)
295    }
296
297    /// Whether the two voices close a clausula vera: stepwise contrary
298    /// motion, one voice by a semitone and the other by a tone, onto a unison
299    /// or octave on the tonic. Errors without a key.
300    pub fn clausula_vera(&self) -> Result<bool> {
301        let Some(key) = &self.key else {
302            return Err(Error::Analysis(
303                "clausulaVera requires a key to be set on the VoiceLeadingQuartet".to_string(),
304            ));
305        };
306        let tonic = key.tonic().name();
307        let mut horizontal = [
308            self.horizontal[0].short_name(),
309            self.horizontal[1].short_name(),
310        ];
311        horizontal.sort_unstable();
312        Ok(horizontal == ["M2", "m2"]
313            && self.contrary_motion()
314            && matches!(self.vertical[1].short_name().as_str(), "P1" | "P8")
315            && self.v1n2.name() == tonic
316            && self.v2n2.name() == tonic)
317    }
318
319    /// The upper voice's first pitch.
320    pub fn v1n1(&self) -> &Pitch {
321        &self.v1n1
322    }
323
324    /// The upper voice's second pitch.
325    pub fn v1n2(&self) -> &Pitch {
326        &self.v1n2
327    }
328
329    /// The lower voice's first pitch.
330    pub fn v2n1(&self) -> &Pitch {
331        &self.v2n1
332    }
333
334    /// The lower voice's second pitch.
335    pub fn v2n2(&self) -> &Pitch {
336        &self.v2n2
337    }
338
339    /// The harmonic intervals from the upper voice to the lower voice, at the
340    /// first and second moment.
341    pub fn vertical_intervals(&self) -> &[Interval; 2] {
342        &self.vertical
343    }
344
345    /// The melodic intervals each voice moves through, upper voice first.
346    pub fn horizontal_intervals(&self) -> &[Interval; 2] {
347        &self.horizontal
348    }
349
350    /// Classifies the motion. Anti-parallel motion is reported as contrary
351    /// unless `allow_anti_parallel` is set.
352    pub fn motion_type(&self, allow_anti_parallel: bool) -> MotionType {
353        if self.oblique_motion() {
354            MotionType::Oblique
355        } else if self.parallel_motion(None, false) {
356            MotionType::Parallel
357        } else if self.similar_motion() {
358            MotionType::Similar
359        } else if allow_anti_parallel && self.anti_parallel_motion(None) {
360            MotionType::AntiParallel
361        } else if self.contrary_motion() {
362            MotionType::Contrary
363        } else {
364            MotionType::NoMotion
365        }
366    }
367
368    /// Returns whether neither voice moves.
369    pub fn no_motion(&self) -> bool {
370        self.horizontal.iter().all(Interval::is_perfect_unison)
371    }
372
373    /// Returns whether exactly one voice holds its pitch.
374    pub fn oblique_motion(&self) -> bool {
375        !self.no_motion() && self.horizontal.iter().any(Interval::is_perfect_unison)
376    }
377
378    /// Returns whether both voices move in the same direction.
379    pub fn similar_motion(&self) -> bool {
380        !self.no_motion() && self.horizontal[0].direction() == self.horizontal[1].direction()
381    }
382
383    /// Returns whether the voices move in the same direction keeping the same
384    /// generic interval. With `required`, the interval must also be that one;
385    /// `allow_octave_displacement` accepts a fifth answered by a twelfth.
386    pub fn parallel_motion(
387        &self,
388        required: Option<&ParallelRequirement>,
389        allow_octave_displacement: bool,
390    ) -> bool {
391        let [first, second] = &self.vertical;
392        if !self.similar_motion() {
393            return false;
394        }
395        if first.generic().directed() != second.generic().directed() && !allow_octave_displacement {
396            return false;
397        }
398        if first.generic().semi_simple_undirected() != second.generic().semi_simple_undirected() {
399            return false;
400        }
401        match required {
402            None => true,
403            Some(ParallelRequirement::Wide(steps)) => {
404                first.generic().semi_simple_undirected() == *steps
405            }
406            Some(ParallelRequirement::Named(required)) => {
407                first.semi_simple_key() == required.semi_simple_key()
408                    && second.semi_simple_key() == required.semi_simple_key()
409            }
410        }
411    }
412
413    /// Returns whether the voices move in opposite directions.
414    pub fn contrary_motion(&self) -> bool {
415        !self.no_motion()
416            && !self.oblique_motion()
417            && self.horizontal[0].direction() != self.horizontal[1].direction()
418    }
419
420    /// Returns whether the voices move apart.
421    pub fn outward_contrary_motion(&self) -> bool {
422        self.contrary_motion() && self.horizontal[0].direction() == IntervalDirection::Ascending
423    }
424
425    /// Returns whether the voices move towards each other.
426    pub fn inward_contrary_motion(&self) -> bool {
427        self.contrary_motion() && self.horizontal[0].direction() == IntervalDirection::Descending
428    }
429
430    /// Returns whether contrary motion lands on the same simple interval it
431    /// left, such as a fifth opening out to a twelfth. With `required`, that
432    /// interval must also be the given one.
433    pub fn anti_parallel_motion(&self, required: Option<&Interval>) -> bool {
434        let [first, second] = &self.vertical;
435        self.contrary_motion()
436            && first.simple_key() == second.simple_key()
437            && required.is_none_or(|required| first.simple_key() == required.simple_key())
438    }
439
440    /// Returns whether the voices move in parallel or anti-parallel through
441    /// the given interval, in any octave.
442    pub fn parallel_interval(&self, interval: &Interval) -> bool {
443        let required = ParallelRequirement::Named(Box::new(interval.clone()));
444        self.parallel_motion(Some(&required), true) || self.anti_parallel_motion(Some(interval))
445    }
446
447    /// Returns whether the voices move in parallel fifths.
448    pub fn parallel_fifth(&self) -> bool {
449        self.parallel_interval(&PERFECT_FIFTH)
450    }
451
452    /// Returns whether the voices move in parallel octaves.
453    pub fn parallel_octave(&self) -> bool {
454        self.parallel_interval(&PERFECT_OCTAVE)
455    }
456
457    /// Returns whether the voices move in parallel unisons.
458    pub fn parallel_unison(&self) -> bool {
459        self.parallel_interval(&PERFECT_UNISON)
460    }
461
462    /// Returns whether the voices move in parallel unisons or octaves.
463    pub fn parallel_unison_or_octave(&self) -> bool {
464        self.parallel_unison() || self.parallel_octave()
465    }
466
467    /// Returns whether similar motion arrives at the given interval without
468    /// having started from it.
469    pub fn hidden_interval(&self, interval: &Interval) -> bool {
470        if self.parallel_motion(None, true) || !self.similar_motion() {
471            return false;
472        }
473        self.vertical[1].simple_key() == interval.simple_key()
474    }
475
476    /// Returns whether similar motion arrives at a perfect fifth.
477    pub fn hidden_fifth(&self) -> bool {
478        self.hidden_interval(&PERFECT_FIFTH)
479    }
480
481    /// Returns whether similar motion arrives at a perfect octave.
482    pub fn hidden_octave(&self) -> bool {
483        self.hidden_interval(&PERFECT_OCTAVE)
484    }
485
486    /// Returns whether a voice moves past where the other voice just was.
487    pub fn voice_overlap(&self) -> bool {
488        self.v1n2.ps() < self.v2n1.ps() || self.v2n2.ps() > self.v1n1.ps()
489    }
490
491    /// Returns whether the lower voice is above the upper voice at either
492    /// moment.
493    pub fn voice_crossing(&self) -> bool {
494        self.v1n1.ps() < self.v2n1.ps() || self.v1n2.ps() < self.v2n2.ps()
495    }
496}
497
498/// Which quartets [`iterate_all_voice_leading_quartets`] gives: music21's
499/// keyword arguments of the same names, with its defaults.
500#[derive(Clone, Copy, Debug, Eq, PartialEq)]
501#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
502#[non_exhaustive]
503pub struct QuartetOptions {
504    /// Whether a voice may come to its note out of a rest. Without it, the
505    /// note before has to stop exactly where the next one starts.
506    pub include_rests: bool,
507    /// Whether a voice that holds or repeats its note counts as moving.
508    pub include_oblique: bool,
509    /// Whether a quartet in which neither voice changes pitch is given.
510    pub include_no_motion: bool,
511}
512
513impl Default for QuartetOptions {
514    fn default() -> Self {
515        Self {
516            include_rests: true,
517            include_oblique: true,
518            include_no_motion: false,
519        }
520    }
521}
522
523/// One thing on a part's flattened timeline, as the walk sees it.
524struct Span<'a> {
525    offset: FloatType,
526    end: FloatType,
527    part: usize,
528    /// Where the element stands in [`Stream::leaves`].
529    position: usize,
530    element: &'a StreamElement,
531}
532
533impl Span<'_> {
534    fn is_pitched(&self) -> bool {
535        self.element.is_note_or_chord()
536    }
537
538    /// music21's `classSortOrder`, which decides what comes first among the
539    /// things starting together.
540    fn class_sort_order(&self) -> i8 {
541        match self.element {
542            StreamElement::TextExpression(_) => -30,
543            StreamElement::Instrument(_) => -25,
544            StreamElement::Clef(_) => 0,
545            StreamElement::MetronomeMark(_) | StreamElement::TempoText(_) => 1,
546            StreamElement::KeySignature(_) | StreamElement::Key(_) => 2,
547            StreamElement::TimeSignature(_) => 4,
548            StreamElement::Dynamic(_) => 10,
549            StreamElement::ChordSymbol(_) => 19,
550            _ => 20,
551        }
552    }
553}
554
555/// Each part of a stream as a flat timeline: every element of the part with
556/// its offset from the stream's start and its position in
557/// [`Stream::leaves`], in the order `flatten` would give them. A stream
558/// holding no parts is one part itself.
559fn parts_by_position(stream: &Stream) -> Vec<Vec<(usize, FloatType, &StreamElement)>> {
560    let tops: Vec<usize> = stream
561        .events()
562        .iter()
563        .enumerate()
564        .filter(|(_, event)| {
565            event
566                .element()
567                .as_stream()
568                .is_some_and(|part| matches!(part.kind(), StreamKind::Part | StreamKind::PartStaff))
569        })
570        .map(|(top, _)| top)
571        .collect();
572    let leaves = stream.leaves_under_top();
573    let mut parts: Vec<Vec<(usize, FloatType, &StreamElement)>> = if tops.is_empty() {
574        vec![
575            leaves
576                .iter()
577                .enumerate()
578                .map(|(position, &(_, offset, element))| (position, offset, element))
579                .collect(),
580        ]
581    } else {
582        tops.iter()
583            .map(|&top| {
584                leaves
585                    .iter()
586                    .enumerate()
587                    .filter(|(_, (under, _, _))| *under == top)
588                    .map(|(position, &(_, offset, element))| (position, offset, element))
589                    .collect()
590            })
591            .collect()
592    };
593    // Stable, as `flatten` is.
594    for part in &mut parts {
595        part.sort_by(|left, right| left.1.total_cmp(&right.1));
596    }
597    parts
598}
599
600/// Every [`VoiceLeadingQuartet`] in a stream, generally a score: music21's
601/// `iterateAllVoiceLeadingQuartets`.
602///
603/// Wherever a note starts, each voice that moves there is paired with the
604/// note it came from, each voice holding through is paired with itself, and
605/// every two such pairs make a quartet. Only notes make one; a pair with a
606/// chord in it is passed over, as it is upstream.
607///
608/// The quartets come in the order of the score: by offset, then by part,
609/// the first-named voice being the earlier part. music21 orders the voices
610/// within an offset by a counter of when each note object was put into its
611/// stream, which says nothing about the music, so its order within an offset
612/// can differ; the quartets themselves are the same.
613///
614/// A voice comes *from* the first thing in its part at the nearest earlier
615/// offset where anything in that part starts. If that is not a note or
616/// chord -- a rest, or a time signature standing where a note also starts --
617/// the voice has no pair there. That is music21's reading and is kept.
618pub fn iterate_all_voice_leading_quartets(
619    stream: &Stream,
620    options: QuartetOptions,
621) -> Result<Vec<VoiceLeadingQuartet>> {
622    let leaves = stream.leaves();
623    let pitch = |position: usize| match leaves[position].1 {
624        StreamElement::Note(note) => note.pitch().clone(),
625        _ => unreachable!("a quartet is made of notes alone"),
626    };
627    voice_leading_quartet_positions(stream, options)
628        .into_iter()
629        .map(|(_, [v1n1, v1n2, v2n1, v2n2])| {
630            VoiceLeadingQuartet::new(pitch(v1n1), pitch(v1n2), pitch(v2n1), pitch(v2n2))
631        })
632        .collect()
633}
634
635/// Where the four notes of every quartet [`iterate_all_voice_leading_quartets`]
636/// finds stand in the stream: each quartet as the offset it is found at and
637/// the positions in [`Stream::leaves`] of its `v1n1`, `v1n2`, `v2n1` and
638/// `v2n2`, in that order. This is the walk itself, for a caller that keeps
639/// something of its own beside each note and wants that back rather than a
640/// copy of the pitch; the offset is what music21's `reverse` walks backwards
641/// by, since a voice held through has its notes start earlier.
642///
643/// ```
644/// use music21::{voiceleading::{voice_leading_quartet_positions, QuartetOptions}, Note, Stream, StreamKind};
645///
646/// let mut upper = Stream::with_kind(StreamKind::Part);
647/// upper.insert(0.0, Note::from_name("C5")?);
648/// upper.insert(1.0, Note::from_name("D5")?);
649/// let mut lower = Stream::with_kind(StreamKind::Part);
650/// lower.insert(0.0, Note::from_name("C4")?);
651/// lower.insert(1.0, Note::from_name("B3")?);
652/// let mut score = Stream::with_kind(StreamKind::Score);
653/// score.insert(0.0, upper);
654/// score.insert(0.0, lower);
655/// assert_eq!(voice_leading_quartet_positions(&score, QuartetOptions::default()), [(1.0, [0, 1, 2, 3])]);
656/// # Ok::<(), music21::Error>(())
657/// ```
658pub fn voice_leading_quartet_positions(
659    stream: &Stream,
660    options: QuartetOptions,
661) -> Vec<(FloatType, [usize; 4])> {
662    let parts = parts_by_position(stream);
663    let mut spans: Vec<Span<'_>> = Vec::new();
664    for (part, flat) in parts.iter().enumerate() {
665        for &(position, offset, element) in flat {
666            spans.push(Span {
667                offset,
668                end: offset + element.quarter_length(),
669                part,
670                position,
671                element,
672            });
673        }
674    }
675    // Stable, so things starting together stay in part order and then in
676    // the order their part holds them.
677    spans.sort_by(|left, right| {
678        left.offset
679            .total_cmp(&right.offset)
680            .then(left.class_sort_order().cmp(&right.class_sort_order()))
681    });
682
683    let mut quartets = Vec::new();
684    let mut start = 0;
685    while start < spans.len() {
686        let offset = spans[start].offset;
687        let stop = start
688            + spans[start..]
689                .iter()
690                .take_while(|span| span.offset == offset)
691                .count();
692
693        let mut motions: Vec<(&Span<'_>, &Span<'_>)> = Vec::new();
694        for starting in spans[start..stop].iter().filter(|span| span.is_pitched()) {
695            // The nearest earlier offset where this part starts anything,
696            // and the first thing it starts there.
697            let Some(nearest) = spans[..start]
698                .iter()
699                .rev()
700                .find(|span| span.part == starting.part)
701                .map(|span| span.offset)
702            else {
703                continue;
704            };
705            let previous = spans[..start]
706                .iter()
707                .find(|span| span.part == starting.part && span.offset == nearest);
708            let Some(previous) = previous.filter(|span| span.is_pitched()) else {
709                continue;
710            };
711            // A hair of latitude, since offsets here are floats where
712            // music21's are fractions.
713            if !options.include_rests && (previous.end - offset).abs() > 1e-9 {
714                continue;
715            }
716            if !options.include_oblique && previous.element.pitches() == starting.element.pitches()
717            {
718                continue;
719            }
720            motions.push((previous, starting));
721        }
722        if options.include_oblique {
723            for held in spans[..start]
724                .iter()
725                .filter(|span| span.is_pitched() && span.end - offset > 1e-9)
726            {
727                motions.push((held, held));
728            }
729        }
730
731        for (index, first) in motions.iter().enumerate() {
732            for second in &motions[index + 1..] {
733                if !options.include_no_motion
734                    && first.0.element.pitches() == first.1.element.pitches()
735                    && second.0.element.pitches() == second.1.element.pitches()
736                {
737                    continue;
738                }
739                let four = [first.0, first.1, second.0, second.1];
740                if four
741                    .iter()
742                    .all(|span| matches!(span.element, StreamElement::Note(_)))
743                {
744                    quartets.push((offset, four.map(|span| span.position)));
745                }
746            }
747        }
748        start = stop;
749    }
750    quartets
751}
752
753/// What is sounding or standing in each part at an offset, part by part:
754/// music21's `getVerticalityFromObject`, asked by offset since a stream here
755/// owns its notes and an offset is what says where one is.
756///
757/// A thing counts if it starts there or is still going, and a thing of no
758/// length counts where it stands. Parts holding nothing there are left out,
759/// so each answer carries the index of its part.
760pub fn verticality_at(stream: &Stream, offset: FloatType) -> Vec<(usize, Vec<StreamElement>)> {
761    let leaves = stream.leaves();
762    verticality_positions_at(stream, offset)
763        .into_iter()
764        .map(|(part, positions)| {
765            (
766                part,
767                positions
768                    .into_iter()
769                    .map(|position| leaves[position].1.clone())
770                    .collect(),
771            )
772        })
773        .collect()
774}
775
776/// [`verticality_at`] by position: for each part holding anything at the
777/// offset, the part's index and the positions in [`Stream::leaves`] of what
778/// it holds there, for a caller that keeps something of its own beside each
779/// element.
780///
781/// ```
782/// use music21::{voiceleading::verticality_positions_at, Note, Stream};
783///
784/// let mut stream = Stream::new();
785/// stream.insert(0.0, Note::from_name("C4")?);
786/// stream.insert(1.0, Note::from_name("D4")?);
787/// assert_eq!(verticality_positions_at(&stream, 0.5), [(0, vec![0])]);
788/// # Ok::<(), music21::Error>(())
789/// ```
790pub fn verticality_positions_at(stream: &Stream, offset: FloatType) -> Vec<(usize, Vec<usize>)> {
791    parts_by_position(stream)
792        .iter()
793        .enumerate()
794        .filter_map(|(part, flat)| {
795            let positions: Vec<usize> = flat
796                .iter()
797                .filter(|(_, start, element)| {
798                    *start == offset
799                        || (*start < offset && offset < *start + element.quarter_length())
800                })
801                .map(|(position, _, _)| *position)
802                .collect();
803            (!positions.is_empty()).then_some((part, positions))
804        })
805        .collect()
806}
807
808#[cfg(test)]
809mod tests {
810
811    #[test]
812    fn a_resolution_is_counted_in_the_scale_the_signature_implies() {
813        // music21 counts in `key.getScale(key.mode)`, which a tonic moved by
814        // hand leaves on the signature: under no sharps the lower voice's B
815        // is the leading note of C major whatever the tonic says, and the
816        // diminished fifth does not resolve. music21 answers False for both.
817        let quartet = VoiceLeadingQuartet::from_names("F4", "E4", "B3", "A3").unwrap();
818        for key in [
819            Key::from_tonic("C").unwrap(),
820            Key::from_tonic("F#").unwrap().with_sharps(0),
821        ] {
822            assert!(
823                !quartet
824                    .clone()
825                    .with_key(key)
826                    .is_proper_resolution()
827                    .unwrap()
828            );
829        }
830        // Written under its own signature, F# major's B is its fourth.
831        let fresh = quartet.with_key(Key::from_tonic("F#").unwrap());
832        assert!(fresh.is_proper_resolution().unwrap());
833    }
834
835    #[test]
836    fn a_quartet_hands_back_its_pitches_intervals_and_key() {
837        use super::ParallelRequirement;
838        use crate::Interval;
839
840        let mut quartet = VoiceLeadingQuartet::from_names("C5", "D5", "C4", "D4").unwrap();
841        assert_eq!(quartet.v1n1().name_with_octave(), "C5");
842        assert_eq!(quartet.v1n2().name_with_octave(), "D5");
843        assert_eq!(quartet.v2n1().name_with_octave(), "C4");
844        assert_eq!(quartet.v2n2().name_with_octave(), "D4");
845        let vertical: Vec<String> = quartet
846            .vertical_intervals()
847            .iter()
848            .map(Interval::short_name)
849            .collect();
850        assert_eq!(vertical, ["P8", "P8"]);
851        let horizontal: Vec<String> = quartet
852            .horizontal_intervals()
853            .iter()
854            .map(Interval::short_name)
855            .collect();
856        assert_eq!(horizontal, ["M2", "M2"]);
857        assert!(quartet.parallel_unison_or_octave());
858        assert!(quartet.key().is_none());
859        quartet.set_key(Some(Key::from_tonic("C").unwrap()));
860        assert!(quartet.key().is_some());
861        assert!(matches!(
862            ParallelRequirement::from(8),
863            ParallelRequirement::Wide(8)
864        ));
865        assert!(matches!(
866            ParallelRequirement::from(Interval::from_name("P5").unwrap()),
867            ParallelRequirement::Named(_)
868        ));
869    }
870
871    #[test]
872    fn opening_and_closing_incorrectly_are_the_opposites_of_the_rules() {
873        let quartet = |v1n1, v1n2, v2n1, v2n2, key: &str| {
874            VoiceLeadingQuartet::from_names(v1n1, v1n2, v2n1, v2n2)
875                .unwrap()
876                .with_key(Key::from_tonic(key).unwrap())
877        };
878        let opens = quartet("D", "D", "D", "F#", "D");
879        assert!(opens.modal_opening().unwrap());
880        assert!(!opens.opens_incorrectly().unwrap());
881        let closes = quartet("B4", "C5", "D4", "C4", "C");
882        assert_eq!(
883            closes.closes_incorrectly().unwrap(),
884            !closes.clausula_vera().unwrap()
885        );
886        let unkeyed = VoiceLeadingQuartet::from_names("D", "D", "D", "F#").unwrap();
887        assert!(unkeyed.opens_incorrectly().is_err());
888        assert!(unkeyed.closes_incorrectly().is_err());
889    }
890
891    #[test]
892    fn a_modal_opening_needs_a_perfect_interval_and_a_tonic_or_dominant() {
893        // music21's own examples.
894        let opening = |v1n1, v1n2, v2n1, v2n2, key: &str| {
895            VoiceLeadingQuartet::from_names(v1n1, v1n2, v2n1, v2n2)
896                .unwrap()
897                .with_key(Key::from_tonic(key).unwrap())
898                .modal_opening()
899                .unwrap()
900        };
901        assert!(opening("D", "D", "D", "F#", "D"));
902        assert!(opening("B", "A", "G#", "A", "A"));
903        assert!(opening("A", "A", "F#", "D", "A"));
904        assert!(!opening("C#", "C#", "D", "E", "A"));
905        assert!(!opening("B", "B", "A", "A", "C"));
906
907        // Without a key there is nothing to be the tonic of.
908        assert!(
909            VoiceLeadingQuartet::from_names("D", "D", "D", "F#")
910                .unwrap()
911                .modal_opening()
912                .is_err()
913        );
914    }
915
916    #[test]
917    #[allow(clippy::type_complexity)]
918    fn proper_resolution_leaps_and_clausula_vera_match_music21() {
919        let quartet = |a: &str, b: &str, c: &str, d: &str, key: Option<&str>| {
920            let built = VoiceLeadingQuartet::from_names(a, b, c, d).unwrap();
921            match key {
922                Some(key) => built.with_key(Key::from_tonic(key).unwrap()),
923                None => built,
924            }
925        };
926        let cases: [(
927            &str,
928            &str,
929            &str,
930            &str,
931            Option<&str>,
932            bool,
933            bool,
934            Option<bool>,
935        ); 20] = [
936            ("C4", "B3", "F4", "E4", None, true, false, None),
937            ("C4", "B3", "F4", "E4", Some("C"), true, false, Some(false)),
938            ("B3", "C4", "F4", "E4", Some("C"), true, false, Some(false)),
939            ("B3", "C4", "F4", "E4", None, false, false, None),
940            ("F4", "E4", "B3", "C4", Some("C"), true, false, Some(false)),
941            ("F4", "E4", "B3", "C4", None, true, false, None),
942            ("G3", "C4", "F4", "E4", Some("C"), true, false, Some(false)),
943            ("G3", "C4", "F4", "E4", Some("G"), true, false, Some(false)),
944            ("C4", "C4", "E4", "E4", Some("C"), true, false, Some(false)),
945            ("D4", "F4", "F4", "A4", None, true, true, None),
946            ("C4", "F4", "E4", "F4", None, true, false, None),
947            ("C4", "E4", "E4", "G4", None, true, true, None),
948            ("C4", "E4", "E4", "C4", None, true, false, None),
949            ("B3", "C4", "D4", "C4", Some("C"), true, false, Some(true)),
950            ("B3", "C4", "D4", "C4", Some("G"), true, false, Some(false)),
951            ("B3", "C4", "D4", "C5", Some("C"), true, false, Some(false)),
952            ("F4", "E4", "G3", "C4", Some("C"), true, false, Some(false)),
953            ("F4", "E4", "B3", "C4", Some("F"), true, false, Some(false)),
954            ("D4", "C4", "B3", "C4", Some("C"), true, false, Some(true)),
955            ("C4", "D4", "G4", "F4", None, true, false, None),
956        ];
957        for (a, b, c, d, key, proper, leap, clausula) in cases {
958            let vlq = quartet(a, b, c, d, key);
959            assert_eq!(
960                vlq.is_proper_resolution().unwrap(),
961                proper,
962                "{a} {b} {c} {d} {key:?}"
963            );
964            assert_eq!(
965                vlq.leap_not_set_with_step(),
966                leap,
967                "{a} {b} {c} {d} {key:?}"
968            );
969            match clausula {
970                Some(expected) => assert_eq!(
971                    vlq.clausula_vera().unwrap(),
972                    expected,
973                    "{a} {b} {c} {d} {key:?}"
974                ),
975                None => assert!(vlq.clausula_vera().is_err(), "{a} {b} {c} {d}"),
976            }
977            assert_eq!(vlq.key().is_some(), key.is_some());
978        }
979    }
980    use super::*;
981
982    struct Expected {
983        motion: MotionType,
984        with_anti_parallel: MotionType,
985        flags: [bool; 15],
986    }
987
988    #[test]
989    fn quartets_match_music21() {
990        use MotionType::*;
991        let t = true;
992        let f = false;
993        let cases = [
994            (
995                ("C4", "D4", "C3", "D3"),
996                Expected {
997                    motion: Parallel,
998                    with_anti_parallel: Parallel,
999                    flags: [f, f, t, t, f, f, f, f, f, t, f, f, f, f, f],
1000                },
1001            ),
1002            (
1003                ("C4", "D4", "E3", "F3"),
1004                Expected {
1005                    motion: Parallel,
1006                    with_anti_parallel: Parallel,
1007                    flags: [f, f, t, t, f, f, f, f, f, f, f, f, f, f, f],
1008                },
1009            ),
1010            (
1011                ("C4", "G4", "C3", "C3"),
1012                Expected {
1013                    motion: Oblique,
1014                    with_anti_parallel: Oblique,
1015                    flags: [f, t, f, f, f, f, f, f, f, f, f, f, f, f, f],
1016                },
1017            ),
1018            (
1019                ("C4", "C4", "C3", "C3"),
1020                Expected {
1021                    motion: NoMotion,
1022                    with_anti_parallel: NoMotion,
1023                    flags: [t, f, f, f, f, f, f, f, f, f, f, f, f, f, f],
1024                },
1025            ),
1026            (
1027                ("C4", "D4", "G3", "F3"),
1028                Expected {
1029                    motion: Contrary,
1030                    with_anti_parallel: Contrary,
1031                    flags: [f, f, f, f, t, t, f, f, f, f, f, f, f, f, f],
1032                },
1033            ),
1034            (
1035                ("C4", "C5", "F3", "F4"),
1036                Expected {
1037                    motion: Parallel,
1038                    with_anti_parallel: Parallel,
1039                    flags: [f, f, t, t, f, f, f, f, t, f, f, f, f, t, f],
1040                },
1041            ),
1042            (
1043                ("C5", "D5", "C4", "D4"),
1044                Expected {
1045                    motion: Parallel,
1046                    with_anti_parallel: Parallel,
1047                    flags: [f, f, t, t, f, f, f, f, f, t, f, f, f, f, f],
1048                },
1049            ),
1050            (
1051                ("C4", "D4", "G4", "A4"),
1052                Expected {
1053                    motion: Parallel,
1054                    with_anti_parallel: Parallel,
1055                    flags: [f, f, t, t, f, f, f, f, t, f, f, f, f, t, t],
1056                },
1057            ),
1058            (
1059                ("C5", "D5", "G4", "E5"),
1060                Expected {
1061                    motion: Similar,
1062                    with_anti_parallel: Similar,
1063                    flags: [f, f, t, f, f, f, f, f, f, f, f, f, f, t, t],
1064                },
1065            ),
1066            (
1067                ("E4", "F4", "C4", "A3"),
1068                Expected {
1069                    motion: Contrary,
1070                    with_anti_parallel: Contrary,
1071                    flags: [f, f, f, f, t, t, f, f, f, f, f, f, f, f, f],
1072                },
1073            ),
1074            (
1075                ("G4", "F4", "C4", "D4"),
1076                Expected {
1077                    motion: Contrary,
1078                    with_anti_parallel: Contrary,
1079                    flags: [f, f, f, f, t, f, t, f, f, f, f, f, f, f, f],
1080                },
1081            ),
1082            (
1083                ("C4", "C#4", "C3", "C#3"),
1084                Expected {
1085                    motion: Parallel,
1086                    with_anti_parallel: Parallel,
1087                    flags: [f, f, t, t, f, f, f, f, f, t, f, f, f, f, f],
1088                },
1089            ),
1090            (
1091                ("C4", "B3", "F3", "G3"),
1092                Expected {
1093                    motion: Contrary,
1094                    with_anti_parallel: Contrary,
1095                    flags: [f, f, f, f, t, f, t, f, f, f, f, f, f, f, f],
1096                },
1097            ),
1098            (
1099                ("D5", "A5", "G3", "D4"),
1100                Expected {
1101                    motion: Parallel,
1102                    with_anti_parallel: Parallel,
1103                    flags: [f, f, t, t, f, f, f, f, t, f, f, f, f, f, f],
1104                },
1105            ),
1106            (
1107                ("A4", "B4", "F4", "E4"),
1108                Expected {
1109                    motion: Contrary,
1110                    with_anti_parallel: Contrary,
1111                    flags: [f, f, f, f, t, t, f, f, f, f, f, f, f, f, f],
1112                },
1113            ),
1114            (
1115                ("C5", "C5", "E4", "F4"),
1116                Expected {
1117                    motion: Oblique,
1118                    with_anti_parallel: Oblique,
1119                    flags: [f, t, f, f, f, f, f, f, f, f, f, f, f, f, f],
1120                },
1121            ),
1122        ];
1123        for ((a, b, c, d), expected) in cases {
1124            let quartet = VoiceLeadingQuartet::from_names(a, b, c, d).unwrap();
1125            let label = format!("{a} {b} / {c} {d}");
1126            assert_eq!(quartet.motion_type(false), expected.motion, "{label}");
1127            assert_eq!(
1128                quartet.motion_type(true),
1129                expected.with_anti_parallel,
1130                "{label}"
1131            );
1132            let actual = [
1133                quartet.no_motion(),
1134                quartet.oblique_motion(),
1135                quartet.similar_motion(),
1136                quartet.parallel_motion(None, false),
1137                quartet.contrary_motion(),
1138                quartet.outward_contrary_motion(),
1139                quartet.inward_contrary_motion(),
1140                quartet.anti_parallel_motion(None),
1141                quartet.parallel_fifth(),
1142                quartet.parallel_octave(),
1143                quartet.parallel_unison(),
1144                quartet.hidden_fifth(),
1145                quartet.hidden_octave(),
1146                quartet.voice_overlap(),
1147                quartet.voice_crossing(),
1148            ];
1149            assert_eq!(actual, expected.flags, "{label}");
1150        }
1151    }
1152
1153    #[test]
1154    fn anti_parallel_fifths_are_contrary_unless_asked_for() {
1155        let quartet = VoiceLeadingQuartet::from_names("G4", "D5", "C4", "G3").unwrap();
1156        assert_eq!(quartet.motion_type(false), MotionType::Contrary);
1157        assert_eq!(quartet.motion_type(true), MotionType::AntiParallel);
1158        assert!(quartet.parallel_fifth());
1159        assert_eq!(MotionType::AntiParallel.to_string(), "Anti-Parallel");
1160    }
1161
1162    #[test]
1163    fn hidden_intervals_need_similar_motion_into_a_perfect_interval() {
1164        let quartet = VoiceLeadingQuartet::from_names("E4", "G4", "C4", "C3").unwrap();
1165        assert!(!quartet.hidden_fifth());
1166        let quartet = VoiceLeadingQuartet::from_names("E4", "D5", "C4", "G4").unwrap();
1167        assert!(quartet.hidden_fifth());
1168        assert!(!quartet.hidden_octave());
1169        let quartet = VoiceLeadingQuartet::from_names("E4", "C5", "C4", "C4").unwrap();
1170        assert!(!quartet.hidden_octave());
1171    }
1172
1173    /// The walk over whole scores is checked against music21 by
1174    /// `voice_leading_parity`; this is the shape of it on two bars.
1175    #[test]
1176    fn a_score_is_walked_into_its_quartets() {
1177        use crate::{Duration, Note, Rest};
1178
1179        let line = |notes: &[(&str, FloatType)]| {
1180            let mut part = Stream::with_kind(StreamKind::Part);
1181            for (name, length) in notes {
1182                let duration = Duration::new(*length).unwrap();
1183                if name.is_empty() {
1184                    part.push(Rest::new(duration));
1185                } else {
1186                    part.push(Note::from_name(*name).unwrap().with_duration(duration));
1187                }
1188            }
1189            part
1190        };
1191        let mut score = Stream::with_kind(StreamKind::Score);
1192        score.insert(
1193            0.0,
1194            line(&[("C5", 1.0), ("D5", 1.0), ("", 1.0), ("E5", 1.0)]),
1195        );
1196        score.insert(0.0, line(&[("C4", 2.0), ("G3", 2.0)]));
1197
1198        let names = |options| -> Vec<String> {
1199            iterate_all_voice_leading_quartets(&score, options)
1200                .unwrap()
1201                .iter()
1202                .map(|quartet| {
1203                    [
1204                        quartet.v1n1(),
1205                        quartet.v1n2(),
1206                        quartet.v2n1(),
1207                        quartet.v2n2(),
1208                    ]
1209                    .map(Pitch::name_with_octave)
1210                    .join(" ")
1211                })
1212                .collect()
1213        };
1214        // The upper voice moves while the lower holds; then the lower moves
1215        // under a rest, which leaves it nothing to be paired with; and the
1216        // upper voice comes back out of a rest it has no note to come from.
1217        assert_eq!(names(QuartetOptions::default()), ["C5 D5 C4 C4"]);
1218        let strict = QuartetOptions {
1219            include_oblique: false,
1220            ..QuartetOptions::default()
1221        };
1222        assert!(names(strict).is_empty());
1223
1224        let sounding = verticality_at(&score, 1.5);
1225        assert_eq!(sounding.len(), 2);
1226        assert_eq!(sounding[0].1[0].pitches()[0].name_with_octave(), "D5");
1227        assert_eq!(sounding[1].1[0].pitches()[0].name_with_octave(), "C4");
1228        assert!(verticality_at(&score, 9.0).is_empty());
1229    }
1230}