# -----------------------------------------------------------------------------
# Name: tree/trees.py
# Purpose: Subclasses of tree.core.AVLTree for different purposes
#
# Authors: Joséphine Wolf Oberholtzer
# Michael Scott Asato Cuthbert
#
# Copyright: Copyright © 2013-2016 Michael Scott Asato Cuthbert
# License: BSD, see license.txt
# -----------------------------------------------------------------------------
'''
Tools for grouping elements, timespans, and especially
pitched elements into kinds of searchable tree organized by start and stop offsets
and other positions.
'''
from __future__ import annotations
from math import inf
import unittest
import weakref
from music21 import common
from music21 import environment
from music21 import exceptions21
from music21.sorting import SortTuple
from music21.tree import core
from music21.tree import node as nodeModule
environLocal = environment.Environment('tree.trees')
INFINITY = inf
NEGATIVE_INFINITY = -inf
# -----------------------------------------------------------------------------
class ElementTreeException(exceptions21.TreeException):
pass
# -----------------------------------------------------------------------------
[docs]
class ElementTree(core.AVLTree):
r'''
A data structure for efficiently storing a score: flat or recursed or normal.
This data structure has no connection to the XML ElementTree.
This data structure stores ElementNodes: objects which implement both a
`position` and `endTime` property. It provides fast lookups of such
objects.
>>> et = tree.trees.ElementTree()
>>> et
<ElementTree {0} (-inf to inf)>
>>> s = stream.Stream()
>>> for i in range(100):
... n = note.Note()
... n.duration.quarterLength = 2.0
... s.insert(i * 2, n)
>>> for n in s:
... et.insert(n)
>>> et
<ElementTree {100} (0.0 <0.20...> to 200.0)>
>>> et.rootNode
<ElementNode: Start:126.0 <0.20...> Indices:(l:0 *63* r:100) Payload:<music21.note.Note C>>
>>> n2 = s[-1]
These operations are very fast
>>> et.index(n2, n2.sortTuple())
99
Get a position after a certain position:
>>> st = s[40].sortTuple()
>>> st
SortTuple(atEnd=0, offset=80.0, priority=0, classSortOrder=20, isNotGrace=1, insertIndex=...)
>>> st2 = et.getPositionAfter(st)
>>> st2.shortRepr()
'82.0 <0.20...>'
>>> st2.offset
82.0
>>> st3 = et.getPositionAfter(5.0)
>>> st3.offset
6.0
>>> et.getPositionAfter(4.0).offset
6.0
'''
# TYPING #
rootNode: nodeModule.ElementNode|None
# CLASS VARIABLES #
nodeClass = nodeModule.ElementNode
__slots__ = (
'_source',
'parentTrees',
)
def __init__(self, elements=None, source=None):
super().__init__()
self.parentTrees = weakref.WeakSet()
self._source = None
if elements and elements is not None:
self.insert(elements)
self.source = source
# Special Methods #
def __contains__(self, element):
# noinspection PyShadowingNames
r'''
Is true when the ElementTree contains the object within it
If element.sortTuple(self.source) returns the right information, it's a fast
O(log n) search. If not his is an O(n log n) operation in python not C, so slow.
>>> score = tree.examples.makeExampleScore()
>>> scoreTree = score.asTree(flatten=True)
>>> lastNote = score.flatten().notes[-1]
>>> lastNote in scoreTree
True
>>> n = note.Note('E--')
>>> n in scoreTree
False
>>> s = stream.Stream(id='tinyStream')
>>> s.insert(0, n)
>>> st = s.asTree(flatten=False)
>>> n in st
True
'''
s = self.source
sourcePosition = element.sortTuple(s)
# might be wrong if element not in s or s is None
nodeAtPosition = self.getNodeByPosition(sourcePosition)
if nodeAtPosition is not None:
if nodeAtPosition.payload is element:
return True
# not found, do slow search.
for pl in self:
if pl is element:
return True
return False
[docs]
def __eq__(self, expr):
r'''
Two ElementTrees are equal only if they are the same object.
>>> et1 = tree.trees.ElementTree()
>>> et2 = tree.trees.ElementTree()
>>> et3 = et1
>>> et1 == et2
False
>>> et1 == et3
True
>>> et2 != et1
True
'''
return self is expr
[docs]
def __getitem__(self, i):
# noinspection PyShadowingNames
r'''
Gets elements by integer index or slice. This is pretty fast in computational time
(O(log n)), but it's O(log n) in Python while normal list slicing is O(1) in C, so
don't use trees for __getitem__ searching if you don't have to.
>>> score = tree.examples.makeExampleScore()
>>> scoreTree = score.asTree(flatten=True)
>>> scoreTree
<ElementTree {20} (0.0 <0.-25...> to 8.0) <music21.stream.Score exampleScore>>
>>> scoreTree[0]
<music21.instrument.Instrument 'PartA: : '>
>>> scoreTree[-1]
<music21.bar.Barline type=final>
>>> scoreTree[2000] is None
True
Slices work
>>> scoreTree[2:5]
[<music21.clef.BassClef>, <music21.clef.BassClef>, <music21.meter.TimeSignature 2/4>]
>>> scoreTree[-6:-3]
[<music21.note.Note A>, <music21.note.Note B>, <music21.note.Note D#>]
>>> scoreTree[-100:-200]
[]
>>> for x in scoreTree[:]:
... x
<music21.instrument.Instrument 'PartA: : '>
...
<music21.bar.Barline type=final>
These should all be the same as the flat version:
>>> scoreFlat = score.flatten()
>>> for i in (0, -1, 10):
... if scoreFlat[i] is not scoreTree[i]:
... print('false!')
>>> for i, j in ((2, 5), (-6, -3)):
... sfSlice = scoreFlat[i:j]
... for n in range(i, j):
... sliceOffset = n - i
... if sfSlice[sliceOffset] is not scoreFlat[n]:
... print('false!')
'''
try:
nodeOrNodeList = self.getNodeByIndex(i)
except IndexError:
return None
if nodeOrNodeList is None:
return nodeOrNodeList
elif not isinstance(nodeOrNodeList, list):
return nodeOrNodeList.payload
else:
return [n.payload for n in nodeOrNodeList]
def __hash__(self):
return hash((type(self), id(self)))
def __len__(self):
r'''
Gets the length of the ElementTree, i.e., the number of elements enclosed.
This is a very very fast O(1).
>>> score = tree.examples.makeExampleScore()
>>> scoreTree = score.asTree(flatten=True)
>>> len(scoreTree)
20
Works well on OffsetTrees also, which are more complex, because they can
have multiple elements per Node.
>>> offTree = tree.trees.OffsetTree()
>>> len(offTree)
0
>>> tsList = [(0, 2), (0, 9), (1, 1), (2, 3), (3, 4),
... (4, 9), (5, 6), (5, 8), (6, 8), (7, 7)]
>>> noteList = [note.Note() for _ in tsList]
>>> for i,n in enumerate(noteList):
... n.offset, n.quarterLength = tsList[i]
>>> offTree.insert(noteList)
>>> len(offTree)
10
>>> len(offTree) == len(noteList)
True
>>> offTree.removeElements(noteList)
>>> len(offTree)
0
'''
if self.rootNode is None:
return 0
return self.rootNode.subtreeElementsStopIndex
def __repr__(self):
o = self.source
pos = self.lowestPosition()
endTime = self.endTime
if hasattr(pos, 'shortRepr'):
# sortTuple
pos = pos.shortRepr()
if hasattr(endTime, 'shortRepr'):
# sortTuple
endTime = endTime.shortRepr()
className = type(self).__name__
lenEnclosed = '{' + str(len(self)) + '}'
msg = f'<{className} {lenEnclosed} ({pos} to {endTime})'
if o is not None:
msg += f' {o!r}'
msg += '>'
return msg
def __setitem__(self, i, new):
# noinspection PyShadowingNames
r'''
Sets elements at index `i` to `new`, but keeping the old position
of the element there. (This is different from OffsetTrees, where things can move around).
>>> score = tree.examples.makeExampleScore()
>>> scoreTree = score.asTree(flatten=True)
>>> n = scoreTree[10]
>>> n
<music21.note.Note G#>
>>> scoreTree.getNodeByIndex(10)
<ElementNode: Start:2.0 <0.20...> Indices:(l:10 *10* r:11)
Payload:<music21.note.Note G#>>
>>> scoreTree[10] = note.Note('F#')
>>> scoreTree[10]
<music21.note.Note F#>
>>> scoreTree.getNodeByIndex(10)
<ElementNode: Start:2.0 <0.20...> Indices:(l:10 *10* r:11)
Payload:<music21.note.Note F#>>
>>> scoreTree[10:13]
[<music21.note.Note F#>, <music21.note.Note F>, <music21.note.Note G>]
>>> scoreTree[10:14:2] = [note.Note('E#'), note.Note('F-')]
>>> scoreTree[10:13]
[<music21.note.Note E#>, <music21.note.Note F>, <music21.note.Note F->]
'''
if isinstance(i, int):
n = self.getNodeByIndex(i)
if n is None:
message = f'Index must be less than {len(self)}'
raise TypeError(message)
n.payload = new
elif isinstance(i, slice):
if not isinstance(new, list):
message = f'If {i} is a slice, then {new} must be a list'
raise TypeError(message)
sliceLen = (i.stop - i.start) / i.step
if sliceLen != len(new):
message = f'{i} is a slice of len {sliceLen}, so {new} cannot have len {len(new)}'
raise TypeError(message)
for j, sliceIter in enumerate(range(i.start, i.stop, i.step)):
self[sliceIter] = new[j] # recursive.
else:
message = f'Indices must be ints or slices, got {i}'
raise TypeError(message)
def __str__(self):
'''
Print the whole contents of the tree.
Slow: O(n log n) time, but it's just for debugging
>>> score = tree.examples.makeExampleScore()
>>> scoreTree = score.asTree(flatten=True)
>>> print(scoreTree)
<ElementTree {20} (0.0 <0.-25...> to 8.0) <music21.stream.Score exampleScore>>
PartA: :
PartB: :
<music21.clef.BassClef>
<music21.clef.BassClef>
<music21.meter.TimeSignature 2/4>
<music21.meter.TimeSignature 2/4>
<music21.note.Note C>
<music21.note.Note C#>
<music21.note.Note D>
<music21.note.Note E>
<music21.note.Note G#>
<music21.note.Note F>
<music21.note.Note G>
<music21.note.Note E#>
<music21.note.Note A>
<music21.note.Note B>
<music21.note.Note D#>
<music21.note.Note C>
<music21.bar.Barline type=final>
<music21.bar.Barline type=final>
'''
result = []
result.append(repr(self))
for x in self:
subResult = str(x).splitlines()
subResult = ['\t' + x for x in subResult]
result.extend(subResult)
result = '\n'.join(result)
return result
def __iter__(self):
r'''
Iterates through all the nodes in the offset tree and returns each node's payload
Not an especially efficient way of using this beautiful tree object! But useful
for debugging or a final iteration for conversion.
>>> score = tree.examples.makeExampleScore()
>>> scoreTree = score.asTree(flatten=True)
>>> for x in scoreTree:
... print(x)
PartA: :
PartB: :
<music21.clef.BassClef>
<music21.clef.BassClef>
<music21.meter.TimeSignature 2/4>
<music21.meter.TimeSignature 2/4>
<music21.note.Note C>
<music21.note.Note C#>
...
'''
for node in self.iterNodes():
yield node.payload
# PRIVATE METHODS #
def _updateNodes(self, initialPosition=None, initialEndTime=None, visitedParents=None):
'''
runs updateIndices and updateEndTimes on the rootNode
and if the offset or endTime of the tree differs from
`initialPosition` or `initialEndTime` will run _updateParents()
as well.
Called by insert() and remove().
'''
if self.rootNode is not None:
self.rootNode.updateIndices()
self.rootNode.updateEndTimes()
if (self.lowestPosition() != initialPosition
or self.endTime != initialEndTime):
self._updateParents(initialPosition, visitedParents=visitedParents)
def _updateParents(self, oldPosition, visitedParents=None):
'''
Tells all parents that the position of this tree has
changed.
Not currently used.
'''
if visitedParents is None:
visitedParents = set()
for parent in self.parentTrees:
if parent is None or parent in visitedParents:
continue
visitedParents.add(parent)
parent._removeElementAtPosition(self, oldPosition)
# Trees don't have offsets currently
raise NotImplementedError
# # pylint: disable=all
# parent._insertCore(self.offset, self)
#
# parent._updateNodes(parentPosition, visitedParents=visitedParents)
def _removeElementAtPosition(self, element, position):
'''
removes an element or ElementTree from a position
(either its current .offset or its oldPosition) without updating
the indices, endTimes, etc.
'''
node = self.getNodeByPosition(position)
if node is None:
return
if isinstance(node.payload, list):
# OffsetTree
if element in node.payload:
node.payload.remove(element)
if not node.payload:
self.removeNode(position)
else:
if node.payload is element:
node.payload = None
if node.payload is None:
self.removeNode(position)
# PUBLIC METHODS #
[docs]
def getPositionFromElementUnsafe(self, el):
'''
A quick but dirty method for getting the likely position (or offset) of an element
within the elementTree from the element itself. Such as calling
el.getOffsetBySite(tree.source) or something like that.
Pulled out for subclassing
'''
return el.sortTuple(self.source)
[docs]
def populateFromSortedList(self, listOfTuples):
# noinspection PyShadowingNames
'''
This method assumes that the current tree is empty (or will be wiped) and
that listOfTuples is a non-empty
list where the first element is a unique position to insert,
and the second is the complete payload for that node, and
that the positions are strictly increasing in order.
This is about an order of magnitude faster (3ms vs 21ms for 1000 items; 31 vs. 30ms for
10,000 items) than running createNodeAtPosition() for each element in a list if it is
already sorted. Thus, it should be used when converting a
Stream where .isSorted is True into a tree.
If any of the conditions is not true, expect to get a dangerously
badly sorted tree that will be useless.
>>> bFlat = corpus.parse('bwv66.6').flatten()
>>> bFlat.isSorted
True
>>> listOfTuples = [(e.sortTuple(bFlat), e) for e in bFlat]
>>> listOfTuples[14]
(SortTuple(atEnd=0, offset=0.0, priority=0, ...),
<music21.key.Key of f# minor>)
>>> et = tree.trees.ElementTree()
>>> et.rootNode is None
True
>>> et.populateFromSortedList(listOfTuples)
>>> et.rootNode
<ElementNode: Start:14.5 <0.20...> Indices:(l:0 *99* r:199)
Payload:<music21.note.Note A>>
>>> n = et.rootNode
>>> while n is not None:
... print(n)
... n = n.leftChild
<ElementNode: Start:14.5 <0.20...> Indices:(l:0 *99* r:199) Payload:<music21.note.Note A>>
<ElementNode: Start:5.5 <0.20...> Indices:(l:0 *49* r:99) Payload:<music21.note.Note A>>
<ElementNode: Start:0.0 <0.20...> Indices:(l:0 *24* r:49) Payload:<music21.note.Note A>>
<ElementNode: Start:0.0 <0.1...> Indices:(l:0 *12* r:24)
Payload:<music21.tempo.MetronomeMark Quarter=96 (playback only)>>
<ElementNode: Start:0.0 <0.0...> Indices:(l:0 *6* r:12) Payload:<music21.clef.TrebleClef>>
<ElementNode: Start:0.0 <0.-25...> Indices:(l:0 *3* r:6)
Payload:<music21.instrument.Instrument 'P3: Tenor: Instrument 3'>>
<ElementNode: Start:0.0 <0.-25...> Indices:(l:0 *1* r:3)
Payload:<music21.instrument.Instrument 'P1: Soprano: Instrument 1'>>
<ElementNode: Start:0.0 <0.-30...> Indices:(l:0 *0* r:1)
Payload:<music21.metadata.Metadata object at 0x104adbdd8>>
>>> n = et.rootNode
>>> while n is not None:
... print(n)
... n = n.rightChild
<ElementNode: Start:14.5 <0.20...> Indices:(l:0 *99* r:199)
Payload:<music21.note.Note A>>
<ElementNode: Start:25.0 <0.20...> Indices:(l:100 *149* r:199)
Payload:<music21.note.Note G#>>
<ElementNode: Start:31.0 <0.20...> Indices:(l:150 *174* r:199)
Payload:<music21.note.Note B>>
<ElementNode: Start:34.0 <0.20...> Indices:(l:175 *187* r:199)
Payload:<music21.note.Note D>>
<ElementNode: Start:35.0 <0.20...> Indices:(l:188 *193* r:199)
Payload:<music21.note.Note A#>>
<ElementNode: Start:36.0 <0.-5...> Indices:(l:194 *196* r:199)
Payload:<music21.bar.Barline type=final>>
<ElementNode: Start:36.0 <0.-5...> Indices:(l:197 *198* r:199)
Payload:<music21.bar.Barline type=final>>
'''
def recurse(subListOfTuples, globalStartOffset) -> core.AVLNode|None:
'''
Divide and conquer.
'''
lenL = len(subListOfTuples)
if lenL == 0:
return None
midpoint = lenL // 2
midtuple = subListOfTuples[midpoint]
n = NodeClass(midtuple[0], midtuple[1])
n.payloadElementIndex = globalStartOffset + midpoint
n.subtreeElementsStartIndex = globalStartOffset
n.subtreeElementsStopIndex = globalStartOffset + lenL
n.leftChild = recurse(subListOfTuples[:midpoint],
globalStartOffset)
n.rightChild = recurse(subListOfTuples[midpoint + 1:],
globalStartOffset + midpoint + 1)
n.update()
return n
NodeClass = self.nodeClass
self.rootNode = recurse(listOfTuples, 0)
[docs]
def getNodeByIndex(self, i):
'''
Get a node whose element is at a particular index (not position). Works with slices too
See __getitem__ for caveats about speed.
>>> score = tree.examples.makeExampleScore()
>>> scoreTree = score.asTree(flatten=True)
>>> scoreTree
<ElementTree {20} (0.0 <0.-25...> to 8.0) <music21.stream.Score exampleScore>>
>>> scoreTree.getNodeByIndex(0)
<ElementNode: Start:0.0 <0.-25...> Indices:(l:0 *0* r:2)
Payload:<music21.instrument.Instrument 'PartA: : '>>
>>> scoreTree.getNodeByIndex(-1)
<ElementNode: Start:End <0.-5...> Indices:(l:19 *19* r:20)
Payload:<music21.bar.Barline type=final>>
>>> scoreTree.getNodeByIndex(slice(2, 5))
[<ElementNode: Start:0.0 <0.0...> Indices:(l:0 *2* r:4) Payload:<music21.clef.BassClef>>,
<ElementNode: Start:0.0 <0.0...> Indices:(l:3 *3* r:4) Payload:<music21.clef.BassClef>>,
<ElementNode: Start:0.0 <0.4...> Indices:(l:0 *4* r:8)
Payload:<music21.meter.TimeSignature 2/4>>]
>>> scoreTree.getNodeByIndex(slice(-6, -3))
[<ElementNode: Start:5.0 <0.20...> Indices:(l:9 *14* r:20) Payload:<music21.note.Note A>>,
<ElementNode: Start:6.0 <0.20...> Indices:(l:15 *15* r:17) Payload:<music21.note.Note B>>,
<ElementNode: Start:6.0 <0.20...> Indices:(l:16 *16* r:17) Payload:<music21.note.Note D#>>]
>>> scoreTree.getNodeByIndex(slice(-100, -200))
[]
'''
def recurseByIndex(node, index):
'''
Return the node element at a given index
'''
if node.payloadElementIndex == index:
return node
elif node.leftChild and index < node.payloadElementIndex:
return recurseByIndex(node.leftChild, index)
elif node.rightChild and node.payloadElementIndex <= index:
return recurseByIndex(node.rightChild, index)
def recurseBySlice(node, start, stop):
'''
Return a slice of the nodes (plural) whose indices are between start <= index < stop.
'''
result = []
if node is None:
return result
if start < node.payloadElementIndex and node.leftChild:
result.extend(recurseBySlice(node.leftChild, start, stop))
if start <= node.payloadElementIndex < stop:
result.append(node)
if node.payloadElementIndex < stop and node.rightChild:
result.extend(recurseBySlice(node.rightChild, start, stop))
return result
if isinstance(i, int):
if self.rootNode is None:
raise IndexError
if i < 0:
i = self.rootNode.subtreeElementsStopIndex + i
if i < 0 or self.rootNode.subtreeElementsStopIndex <= i:
raise IndexError
return recurseByIndex(self.rootNode, i)
elif isinstance(i, slice):
if self.rootNode is None:
return []
indices = i.indices(self.rootNode.subtreeElementsStopIndex)
outer_start, outer_stop = indices[0], indices[1]
return recurseBySlice(self.rootNode, outer_start, outer_stop)
else:
raise TypeError(f'Indices must be integers or slices, got {i}')
[docs]
def iterNodes(self):
'''
Identical to the iterating on a core.AVLTree -- yields each node in order
Slow: O(n log n) time so don't make this your main thing.
>>> score = tree.examples.makeExampleScore()
>>> scoreTree = score.asTree(flatten=True)
>>> scoreTree
<ElementTree {20} (0.0 <0.-25...> to 8.0) <music21.stream.Score exampleScore>>
>>> for node in scoreTree.iterNodes():
... print(node)
<ElementNode: Start:0.0 <0.-25...> Indices:(l:0 *0* r:2)
Payload:<music21.instrument.Instrument 'PartA: : '>>
<ElementNode: Start:0.0 <0.-25...> Indices:(l:1 *1* r:2)
Payload:<music21.instrument.Instrument 'PartB: : '>>
<ElementNode: Start:0.0 <0.0...> Indices:(l:0 *2* r:4) Payload:<music21.clef.BassClef>>
<ElementNode: Start:0.0 <0.0...> Indices:(l:3 *3* r:4) Payload:<music21.clef.BassClef>>
<ElementNode: Start:0.0 <0.4...> Indices:(l:0 *4* r:8)
Payload:<music21.meter.TimeSignature 2/4>>
<ElementNode: Start:0.0 <0.4...> Indices:(l:5 *5* r:6)
Payload:<music21.meter.TimeSignature 2/4>>
<ElementNode: Start:0.0 <0.20...> Indices:(l:5 *6* r:8) Payload:<music21.note.Note C>>
<ElementNode: Start:0.0 <0.20...> Indices:(l:7 *7* r:8) Payload:<music21.note.Note C#>>
<ElementNode: Start:1.0 <0.20...> Indices:(l:0 *8* r:20) Payload:<music21.note.Note D>>
<ElementNode: Start:2.0 <0.20...> Indices:(l:9 *9* r:11) Payload:<music21.note.Note E>>
...
<ElementNode: Start:7.0 <0.20...> Indices:(l:15 *17* r:20)
Payload:<music21.note.Note C>>
<ElementNode: Start:End <0.-5...> Indices:(l:18 *18* r:20)
Payload:<music21.bar.Barline type=final>>
<ElementNode: Start:End <0.-5...> Indices:(l:19 *19* r:20)
Payload:<music21.bar.Barline type=final>>
'''
# py 3 only
# yield from super().__iter__()
for n in super().__iter__():
yield n
[docs]
def index(self, element, position=None):
# noinspection PyShadowingNames
r'''
Gets index of `element` in tree. position could be none.
If the element is in the original score, then it should be very fast (O(log n))
>>> score = tree.examples.makeExampleScore()
>>> scoreFlat = score.flatten()
>>> n = scoreFlat.notes[-1]
>>> flatTree = scoreFlat.asTree()
>>> flatTree.index(n)
17
If it's not in the original stream, then it should be slower than doing
it on a stream (O (n log n)).
>>> scoreTree = score.asTree(flatten=True)
>>> n = score.flatten().notes[-1]
>>> scoreTree.index(n)
17
And if it's nowhere at all, you get a ValueError!
>>> scoreTree.index(note.Note('F-'))
Traceback (most recent call last):
ValueError: <music21.note.Note F-> not in Tree at position
SortTuple(atEnd=0, offset=0.0, priority=0, ...).
'''
if position is None:
position = self.getPositionFromElementUnsafe(element)
node = self.getNodeByPosition(position)
if node is None or node.payload is not element:
for i, n in enumerate(self):
if n is element:
return i
raise ValueError(f'{element} not in Tree at position {position}.')
return node.payloadElementIndex
def _getPositionsFromElements(self, elements):
'''
takes a list of elements and returns a list of positions.
In an ElementTree, this will be a list of .sortTuple() calls.
In an OffsetTree, this will be a list of .offset calls
'''
return [self.getPositionFromElementUnsafe(el) for el in elements]
[docs]
def insert(self, positionsOrElements, elements=None):
r'''
Inserts elements or `Timespans` into this tree.
>>> n = note.Note()
>>> ot = tree.trees.OffsetTree()
>>> ot
<OffsetTree {0} (-inf to inf)>
>>> ot.insert(10.0, n)
>>> ot
<OffsetTree {1} (10.0 to 11.0)>
>>> n2 = note.Note('D')
>>> n2.offset = 20
>>> n3 = note.Note('E')
>>> n3.offset = 5
>>> ot.insert([n2, n3])
>>> ot
<OffsetTree {3} (5.0 to 21.0)>
'''
initialPosition = self.lowestPosition()
initialEndTime = self.endTime
if elements is None:
elements = positionsOrElements
positions = None
else:
positions = positionsOrElements
if not common.isListLike(positions) or hasattr(positions, 'shortRepr'):
# is not a list and not a sortTuple
positions = [positions]
if (not common.isListLike(elements)
and not isinstance(elements, (set, frozenset))):
# not a list. a single element or timespan
elements = [elements]
if positions is None:
positions = self._getPositionsFromElements(elements)
for i, el in enumerate(elements):
pos = positions[i]
self._insertCore(pos, el)
self._updateNodes(initialPosition, initialEndTime)
def _insertCore(self, position, el):
'''
Inserts a single element at an offset, creating new nodes as necessary,
but does not updateIndices or updateEndTimes or updateParents
'''
self.createNodeAtPosition(position)
node = self.getNodeByPosition(position)
node.payload = el
[docs]
def highestPosition(self):
r'''
Gets the latest position in this tree.
Keep as a property, because a similar property exists on streams.
>>> score = corpus.parse('bwv66.6')
>>> tsTree = score.asTimespans(classList=(note.Note,))
>>> tsTree.highestPosition()
35.0
'''
def recurse(node):
if node.rightChild is not None:
return recurse(node.rightChild)
else:
return node.position
if self.rootNode is not None:
return recurse(self.rootNode)
else:
return NEGATIVE_INFINITY
[docs]
def lowestPosition(self):
r'''
Gets the earliest position in this tree.
>>> score = tree.examples.makeExampleScore()
>>> elTree = score.asTree()
>>> elTree.lowestPosition().shortRepr()
'0.0 <0.-20...>'
>>> tsTree = score.asTimespans()
>>> tsTree.lowestPosition()
0.0
'''
def recurse(node):
if node.leftChild is not None:
return recurse(node.leftChild)
return node.position
if self.rootNode is not None:
return recurse(self.rootNode)
else:
return NEGATIVE_INFINITY
# PROPERTIES #
@property
def source(self):
'''
the original stream. (stored as a weakref but returned unwrapped)
>>> example = tree.examples.makeExampleScore()
>>> eTree = example.asTree()
>>> eTree.source is example
True
>>> s = stream.Stream()
>>> eTree.source = s
>>> eTree.source is s
True
'''
return common.unwrapWeakref(self._source)
@source.setter
def source(self, expr):
# uses weakrefs so that garbage collection on the stream cache is possible
self._source = common.wrapWeakref(expr)
@property
def endTime(self):
r'''
Gets the latest stop position in this element-tree.
This is cast as a property so that it can be used like a TimeSpan in a TimeSpanTree
>>> score = corpus.parse('bwv66.6')
>>> tsTree = score.asTree()
>>> tsTree.endTime
36.0
Returns infinity if no elements exist:
>>> et = tree.trees.ElementTree()
>>> et.endTime
inf
'''
if self.rootNode is not None:
return self.rootNode.endTimeHigh
return INFINITY
# ---------------------------------------------------------------
[docs]
class OffsetTree(ElementTree):
'''
A tree representation where positions are offsets in the score
and each node has a payload which is a list of elements at
that offset (unsorted by sort order).
'''
__slots__ = ()
# TYPING #
rootNode: nodeModule.OffsetNode|None
nodeClass = nodeModule.OffsetNode
# SPECIAL METHODS #
def __contains__(self, element):
r'''
Is true when the ElementTree contains the object within it;
TRUE IF and ONLY if the
.offset of the element matches the position in the tree -- thus it is very fast!
>>> score = tree.examples.makeExampleScore()
>>> scoreTree = score.asTree(flatten=True, groupOffsets=True)
>>> score.flatten()[5] in scoreTree
True
Note that this way of finding an item won't work because the offset is different
from the flat offset:
>>> n = score.parts[0].measure(2).notes[1]
>>> n
<music21.note.Note F>
>>> n.offset
1.0
>>> n in scoreTree
False
'''
try:
offset = element.offset
except AttributeError as ae:
raise ElementTreeException(
'element must be a Music21Object, i.e., must have offset'
) from ae
candidates = self.elementsStartingAt(offset)
if element in candidates:
return True
else:
return False
[docs]
def __getitem__(self, i):
r'''
Gets elements by integer index or slice.
>>> score = tree.examples.makeExampleScore()
>>> scoreTree = score.asTree(flatten=True, groupOffsets=True)
>>> scoreTree[0]
<music21.instrument.Instrument 'PartA: : '>
>>> scoreTree[-1]
<music21.bar.Barline type=final>
>>> scoreTree[2:5]
[<music21.clef.BassClef>, <music21.clef.BassClef>, <music21.meter.TimeSignature 2/4>]
>>> scoreTree[-6:-3]
[<music21.note.Note A>, <music21.note.Note B>, <music21.note.Note D#>]
>>> scoreTree[-100:-200]
[]
'''
def recurseByIndex(node, index):
'''
Return the payload element at a given index
'''
if node.payloadElementsStartIndex <= index < node.payloadElementsStopIndex:
return node.payload[index - node.payloadElementsStartIndex]
elif node.leftChild and index < node.payloadElementsStartIndex:
return recurseByIndex(node.leftChild, index)
elif node.rightChild and node.payloadElementsStopIndex <= index:
return recurseByIndex(node.rightChild, index)
def recurseBySlice(node: nodeModule.OffsetNode, start, stop):
'''
Return a slice of the payload elements (plural) where start <= index < stop.
'''
result = []
if node is None:
return result
if start < node.payloadElementsStartIndex and node.leftChild:
result.extend(recurseBySlice(node.leftChild, start, stop))
if start < node.payloadElementsStopIndex and node.payloadElementsStartIndex < stop:
indexStart = start - node.payloadElementsStartIndex
indexStart = max(indexStart, 0)
indexStop = stop - node.payloadElementsStartIndex
result.extend(node.payload[indexStart:indexStop])
if node.payloadElementsStopIndex <= stop and node.rightChild:
result.extend(recurseBySlice(node.rightChild, start, stop))
return result
if isinstance(i, int):
if self.rootNode is None:
raise IndexError
if i < 0:
i = self.rootNode.subtreeElementsStopIndex + i
if i < 0 or self.rootNode.subtreeElementsStopIndex <= i:
raise IndexError
return recurseByIndex(self.rootNode, i)
elif isinstance(i, slice):
if self.rootNode is None:
return []
indices = i.indices(self.rootNode.subtreeElementsStopIndex)
outer_start, outer_stop = indices[0], indices[1]
return recurseBySlice(self.rootNode, outer_start, outer_stop)
else:
raise TypeError(f'Indices must be integers or slices, got {i}')
# def __setitem__(self, i, new):
# r'''
# Sets elements or timespans at index `i` to `new`.
# TODO: this should be a bit different for OffsetTrees, probably more like ElementTrees
# >>> tss = [
# ... tree.spans.Timespan(0, 2),
# ... tree.spans.Timespan(0, 9),
# ... tree.spans.Timespan(1, 1),
# ... ]
# >>> tsTree = tree.timespanTree.TimespanTree()
# >>> tsTree.insert(tss)
# >>> tsTree[0] = tree.spans.Timespan(-1, 6)
# >>> for x in tsTree:
# ... x
# <Timespan -1.0 6.0>
# <Timespan 0.0 9.0>
# <Timespan 1.0 1.0>
# Note however, that calling __getitem__ after __setitem__ will not return
# what you just set if the timing is wrong. This is different from the
# behavior on ElementTree which assumes that the new element wants to be
# at the old element's offset.
# >>> tsTree[2] = tree.spans.Timespan(-0.5, 4)
# >>> tsTree[2]
# <Timespan 0.0 9.0>
# >>> for x in tsTree:
# ... x
# <Timespan -1.0 6.0>
# <Timespan -0.5 4.0>
# <Timespan 0.0 9.0>
# Works with slices too.
# >>> tsTree[1:] = [tree.spans.Timespan(10, 20)]
# >>> for x in tsTree:
# ... x
# <Timespan -1.0 6.0>
# <Timespan 10.0 20.0>
# '''
# raise NotImplementedError
# # pylint: disable=all
# if isinstance(i, (int, slice)):
# old = self[i]
# self.removeTimespan(old)
# self.insert(new)
# else:
# message = f'Indices must be ints or slices, got {i}'
# raise TypeError(message)
def __iter__(self):
r'''
Iterates through all the nodes in the offset tree and returns each thing
in the payload.
Not an especially efficient way of using this beautiful tree object.
>>> score = tree.examples.makeExampleScore()
>>> scoreTree = score.asTree(flatten=True, groupOffsets=True)
>>> for x in scoreTree:
... print(x)
PartA: :
PartB: :
<music21.clef.BassClef>
<music21.clef.BassClef>
<music21.meter.TimeSignature 2/4>
<music21.meter.TimeSignature 2/4>
<music21.note.Note C>
<music21.note.Note C#>
...
'''
for node in self.iterNodes():
for el in node.payload:
yield el
# ---------static methods ------------------------
[docs]
@staticmethod
def elementEndTime(el, node):
'''
Use so that both OffsetTrees, which have elements which do not have a .endTime, and
TimespanTrees, which have element that have an .endTime but not a duration, can
use most of the same code.
'''
return node.position + el.duration.quarterLength
# ---------public methods ------------------------
[docs]
def getPositionFromElementUnsafe(self, el):
'''
A quick but dirty method for getting the likely position (or offset) of an element
within the elementTree from the element itself. Such as calling
el.getOffsetBySite(tree.source) or something like that.
Pulled out for subclassing
'''
return el.offset
[docs]
def append(self, el):
'''
Add an element to the end, making certain speed savings.
'''
initialPosition = self.lowestPosition() # will only change if is empty
endTime = self.endTime
if endTime == INFINITY:
endTime = 0
self._insertCore(endTime, el)
self._updateNodes(initialPosition, initialEndTime=None)
@staticmethod
def _insertCorePayloadSortKey(x):
'''
Payload sorting is done the old-fashioned way, because
the number of elements at a single offset should be few enough that
it is not a problem
'''
return x.sortTuple()[2:] # cut off atEnd and offset
def _insertCore(self, position, el):
'''
Inserts a single element at an offset, creating new nodes as necessary,
but does not updateIndices or updateEndTimes or updateParents
'''
self.createNodeAtPosition(position)
node = self.getNodeByPosition(position)
node.payload.append(el)
node.payload.sort(key=self._insertCorePayloadSortKey)
[docs]
def copy(self):
# noinspection PyShadowingNames
r'''
Creates a new tree with the same payload as this tree.
This is analogous to `dict.copy()`.
Much, much faster than creating a new tree; creating one
with 3600 items took 500ms. Creating the tree the first time
was 40 seconds, so about an 80x speedup.
>>> score = tree.examples.makeExampleScore()
>>> scoreTree = score.asTimespans()
>>> newTree = scoreTree.copy()
>>> newTree
<TimespanTree {20} (0.0 to 8.0) <music21.stream.Score exampleScore>>
>>> scoreTree[16]
<PitchedTimespan (6.0 to 8.0) <music21.note.Note D#>>
>>> newTree[16]
<PitchedTimespan (6.0 to 8.0) <music21.note.Note D#>>
>>> scoreTree[16] is newTree[16]
True
'''
newTree = type(self)()
# this is just as efficient as ._insertCore, since it's given a list.
newTree.insert(list(self))
newTree.source = self.source
newTree.parentTrees = self.parentTrees.copy()
return newTree
[docs]
def elementsStartingAt(self, position):
r'''
Finds elements or timespans in this tree which start at `position`.
>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans()
>>> for timespan in scoreTree.elementsStartingAt(0.5):
... timespan
...
<PitchedTimespan (0.5 to 1.0) <music21.note.Note B>>
<PitchedTimespan (0.5 to 1.0) <music21.note.Note B>>
<PitchedTimespan (0.5 to 1.0) <music21.note.Note G#>>
'''
results = []
node = self.getNodeByPosition(position)
if node is not None:
if isinstance(node.payload, list):
results.extend(node.payload)
elif node.payload is not None:
results.append(node.payload)
return tuple(results)
[docs]
def elementsStoppingAt(self, offset):
r'''
Finds elements in this OffsetTree which stop at `offset`. Elements are ordered
according to (start) offset.
>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTree(flatten=True, groupOffsets=True)
>>> for el in scoreTree.elementsStoppingAt(0.5):
... el
<music21.note.Note C#>
<music21.note.Note A>
<music21.note.Note A>
Works also on timespans for TimespanTrees:
>>> scoreTree = score.asTimespans()
>>> for el in scoreTree.elementsStoppingAt(0.5):
... el
<PitchedTimespan (0.0 to 0.5) <music21.note.Note C#>>
<PitchedTimespan (0.0 to 0.5) <music21.note.Note A>>
<PitchedTimespan (0.0 to 0.5) <music21.note.Note A>>
'''
def recurse(node):
result = []
if node is not None: # could happen in an empty TimespanTree
if node.endTimeLow <= offset <= node.endTimeHigh:
if node.leftChild is not None:
result.extend(recurse(node.leftChild))
for el in node.payload:
if self.elementEndTime(el, node) == offset:
result.append(el)
if node.rightChild is not None:
result.extend(recurse(node.rightChild))
return result
results = recurse(self.rootNode)
return tuple(results)
[docs]
def elementsOverlappingOffset(self, offset):
r'''
Finds elements in this ElementTree which overlap `offset`.
>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTree(flatten=True, groupOffsets=True)
>>> for el in scoreTree.elementsOverlappingOffset(0.5):
... el
...
<music21.note.Note E>
Works with Timespans in TimespanTrees as well.
>>> scoreTree = score.asTimespans()
>>> for el in scoreTree.elementsOverlappingOffset(0.5):
... el
...
<PitchedTimespan (0.0 to 1.0) <music21.note.Note E>>
'''
def recurse(node):
result = [] # collections.deque()
if node is not None:
if node.position < offset < node.endTimeHigh:
result.extend(recurse(node.leftChild))
# This currently requires timespans not elements, and list payloads
# TODO: Fix/disambiguate.
for el in node.payload:
if offset < self.elementEndTime(el, node):
result.append(el)
result.extend(recurse(node.rightChild))
elif offset <= node.position:
result.extend(recurse(node.leftChild))
return result
results = recurse(self.rootNode)
return tuple(results)
[docs]
def removeElements(self, elements, offsets=None, runUpdate=True):
r'''
Removes `elements` which can be Music21Objects or Timespans
(a single one or a list) from this Tree.
Much safer (for non-timespans) if a list of offsets is used, but it is optional.
If runUpdate is False then the tree will be left with incorrect indices and
endTimes; but it can speed up operations where an element is going to be removed
and then immediately replaced: i.e., where the position of an element has changed.
'''
initialPosition = self.lowestPosition()
initialEndTime = self.endTime
if hasattr(elements, 'offset'): # a music21 object or an PitchedTimespan
elements = [elements]
if offsets is not None and not common.isListLike(offsets):
offsets = [offsets]
if offsets is not None and len(elements) != len(offsets):
raise ElementTreeException(
'Number of elements and number of offsets must be the same')
for i, el in enumerate(elements):
if offsets is not None:
self._removeElementAtPosition(el, offsets[i])
else:
self._removeElementAtPosition(el, el.offset)
if runUpdate:
self._updateNodes(initialPosition, initialEndTime)
[docs]
def allOffsets(self):
r'''
Gets all unique offsets of all timespans in this offset-tree.
>>> score = corpus.parse('bwv66.6')
>>> tsTree = score.asTimespans()
>>> for offset in tsTree.allOffsets()[:10]:
... offset
...
0.0
0.5
1.0
2.0
3.0
4.0
5.0
5.5
6.0
6.5
'''
def recurse(node):
result = []
if node is not None:
if node.leftChild is not None:
result.extend(recurse(node.leftChild))
pos = node.position
if isinstance(pos, SortTuple):
result.append(pos.offset)
else:
result.append(pos)
if node.rightChild is not None:
result.extend(recurse(node.rightChild))
return result
return tuple(recurse(self.rootNode))
[docs]
def allTimePoints(self):
r'''
Gets all unique offsets (both starting and stopping) of all elements/timespans
in this offset-tree.
>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans()
>>> for offset in scoreTree.allTimePoints()[:10]:
... offset
...
0.0
0.5
1.0
2.0
3.0
4.0
5.0
5.5
6.0
6.5
'''
def recurse(node):
result = set()
if node is not None:
if node.leftChild is not None:
result.update(recurse(node.leftChild))
result.add(node.position)
result.update(node.payloadEndTimes())
if node.rightChild is not None:
result.update(recurse(node.rightChild))
return result
return tuple(sorted(recurse(self.rootNode)))
[docs]
def overlapTimePoints(self, includeStopPoints=False, returnVerticality=False):
'''
Gets all time-points where some element is starting
(or if includeStopPoints is True, where some element is starting or stopping)
while some other element is still continuing onward.
>>> score = corpus.parse('bwv66.6')
>>> scoreOffsetTree = score.asTree(flatten=True, groupOffsets=True)
>>> scoreOffsetTree.overlapTimePoints()
[0.5, 5.5, 6.5, 10.5, 13.5, 14.5, 15.5...]
if returnVerticality is True, then a mapping of time point to elements is returned.
How cool is that?
>>> otp = scoreOffsetTree.overlapTimePoints(returnVerticality=True)
>>> otp[0]
{0.5: <music21.tree.verticality.Verticality 0.5 {G#3 B3 E4 B4}>}
'''
checkPoints = self.allOffsets() if includeStopPoints is False else self.allTimePoints()
overlaps = []
for cp in checkPoints:
overlappingElements = self.elementsOverlappingOffset(cp)
if not overlappingElements:
continue
if returnVerticality is False:
overlaps.append(cp)
else:
overlaps.append({cp: self.getVerticalityAt(cp)})
return overlaps
[docs]
def getVerticalityAt(self, offset):
r'''
Gets the verticality in this offset-tree which starts at `offset`.
>>> bach = corpus.parse('bwv66.6')
>>> scoreTree = bach.asTimespans()
>>> scoreTree.getVerticalityAt(2.5)
<music21.tree.verticality.Verticality 2.5 {G#3 B3 E4 B4}>
Verticalities outside the range still return a Verticality, but it might be empty:
>>> scoreTree.getVerticalityAt(2000)
<music21.tree.verticality.Verticality 2000 {}>
Test that it still works if the tree is empty:
>>> scoreTree = bach.asTimespans(classList=(instrument.Tuba,))
>>> scoreTree
<TimespanTree {0} (-inf to inf) <music21.stream.Score ...>>
>>> scoreTree.getVerticalityAt(5.0)
<music21.tree.verticality.Verticality 5.0 {}>
Returns a verticality.Verticality object.
'''
from music21.tree.verticality import Verticality
startTimespans = self.elementsStartingAt(offset)
stopTimespans = self.elementsStoppingAt(offset)
overlapTimespans = self.elementsOverlappingOffset(offset)
verticality = Verticality(
overlapTimespans=overlapTimespans,
startTimespans=startTimespans,
offset=offset,
stopTimespans=stopTimespans,
timespanTree=self,
)
return verticality
[docs]
def simultaneityDict(self):
'''
Creates a dictionary of offsets that have more than one element starting at that time,
where the keys are offset times and the values are lists of elements at that moment.
>>> score = tree.examples.makeExampleScore()
>>> scoreTree = score.asTree(flatten=True, groupOffsets=True)
>>> scoreTree
<OffsetTree {20} (0.0 to 8.0) <music21.stream.Score exampleScore>>
>>> sd = scoreTree.simultaneityDict()
>>> len(sd)
5
>>> list(sorted(sd.keys()))
[0.0, 2.0, 4.0, 6.0, 8.0]
>>> sd[0.0]
[<music21.instrument.Instrument 'PartA: : '>,
<music21.instrument.Instrument 'PartB: : '>,
<music21.clef.BassClef>,
<music21.clef.BassClef>,
<music21.meter.TimeSignature 2/4>,
<music21.meter.TimeSignature 2/4>,
<music21.note.Note C>,
<music21.note.Note C#>]
>>> sd[2.0]
[<music21.note.Note E>, <music21.note.Note G#>]
'''
simultaneityDict = {}
for node in self.iterNodes():
pl = node.payload
if len(pl) > 1:
simultaneityDict[node.position] = pl[:]
return simultaneityDict
# ---------------------------------------------------------------
# -----------------------------------------------------------------------------
class Test(unittest.TestCase):
def testGetPositionAfterOffset(self):
'''
test that get position after works with
an offset when the tree is built on SortTuples.
'''
from music21 import note
from music21 import stream
et = ElementTree()
s = stream.Stream()
for i in range(100):
n = note.Note()
n.duration.quarterLength = 2.0
s.insert(i * 2, n)
for n in s:
et.insert(n)
self.assertTrue(repr(et).startswith('<ElementTree {100} (0.0 <0.20'))
n2 = s[-1]
self.assertEqual(et.index(n2, n2.sortTuple()), 99)
st3 = et.getPositionAfter(5.0)
self.assertIsNotNone(st3)
# def testBachDoctest(self):
# from music21 import corpus, note, chord, tree
# bach = corpus.parse('bwv66.6')
# tree = tree.fromStream.asTimespans(bach, flatten=True,
# classList=(note.Note, chord.Chord))
# for verticalities in tree.iterateVerticalitiesNwise(n=3):
# print(verticalities)
# if verticalities[-1].offset == 25:
# pass
# horizontalities = tree.unwrapVerticalities(verticalities)
# for unused_part, horizontality in horizontalities.items():
# if horizontality.hasNeighborTone:
# merged = horizontality[0].new(endTime=horizontality[2].endTime,)
# # tree.remove(horizontality[0])
# # tree.remove(horizontality[1])
# # tree.remove(horizontality[2])
# # tree.insert(merged)
#
#
# newBach = tree.toStream.partwise(tree, templateStream=bach,)
# newBach.parts[1].measure(7).show('text')
# # {0.0} <music21.chord.Chord F#4>
# # {1.5} <music21.chord.Chord F#3>
# # {2.0} <music21.chord.Chord C#4>
#
def testElementsStoppingAt(self):
'''
this was reporting:
<music21.note.Note G#>
<music21.note.Note C#>
<music21.note.Note A>
<music21.note.Note A>
G# was coming from an incorrect activeSite. activeSite should not be used!
'''
from music21 import corpus
from music21 import stream
from music21 import note
s = stream.Stream()
n0 = note.Note('A')
n0.duration.quarterLength = 3.0
s.insert(0, n0)
n1 = note.Note('B')
n1.duration.quarterLength = 2.0
s.insert(1, n1)
n2 = note.Note('C')
n2.duration.quarterLength = 1.0
s.insert(2, n2)
# and one later to be sure that order is right
n3 = note.Note('A#')
n3.duration.quarterLength = 2.5
s.insert(0.5, n3)
st = s.asTree(groupOffsets=True)
stList = st.elementsStoppingAt(3.0)
self.assertEqual(len(stList), 4)
self.assertEqual([n.name for n in stList],
['A', 'A#', 'B', 'C'])
# making the tree more complex does not change anything, I hope?
for i in range(30):
s.insert(0, note.Rest())
for i in range(22):
s.insert(10 + i, note.Rest())
st = s.asTree(groupOffsets=True)
stList = st.elementsStoppingAt(3.0)
self.assertEqual(len(stList), 4)
self.assertEqual([n.name for n in stList],
['A', 'A#', 'B', 'C'])
# real world example
score = corpus.parse('bwv66.6')
scoreTree = score.asTree(flatten=True, groupOffsets=True)
elementList = scoreTree.elementsStoppingAt(0.5)
self.assertEqual(len(elementList), 3)
self.assertEqual(elementList[0].name, 'C#')
self.assertEqual(elementList[1].name, 'A')
self.assertEqual(elementList[2].name, 'A')
# def testBachDoctest(self):
# from music21 import corpus, note, chord, tree
# bach = corpus.parse('bwv66.6')
# scoreTree = tree.fromStream.asTimespans(bach, flatten=True,
# classList=(note.Note, chord.Chord))
# print(scoreTree)
# for verticalities in scoreTree.iterateVerticalitiesNwise(n=3):
# if verticalities[-1].offset == 25:
# pass
# horizontalities = scoreTree.unwrapVerticalities(verticalities)
# for unused_part, horizontality in horizontalities.items():
# if horizontality.hasNeighborTone:
# merged = horizontality[0].new(endTime=horizontality[2].endTime,)
# scoreTree.remove(horizontality[0])
# scoreTree.remove(horizontality[1])
# scoreTree.remove(horizontality[2])
# scoreTree.insert(merged)
#
#
# newBach = tree.toStream.partwise(scoreTree, templateStream=bach,)
# newBach.show()
# newBach.parts[1].measure(7).show('text')
# # {0.0} <music21.chord.Chord F#4>
# # {1.5} <music21.chord.Chord F#3>
# # {2.0} <music21.chord.Chord C#4>
# -----------------------------------------------------------------------------
_DOC_ORDER = (
ElementTree,
OffsetTree,
)
# -----------------------------------------------------------------------------
if __name__ == '__main__':
import music21
music21.mainTest(Test) # , runTest='testElementsStoppingAt')