Source code for music21.voiceLeading

# ------------------------------------------------------------------------------
# Name:         voiceLeading.py
# Purpose:      music21 classes for voice leading
#
# Authors:      Michael Scott Asato Cuthbert
#               Christopher Ariza
#               Jackie Rogoff
#               Beth Hadley
#
# Copyright:    Copyright © 2009-2026 Michael Scott Asato Cuthbert
# License:      BSD, see license.txt
# ------------------------------------------------------------------------------
'''
Objects to represent unique elements in a score that contain special analysis routines
to identify certain aspects of music theory. For use especially with theoryAnalyzer, which will
divide a score up into these segments, returning a list of segments to later analyze.

The list of objects included here are:

* :class:`~music21.voiceLeading.VoiceLeadingQuartet` : two by two matrix of notes
* :func:`~music21.voiceLeading.iterateAllVoiceLeadingQuartets` : yields each VLQ in a piece.
* :class:`~music21.voiceLeading.Verticality` : vertical context in a score,
    composed of any music21 objects
* :class:`~music21.voiceLeading.VerticalityNTuplet` : group of three
    contiguous verticality objects
* :class:`~music21.voiceLeading.VerticalityTriplet` : three verticality objects --
    has special features
* :class:`~music21.voiceLeading.NObjectLinearSegment` : n (any number) of music21 objects
* :class:`~music21.voiceLeading.NNoteLinearSegment` : n (any number) of notes
* :class:`~music21.voiceLeading.ThreeNoteLinearSegment` : three notes in the same part of a score
* :class:`~music21.voiceLeading.NChordLinearSegment` :
    preliminary implementation of n(any number) chords
* :class:`~music21.voiceLeading.TwoChordLinearSegment` : 2 chord objects
'''
from __future__ import annotations

from collections.abc import Generator
import dataclasses
import enum
import typing as t
import unittest

from music21 import base
from music21 import chord
from music21 import clef
from music21 import common
from music21 import exceptions21
from music21 import interval
from music21.key import Key, KeySignature, convertKeyStringToMusic21KeyString
from music21 import meter
from music21 import note
from music21 import pitch
from music21 import scale

if t.TYPE_CHECKING:
    from music21 import stream

# from music21 import harmony can't do this either
# from music21 import roman Can't import roman because of circular
#    importing issue with counterpoint.py and figuredbass


# noinspection PyInvalidCast
@dataclasses.dataclass
class _IntervalCache:
    '''
    The three fixed intervals that VoiceLeadingQuartet compares against when
    testing for parallels and hidden intervals.  Filled in on first `__init__`.
    '''
    unison: interval.Interval = t.cast(interval.Interval, None)
    fifth: interval.Interval = t.cast(interval.Interval, None)
    octave: interval.Interval = t.cast(interval.Interval, None)


# Shared P1/P5/P8 cache; its fields are populated the first time a
# VoiceLeadingQuartet is created.
_intervals = _IntervalCache()


[docs] class MotionType(str, enum.Enum): antiParallel = 'Anti-Parallel' contrary = 'Contrary' noMotion = 'No Motion' oblique = 'Oblique' parallel = 'Parallel' similar = 'Similar'
# ------------------------------------------------------------------------------
[docs] class VoiceLeadingQuartet(base.Music21Object): ''' An object consisting of four pitches: v1n1, v1n2, v2n1, v2n2 where v1n1 moves to v1n2 at the same time as v2n1 moves to v2n2. (v1n1: voice 1(top voice), note 1 (left most note) ) Necessary for classifying types of voice-leading motion. In general, v1 should be the "higher" voice and v2 the "lower" voice in order for methods such as `.voiceCrossing` and `isProperResolution` to make sense. Most routines will work the other way still though. * Changed in v11: all four notes are now required arguments and can no longer be set later; they are always present as :class:`~music21.note.Note` objects. `analyticKey` renamed to `key`. `vIntervals` and ``hIntervals` are now tuples. ''' _DOC_ATTR: dict[str, str] = { 'vIntervals': ''' A two-element tuple of the two harmonic intervals present, vn1n1 to v2n1 and v1n2 to v2n2. ''', 'hIntervals': ''' A two-element tuple of the two melodic intervals present, v1n1 to v1n2 and v2n1 to v2n2. ''', } # All six are populated during __init__: the four notes via the property # setters (which use setattr) and the interval pairs via _findIntervals(). # Declared at class level so type checkers see them as always-present. _v1n1: note.Note _v1n2: note.Note _v2n1: note.Note _v2n2: note.Note vIntervals: tuple[interval.Interval, interval.Interval] hIntervals: tuple[interval.Interval, interval.Interval] def __init__( self, v1n1: str|note.Note|pitch.Pitch, v1n2: str|note.Note|pitch.Pitch, v2n1: str|note.Note|pitch.Pitch, v2n2: str|note.Note|pitch.Pitch, key: str|Key|None = None, **keywords ): # ``analyticKey`` is the former name of ``key``. Remove sometime in deep future. if 'analyticKey' in keywords: key = keywords.pop('analyticKey') super().__init__(**keywords) if _intervals.unison is None: # populate the shared interval cache the first time a VLQ is created _intervals.unison = interval.Interval('P1') _intervals.fifth = interval.Interval('P5') _intervals.octave = interval.Interval('P8') # the four notes (the property setters populate _v1n1.._v2n2 via setattr) self.v1n1 = v1n1 self.v1n2 = v1n2 self.v2n1 = v2n1 self.v2n2 = v2n2 self._key: Key|None = None if key is not None: self.key = key self._findIntervals() def _reprInternal(self) -> str: return (f'v1n1={self.v1n1.nameWithOctave}, v1n2={self.v1n2.nameWithOctave}, ' f'v2n1={self.v2n1.nameWithOctave}, v2n2={self.v2n2.nameWithOctave}') @property def key(self) -> Key|None: ''' Get or set the key of this VoiceLeadingQuartet, for use in theory analysis routines such as clausulaVera. Can be None. >>> vlq = voiceLeading.VoiceLeadingQuartet('D', 'G', 'B', 'G') >>> vlq.key is None True >>> vlq.key = key.Key('G') >>> vlq.key <music21.key.Key of G major> Key can also be given as a string: >>> vlq.key = 'd' >>> vlq.key <music21.key.Key of d minor> Or set back to None: >>> vlq.key = None >>> vlq.key is None True Incorrect key strings raise VoiceLeadingQuartetExceptions. ''' return self._key @key.setter def key(self, keyValue: str|Key|None) -> None: if isinstance(keyValue, str): try: keyValue = Key(convertKeyStringToMusic21KeyString(keyValue)) except Exception as e: # pragma: no cover raise VoiceLeadingQuartetException( f'got a key signature string that is not supported: {keyValue}' ) from e self._key = keyValue def _setVoiceNote( self, value: str|note.Note|pitch.Pitch, which: t.Literal['_v1n1', '_v1n2', '_v2n1', '_v2n2'] ) -> None: if isinstance(value, str): setattr(self, which, note.Note(value)) elif isinstance(value, note.Note): setattr(self, which, value) elif isinstance(value, pitch.Pitch): n = note.Note() n.duration.quarterLength = 0.0 n.pitch = value setattr(self, which, n) else: raise VoiceLeadingQuartetException( f'not a valid note specification: {value!r}' ) @property def v1n1(self) -> note.Note: ''' Get or set note 1 for voice 1. >>> vl = voiceLeading.VoiceLeadingQuartet('C', 'D', 'E', 'F') >>> vl.v1n1 <music21.note.Note C> ''' return self._v1n1 @v1n1.setter def v1n1(self, value: str|note.Note|pitch.Pitch) -> None: self._setVoiceNote(value, '_v1n1') @property def v1n2(self) -> note.Note: ''' Get or set note 2 for voice 1. >>> vl = voiceLeading.VoiceLeadingQuartet('C', 'D', 'E', 'F') >>> vl.v1n2 <music21.note.Note D> ''' return self._v1n2 @v1n2.setter def v1n2(self, value: str|note.Note|pitch.Pitch) -> None: self._setVoiceNote(value, '_v1n2') @property def v2n1(self) -> note.Note: ''' Get or set note 1 for voice 2. >>> vl = voiceLeading.VoiceLeadingQuartet('C', 'D', 'E', 'F') >>> vl.v2n1 <music21.note.Note E> ''' return self._v2n1 @v2n1.setter def v2n1(self, value: str|note.Note|pitch.Pitch) -> None: self._setVoiceNote(value, '_v2n1') @property def v2n2(self) -> note.Note: ''' Get or set note 2 for voice 2. >>> vl = voiceLeading.VoiceLeadingQuartet('C', 'D', 'E', 'F') >>> vl.v2n2 <music21.note.Note F> ''' return self._v2n2 @v2n2.setter def v2n2(self, value: str|note.Note|pitch.Pitch) -> None: self._setVoiceNote(value, '_v2n2') def _findIntervals(self) -> None: vInterval0 = interval.Interval(self.v1n1, self.v2n1) vInterval1 = interval.Interval(self.v1n2, self.v2n2) hInterval0 = interval.Interval(self.v1n1, self.v1n2) hInterval1 = interval.Interval(self.v2n1, self.v2n2) vInterval0.intervalType = 'harmonic' vInterval1.intervalType = 'harmonic' hInterval0.intervalType = 'melodic' hInterval1.intervalType = 'melodic' self.vIntervals = (vInterval0, vInterval1) self.hIntervals = (hInterval0, hInterval1)
[docs] def motionType(self, *, allowAntiParallel: bool = False) -> MotionType: ''' Returns the type of motion from the MotionType Enum object that exists in this voice leading quartet. >>> for mt in voiceLeading.MotionType: ... print(repr(mt)) <MotionType.antiParallel: 'Anti-Parallel'> <MotionType.contrary: 'Contrary'> <MotionType.noMotion: 'No Motion'> <MotionType.oblique: 'Oblique'> <MotionType.parallel: 'Parallel'> <MotionType.similar: 'Similar'> >>> n1_d4 = note.Note('D4') >>> n2_e4 = note.Note('E4') >>> m1_f4 = note.Note('F4') >>> m2_b4 = note.Note('B4') >>> vl = voiceLeading.VoiceLeadingQuartet(n1_d4, n2_e4, m1_f4, m2_b4) >>> vl.motionType() <MotionType.similar: 'Similar'> >>> n1_a4 = note.Note('A4') >>> n2_c5 = note.Note('C5') >>> m1_d4 = note.Note('D4') >>> m2_f4 = note.Note('F4') >>> vl = voiceLeading.VoiceLeadingQuartet(n1_a4, n2_c5, m1_d4, m2_f4) >>> vl.motionType() <MotionType.parallel: 'Parallel'> >>> print(vl.motionType()) MotionType.parallel >>> vl.motionType() == 'Parallel' True Demonstrations of other motion types. Contrary: >>> n1_d5 = note.Note('D5') # D5, C5 against D4, F4 >>> vl = voiceLeading.VoiceLeadingQuartet(n1_d5, n2_c5, m1_d4, m2_f4) >>> vl.motionType() <MotionType.contrary: 'Contrary'> Oblique: >>> n1_c5 = note.Note('C5') # C5, C5 against D4, F4 >>> vl = voiceLeading.VoiceLeadingQuartet(n1_c5, n2_c5, m1_d4, m2_f4) >>> vl.motionType() <MotionType.oblique: 'Oblique'> No motion (if I had a dollar for every time I forgot to teach that this is not a form of oblique motion): >>> m1_f4 = note.Note('F4') # C5, C5 against F4, F4 >>> vl = voiceLeading.VoiceLeadingQuartet(n1_c5, n2_c5, m1_f4, m2_f4) >>> vl.motionType() <MotionType.noMotion: 'No Motion'> Anti-parallel motion has to be explicitly enabled to appear: >>> n1_a5 = note.Note('A5') >>> vl = voiceLeading.VoiceLeadingQuartet(n1_a5, n2_c5, m1_d4, m2_f4) >>> vl.motionType() # anti-parallel fifths <MotionType.contrary: 'Contrary'> >>> vl.motionType(allowAntiParallel=True) <MotionType.antiParallel: 'Anti-Parallel'> * Changed in v6: anti-parallel motion was supposed to be able to be returned in previous versions, but a bug prevented it. To preserve backwards compatibility, it must be explicitly enabled. ''' motionType: MotionType if self.obliqueMotion(): motionType = MotionType.oblique elif self.parallelMotion(): motionType = MotionType.parallel elif self.similarMotion(): motionType = MotionType.similar elif allowAntiParallel and self.antiParallelMotion(): motionType = MotionType.antiParallel elif self.contraryMotion(): motionType = MotionType.contrary elif self.noMotion(): motionType = MotionType.noMotion else: raise VoiceLeadingQuartetException('Not any type of motion?') return motionType
[docs] def noMotion(self) -> bool: ''' Returns True if no voice moves in this "voice-leading" moment. >>> n1 = note.Note('G4') >>> n2 = note.Note('G4') >>> m1 = note.Note('D4') >>> m2 = note.Note('D4') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.noMotion() True >>> n2.octave = 5 >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.noMotion() False ''' for iV in self.hIntervals: if iV.name != 'P1': return False return True
[docs] def obliqueMotion(self) -> bool: ''' Returns True if one voice remains the same and another moves. i.e., noMotion must be False if obliqueMotion is True. >>> n1 = note.Note('G4') >>> n2 = note.Note('G4') >>> m1 = note.Note('D4') >>> m2 = note.Note('D4') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.obliqueMotion() False >>> n2.octave = 5 >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.obliqueMotion() True >>> m2.octave = 5 >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.obliqueMotion() False ''' if self.noMotion(): return False else: iNames = [self.hIntervals[0].name, self.hIntervals[1].name] if 'P1' not in iNames: return False else: return True
[docs] def similarMotion(self) -> bool: ''' Returns True if the two voices both move in the same direction. Parallel Motion will also return true, as it is a special case of similar motion. If there is no motion, returns False. >>> n1 = note.Note('G4') >>> n2 = note.Note('G4') >>> m1 = note.Note('G4') >>> m2 = note.Note('G4') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.similarMotion() False >>> n2.octave = 5 >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.similarMotion() False >>> m2.octave = 5 >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.similarMotion() True >>> m2 = note.Note('A5') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.similarMotion() True ''' if self.noMotion(): return False else: if self.hIntervals[0].direction == self.hIntervals[1].direction: return True else: return False
[docs] def parallelMotion( self, requiredInterval: str|int|interval.IntervalBase|None = None, allowOctaveDisplacement: bool = False ) -> bool: ''' Returns True if both the first and second intervals are the same sized generic interval. If requiredInterval is set, returns True only if both intervals are that generic or specific interval. allowOctaveDisplacement treats motion as parallel even if any of the intervals are displaced by octaves, except in the case of unisons and octaves, which are always treated as distinct. We will make the examples shorter with this abbreviation: >>> N = note.Note >>> vl = voiceLeading.VoiceLeadingQuartet(N('G4'), N('G4'), N('G3'), N('G3')) >>> vl.parallelMotion() # not even similar motion False >>> vl = voiceLeading.VoiceLeadingQuartet(N('G4'), N('B4'), N('G3'), N('A3')) >>> vl.parallelMotion() # similar motion, but no kind of parallel False >>> vl = voiceLeading.VoiceLeadingQuartet(N('G4'), N('G5'), N('G4'), N('G5')) >>> vl.parallelMotion() # parallel unisons True >>> vl.parallelMotion('P1') True octaves never equivalent to unisons >>> vl.parallelMotion('P8', allowOctaveDisplacement=True) False >>> vl = voiceLeading.VoiceLeadingQuartet(N('A4'), N('B4'), N('D3'), N('E3')) >>> vl.parallelMotion() # parallel fifths True >>> vl = voiceLeading.VoiceLeadingQuartet(N('A4'), N('B5'), N('D3'), N('E3')) >>> vl.parallelMotion() # 5th to a 12th False >>> vl.parallelMotion(allowOctaveDisplacement=True) True >>> vl = voiceLeading.VoiceLeadingQuartet(N('A4'), N('Bb4'), N('F4'), N('G4')) >>> vl.parallelMotion(3) # parallel thirds ... True >>> vl.parallelMotion('M3') # ... but not parallel MAJOR thirds False >>> vl = voiceLeading.VoiceLeadingQuartet(N('D4'), N('E4'), N('F3'), N('G3')) >>> gi = interval.GenericInterval(6) >>> vl.parallelMotion(gi) # these are parallel sixths ... True These are also parallel major sixths >>> i = interval.Interval('M6') >>> di = interval.DiatonicInterval('major', 6) >>> vl.parallelMotion(i) and vl.parallelMotion(di) True >>> vl = voiceLeading.VoiceLeadingQuartet(N('D5'), N('E6'), N('F3'), N('G3')) >>> vl.parallelMotion(gi) # octave displacement False >>> vl.parallelMotion(gi, allowOctaveDisplacement=True) True ''' vInt0 = self.vIntervals[0] vInt0_generic = vInt0.generic vInt1 = self.vIntervals[1] vInt1_generic = vInt1.generic if t.TYPE_CHECKING: assert vInt0_generic is not None assert vInt1_generic is not None if not self.similarMotion(): return False elif (vInt0_generic.directed != vInt1_generic.directed and not allowOctaveDisplacement): return False elif (vInt0_generic.semiSimpleUndirected != vInt1_generic.semiSimpleUndirected): return False elif requiredInterval is None: return True else: intervalsAreValid = False if isinstance(requiredInterval, interval.GenericInterval): intervalsAreValid = (vInt0_generic.semiSimpleUndirected == requiredInterval.semiSimpleUndirected) if isinstance(requiredInterval, int): # assume the user wants a parallel generic interval requiredInterval = interval.GenericInterval(requiredInterval) intervalsAreValid = (vInt0_generic.semiSimpleUndirected == requiredInterval.semiSimpleUndirected) if isinstance(requiredInterval, str): requiredInterval = interval.Interval(requiredInterval) intervalsAreValid = (vInt0.semiSimpleName == requiredInterval.semiSimpleName and vInt1.semiSimpleName == requiredInterval.semiSimpleName) elif isinstance(requiredInterval, (interval.Interval, interval.DiatonicInterval)): intervalsAreValid = (vInt0.semiSimpleName == requiredInterval.semiSimpleName and vInt1.semiSimpleName == requiredInterval.semiSimpleName) return intervalsAreValid
[docs] def contraryMotion(self) -> bool: ''' Returns True if both voices move in opposite directions. >>> n1 = note.Note('G4') >>> n2 = note.Note('G4') >>> m1 = note.Note('G4') >>> m2 = note.Note('G4') No motion, so False: >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.contraryMotion() False Oblique motion, so False: >>> n2.octave = 5 >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.contraryMotion() False Parallel motion, so False >>> m2.octave = 5 >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.contraryMotion() False Similar motion, so False >>> m2 = note.Note('A5') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.contraryMotion() False Finally, contrary motion, so True! >>> m2 = note.Note('C4') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.contraryMotion() True ''' if self.noMotion(): return False elif self.obliqueMotion(): return False else: if self.hIntervals[0].direction == self.hIntervals[1].direction: return False else: return True
[docs] def outwardContraryMotion(self) -> bool: ''' Returns True if both voices move outward by contrary motion. >>> n1 = note.Note('D5') >>> n2 = note.Note('E5') >>> m1 = note.Note('G4') >>> m2 = note.Note('F4') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.outwardContraryMotion() True >>> vl.inwardContraryMotion() False ''' return (self.contraryMotion() and self.hIntervals[0].direction == interval.Direction.ASCENDING)
[docs] def inwardContraryMotion(self) -> bool: ''' Returns True if both voices move inward by contrary motion. >>> n1 = note.Note('C5') >>> n2 = note.Note('B4') >>> m1 = note.Note('G4') >>> m2 = note.Note('A4') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.inwardContraryMotion() True >>> vl.outwardContraryMotion() False ''' return (self.contraryMotion() and self.hIntervals[0].direction == interval.Direction.DESCENDING)
[docs] def antiParallelMotion(self, simpleName: str|interval.Interval|None = None) -> bool: ''' Returns True if the simple interval before is the same as the simple interval after and the motion is contrary. If simpleName is specified as an Interval object or a string then it only returns true if the simpleName of both intervals is the same as simpleName (i.e., use to find antiParallel fifths) >>> n11 = note.Note('C4') >>> n12 = note.Note('D3') # descending 7th >>> n21 = note.Note('G4') >>> n22 = note.Note('A4') # ascending 2nd >>> vlq1 = voiceLeading.VoiceLeadingQuartet(n11, n12, n21, n22) >>> vlq1.antiParallelMotion() True >>> vlq1.antiParallelMotion('M2') False >>> vlq1.antiParallelMotion('P5') True We can also use interval objects >>> p5Obj = interval.Interval('P5') >>> p8Obj = interval.Interval('P8') >>> vlq1.antiParallelMotion(p5Obj) True >>> p8Obj = interval.Interval('P8') >>> vlq1.antiParallelMotion(p8Obj) False >>> n1 = note.Note('G4') >>> n2 = note.Note('G4') >>> m1 = note.Note('G4') >>> m2 = note.Note('G3') >>> vl2 = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl2.antiParallelMotion() False ''' if not self.contraryMotion(): return False else: if self.vIntervals[0].simpleName == self.vIntervals[1].simpleName: if simpleName is None: return True else: if isinstance(simpleName, str): if self.vIntervals[0].simpleName == simpleName: return True else: return False else: # assume Interval object if self.vIntervals[0].simpleName == simpleName.simpleName: return True else: return False else: return False
[docs] def parallelInterval(self, thisInterval: interval.Interval) -> bool: ''' Returns True if there is a parallel motion or antiParallel motion of this type (thisInterval should be an Interval object). >>> n11 = note.Note('G4') >>> n12a = note.Note('A4') # ascending 2nd >>> n21 = note.Note('C4') >>> n22a = note.Note('D4') # ascending 2nd >>> vlq1 = voiceLeading.VoiceLeadingQuartet(n11, n12a, n21, n22a) >>> vlq1.parallelInterval(interval.Interval('P5')) True >>> vlq1.parallelInterval(interval.Interval('P8')) False Antiparallel fifths also are True >>> n22b = note.Note('D3') # descending 7th >>> vlq2 = voiceLeading.VoiceLeadingQuartet(n11, n12a, n21, n22b) >>> vlq2.parallelInterval(interval.Interval('P5')) True But Antiparallel other intervals are not: >>> N = note.Note >>> vlq2a = voiceLeading.VoiceLeadingQuartet(N('C5'), N('C6'), N('C4'), N('C3')) >>> vlq2a.parallelInterval(interval.Interval('P5')) False >>> vlq2a.parallelInterval(interval.Interval('P8')) True Non-parallel intervals are, of course, False >>> n12b = note.Note('B4') # ascending 3rd >>> vlq3 = voiceLeading.VoiceLeadingQuartet(n11, n12b, n21, n22b) >>> vlq3.parallelInterval(interval.Interval('P5')) False ''' return ( self.parallelMotion( requiredInterval=thisInterval, allowOctaveDisplacement=True ) or self.antiParallelMotion(thisInterval) )
[docs] def parallelFifth(self) -> bool: ''' Returns True if the motion is a parallel or antiparallel Perfect Fifth, allowing displacement by an octave (e.g., 5th to a 12th). We will make the examples shorter with this abbreviation: >>> N = note.Note Parallel fifths >>> vlq = voiceLeading.VoiceLeadingQuartet(N('G4'), N('A4'), N('C4'), N('D4')) >>> vlq.parallelFifth() True 5th -> 12th in similar motion >>> vlq = voiceLeading.VoiceLeadingQuartet(N('G4'), N('A5'), N('C4'), N('D4')) >>> vlq.parallelFifth() True 5th -> 12th in antiparallel motion >>> vlq = voiceLeading.VoiceLeadingQuartet(N('G4'), N('A4'), N('C4'), N('D3')) >>> vlq.parallelFifth() True Note that diminished fifth moving to perfect fifth is not a parallelFifth >>> vlq = voiceLeading.VoiceLeadingQuartet(N('G4'), N('A4'), N('C#4'), N('D4')) >>> vlq.parallelFifth() False Nor is P5 moving to d5. >>> vlq = voiceLeading.VoiceLeadingQuartet(N('G4'), N('Ab4'), N('C4'), N('D4')) >>> vlq.parallelFifth() False ''' return self.parallelInterval(_intervals.fifth)
[docs] def parallelOctave(self) -> bool: ''' Returns True if the motion is a parallel Perfect Octave. (This is a concept so abhorrent we shudder to illustrate it with an example, but alas, we must:) We will make the examples shorter with this abbreviation: >>> N = note.Note >>> vlq = voiceLeading.VoiceLeadingQuartet(N('C5'), N('D5'), N('C4'), N('D4')) >>> vlq.parallelOctave() True >>> vlq = voiceLeading.VoiceLeadingQuartet(N('C6'), N('D6'), N('C4'), N('D4')) >>> vlq.parallelOctave() True Or False if the motion is according to the rules of God's own creation :-) >>> vlq = voiceLeading.VoiceLeadingQuartet(N('C4'), N('D4'), N('C4'), N('D4')) >>> vlq.parallelOctave() False (P.S., we were joking about parallel octaves being abhorant or out of God's creation! music21 works just as well with popular music and other styles that do not have problems with parallel octaves.) ''' return self.parallelInterval(_intervals.octave)
[docs] def parallelUnison(self) -> bool: ''' Returns True if the motion is a parallel Perfect Unison (and not Perfect Octave, etc.) We will make the examples shorter with this abbreviation: >>> N = note.Note >>> vlq = voiceLeading.VoiceLeadingQuartet(N('C4'), N('D4'), N('C4'), N('D4')) >>> vlq.parallelUnison() True >>> vlq = voiceLeading.VoiceLeadingQuartet(N('C5'), N('D5'), N('C4'), N('D4')) >>> vlq.parallelUnison() False ''' return self.parallelInterval(_intervals.unison)
[docs] def parallelUnisonOrOctave(self) -> bool: ''' Returns True if the VoiceLeadingQuartet has motion by parallel octave or parallel unison >>> voiceLeading.VoiceLeadingQuartet( ... note.Note('C4'), ... note.Note('D4'), ... note.Note('C3'), ... note.Note('D3') ... ).parallelUnisonOrOctave() True >>> voiceLeading.VoiceLeadingQuartet( ... note.Note('C4'), ... note.Note('D4'), ... note.Note('C4'), ... note.Note('D4') ... ).parallelUnisonOrOctave() True ''' return self.parallelUnison() or self.parallelOctave()
[docs] def hiddenInterval(self, thisInterval: str|interval.Interval) -> bool: ''' Returns True if there is a hidden interval that matches thisInterval. N.B. -- this method finds ALL hidden intervals, not just those that are forbidden under traditional common practice counterpoint rules. Takes thisInterval, an Interval object. >>> n1 = note.Note('C4') >>> n2 = note.Note('G4') >>> m1 = note.Note('B4') >>> m2 = note.Note('D5') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.hiddenInterval(interval.Interval('P5')) True >>> n1 = note.Note('E4') >>> n2 = note.Note('G4') >>> m1 = note.Note('B4') >>> m2 = note.Note('D5') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.hiddenInterval(interval.Interval('P5')) False >>> n1 = note.Note('E4') >>> n2 = note.Note('G4') >>> m1 = note.Note('B4') >>> m2 = note.Note('D6') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.hiddenInterval(interval.Interval('P5')) False ''' if self.parallelMotion(allowOctaveDisplacement=True): return False elif not self.similarMotion(): return False else: if isinstance(thisInterval, str): thisInterval = interval.Interval(thisInterval) if self.vIntervals[1].simpleName == thisInterval.simpleName: return True else: return False
[docs] def hiddenFifth(self) -> bool: ''' Calls :meth:`~music21.voiceLeading.VoiceLeadingQuartet.hiddenInterval` by passing a fifth. ''' return self.hiddenInterval(_intervals.fifth)
[docs] def hiddenOctave(self) -> bool: ''' Calls hiddenInterval by passing an octave. ''' return self.hiddenInterval(_intervals.octave)
[docs] def voiceOverlap(self) -> bool: ''' Returns True if the second note in V1 is lower than the first in V2, or if the second note in V2 is higher than the first note in V1. We will make the examples shorter with this abbreviation: >>> N = note.Note >>> vl = voiceLeading.VoiceLeadingQuartet(N('A4'), N('B4'), N('F4'), N('G4')) >>> vl.voiceOverlap() # no overlap False >>> vl = voiceLeading.VoiceLeadingQuartet(N('A4'), N('B4'), N('F4'), N('A4')) >>> vl.voiceOverlap() # Motion to the SAME note is not considered overlap False >>> vl = voiceLeading.VoiceLeadingQuartet(N('A4'), N('C4'), N('F4'), N('Bb4')) >>> vl.voiceOverlap() # V2 overlaps V1 True >>> vl = voiceLeading.VoiceLeadingQuartet(N('A4'), N('E4'), N('F4'), N('D4')) >>> vl.voiceOverlap() # V1 overlaps V2 True ''' if self.v1n2.pitch < self.v2n1.pitch or self.v2n2.pitch > self.v1n1.pitch: return True else: return False
[docs] def voiceCrossing(self) -> bool: ''' Returns True if either note in V1 is lower than the simultaneous note in V2. We will make the examples shorter with this abbreviation: >>> N = note.Note >>> vl = voiceLeading.VoiceLeadingQuartet(N('A4'), N('A4'), N('G4'), N('G4')) >>> vl.voiceCrossing() # nothing crossed False >>> vl = voiceLeading.VoiceLeadingQuartet(N('A4'), N('F4'), N('G4'), N('G4')) >>> vl.voiceCrossing() # second interval is crossed True >>> vl = voiceLeading.VoiceLeadingQuartet(N('F4'), N('A4'), N('G4'), N('G4')) >>> vl.voiceCrossing() # first interval crossed True >>> vl = voiceLeading.VoiceLeadingQuartet(N('F4'), N('F4'), N('G4'), N('G4')) >>> vl.voiceCrossing() # both crossed True ''' if self.v1n1.pitch < self.v2n1.pitch or self.v1n2.pitch < self.v2n2.pitch: return True else: return False
[docs] def isProperResolution(self) -> bool: ''' Checks whether the voice-leading quartet resolves correctly according to standard counterpoint rules. If the first harmony is dissonant (P4, d5, A4, or m7) it checks that these are correctly resolved. If the first harmony is consonant, True is returned. The key parameter should be specified to check for motion in the bass from specific note degrees. If it is not set, then no checking for scale degrees takes place. Currently implements the following resolutions: P4: Top voice must resolve downward. A4: out by contrary motion to a sixth, with chordal seventh resolving down to a third in the bass. d5: in by contrary motion to a third, with 7 resolving up to 1 in the bass m7: Resolves to a third with a leap from 5 to 1 in the bass We will make the examples shorter with this abbreviation: >>> N = note.Note >>> n1 = note.Note('B-4') >>> n2 = note.Note('A4') >>> m1 = note.Note('E4') >>> m2 = note.Note('F4') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.isProperResolution() # d5 resolves inward True >>> m2.pitch.name = 'D' >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.isProperResolution() # d5 resolves outward False >>> vl.key = 'B-' >>> vl.isProperResolution() # not on scale degrees that need resolution True >>> n1 = note.Note('D4') >>> n2 = note.Note('C4') >>> m1 = note.Note('G#3') >>> m2 = note.Note('A3') >>> k = key.Key('a') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2, k) >>> vl.isProperResolution() # d5 with #7 in minor handled correctly True >>> n1 = note.Note('E5') >>> n2 = note.Note('F5') >>> m1 = note.Note('B-4') >>> m2 = note.Note('A4') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.isProperResolution() # A4 resolves outward True >>> m2.pitch.nameWithOctave = 'D5' >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.isProperResolution() # A4 resolves inward False >>> vl.key = 'B-' >>> vl.isProperResolution() # A4 not on scale degrees that need resolution True >>> vl.key = 'F' >>> vl.isProperResolution() # A4 on scale degrees that need resolution False >>> n1 = note.Note('B-4') >>> n2 = note.Note('A4') >>> m1 = note.Note('C4') >>> m2 = note.Note('F4') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.isProperResolution() # m7 True >>> m2.pitch.nameWithOctave = 'F3' >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.isProperResolution() # m7 with similar motion True >>> vl.key = 'B-' >>> vl.isProperResolution() # m7 not on scale degrees that need resolution True >>> vl.key = 'F' >>> vl.isProperResolution() # m7 on scale degrees that need resolution True P4 on the initial harmony must move down. >>> n1 = note.Note('F5') >>> n2 = note.Note('G5') >>> m1 = note.Note('C4') >>> m2 = note.Note('C4') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.isProperResolution() # P4 must move down or remain static False >>> n2.step = 'E' >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.isProperResolution() # P4 can move down by step or leap True >>> vl = voiceLeading.VoiceLeadingQuartet('B-4', 'A4', 'C2', 'F2') >>> vl.key = key.Key('F') >>> vl.isProperResolution() # not dissonant, True returned True ''' if self.noMotion(): return True if self.key: keyScale = self.key.getScale(self.key.mode) n1degree = keyScale.getScaleDegreeFromPitch(self.v2n1) n2degree = keyScale.getScaleDegreeFromPitch(self.v2n2) # catches case of #7 in minor if self.key.mode == 'minor' and n1degree is None: minorScale = scale.MelodicMinorScale(self.key.tonic) n1degree = minorScale.getScaleDegreeFromPitch( self.v2n1, direction=scale.Direction.ASCENDING) else: keyScale = None n1degree = None n2degree = None firstHarmony = self.vIntervals[0].simpleName secondGeneric = self.vIntervals[1].generic if t.TYPE_CHECKING: assert secondGeneric is not None secondHarmony = secondGeneric.simpleUndirected if firstHarmony == 'P4': if self.v1n1 >= self.v1n2: return True else: return False elif firstHarmony == 'A4': if keyScale and n1degree != 4: return True if keyScale and n2degree != 3: return False return (self.outwardContraryMotion() and secondHarmony == 6) elif firstHarmony == 'd5': if keyScale and n1degree != 7: return True if keyScale and n2degree != 1: return False return (self.inwardContraryMotion() and secondHarmony == 3) elif firstHarmony == 'm7': if keyScale and n1degree != 5: return True if keyScale and n2degree != 1: return False return secondHarmony == 3 else: return True
[docs] def leapNotSetWithStep(self) -> bool: ''' Returns True if there is a leap or skip in one voice then the other voice must be a step or unison. If neither part skips then False is returned. Returns False if the two voices skip thirds in contrary motion. >>> n1 = note.Note('G4') >>> n2 = note.Note('C5') >>> m1 = note.Note('B3') >>> m2 = note.Note('A3') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.leapNotSetWithStep() False >>> n1 = note.Note('G4') >>> n2 = note.Note('C5') >>> m1 = note.Note('B3') >>> m2 = note.Note('F3') >>> vl = voiceLeading.VoiceLeadingQuartet(n1, n2, m1, m2) >>> vl.leapNotSetWithStep() True >>> vl = voiceLeading.VoiceLeadingQuartet('E', 'G', 'G', 'E') >>> vl.leapNotSetWithStep() False ''' if self.noMotion(): return False hInt0_generic = self.hIntervals[0].generic hInt1_generic = self.hIntervals[1].generic if t.TYPE_CHECKING: assert hInt0_generic is not None assert hInt1_generic is not None if (hInt0_generic.undirected == 3 and hInt1_generic.undirected == 3 and self.contraryMotion()): return False if hInt0_generic.isSkip: return not (hInt1_generic.isDiatonicStep or hInt1_generic.isUnison) elif hInt1_generic.isSkip: return not (hInt0_generic.isDiatonicStep or hInt0_generic.isUnison) else: return False
[docs] def modalOpening(self) -> bool: ''' Returns True if the VLQ would be an acceptable opening in the style of 16th century Counterpoint (not Bach Chorale style). Returns True if the opening or second harmonic interval is PU, P8, or P5, to accommodate an anacrusis, and the opening establishes tonic or dominant harmony (uses :meth:`~music21.roman.identifyAsTonicOrDominant`). >>> vl = voiceLeading.VoiceLeadingQuartet('D', 'D', 'D', 'F#') >>> vl.key = 'D' >>> vl.modalOpening() True >>> vl = voiceLeading.VoiceLeadingQuartet('B', 'A', 'G#', 'A') >>> vl.key = 'A' >>> vl.modalOpening() True >>> vl = voiceLeading.VoiceLeadingQuartet('A', 'A', 'F#', 'D') >>> vl.key = 'A' >>> vl.modalOpening() True >>> vl = voiceLeading.VoiceLeadingQuartet('C#', 'C#', 'D', 'E') >>> vl.key = 'A' >>> vl.modalOpening() False >>> vl = voiceLeading.VoiceLeadingQuartet('B', 'B', 'A', 'A') >>> vl.key = 'C' >>> vl.modalOpening() False * New in v11: replaces the deprecated ``opensIncorrectly``, with the sense of the returned boolean reversed. ''' from music21 import roman if self.key is None: raise VoiceLeadingQuartetException( 'modalOpening requires a key to be set ' 'on the VoiceLeadingQuartet' ) v0 = self.vIntervals[0] v1 = self.vIntervals[1] v0ns = v0.noteStart v0ne = v0.noteEnd v1ns = v1.noteStart v1ne = v1.noteEnd if t.TYPE_CHECKING: assert v0ns is not None and v0ne is not None assert v1ns is not None and v1ne is not None c1 = chord.Chord([v0ns, v0ne]) c2 = chord.Chord([v1ns, v1ne]) r1 = roman.identifyAsTonicOrDominant(c1, self.key) r2 = roman.identifyAsTonicOrDominant(c2, self.key) openingIntervals = ('P1', 'P5') openingFunctions = ('I', 'V') return ((v0.simpleName in openingIntervals or v1.simpleName in openingIntervals) and (r1[0].upper() in openingFunctions if r1 is not False else False or r2[0].upper() in openingFunctions if r2 is not False else False))
[docs] @common.decorators.deprecated( 'June 2026', 'v12', 'Use `not vlq.modalOpening()` instead.' ) def opensIncorrectly(self) -> bool: # pragma: no cover ''' Deprecated synonym for ``not self.modalOpening()``. ''' return not self.modalOpening()
[docs] def clausulaVera(self) -> bool: ''' Returns True if the VLQ is an acceptable closing (a *clausula vera* cadence) in the style of 16th century Counterpoint (not Bach Chorale style). The closing must be a stepwise, contrary-motion approach to the tonic: the two melodic motions are a minor second in one voice (the leading tone) and a major second in the other, both voices land on the tonic of :attr:`key`, and the closing harmonic interval is a unison or a single octave (not a double octave or wider). >>> vl = voiceLeading.VoiceLeadingQuartet('C#', 'D', 'E', 'D') >>> vl.key = key.Key('d') >>> vl.clausulaVera() True A close on the octave works too: >>> vl = voiceLeading.VoiceLeadingQuartet('B4', 'C5', 'D4', 'C4') >>> vl.key = key.Key('C') >>> vl.clausulaVera() True A leap to the tonic (here the bass leaps rather than steps) is not a clausula vera: >>> vl = voiceLeading.VoiceLeadingQuartet('B3', 'C4', 'G3', 'C2') >>> vl.key = key.Key('C') >>> vl.clausulaVera() False Similar motion (rather than contrary) is rejected: >>> vl = voiceLeading.VoiceLeadingQuartet('F', 'G', 'D', 'G') >>> vl.key = key.Key('g') >>> vl.clausulaVera() False And a stepwise contrary close onto the tonic two octaves apart is rejected, since the closing interval is a double octave: >>> vl = voiceLeading.VoiceLeadingQuartet('B4', 'C5', 'D3', 'C3') >>> vl.key = key.Key('C') >>> vl.clausulaVera() False Note that Phrygian clausula vera forms (in which the semitone is the descending upper-voice motion to the final rather than an ascending leading tone) are not currently respected. * New in v11: replaces the deprecated ``closesIncorrectly``, with the sense of the returned boolean reversed and the test simplified to a strict clausula vera. ''' if self.key is None: raise VoiceLeadingQuartetException( 'clausulaVera requires a key to be set ' 'on the VoiceLeadingQuartet' ) tonicName = self.key.tonic.name hIntervalNames = {self.hIntervals[0].name, self.hIntervals[1].name} return (hIntervalNames == {'m2', 'M2'} and self.contraryMotion() and self.vIntervals[1].name in ('P1', 'P8') and self.v1n2.name == tonicName and self.v2n2.name == tonicName)
[docs] @common.decorators.deprecated( 'June 2026', 'v12', 'Use `not vlq.clausulaVera()` instead.' ) def closesIncorrectly(self) -> bool: # pragma: no cover ''' Deprecated synonym for ``not self.clausulaVera()``. ''' return not self.clausulaVera()
class VoiceLeadingQuartetException(exceptions21.Music21Exception): pass
[docs] def getVerticalityFromObject(music21Obj, scoreObjectIsFrom, classFilterList=None): ''' Returns the :class:`~music21.voiceLeading.Verticality` object given a score, and a music21 object within this score (under development). >>> c = corpus.parse('bach/bwv66.6') >>> n1 = c.flatten().getElementsByClass(note.Note).first() >>> voiceLeading.getVerticalityFromObject(n1, c) <music21.voiceLeading.Verticality contentDict={0: [<music21.instrument.Instrument 'P1: Soprano: Instrument 1'>, <music21.clef.TrebleClef>, <music21.tempo.MetronomeMark Quarter=96 (playback only)>, <music21.key.Key of f# minor>, <music21.meter.TimeSignature 4/4>, <music21.note.Note C#>], 1: [<music21.instrument.Instrument 'P2: Alto: Instrument 2'>, <music21.clef.TrebleClef>, <music21.tempo.MetronomeMark Quarter=96 (playback only)>, <music21.key.Key of f# minor>, <music21.meter.TimeSignature 4/4>, <music21.note.Note E>], 2: [<music21.instrument.Instrument 'P3: Tenor: Instrument 3'>, <music21.clef.BassClef>, <music21.tempo.MetronomeMark Quarter=96 (playback only)>, <music21.key.Key of f# minor>, <music21.meter.TimeSignature 4/4>, <music21.note.Note A>], 3: [<music21.instrument.Instrument 'P4: Bass: Instrument 4'>, <music21.clef.BassClef>, <music21.tempo.MetronomeMark Quarter=96 (playback only)>, <music21.key.Key of f# minor>, <music21.meter.TimeSignature 4/4>, <music21.note.Note A>]}> For getting things at the beginning of scores, probably better to use a classFilterList: >>> voiceLeading.getVerticalityFromObject(n1, c, ... classFilterList=[note.Note, chord.Chord, note.Rest]) <music21.voiceLeading.Verticality contentDict={0: [<music21.note.Note C#>], 1: [<music21.note.Note E>], 2: [<music21.note.Note A>], 3: [<music21.note.Note A>]}> ''' offsetOfObject = music21Obj.getOffsetBySite(scoreObjectIsFrom.flatten()) contentDict = {} for partNum, partObj in enumerate(scoreObjectIsFrom.parts): elementSelection = partObj.flatten().getElementsByOffset( offsetOfObject, mustBeginInSpan=False, classList=classFilterList, ) for el in elementSelection: if partNum in contentDict: contentDict[partNum].append(el) else: contentDict[partNum] = [el] return Verticality(contentDict)
[docs] class Verticality(base.Music21Object): ''' DEPRECATED in favor of tree.verticality.Verticality (but still available since not every feature of it has been replicated there). A Verticality (previously called "vertical slice") object provides more accessible information about vertical moments in a score. A Verticality is instantiated by passing in a dictionary of the form {partNumber: [ music21Objects ] } Verticalities are useful to provide direct and easy access to objects in a part. A list of Verticalities, although similar to the list of chords from a chordified score, provides easier access to part number information and identity of objects in the score. Plus, the objects in a Verticality point directly to the objects in the score, so modifying a Verticality taken from a score is the same as modifying the elements of the Verticality in the score directly. >>> vs1 = voiceLeading.Verticality({0: [note.Note('A4'), harmony.ChordSymbol('Cm')], ... 1: [note.Note('F2')]}) >>> vs1.getObjectsByClass(note.Note) [<music21.note.Note A>, <music21.note.Note F>] >>> vs1.getObjectsByPart(0, note.Note) <music21.note.Note A> ''' # obsolete: To create Verticalities out of a score, call # by :meth:`~music21.theoryAnalyzer.getVerticalities` _DOC_ATTR: dict[str, str] = { 'contentDict': '''Dictionary representing contents of Verticalities. The keys of the dictionary are the part numbers and the element at each key is a list of music21 objects (allows for multiple voices in a single part).''', } def __init__(self, contentDict: dict|None = None, **keywords): super().__init__(**keywords) if contentDict is None: contentDict = {} for partNum, element in contentDict.items(): if not isinstance(element, list): contentDict[partNum] = [element] self.contentDict = contentDict
[docs] def isConsonant(self): ''' Evaluates whether this Verticality moment is consonant or dissonant according to the common-practice consonance rules. Method generates chord of all simultaneously sounding pitches, then calls :meth:`~music21.chord.isConsonant`. >>> V = voiceLeading.Verticality >>> N = note.Note >>> V({0: N('A4'), 1: N('B4'), 2: N('A4')}).isConsonant() False >>> V({0: N('A4'), 1: N('B4'), 2: N('C#4')}).isConsonant() False >>> V({0: N('C3'), 1: N('G5'), 2: chord.Chord(['C3', 'E4', 'G5'])}).isConsonant() True >>> V({0: N('A3'), 1: N('B3'), 2: N('C4')}).isConsonant() False >>> V({0: N('C1'), 1: N('C2'), 2: N('C3'), ... 3: N('G1'), 4: N('G2'), 5: N('G3')}).isConsonant() True >>> V({0: N('A3'), 1: harmony.ChordSymbol('Am')}).isConsonant() True ''' return self.getChord().isConsonant()
[docs] def getChord(self): ''' Extracts all simultaneously sounding pitches (from chords, notes, harmony objects, etc.) and returns as a chord. Pretty much returns the Verticality to a chordified output. >>> N = note.Note >>> vs1 = voiceLeading.Verticality({0:N('A4'), 1:chord.Chord(['B', 'C', 'A']), 2:N('A')}) >>> vs1.getChord() <music21.chord.Chord A4 B C A A> >>> voiceLeading.Verticality({0:N('A3'), ... 1:chord.Chord(['F3', 'D4', 'A4']), ... 2:harmony.ChordSymbol('Am')}).getChord() <music21.chord.Chord A3 F3 D4 A4 A2 C3 E3> ''' pitches = [] for el in self.objects: if isinstance(el, chord.Chord): for x in el.pitches: pitches.append(x.nameWithOctave) elif isinstance(el, note.Note): pitches.append(el) ch = chord.Chord(pitches) ch.style = self.style return ch
[docs] def makeAllSmallestDuration(self): ''' Locates the smallest duration of all elements in the Verticality and assigns this duration to each element. >>> n1 = note.Note('C4') >>> n1.quarterLength = 1 >>> n2 = note.Note('G4') >>> n2.quarterLength = 2 >>> cs = harmony.ChordSymbol('C') >>> cs.quarterLength = 4 >>> vs1 = voiceLeading.Verticality({0:n1, 1:n2, 2:cs}) >>> vs1.makeAllSmallestDuration() >>> [x.quarterLength for x in vs1.objects] [1.0, 1.0, 1.0] ''' self.changeDurationOfAllObjects(self.getShortestDuration())
[docs] def makeAllLargestDuration(self): ''' Locates the largest duration of all elements in the Verticality and assigns this duration to each element. >>> n1 = note.Note('C4') >>> n1.quarterLength = 1 >>> n2 = note.Note('G4') >>> n2.quarterLength = 2 >>> cs = harmony.ChordSymbol('C') >>> cs.quarterLength = 4 >>> vs1 = voiceLeading.Verticality({0:n1, 1:n2, 2:cs}) >>> vs1.makeAllLargestDuration() >>> [x.quarterLength for x in vs1.objects] [4.0, 4.0, 4.0] ''' self.changeDurationOfAllObjects(self.getLongestDuration())
[docs] def getShortestDuration(self): ''' Returns the smallest quarterLength that exists among all elements. >>> n1 = note.Note('C4') >>> n1.quarterLength = 1 >>> n2 = note.Note('G4') >>> n2.quarterLength = 2 >>> cs = harmony.ChordSymbol('C') >>> cs.quarterLength = 4 >>> vs1 = voiceLeading.Verticality({0:n1, 1:n2, 2:cs}) >>> vs1.getShortestDuration() 1.0 ''' return min([obj.quarterLength for obj in self.objects])
[docs] def getLongestDuration(self): ''' Returns the longest duration that exists among all elements. >>> n1 = note.Note('C4') >>> n1.quarterLength = 1 >>> n2 = note.Note('G4') >>> n2.quarterLength = 2 >>> cs = harmony.ChordSymbol('C') >>> cs.quarterLength = 4 >>> vs1 = voiceLeading.Verticality({0:n1, 1:n2, 2:cs}) >>> vs1.getLongestDuration() 4.0 ''' return max([obj.quarterLength for obj in self.objects])
[docs] def changeDurationOfAllObjects(self, newQuarterLength): ''' Changes the duration of all objects in Verticality. >>> n1 = note.Note('C4') >>> n1.quarterLength = 1 >>> n2 = note.Note('G4') >>> n2.quarterLength = 2 >>> cs = harmony.ChordSymbol('C') >>> cs.quarterLength = 4 >>> vs1 = voiceLeading.Verticality({0:n1, 1:n2, 2:cs}) >>> vs1.changeDurationOfAllObjects(1.5) >>> [x.quarterLength for x in vs1.objects] [1.5, 1.5, 1.5] Note: capitalization of function changed in v5.7 ''' for obj in self.objects: obj.quarterLength = newQuarterLength
[docs] def getObjectsByPart(self, partNum, classFilterList=None): ''' Returns the list of music21 objects associated with a given part number (if more than one). Returns the single object if only one. Optionally specify which type of objects to return with classFilterList. >>> vs1 = voiceLeading.Verticality({0: [note.Note('A4'), harmony.ChordSymbol('C')], ... 1: [note.Note('C')]}) >>> vs1.getObjectsByPart(0, classFilterList=['Harmony']) <music21.harmony.ChordSymbol C> >>> vs1.getObjectsByPart(0) [<music21.note.Note A>, <music21.harmony.ChordSymbol C>] >>> vs1.getObjectsByPart(1) <music21.note.Note C> ''' if not common.isIterable(classFilterList): classFilterList = [classFilterList] retList = [] for el in [el for el in self.contentDict[partNum] if el is not None]: if classFilterList == [None]: retList.append(el) else: if not el.classSet.isdisjoint(classFilterList): retList.append(el) if len(retList) > 1: return retList elif len(retList) == 1: return retList[0] else: return None
[docs] def getObjectsByClass(self, classFilterList, partNums=None): ''' Returns a list of all objects in the Verticality of a type contained in the classFilterList. Optionally specify part numbers to only search for matching objects. >>> N = note.Note >>> vs1 = voiceLeading.Verticality({0: [N('A4'), harmony.ChordSymbol('C')], ... 1: [N('C')], ... 2: [N('B'), N('F#')]}) >>> vs1.getObjectsByClass('Note') [<music21.note.Note A>, <music21.note.Note C>, <music21.note.Note B>, <music21.note.Note F#>] >>> vs1.getObjectsByClass('Note', [1, 2]) [<music21.note.Note C>, <music21.note.Note B>, <music21.note.Note F#>] ''' if not common.isIterable(classFilterList): classFilterList = [classFilterList] retList = [] for part, objList in self.contentDict.items(): for m21object in objList: if m21object is None or m21object.classSet.isdisjoint(classFilterList): continue else: if partNums and part not in partNums: continue retList.append(m21object) return retList
@property def objects(self): ''' Return a list of all the music21 objects in the Verticality. >>> vs1 = voiceLeading.Verticality({0: [harmony.ChordSymbol('C'), note.Note('A4'),], ... 1: [note.Note('C')]}) >>> vs1.objects [<music21.harmony.ChordSymbol C>, <music21.note.Note A>, <music21.note.Note C>] ''' retList = [] for unused_part, objList in self.contentDict.items(): for m21object in objList: retList.append(m21object) return retList
[docs] def getStream(self): ''' Returns a stream representation of this Verticality. Correct key, meter, and time signatures will be included if they are found in the context of the first part. >>> vs1 = voiceLeading.Verticality({0: [harmony.ChordSymbol('C'), note.Note('A4'),], ... 1: [note.Note('C')]}) >>> vsStream = vs1.getStream() >>> vsStream.show('text') {0.0} <music21.stream.Part part-0> {0.0} <music21.harmony.ChordSymbol C> {0.0} <music21.note.Note A> {0.0} <music21.stream.Part part-1> {0.0} <music21.note.Note C> How many notes are there anywhere in the hierarchy? >>> len(vsStream[note.Note]) 2 >>> len(vsStream[harmony.Harmony]) 1 ''' from music21 import stream retStream = stream.Score() for partNum, elementList in self.contentDict.items(): p = stream.Part(id=f'part-{partNum}') foundObj = elementList[0] cl = foundObj.getContextByClass(clef.Clef) ks = foundObj.getContextByClass(KeySignature) ts = foundObj.getContextByClass(meter.TimeSignature) if cl: p.append(cl) if ks: p.append(ks) if ts: p.append(ts) for el in elementList: p.insert(0, el) # probably wrong! Need to fix!!! retStream.insert(p) return retStream
[docs] def getVerticalityOffset(self, *, leftAlign=True): ''' Returns the overall offset of the Verticality. Typically, this would just be the offset of each object in the Verticality, and each object would have the same offset. However, if the duration of one object in the slice is different from the duration of another, and that other starts after the first, but the first is still sounding, then the offsets would be different. In this case, specify leftAlign=True to return the lowest valued-offset of all the objects in the Verticality. If you prefer the offset of the right-most starting object, then specify leftAlign=False. >>> s = stream.Score() >>> n1 = note.Note('A4', quarterLength=1.0) >>> s.append(n1) >>> n1.offset 0.0 >>> n2 = note.Note('F2', quarterLength =0.5) >>> s.append(n2) >>> n2.offset 1.0 >>> vs = voiceLeading.Verticality({0:n1, 1: n2}) >>> vs.getObjectsByClass(note.Note) [<music21.note.Note A>, <music21.note.Note F>] >>> vs.getVerticalityOffset(leftAlign=True) 0.0 >>> vs.getVerticalityOffset(leftAlign=False) 1.0 * Changed in v8: renamed getVerticalityOffset to not conflict with .offset property. Made leftAlign keyword only. ''' if not self.objects: return 0.0 if leftAlign: return sorted(self.objects, key=lambda m21Obj: m21Obj.offset)[0].offset else: return sorted(self.objects, key=lambda m21Obj: m21Obj.offset)[-1].offset
def _setLyric(self, value): newList = sorted(self.objects, key=lambda x: x.offset, reverse=True) newList[0].lyric = value def _getLyric(self): newList = sorted(self.objects, key=lambda x: x.offset, reverse=True) return newList[0].lyric lyric = property(_getLyric, _setLyric, doc=''' Sets each object on the Verticality to have the passed in lyric. >>> h = voiceLeading.Verticality({1: note.Note('C'), 2: harmony.ChordSymbol('C')}) >>> h.lyric = 'Verticality 1' >>> h.getStream().flatten().getElementsByClass(note.Note).first().lyric 'Verticality 1' ''') def _reprInternal(self): return f'contentDict={self.contentDict}' def _setColor(self, color): self.style.color = color for obj in self.objects: obj.style.color = color def _getColor(self): return self.style.color color = property(_getColor, _setColor, doc=''' Sets the color of each element in the Verticality. >>> vs1 = voiceLeading.Verticality({1:note.Note('C'), 2:harmony.ChordSymbol('D')}) >>> vs1.color = 'blue' >>> [(x, x.style.color) for x in vs1.objects] [(<music21.note.Note C>, 'blue'), (<music21.harmony.ChordSymbol D>, 'blue')] ''')
[docs] class VerticalityNTuplet(base.Music21Object): ''' A collection of n number of Verticalities. These objects are useful when analyzing counterpoint motion and music theory elements such as passing tones. ''' def __init__(self, listOfVerticalities=(), **keywords): super().__init__(**keywords) self.verticalities = listOfVerticalities self.nTupletNum = len(listOfVerticalities) self.chordList = [] if listOfVerticalities: self._calcChords() def _calcChords(self): for vs in self.verticalities: self.chordList.append(chord.Chord(vs.getObjectsByClass(note.Note))) def _reprInternal(self): return f'listOfVerticalities={self.verticalities}'
[docs] class VerticalityTriplet(VerticalityNTuplet): ''' A collection of three Verticalities. ''' def __init__(self, listOfVerticalities=(), **keywords): super().__init__(listOfVerticalities, **keywords) self.tnlsDict = {} # Three Note Linear Segments if listOfVerticalities: self._calcTNLS() def _calcTNLS(self): ''' Calculates the three note linear segments if only three Verticalities provided. ''' for partNum in range(min(len(self.verticalities[0].getObjectsByClass(note.Note)), len(self.verticalities[1].getObjectsByClass(note.Note)), len(self.verticalities[2].getObjectsByClass(note.Note))) ): self.tnlsDict[partNum] = ThreeNoteLinearSegment( [ self.verticalities[0].getObjectsByPart(partNum, note.Note), self.verticalities[1].getObjectsByPart(partNum, note.Note), self.verticalities[2].getObjectsByPart(partNum, note.Note) ] )
[docs] def hasPassingTone(self, partNumToIdentify, unaccentedOnly=False): ''' Return true if this Verticality triplet contains a passing tone. music21 currently identifies passing tones by analyzing both horizontal motion and vertical motion. It first checks to see if the note could be a passing tone based on the notes linearly adjacent to it. It then checks to see if the note's vertical context is dissonant, while the Verticalities to the left and right are consonant. partNum is the part (starting with 0) to identify the passing tone. >>> vs1 = voiceLeading.Verticality({0:note.Note('A4'), 1:note.Note('F2')}) >>> vs2 = voiceLeading.Verticality({0:note.Note('B-4'), 1:note.Note('F2')}) >>> vs3 = voiceLeading.Verticality({0:note.Note('C5'), 1:note.Note('E2')}) >>> vt = voiceLeading.VerticalityTriplet([vs1, vs2, vs3]) >>> vt.hasPassingTone(0) True >>> vt.hasPassingTone(1) False ''' if partNumToIdentify in self.tnlsDict: ret = self.tnlsDict[partNumToIdentify].couldBePassingTone() else: return False if unaccentedOnly: try: ret = ret and (self.tnlsDict[partNumToIdentify].n2.beatStrength < 0.5) except (AttributeError, NameError, base.Music21ObjectException): pass if (ret and self.chordList[0].isConsonant() and not self.chordList[1].isConsonant() and self.chordList[2].isConsonant()): return True else: return False
# check that the Verticality containing the passing tone is dissonant
[docs] def hasNeighborTone(self, partNumToIdentify, unaccentedOnly=False): ''' Return true if this Verticality triplet contains a neighbor tone. music21 currently identifies neighbor tones by analyzing both horizontal motion and vertical motion. It first checks to see if the note could be a neighbor tone based on the notes linearly adjacent to it. It then checks to see if the note's vertical context is dissonant, while the Verticalities to the left and right are consonant. partNum is the part (starting with 0) to identify the passing tone for use on 3 Verticalities (3-tuplet). >>> vs1 = voiceLeading.Verticality({0:note.Note('E-4'), 1: note.Note('C3')}) >>> vs2 = voiceLeading.Verticality({0:note.Note('E-4'), 1: note.Note('B2')}) >>> vs3 = voiceLeading.Verticality({0:note.Note('C5'), 1: note.Note('C3')}) >>> vt = voiceLeading.VerticalityTriplet([vs1, vs2, vs3]) >>> vt.hasNeighborTone(1) True ''' if partNumToIdentify in self.tnlsDict: ret = self.tnlsDict[partNumToIdentify].couldBeNeighborTone() else: return False if unaccentedOnly: try: ret = ret and (self.tnlsDict[partNumToIdentify].n2.beatStrength < 0.5) except (AttributeError, NameError, base.Music21ObjectException): pass return ret and not self.chordList[1].isConsonant()
[docs] class NNoteLinearSegment(base.Music21Object): ''' A list of n notes strung together in a sequence noteList = [note1, note2, note3, ..., note-n ] Once this object is created with a noteList, the noteList may not be changed. >>> n = voiceLeading.NNoteLinearSegment(['A', 'C', 'D']) >>> n.noteList [<music21.note.Note A>, <music21.note.Note C>, <music21.note.Note D>] ''' def __init__(self, noteList=(), **keywords): super().__init__(**keywords) self._noteList = [] for value in noteList: if value is None: self._noteList.append(None) elif isinstance(value, str): self._noteList.append(note.Note(value)) else: try: if not value.classSet.isdisjoint([note.Note, pitch.Pitch]): self._noteList.append(value) except (AttributeError, NameError): self._noteList.append(None) @property def noteList(self): ''' Read-only property -- returns a copy of the list of notes in the linear segment. >>> n = voiceLeading.NNoteLinearSegment(['A', 'B5', 'C', 'F#']) >>> n.noteList [<music21.note.Note A>, <music21.note.Note B>, <music21.note.Note C>, <music21.note.Note F#>] ''' return self._noteList[:] def _getMelodicIntervals(self): tempListOne = self.noteList[:-1] tempListTwo = self.noteList[1:] melodicIntervalList = [] for n1, n2 in zip(tempListOne, tempListTwo, strict=True): if n1 and n2: melodicIntervalList.append(interval.Interval(n1, n2)) else: melodicIntervalList.append(None) return melodicIntervalList melodicIntervals = property(_getMelodicIntervals, doc=''' Calculates the melodic intervals and returns them as a list, with the interval at 0 being the interval between the first and second note. >>> linSeg = voiceLeading.NNoteLinearSegment([note.Note('A'), note.Note('B'), ... note.Note('C'), note.Note('D')]) >>> linSeg.melodicIntervals [<music21.interval.Interval M2>, <music21.interval.Interval M-7>, <music21.interval.Interval M2>] ''')
class ThreeNoteLinearSegmentException(exceptions21.Music21Exception): pass
[docs] class ThreeNoteLinearSegment(NNoteLinearSegment): ''' An object consisting of three sequential notes. The middle tone in a ThreeNoteLinearSegment can be classified using methods enclosed in this class to identify it as types of embellishing tones. Further methods can be used on the entire stream to identify these as non-harmonic. Accepts a sequence of strings, pitches, or notes. >>> ex = voiceLeading.ThreeNoteLinearSegment('C#4', 'D4', 'E-4') >>> ex.n1 <music21.note.Note C#> >>> ex.n2 <music21.note.Note D> >>> ex.n3 <music21.note.Note E-> >>> ex = voiceLeading.ThreeNoteLinearSegment(note.Note('A4'),note.Note('D4'),'F5') >>> ex.n1 <music21.note.Note A> >>> ex.n2 <music21.note.Note D> >>> ex.n3 <music21.note.Note F> >>> ex.iLeftToRight <music21.interval.Interval m6> >>> ex.iLeft <music21.interval.Interval P-5> >>> ex.iRight <music21.interval.Interval m10> If no octave specified, default octave of 4 is assumed. >>> ex2 = voiceLeading.ThreeNoteLinearSegment('a', 'b', 'c') >>> ex2.n1 <music21.note.Note A> >>> defaults.pitchOctave 4 ''' _DOC_ORDER = ['couldBePassingTone', 'couldBeDiatonicPassingTone', 'couldBeChromaticPassingTone', 'couldBeNeighborTone', 'couldBeDiatonicNeighborTone', 'couldBeChromaticNeighborTone'] def __init__(self, noteListOrN1=None, n2=None, n3=None, **keywords): if common.isIterable(noteListOrN1): super().__init__(noteListOrN1, **keywords) else: super().__init__([noteListOrN1, n2, n3], **keywords) def _getN1(self): return self.noteList[0] def _setN1(self, value): self.noteList[0] = self._correctNoteInput(value) def _getN2(self): return self.noteList[1] def _setN2(self, value): self.noteList[1] = self._correctNoteInput(value) def _getN3(self): return self.noteList[2] def _setN3(self, value): self.noteList[2] = self._correctNoteInput(value) def _correctNoteInput(self, value): if value is None: return None elif isinstance(value, str): return note.Note(value) else: try: if not value.classSet.isdisjoint([note.Note, pitch.Pitch]): return value else: return None except AttributeError as e: # pragma: no cover raise ThreeNoteLinearSegmentException( f'not a valid note specification: {value!r}' ) from e n1 = property(_getN1, _setN1, doc=''' Get or set the first note (left-most) in the segment. ''') n2 = property(_getN2, _setN2, doc=''' Get or set the middle note in the segment. ''') n3 = property(_getN3, _setN3, doc=''' Get or set the last note (right-most) in the segment. ''') def _getILeftToRight(self): if self.n1 and self.n3: return interval.Interval(self.n1, self.n3) else: return None def _getILeft(self): return self.melodicIntervals[0] def _getIRight(self): return self.melodicIntervals[1] iLeftToRight = property(_getILeftToRight, doc=''' Get the interval between the left-most note and the right-most note (read-only property). >>> tnls = voiceLeading.ThreeNoteLinearSegment('C', 'E', 'G') >>> tnls.iLeftToRight <music21.interval.Interval P5> ''') iLeft = property(_getILeft, doc=''' Get the interval between the left-most note and the middle note (read-only property). >>> tnls = voiceLeading.ThreeNoteLinearSegment('A', 'B', 'G') >>> tnls.iLeft <music21.interval.Interval M2> ''') iRight = property(_getIRight, doc=''' Get the interval between the middle note and the right-most note (read-only property). >>> tnls = voiceLeading.ThreeNoteLinearSegment('A', 'B', 'G') >>> tnls.iRight <music21.interval.Interval M-3> ''') def _reprInternal(self): return f'n1={self.n1} n2={self.n2} n3={self.n3}' def _isComplete(self) -> bool: return (self.n1 is not None) and (self.n2 is not None) and (self.n3 is not None) # if any of these are None, it isn't complete
[docs] def couldBePassingTone(self) -> bool: ''' Checks if the two intervals are steps and if these steps are moving in the same direction. Returns True if the tone is identified as either a chromatic passing tone or a diatonic passing tone. Only major and minor diatonic passing tones are recognized (not pentatonic or scales beyond twelve-notes). Does NOT check if tone is non-harmonic. Accepts pitch or note objects; method is dependent on octave information. >>> voiceLeading.ThreeNoteLinearSegment('C#4', 'D4', 'E-4').couldBePassingTone() True >>> voiceLeading.ThreeNoteLinearSegment('C3', 'D3', 'E3').couldBePassingTone() True >>> voiceLeading.ThreeNoteLinearSegment('E-3', 'F3', 'G-3').couldBePassingTone() True >>> voiceLeading.ThreeNoteLinearSegment('C3', 'C3', 'C3').couldBePassingTone() False >>> voiceLeading.ThreeNoteLinearSegment('A3', 'C3', 'D3').couldBePassingTone() False Directionality must be maintained. >>> voiceLeading.ThreeNoteLinearSegment('B##3', 'C4', 'D--4').couldBePassingTone() False If no octave is given then ._defaultOctave is used. This is generally octave 4. >>> voiceLeading.ThreeNoteLinearSegment('C', 'D', 'E').couldBePassingTone() True >>> voiceLeading.ThreeNoteLinearSegment('C4', 'D', 'E').couldBePassingTone() True >>> voiceLeading.ThreeNoteLinearSegment('C5', 'D', 'E').couldBePassingTone() False Method returns True if either a chromatic passing tone or a diatonic passing tone is identified. Spelling of the pitch does matter! >>> voiceLeading.ThreeNoteLinearSegment('B3', 'C4', 'B##3').couldBePassingTone() False >>> voiceLeading.ThreeNoteLinearSegment('A##3', 'C4', 'E---4').couldBePassingTone() False >>> voiceLeading.ThreeNoteLinearSegment('B3', 'C4', 'D-4').couldBePassingTone() True >>> voiceLeading.ThreeNoteLinearSegment('B3', 'C4', 'C#4').couldBePassingTone() True ''' if not self._isComplete(): return False else: return self.couldBeDiatonicPassingTone() or self.couldBeChromaticPassingTone()
[docs] def couldBeDiatonicPassingTone(self): ''' A note could be a diatonic passing tone (and therefore a passing tone in general) if the generic interval between the previous and the current is 2 or -2; same for the next; and both move in the same direction (that is, the two intervals multiplied by each other are 4, not -4). >>> tls = voiceLeading.ThreeNoteLinearSegment('B3', 'C4', 'C#4') >>> tls.couldBeDiatonicPassingTone() False >>> tls = voiceLeading.ThreeNoteLinearSegment('C3', 'D3', 'E3') >>> tls.couldBeDiatonicPassingTone() True ''' return (self._isComplete() and self.iLeftToRight.generic.isSkip and self.iLeft.generic.undirected == 2 and self.iRight.generic.undirected == 2 and self.iLeft.generic.undirected * self.iRight.generic.undirected == 4 and self.iLeft.direction * self.iRight.direction == 1)
[docs] def couldBeChromaticPassingTone(self): ''' A note could be a chromatic passing tone (and therefore a passing tone in general) if the generic interval between the previous and the current is -2, 1, or 2; the generic interval between the current and next is -2, 1, 2; the two generic intervals multiply to -2 or 2 (if 4 then it's a diatonic interval; if 1 then not a passing tone; i.e, C -> C# -> C## is not a chromatic passing tone); AND between each of the notes there is a chromatic interval of 1 or -1 and multiplied together it is 1. (i.e.: C -> D-- -> D- is not a chromatic passing tone). >>> voiceLeading.ThreeNoteLinearSegment('B3', 'C4', 'C#4').couldBeChromaticPassingTone() True >>> voiceLeading.ThreeNoteLinearSegment('B3', 'C4', 'C#4').couldBeChromaticPassingTone() True >>> voiceLeading.ThreeNoteLinearSegment('B3', 'B#3', 'C#4').couldBeChromaticPassingTone() True >>> voiceLeading.ThreeNoteLinearSegment('B3', 'D-4', 'C#4').couldBeChromaticPassingTone() False >>> voiceLeading.ThreeNoteLinearSegment('B3', 'C##4', 'C#4').couldBeChromaticPassingTone() False >>> voiceLeading.ThreeNoteLinearSegment('C#4', 'C4', 'C##4').couldBeChromaticPassingTone() False >>> voiceLeading.ThreeNoteLinearSegment('D--4', 'C4', 'D-4').couldBeChromaticPassingTone() False ''' return (self._isComplete() and (self.iLeft.generic.undirected in (1, 2) and self.iRight.generic.undirected in (1, 2) and self.iLeft.generic.undirected * self.iRight.generic.undirected == 2 and self.iLeft.isChromaticStep and self.iRight.isChromaticStep and self.iLeft.direction * self.iRight.direction == 1))
[docs] def couldBeNeighborTone(self): ''' Checks if noteToAnalyze could be a neighbor tone, either a diatonic neighbor tone or a chromatic neighbor tone. Does NOT check if tone is non-harmonic. >>> voiceLeading.ThreeNoteLinearSegment('E3', 'F3', 'E3').couldBeNeighborTone() True >>> voiceLeading.ThreeNoteLinearSegment('B-4', 'C5', 'B-4').couldBeNeighborTone() True >>> voiceLeading.ThreeNoteLinearSegment('B4', 'C5', 'B4').couldBeNeighborTone() True >>> voiceLeading.ThreeNoteLinearSegment('G4', 'F#4', 'G4').couldBeNeighborTone() True >>> voiceLeading.ThreeNoteLinearSegment('E-3', 'F3', 'E-4').couldBeNeighborTone() False >>> voiceLeading.ThreeNoteLinearSegment('C3', 'D3', 'E3').couldBeNeighborTone() False >>> voiceLeading.ThreeNoteLinearSegment('A3', 'C3', 'D3').couldBeNeighborTone() False ''' if not self._isComplete(): return False else: return self.couldBeDiatonicNeighborTone() or self.couldBeChromaticNeighborTone()
[docs] def couldBeDiatonicNeighborTone(self) -> bool: ''' Returns True if and only if noteToAnalyze could be a diatonic neighbor tone, that is, the left and right notes are identical while the middle is a diatonic step up or down. >>> voiceLeading.ThreeNoteLinearSegment('C3', 'D3', 'C3').couldBeDiatonicNeighborTone() True >>> voiceLeading.ThreeNoteLinearSegment('C3', 'C#3', 'C3').couldBeDiatonicNeighborTone() False >>> voiceLeading.ThreeNoteLinearSegment('C3', 'D-3', 'C3').couldBeDiatonicNeighborTone() False ''' return (self._isComplete() and self.n1.nameWithOctave == self.n3.nameWithOctave and self.iLeft.chromatic.undirected == 2 and self.iRight.chromatic.undirected == 2 and self.iLeft.direction * self.iRight.direction == -1)
[docs] def couldBeChromaticNeighborTone(self) -> bool: ''' Returns True if and only if noteToAnalyze could be a chromatic neighbor tone, that is, the left and right notes are identical while the middle is a chromatic step up or down. >>> voiceLeading.ThreeNoteLinearSegment('C3', 'D3', 'C3').couldBeChromaticNeighborTone() False >>> voiceLeading.ThreeNoteLinearSegment('C3', 'D-3', 'C3').couldBeChromaticNeighborTone() True >>> voiceLeading.ThreeNoteLinearSegment('C#3', 'D3', 'C#3').couldBeChromaticNeighborTone() True >>> voiceLeading.ThreeNoteLinearSegment('C#3', 'D3', 'D-3').couldBeChromaticNeighborTone() False ''' return (self._isComplete() and (self.n1.nameWithOctave == self.n3.nameWithOctave and self.iLeft.isChromaticStep and self.iRight.isChromaticStep and (self.iLeft.direction * self.iRight.direction == -1)))
# Below: beginnings of an implementation for any object segments, # such as two chord linear segments # currently only used by theoryAnalyzer class NChordLinearSegmentException(exceptions21.Music21Exception): pass
[docs] class NObjectLinearSegment(base.Music21Object): def __init__(self, objectList=(), **keywords): super().__init__(**keywords) self.objectList = objectList def _reprInternal(self): return f'objectList={self.objectList}'
[docs] class NChordLinearSegment(NObjectLinearSegment): def __init__(self, chordList=(), **keywords): super().__init__(chordList, **keywords) self._chordList = [] for value in chordList: if value is None: self._chordList.append(None) else: try: if not value.classSet.isdisjoint(['Chord', 'Harmony']): self._chordList.append(value) # else: # raise NChordLinearSegmentException( # f'not a valid chord specification: {value}') except AttributeError as e: # pragma: no cover raise NChordLinearSegmentException( f'not a valid chord specification: {value!r}' ) from e @property def chordList(self): ''' Returns a list of all chord symbols in this linear segment. Modifying the list does not change the linear segment. >>> n = voiceLeading.NChordLinearSegment([harmony.ChordSymbol('Am'), ... harmony.ChordSymbol('F7'), ... harmony.ChordSymbol('G9')]) >>> n.chordList [<music21.harmony.ChordSymbol Am>, <music21.harmony.ChordSymbol F7>, <music21.harmony.ChordSymbol G9>] ''' return self._chordList[:] def _reprInternal(self): return f'chordList={self.chordList}'
[docs] class TwoChordLinearSegment(NChordLinearSegment): def __init__(self, chordList=(), chord2=None, **keywords): if isinstance(chordList, (list, tuple)): if chordList and len(chordList) != 2: # pragma: no cover raise ValueError( f'First argument must be a list of length 2, not {chordList!r}' ) super().__init__(chordList, **keywords) else: super().__init__([chordList, chord2], **keywords)
[docs] def rootInterval(self): ''' Returns the chromatic interval between the roots of the two chord symbols. >>> h = voiceLeading.TwoChordLinearSegment([harmony.ChordSymbol('C'), ... harmony.ChordSymbol('G')]) >>> h.rootInterval() <music21.interval.ChromaticInterval 7> ''' return interval.notesToChromatic(self.chordList[0].root(), self.chordList[1].root())
[docs] def bassInterval(self): ''' Returns the chromatic interval between the basses of the two chord symbols. >>> h = voiceLeading.TwoChordLinearSegment(harmony.ChordSymbol('C/E'), ... harmony.ChordSymbol('G')) >>> h.bassInterval() <music21.interval.ChromaticInterval 3> ''' return interval.notesToChromatic(self.chordList[0].bass(), self.chordList[1].bass())
[docs] def iterateAllVoiceLeadingQuartets( s: stream.Stream, *, includeRests: bool = True, includeOblique: bool = True, includeNoMotion: bool = False, reverse: bool = False, ) -> Generator[VoiceLeadingQuartet, None, None]: ''' Iterate through all VoiceLeading quartets in a Stream (generally a Score), yielding a generator of VoiceLeadingQuartets. N.B. does not yet support Streams with Chords in them. >>> b = corpus.parse('bwv66.6') >>> for vlq in voiceLeading.iterateAllVoiceLeadingQuartets(b): ... print(vlq.v1n1.measureNumber, ... vlq.v1n1.getContextByClass(stream.Part).id, ... vlq.v2n1.getContextByClass(stream.Part).id, ... vlq) 0 Soprano Tenor <music21.voiceLeading.VoiceLeadingQuartet v1n1=B4, v1n2=A4, v2n1=B3, v2n2=C#4> 0 Soprano Bass <music21.voiceLeading.VoiceLeadingQuartet v1n1=B4, v1n2=A4, v2n1=G#3, v2n2=F#3> 0 Tenor Bass <music21.voiceLeading.VoiceLeadingQuartet v1n1=B3, v1n2=C#4, v2n1=G#3, v2n2=F#3> 1 Soprano Alto <music21.voiceLeading.VoiceLeadingQuartet v1n1=A4, v1n2=B4, v2n1=F#4, v2n2=E4> 1 Soprano Tenor <music21.voiceLeading.VoiceLeadingQuartet v1n1=A4, v1n2=B4, v2n1=C#4, v2n2=B3> 1 Soprano Bass <music21.voiceLeading.VoiceLeadingQuartet v1n1=A4, v1n2=B4, v2n1=F#3, v2n2=G#3> 1 Alto Tenor <music21.voiceLeading.VoiceLeadingQuartet v1n1=F#4, v1n2=E4, v2n1=C#4, v2n2=B3> 1 Alto Bass <music21.voiceLeading.VoiceLeadingQuartet v1n1=F#4, v1n2=E4, v2n1=F#3, v2n2=G#3> ... ''' for v in s.asTimespans().iterateVerticalities(reverse=reverse): yield from v.getAllVoiceLeadingQuartets( includeRests=includeRests, includeOblique=includeOblique, includeNoMotion=includeNoMotion )
# ------------------------------------------------------------------------------ class Test(unittest.TestCase): def testCopyAndDeepcopy(self): from music21.test.commonTest import testCopyAll testCopyAll(self, globals()) def test_unifiedTest(self): c4 = note.Note('C4') d4 = note.Note('D4') e4 = note.Note('E4') # f4 = note.Note('F4') g4 = note.Note('G4') a4 = note.Note('A4') # b4 = note.Note('B4') c5 = note.Note('C5') # d5 = note.Note('D5') a = VoiceLeadingQuartet(c4, d4, g4, a4) assert a.similarMotion() is True assert a.parallelMotion() is True assert a.antiParallelMotion() is False assert a.obliqueMotion() is False assert a.parallelInterval(interval.Interval('P5')) is True assert a.parallelInterval(interval.Interval('M3')) is False b = VoiceLeadingQuartet(c4, c4, g4, g4) assert b.noMotion() is True assert b.parallelMotion() is False assert b.antiParallelMotion() is False assert b.obliqueMotion() is False c = VoiceLeadingQuartet(c4, g4, c5, g4) assert c.antiParallelMotion() is True assert c.hiddenInterval(interval.Interval('P5')) is False d = VoiceLeadingQuartet(c4, d4, e4, a4) assert d.hiddenInterval(interval.Interval('P5')) is True assert d.hiddenInterval(interval.Interval('A4')) is False assert d.hiddenInterval(interval.Interval('AA4')) is False class TestExternal(unittest.TestCase): pass # ----------------------------------------------------------------------------- _DOC_ORDER = [VoiceLeadingQuartet, ThreeNoteLinearSegment, Verticality, VerticalityNTuplet] if __name__ == '__main__': import music21 music21.mainTest(Test)