Source code for music21.tree.analysis
# -----------------------------------------------------------------------------
# Name: tree/analysis.py
# Purpose: horizontal analysis tools on timespan trees
#
# Authors: Joséphine Wolf Oberholtzer
#
# Copyright: Copyright © 2013-2014 Michael Scott Asato Cuthbert
# License: BSD, see license.txt
# -----------------------------------------------------------------------------
'''
Tools for performing voice-leading analysis with trees.
'''
from __future__ import annotations
import collections.abc
import unittest
from music21 import environment
from music21 import exceptions21
environLocal = environment.Environment('tree.analysis')
class HorizontalityException(exceptions21.TreeException):
pass
# -----------------------------------------------------------------------------
[docs]
class Horizontality(collections.abc.Sequence):
r'''
A horizontality of consecutive PitchedTimespan objects.
It must be initiated with a list or tuple of Timespan objects.
'''
# CLASS VARIABLES #
__slots__ = (
'timespans',
)
# INITIALIZER #
def __init__(self, timespans=None):
if not isinstance(timespans, collections.abc.Sequence):
raise HorizontalityException(f'timespans must be a sequence, not {timespans!r}')
if not timespans:
raise HorizontalityException(
'there must be at least one timespan in the timespans list')
if not all(hasattr(x, 'offset') and hasattr(x, 'endTime') for x in timespans):
raise HorizontalityException('only Timespan objects can be added to a horizontality')
self.timespans = tuple(timespans)
# SPECIAL METHODS #
[docs]
def __getitem__(self, item):
return self.timespans[item]
def __len__(self):
return len(self.timespans)
def __repr__(self):
pitchStrings = []
for x in self:
joinedPitches = ', '.join(y.nameWithOctave for y in x.pitches)
out = f'({joinedPitches},)'
pitchStrings.append(out)
pitchStr = ' '.join(pitchStrings)
return f'<{type(self).__name__}: {pitchStr}>'
# PROPERTIES #
@property
def hasPassingTone(self):
r'''
Is true if the Horizontality contains a passing tone; currently defined as three tones in
one direction.
(TODO: better check)
'''
if len(self) < 3:
return False
elif not all(len(x.pitches) for x in self):
return False
pitches = (
self[0].pitches[0],
self[1].pitches[0],
self[2].pitches[0],
)
if pitches[0] < pitches[1] < pitches[2]:
return True
elif pitches[0] > pitches[1] > pitches[2]:
return True
return False
@property
def hasNeighborTone(self):
r'''
Is true if the Horizontality contains a neighbor tone.
'''
if len(self) < 3:
return False
elif not all(len(x.pitches) for x in self):
return False
pitches = (
self[0].pitches[0],
self[1].pitches[0],
self[2].pitches[0],
)
if pitches[0] == pitches[2]:
if abs(pitches[1].ps - pitches[0].ps) < 3:
return True
return False
@property
def hasNoMotion(self):
r'''
Is true if the Horizontality contains no motion (including enharmonic restatements)
'''
pitchSets = set()
for x in self:
pitchSets.add(tuple(x.pitches))
if len(pitchSets) == 1:
return True
return False
# -----------------------------------------------------------------------------
class Test(unittest.TestCase):
pass
# -----------------------------------------------------------------------------
_DOC_ORDER = ()
# -----------------------------------------------------------------------------
if __name__ == '__main__':
import music21
music21.mainTest(Test)