Internal data structures for timespan collections.
This is an implementation detail of the TimespanTree class.
A node in an TimespanTree.
Here’s an example of what it means and does:
>>> score = timespans.makeExampleScore()
>>> sfn = score.flat.notes
>>> sfn.show('text', addEndTimes=True)
{0.0 - 1.0} <music21.note.Note C>
{0.0 - 2.0} <music21.note.Note C>
{1.0 - 2.0} <music21.note.Note D>
{2.0 - 3.0} <music21.note.Note E>
{2.0 - 4.0} <music21.note.Note G>
{3.0 - 4.0} <music21.note.Note F>
{4.0 - 5.0} <music21.note.Note G>
{4.0 - 6.0} <music21.note.Note E>
{5.0 - 6.0} <music21.note.Note A>
{6.0 - 7.0} <music21.note.Note B>
{6.0 - 8.0} <music21.note.Note D>
{7.0 - 8.0} <music21.note.Note C>
>>> tree = timespans.streamToTimespanTree(score, flatten=True, classList=(note.Note, chord.Chord))
>>> rn = tree.rootNode
The RootNode here represents the starting position of the Note F at 3.0; It is the center of the elements in the flat Stream. Its index is 5 (that is, it’s the sixth note in the element list) and its offset is 3.0
>>> rn
<Node: Start:3.0 Indices:(0:5:6:12) Length:{1}>
>>> sfn[5]
<music21.note.Note F>
>>> sfn[5].offset
3.0
Thus, the indices of 0:5:6:12 indicate that the left-side of the node handles indices from >= 0 to < 5; and the right-side of the node handles indices >= 6 and < 12, and this node handles indices >= 5 and < 6.
The Length: {1} indicates that there is exactly one element at this location, that is, the F.
The “payload” of the node, is just that note wrapped in a list wrapped in an ElementTimeSpan:
>>> rn.payload
[<ElementTimespan (3.0 to 4.0) <music21.note.Note F>>]
>>> rn.payload[0].element
<music21.note.Note F>
>>> rn.payload[0].element is sfn[5]
True
We can look at the leftChild of the root node to get some more interesting cases:
>>> left = rn.leftChild
>>> left
<Node: Start:1.0 Indices:(0:2:3:5) Length:{1}>
>>> leftLeft = left.leftChild
>>> leftLeft
<Node: Start:0.0 Indices:(0:0:2:2) Length:{2}>
In the leftNode of the leftNode of the rootNode there are two elements: both notes that begin on offset 0.0:
>>> leftLeft.payload
[<ElementTimespan (0.0 to 1.0) <music21.note.Note C>>,
<ElementTimespan (0.0 to 2.0) <music21.note.Note C>>]
The Indices:(0:0:2:2) indicates that leftLeft has neither left nor right children >>> leftLeft.leftChild is None True >>> leftLeft.rightChild is None True
What makes a TimespanNode more interesting than other AWL Nodes is that it is aware of the fact that it might have objects that end at different times:
>>> leftLeft.endTimeLow
1.0
>>> leftLeft.endTimeHigh
2.0
TimespanTreeNode bases
TimespanTreeNode read/write properties
Read/write properties inherited from AVLNode:
TimespanTreeNode methods
Methods inherited from AVLNode:
TimespanTreeNode instance variables
The highest stop offset of any timespan in any node of the subtree rooted on this node.
The lowest stop offset of any timespan in any node of the subtree rooted on this node.
The timespan start index (i.e., the first x where s[x] is found in this Node’s payload) of only those timespans stored in the payload of this node.
The timespan stop index (i.e., the last x where s[x] is found in this Node’s payload) of only those timespans stored in the payload of this node.
A list of Timespans starting at this node’s start offset.
>>> score = timespans.makeExampleScore()
>>> tree = timespans.streamToTimespanTree(score, flatten=True, classList=(note.Note, chord.Chord))
>>> print(tree.rootNode.debug())
<Node: Start:3.0 Indices:(0:5:6:12) Length:{1}>
L: <Node: Start:1.0 Indices:(0:2:3:5) Length:{1}>
L: <Node: Start:0.0 Indices:(0:0:2:2) Length:{2}>
R: <Node: Start:2.0 Indices:(3:3:5:5) Length:{2}>
R: <Node: Start:5.0 Indices:(6:8:9:12) Length:{1}>
L: <Node: Start:4.0 Indices:(6:6:8:8) Length:{2}>
R: <Node: Start:6.0 Indices:(9:9:11:12) Length:{2}>
R: <Node: Start:7.0 Indices:(11:11:12:12) Length:{1}>
>>> tree.rootNode.payload
[<ElementTimespan (3.0 to 4.0) <music21.note.Note F>>]
>>> tree.rootNode.leftChild.payload
[<ElementTimespan (1.0 to 2.0) <music21.note.Note D>>]
>>> for x in tree.rootNode.leftChild.rightChild.payload:
... x
...
<ElementTimespan (2.0 to 3.0) <music21.note.Note E>>
<ElementTimespan (2.0 to 4.0) <music21.note.Note G>>
>>> tree.rootNode.rightChild.payload
[<ElementTimespan (5.0 to 6.0) <music21.note.Note A>>]
The lowest timespan start index of any timespan in any node of the subtree rooted on this node.
The highest timespan stop index of any timespan in any node of the subtree rooted on this node.
Instance variables inherited from AVLNode:
An AVL Tree Node, not specialized in any way, just contains offsets.
This class is only used by TimespanTree, and should not be instantiated by hand. It stores a list of ElementTimespans, as well as various data which describes the internal structure of the tree.
>>> offset = 1.0
>>> node = timespans.node.AVLNode(offset)
>>> node
<Node: Start:1.0 Height:0 L:None R:None>
>>> n2 = timespans.node.AVLNode(2.0)
>>> node.rightChild = n2
>>> node
<Node: Start:1.0 Height:1 L:None R:0>
Note that nodes cannot rebalance themselves, that’s what a Tree is for.
Please consult the Wikipedia entry on AVL trees (https://en.wikipedia.org/wiki/AVL_tree) for a very detailed description of how this datastructure works.
AVLNode bases
AVLNode read/write properties
The left child of this node.
Setting the left child triggers a node update.
>>> score = timespans.makeExampleScore()
>>> tree = timespans.streamToTimespanTree(score, flatten=True, classList=(note.Note, chord.Chord))
>>> print(tree.rootNode.debug())
<Node: Start:3.0 Indices:(0:5:6:12) Length:{1}>
L: <Node: Start:1.0 Indices:(0:2:3:5) Length:{1}>
L: <Node: Start:0.0 Indices:(0:0:2:2) Length:{2}>
R: <Node: Start:2.0 Indices:(3:3:5:5) Length:{2}>
R: <Node: Start:5.0 Indices:(6:8:9:12) Length:{1}>
L: <Node: Start:4.0 Indices:(6:6:8:8) Length:{2}>
R: <Node: Start:6.0 Indices:(9:9:11:12) Length:{2}>
R: <Node: Start:7.0 Indices:(11:11:12:12) Length:{1}>
>>> print(tree.rootNode.leftChild.debug())
<Node: Start:1.0 Indices:(0:2:3:5) Length:{1}>
L: <Node: Start:0.0 Indices:(0:0:2:2) Length:{2}>
R: <Node: Start:2.0 Indices:(3:3:5:5) Length:{2}>
The right child of this node.
Setting the right child triggers a node update.
>>> score = timespans.makeExampleScore()
>>> tree = timespans.streamToTimespanTree(score, flatten=True, classList=(note.Note, chord.Chord))
>>> print(tree.rootNode.debug())
<Node: Start:3.0 Indices:(0:5:6:12) Length:{1}>
L: <Node: Start:1.0 Indices:(0:2:3:5) Length:{1}>
L: <Node: Start:0.0 Indices:(0:0:2:2) Length:{2}>
R: <Node: Start:2.0 Indices:(3:3:5:5) Length:{2}>
R: <Node: Start:5.0 Indices:(6:8:9:12) Length:{1}>
L: <Node: Start:4.0 Indices:(6:6:8:8) Length:{2}>
R: <Node: Start:6.0 Indices:(9:9:11:12) Length:{2}>
R: <Node: Start:7.0 Indices:(11:11:12:12) Length:{1}>
>>> print(tree.rootNode.rightChild.debug())
<Node: Start:5.0 Indices:(6:8:9:12) Length:{1}>
L: <Node: Start:4.0 Indices:(6:6:8:8) Length:{2}>
R: <Node: Start:6.0 Indices:(9:9:11:12) Length:{2}>
R: <Node: Start:7.0 Indices:(11:11:12:12) Length:{1}>
>>> print(tree.rootNode.rightChild.rightChild.debug())
<Node: Start:6.0 Indices:(9:9:11:12) Length:{2}>
R: <Node: Start:7.0 Indices:(11:11:12:12) Length:{1}>
>>> print(tree.rootNode.rightChild.rightChild.rightChild.debug())
<Node: Start:7.0 Indices:(11:11:12:12) Length:{1}>
AVLNode methods
Get a debug of the Node:
>>> score = timespans.makeExampleScore()
>>> tree = timespans.streamToTimespanTree(score, flatten=True, classList=(note.Note, chord.Chord))
>>> rn = tree.rootNode
>>> print(rn.debug())
<Node: Start:3.0 Indices:(0:5:6:12) Length:{1}>
L: <Node: Start:1.0 Indices:(0:2:3:5) Length:{1}>
L: <Node: Start:0.0 Indices:(0:0:2:2) Length:{2}>
R: <Node: Start:2.0 Indices:(3:3:5:5) Length:{2}>
R: <Node: Start:5.0 Indices:(6:8:9:12) Length:{1}>
L: <Node: Start:4.0 Indices:(6:6:8:8) Length:{2}>
R: <Node: Start:6.0 Indices:(9:9:11:12) Length:{2}>
R: <Node: Start:7.0 Indices:(11:11:12:12) Length:{1}>
Return a list of the debugging information of the tree (used for debug):
>>> score = timespans.makeExampleScore()
>>> tree = timespans.streamToTimespanTree(score, flatten=True, classList=(note.Note, chord.Chord))
>>> rn = tree.rootNode
>>> rn.getDebugPieces()
['<Node: Start:3.0 Indices:(0:5:6:12) Length:{1}>',
'\tL: <Node: Start:1.0 Indices:(0:2:3:5) Length:{1}>',
'\t\tL: <Node: Start:0.0 Indices:(0:0:2:2) Length:{2}>',
'\t\tR: <Node: Start:2.0 Indices:(3:3:5:5) Length:{2}>',
'\tR: <Node: Start:5.0 Indices:(6:8:9:12) Length:{1}>',
'\t\tL: <Node: Start:4.0 Indices:(6:6:8:8) Length:{2}>',
'\t\tR: <Node: Start:6.0 Indices:(9:9:11:12) Length:{2}>',
'\t\t\tR: <Node: Start:7.0 Indices:(11:11:12:12) Length:{1}>']
Updates the height and balance attributes of the nodes.
Called automatically when the .leftChild or .rightChild are changed.
Returns None
AVLNode instance variables
Returns the current state of the difference in heights of the two subtrees rooted on this node.
This attribute is used to help balance the AVL tree.
>>> score = timespans.makeExampleScore()
>>> tree = timespans.streamToTimespanTree(score, flatten=True, classList=(note.Note, chord.Chord))
>>> print(tree.debug())
<Node: Start:3.0 Indices:(0:5:6:12) Length:{1}>
L: <Node: Start:1.0 Indices:(0:2:3:5) Length:{1}>
L: <Node: Start:0.0 Indices:(0:0:2:2) Length:{2}>
R: <Node: Start:2.0 Indices:(3:3:5:5) Length:{2}>
R: <Node: Start:5.0 Indices:(6:8:9:12) Length:{1}>
L: <Node: Start:4.0 Indices:(6:6:8:8) Length:{2}>
R: <Node: Start:6.0 Indices:(9:9:11:12) Length:{2}>
R: <Node: Start:7.0 Indices:(11:11:12:12) Length:{1}>
This tree has one more depth on the right than on the left
>>> tree.rootNode.balance
1
The leftChild of the rootNote is perfectly balanced, while the rightChild is off by one (acceptable).
>>> tree.rootNode.leftChild.balance
0
>>> tree.rootNode.rightChild.balance
1
The rightChild’s children are also (acceptably) unbalanced:
>>> tree.rootNode.rightChild.leftChild.balance
0
>>> tree.rootNode.rightChild.rightChild.balance
1
The height of the subtree rooted on this node.
This property is used to help balance the AVL tree.
>>> score = timespans.makeExampleScore()
>>> tree = timespans.streamToTimespanTree(score, flatten=True, classList=(note.Note, chord.Chord))
>>> print(tree.debug())
<Node: Start:3.0 Indices:(0:5:6:12) Length:{1}>
L: <Node: Start:1.0 Indices:(0:2:3:5) Length:{1}>
L: <Node: Start:0.0 Indices:(0:0:2:2) Length:{2}>
R: <Node: Start:2.0 Indices:(3:3:5:5) Length:{2}>
R: <Node: Start:5.0 Indices:(6:8:9:12) Length:{1}>
L: <Node: Start:4.0 Indices:(6:6:8:8) Length:{2}>
R: <Node: Start:6.0 Indices:(9:9:11:12) Length:{2}>
R: <Node: Start:7.0 Indices:(11:11:12:12) Length:{1}>
>>> tree.rootNode.height
3
>>> tree.rootNode.rightChild.height
2
>>> tree.rootNode.rightChild.rightChild.height
1
>>> tree.rootNode.rightChild.rightChild.rightChild.height
0
The offset of this node.
>>> score = timespans.makeExampleScore()
>>> tree = timespans.streamToTimespanTree(score, flatten=True, classList=(note.Note, chord.Chord))
>>> print(tree.rootNode.debug())
<Node: Start:3.0 Indices:(0:5:6:12) Length:{1}>
L: <Node: Start:1.0 Indices:(0:2:3:5) Length:{1}>
L: <Node: Start:0.0 Indices:(0:0:2:2) Length:{2}>
R: <Node: Start:2.0 Indices:(3:3:5:5) Length:{2}>
R: <Node: Start:5.0 Indices:(6:8:9:12) Length:{1}>
L: <Node: Start:4.0 Indices:(6:6:8:8) Length:{2}>
R: <Node: Start:6.0 Indices:(9:9:11:12) Length:{2}>
R: <Node: Start:7.0 Indices:(11:11:12:12) Length:{1}>
>>> tree.rootNode.offset
3.0
>>> tree.rootNode.leftChild.offset
1.0
>>> tree.rootNode.rightChild.offset
5.0