Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions music21/analysis/reduceChords.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,31 @@ def testSimpleMeasure(self):
for c in [c1, c2, c3]:
s.append(c)

def testPartsWithoutPartId(self):
# parts from MIDI files often have no partId; this used to raise
# a TypeError when sorting the parts
from music21 import instrument
upper = stream.Part([instrument.Instrument(),
stream.Measure([chord.Chord('C4 E4', type='whole')])])
lower = stream.Part([instrument.Instrument(),
stream.Measure([note.Note('G3', type='whole')])])
score = stream.Score([upper, lower])
scoreTree = score.asTimespans()
self.assertEqual(scoreTree.allParts(), [upper, lower])
ChordReducer().run(score)

def testPartsOrderedAsInScore(self):
# the last part is used as the bass, so the order must follow the
# score even when partIds do not sort that way
from music21 import instrument
parts = []
for partId in ('P2', 'P10', 'P1'):
inst = instrument.Instrument()
inst.partId = partId
parts.append(stream.Part([inst, note.Note('C4', type='whole')]))
score = stream.Score(parts)
self.assertEqual(score.asTimespans().allParts(), parts)


class TestExternal(unittest.TestCase):
show = True
Expand Down
32 changes: 27 additions & 5 deletions music21/tree/timespanTree.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,11 +737,33 @@ def unwrapVerticalities(verticalities):
# PUBLIC PROPERTIES #

def allParts(self):
parts = set()
for timespan in self:
parts.add(timespan.part)
parts = sorted(parts, key=lambda x: x.getInstrument().partId)
return parts
'''
Returns a list of the parts that the timespans in this tree belong to.

If the tree's source has parts (such as a Score), the parts are returned
in the order they appear in the source. Other parts come after them,
sorted by the partId of their instrument.

Parts do not need a partId:

>>> upper = stream.Part([instrument.Instrument(), note.Note('E4')])
>>> lower = stream.Part([instrument.Instrument(), note.Note('C3')])
>>> score = stream.Score([upper, lower])
>>> scoreTree = score.asTimespans()
>>> scoreTree.allParts() == [upper, lower]
True
'''
parts = {timespan.part for timespan in self}
source = self.source
sourceOrder = {}
if source is not None and hasattr(source, 'parts'):
sourceOrder = {id(part): i for i, part in enumerate(source.parts)}

def sortKey(part):
partId = part.getInstrument().partId
return (sourceOrder.get(id(part), len(sourceOrder)), partId is None, str(partId))

return sorted(parts, key=sortKey)

def maximumOverlap(self):
'''
Expand Down
Loading