music21.tree.timespanTree

Tools for grouping elements, timespans, and especially pitched elements into kinds of searchable tree organized by start and stop offsets and other positions.

TimespanTree

class music21.tree.timespanTree.TimespanTree(elements=None, source=None)

A data structure for efficiently slicing a score for pitches.

While you can construct an TimespanTree by hand, inserting timespans one at a time, the common use-case is to construct the offset-tree from an entire score at once:

>>> bach = corpus.parse('bwv66.6')
>>> scoreTree = tree.fromStream.asTimespans(bach, flatten=True,
...            classList=(note.Note, chord.Chord))
>>> print(scoreTree.getVerticalityAt(17.0))
<Verticality 17.0 {F#3 C#4 A4}>

All offsets are assumed to be relative to the score’s source if flatten is True

Example: How many moments in Bach are consonant and how many are dissonant:

>>> totalConsonances = 0
>>> totalDissonances = 0
>>> for v in scoreTree.iterateVerticalities():
...     if v.toChord().isConsonant():
...        totalConsonances += 1
...     else:
...        totalDissonances += 1
>>> (totalConsonances, totalDissonances)
(34, 17)

So 1/3 of the vertical moments in Bach are dissonant! But is this an accurate perception? Let’s sum up the total consonant duration vs. dissonant duration.

Do it again pairwise to figure out the length (actually this won’t include the last element)

>>> totalConsonanceDuration = 0
>>> totalDissonanceDuration = 0
>>> iterator = scoreTree.iterateVerticalitiesNwise(n=2)
>>> for verticality1, verticality2 in iterator:
...     offset1 = verticality1.offset
...     offset2 = verticality2.offset
...     quarterLength = offset2 - offset1
...     if verticality1.toChord().isConsonant():
...        totalConsonanceDuration += quarterLength
...     else:
...        totalDissonanceDuration += quarterLength
>>> (totalConsonanceDuration, totalDissonanceDuration)
(25.5, 9.5)

Remove neighbor tones from the Bach chorale. (It’s actually quite viscous in its pruning…)

Here in Alto, measure 7, there’s a neighbor tone E#.

>>> bach.parts['Alto'].measure(7).show('text')
{0.0} <music21.note.Note F#>
{0.5} <music21.note.Note E#>
{1.0} <music21.note.Note F#>
{1.5} <music21.note.Note F#>
{2.0} <music21.note.Note C#>

We’ll get rid of it and a lot of other neighbor tones.

>>> for verticalities in scoreTree.iterateVerticalitiesNwise(n=3):
...     horizontalities = scoreTree.unwrapVerticalities(verticalities)
...     for unused_part, horizontality in horizontalities.items():
...         if horizontality.hasNeighborTone:
...             merged = horizontality[0].new(
...                endTime=horizontality[2].endTime,
...             ) # merged is a new PitchedTimespan
...             scoreTree.removeTimespan(horizontality[0])
...             scoreTree.removeTimespan(horizontality[1])
...             scoreTree.removeTimespan(horizontality[2])
...             scoreTree.insert(merged)
>>> newBach = tree.toStream.partwise(
...     scoreTree,
...     templateStream=bach,
...     )
>>> newBach.parts['Alto'].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>

The second F# is an octave lower, so it wouldn’t get merged even if adjacent notes were fused together (which they’re not).

Note

TimespanTree is an implementation of an extended AVL tree. AVL trees are a type of binary tree, like Red-Black trees. AVL trees are very efficient at insertion when the objects being inserted are already sorted - which is usually the case with data extracted from a score. TimespanTree is an extended AVL tree because each node in the tree keeps track of not just the start offsets of PitchedTimespans stored at that node, but also the earliest and latest stop offset of all PitchedTimespans stores at both that node and all nodes which are children of that node. This lets us quickly located PitchedTimespans which overlap offsets or which are contained within ranges of offsets. This also means that the contents of a TimespanTree are always sorted.

TimespanTree bases

TimespanTree read-only properties

Read-only properties inherited from ElementTree:

TimespanTree read/write properties

TimespanTree.element

defined so a TimespanTree can be used like an PitchedTimespan

TODO: Look at subclassing or at least deriving from a common base…

Read/write properties inherited from ElementTree:

TimespanTree methods

TimespanTree.allParts()
static TimespanTree.elementEndTime(el, unused_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.

TimespanTree.findNextPitchedTimespanInSameStreamByClass(pitchedTimespan, classList=None)

Finds next element timespan in the same stream class as PitchedTimespan.

Default classList is (stream.Part, )

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans(classList=(note.Note,))
>>> timespan = scoreTree[0]
>>> timespan
<PitchedTimespan (0.0 to 0.5) <music21.note.Note C#>>
>>> timespan.part
<music21.stream.Part Soprano>
>>> timespan = scoreTree.findNextPitchedTimespanInSameStreamByClass(timespan)
>>> timespan
<PitchedTimespan (0.5 to 1.0) <music21.note.Note B>>
>>> timespan.part
<music21.stream.Part Soprano>
>>> timespan = scoreTree.findNextPitchedTimespanInSameStreamByClass(timespan)
>>> timespan
<PitchedTimespan (1.0 to 2.0) <music21.note.Note A>>
>>> timespan.part
<music21.stream.Part Soprano>
TimespanTree.findPreviousPitchedTimespanInSameStreamByClass(pitchedTimespan, classList=None)

Finds next element timespan in the same Part/Measure, etc. (specify in classList) as the pitchedTimespan.

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans(classList=(note.Note,))
>>> timespan = scoreTree[-1]
>>> timespan
<PitchedTimespan (35.0 to 36.0) <music21.note.Note F#>>
>>> timespan.part
<music21.stream.Part Bass>
>>> timespan = scoreTree.findPreviousPitchedTimespanInSameStreamByClass(timespan)
>>> timespan
<PitchedTimespan (34.0 to 35.0) <music21.note.Note B>>
>>> timespan.part
<music21.stream.Part Bass>
>>> timespan = scoreTree.findPreviousPitchedTimespanInSameStreamByClass(timespan)
>>> timespan
<PitchedTimespan (33.0 to 34.0) <music21.note.Note D>>
>>> timespan.part
<music21.stream.Part Bass>
TimespanTree.getVerticalityAtOrBefore(offset)

Gets the verticality in this offset-tree which starts at offset.

If the found verticality has no start timespans, the function returns the next previous verticality with start timespans.

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans()
>>> scoreTree.getVerticalityAtOrBefore(0.125)
<Verticality 0.0 {A3 E4 C#5}>
>>> scoreTree.getVerticalityAtOrBefore(0.)
<Verticality 0.0 {A3 E4 C#5}>
TimespanTree.index(span)

Gets index of a TimeSpan in a TimespanTree.

Since Timespans do not have .sites, there is only one offset to deal with…

>>> tsList = [(0, 2), (0, 9), (1, 1), (2, 3), (3, 4),
...           (4, 9), (5, 6), (5, 8), (6, 8), (7, 7)]
>>> ts = [tree.spans.Timespan(x, y) for x, y in tsList]
>>> tsTree = tree.timespanTree.TimespanTree()
>>> tsTree.insert(ts)
>>> for timespan in ts:
...     print("%r %d" % (timespan, tsTree.index(timespan)))
...
<Timespan 0.0 2.0> 0
<Timespan 0.0 9.0> 1
<Timespan 1.0 1.0> 2
<Timespan 2.0 3.0> 3
<Timespan 3.0 4.0> 4
<Timespan 4.0 9.0> 5
<Timespan 5.0 6.0> 6
<Timespan 5.0 8.0> 7
<Timespan 6.0 8.0> 8
<Timespan 7.0 7.0> 9
>>> tsTree.index(tree.spans.Timespan(-100, 100))
Traceback (most recent call last):
ValueError: <Timespan -100.0 100.0> not in Tree at offset -100.0.
TimespanTree.iterateConsonanceBoundedVerticalities()

Iterates consonant-bounded verticality subsequences in this offset-tree.

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans()
>>> for subsequence in scoreTree.iterateConsonanceBoundedVerticalities():
...     print('Subequence:')
...     for verticality in subsequence:
...         verticalityChord = verticality.toChord()
...         print('\t[{}] {}: {}'.format(
...             verticality.measureNumber,
...             verticality,
...             verticalityChord.isConsonant(),
...             ))
...
Subequence:
    [2] <Verticality 6.0 {E3 E4 G#4 B4}>: True
    [2] <Verticality 6.5 {E3 D4 G#4 B4}>: False
    [2] <Verticality 7.0 {A2 C#4 E4 A4}>: True
Subequence:
    [3] <Verticality 9.0 {F#3 C#4 F#4 A4}>: True
    [3] <Verticality 9.5 {B2 D4 G#4 B4}>: False
    [3] <Verticality 10.0 {C#3 C#4 E#4 G#4}>: True
Subequence:
    [3] <Verticality 10.0 {C#3 C#4 E#4 G#4}>: True
    [3] <Verticality 10.5 {C#3 B3 E#4 G#4}>: False
    [3] <Verticality 11.0 {F#2 A3 C#4 F#4}>: True
Subequence:
    [3] <Verticality 12.0 {F#3 C#4 F#4 A4}>: True
    [4] <Verticality 13.0 {G#3 B3 F#4 B4}>: False
    [4] <Verticality 13.5 {F#3 B3 F#4 B4}>: False
    [4] <Verticality 14.0 {G#3 B3 E4 B4}>: True
Subequence:
    [4] <Verticality 14.0 {G#3 B3 E4 B4}>: True
    [4] <Verticality 14.5 {A3 B3 E4 B4}>: False
    [4] <Verticality 15.0 {B3 D#4 F#4}>: True
Subequence:
    [4] <Verticality 15.0 {B3 D#4 F#4}>: True
    [4] <Verticality 15.5 {B2 A3 D#4 F#4}>: False
    [4] <Verticality 16.0 {C#3 G#3 C#4 E4}>: True
Subequence:
    [5] <Verticality 17.5 {F#3 D4 F#4 A4}>: True
    [5] <Verticality 18.0 {G#3 C#4 E4 B4}>: False
    [5] <Verticality 18.5 {G#3 B3 E4 B4}>: True
Subequence:
    [6] <Verticality 24.0 {F#3 C#4 F#4 A4}>: True
    [7] <Verticality 25.0 {B2 D4 F#4 G#4}>: False
    [7] <Verticality 25.5 {C#3 C#4 E#4 G#4}>: True
Subequence:
    [7] <Verticality 25.5 {C#3 C#4 E#4 G#4}>: True
    [7] <Verticality 26.0 {D3 C#4 F#4}>: False
    [7] <Verticality 26.5 {D3 F#3 B3 F#4}>: True
Subequence:
    [8] <Verticality 29.0 {A#2 F#3 C#4 F#4}>: True
    [8] <Verticality 29.5 {A#2 F#3 D4 F#4}>: False
    [8] <Verticality 30.0 {A#2 C#4 E4 F#4}>: False
    [8] <Verticality 31.0 {B2 C#4 E4 F#4}>: False
    [8] <Verticality 32.0 {C#3 B3 D4 F#4}>: False
    [8] <Verticality 32.5 {C#3 A#3 C#4 F#4}>: False
    [9] <Verticality 33.0 {D3 B3 F#4}>: True
Subequence:
    [9] <Verticality 33.0 {D3 B3 F#4}>: True
    [9] <Verticality 33.5 {D3 B3 C#4 F#4}>: False
    [9] <Verticality 34.0 {B2 B3 D4 F#4}>: True
Subequence:
    [9] <Verticality 34.0 {B2 B3 D4 F#4}>: True
    [9] <Verticality 34.5 {B2 B3 D4 E#4}>: False
    [9] <Verticality 35.0 {F#3 A#3 C#4 F#4}>: True
TimespanTree.iterateVerticalities(reverse=False)

Iterates all vertical moments in this offset-tree.

Note

The offset-tree can be mutated while its verticalities are iterated over. Each verticality holds a reference back to the offset-tree and will ask for the start-offset after (or before) its own start offset in order to determine the next verticality to yield. If you mutate the tree by adding or deleting timespans, the next verticality will reflect those changes.

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans(classList=(note.Note,))
>>> iterator = scoreTree.iterateVerticalities()
>>> for _ in range(10):
...     next(iterator)
...
<Verticality 0.0 {A3 E4 C#5}>
<Verticality 0.5 {G#3 B3 E4 B4}>
<Verticality 1.0 {F#3 C#4 F#4 A4}>
<Verticality 2.0 {G#3 B3 E4 B4}>
<Verticality 3.0 {A3 E4 C#5}>
<Verticality 4.0 {G#3 B3 E4 E5}>
<Verticality 5.0 {A3 E4 C#5}>
<Verticality 5.5 {C#3 E4 A4 C#5}>
<Verticality 6.0 {E3 E4 G#4 B4}>
<Verticality 6.5 {E3 D4 G#4 B4}>

Verticalities can also be iterated in reverse:

>>> iterator = scoreTree.iterateVerticalities(reverse=True)
>>> for _ in range(10):
...     next(iterator)
...
<Verticality 35.0 {F#3 A#3 C#4 F#4}>
<Verticality 34.5 {B2 B3 D4 E#4}>
<Verticality 34.0 {B2 B3 D4 F#4}>
<Verticality 33.5 {D3 B3 C#4 F#4}>
<Verticality 33.0 {D3 B3 F#4}>
<Verticality 32.5 {C#3 A#3 C#4 F#4}>
<Verticality 32.0 {C#3 B3 D4 F#4}>
<Verticality 31.0 {B2 C#4 E4 F#4}>
<Verticality 30.0 {A#2 C#4 E4 F#4}>
<Verticality 29.5 {A#2 F#3 D4 F#4}>
TimespanTree.iterateVerticalitiesNwise(n=3, reverse=False)

Iterates verticalities in groups of length n.

Note

The offset-tree can be mutated while its verticalities are iterated over. Each verticality holds a reference back to the offset-tree and will ask for the start-offset after (or before) its own start offset in order to determine the next verticality to yield. If you mutate the tree by adding or deleting timespans, the next verticality will reflect those changes.

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans(classList=(note.Note,))
>>> iterator = scoreTree.iterateVerticalitiesNwise(n=2)
>>> for _ in range(4):
...     print(next(iterator))
...
<VerticalitySequence: [
    <Verticality 0.0 {A3 E4 C#5}>,
    <Verticality 0.5 {G#3 B3 E4 B4}>
    ]>
<VerticalitySequence: [
    <Verticality 0.5 {G#3 B3 E4 B4}>,
    <Verticality 1.0 {F#3 C#4 F#4 A4}>
    ]>
<VerticalitySequence: [
    <Verticality 1.0 {F#3 C#4 F#4 A4}>,
    <Verticality 2.0 {G#3 B3 E4 B4}>
    ]>
<VerticalitySequence: [
    <Verticality 2.0 {G#3 B3 E4 B4}>,
    <Verticality 3.0 {A3 E4 C#5}>
    ]>

Grouped verticalities can also be iterated in reverse:

>>> iterator = scoreTree.iterateVerticalitiesNwise(n=2, reverse=True)
>>> for _ in range(4):
...     print(next(iterator))
...
<VerticalitySequence: [
    <Verticality 34.5 {B2 B3 D4 E#4}>,
    <Verticality 35.0 {F#3 A#3 C#4 F#4}>
    ]>
<VerticalitySequence: [
    <Verticality 34.0 {B2 B3 D4 F#4}>,
    <Verticality 34.5 {B2 B3 D4 E#4}>
    ]>
<VerticalitySequence: [
    <Verticality 33.5 {D3 B3 C#4 F#4}>,
    <Verticality 34.0 {B2 B3 D4 F#4}>
    ]>
<VerticalitySequence: [
    <Verticality 33.0 {D3 B3 F#4}>,
    <Verticality 33.5 {D3 B3 C#4 F#4}>
    ]>
TimespanTree.maximumOverlap()

The maximum number of timespans overlapping at any given moment in this timespan collection.

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans(classList=(note.Note,))
>>> scoreTree.maximumOverlap()
4

Returns None if there is no verticality here.

TimespanTree.offset()

this is just for mimicking elements as streams.

TimespanTree.removeTimespan(elements, offsets=None, runUpdate=True)

this will eventually be different from above…

TimespanTree.removeTimespanList(elements, offsets=None, runUpdate=True)

this will eventually be different from above…

TimespanTree.splitAt(offsets)

Splits all timespans in this offset-tree at offsets, operating in place.

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans()
>>> scoreTree.elementsStartingAt(0.1)
()
>>> for timespan in scoreTree.elementsOverlappingOffset(0.1):
...     print("%r, %s" % (timespan, timespan.part.id))
...
<PitchedTimespan (0.0 to 0.5) <music21.note.Note C#>>, Soprano
<PitchedTimespan (0.0 to 0.5) <music21.note.Note A>>, Tenor
<PitchedTimespan (0.0 to 0.5) <music21.note.Note A>>, Bass
<PitchedTimespan (0.0 to 1.0) <music21.note.Note E>>, Alto

Note that the Alto is last in both of these because currently the sorting is done according to the endTime – potentially to be changed soon.

>>> scoreTree.splitAt(0.1)
>>> for timespan in scoreTree.elementsStartingAt(0.1):
...     print("%r, %s" % (timespan, timespan.part.id))
...
<PitchedTimespan (0.1 to 0.5) <music21.note.Note C#>>, Soprano
<PitchedTimespan (0.1 to 0.5) <music21.note.Note A>>, Tenor
<PitchedTimespan (0.1 to 0.5) <music21.note.Note A>>, Bass
<PitchedTimespan (0.1 to 1.0) <music21.note.Note E>>, Alto
>>> scoreTree.elementsOverlappingOffset(0.1)
()
TimespanTree.toPartwiseTimespanTrees()

Returns a dictionary of TimespanTrees where each entry is indexed by a Part object (TODO: Don’t use mutable objects as hash keys!) and each key is a TimeSpan tree containing only element timespans belonging to that part.

Used by reduceChords. May disappear.

static TimespanTree.unwrapVerticalities(verticalities)

Unwraps a sequence of Verticality objects into a dictionary of Part:Horizontality key/value pairs.

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans(classList=(note.Note,))
>>> iterator = scoreTree.iterateVerticalitiesNwise()
>>> verticalities = next(iterator)
>>> unwrapped = scoreTree.unwrapVerticalities(verticalities)
>>> for part in sorted(unwrapped, key=lambda x: x.partName):
...     print(part)
...     horizontality = unwrapped[part]
...     for timespan in horizontality:
...         print('\t%r' % timespan)
...
<music21.stream.Part Alto>
    <PitchedTimespan (0.0 to 1.0) <music21.note.Note E>>
    <PitchedTimespan (1.0 to 2.0) <music21.note.Note F#>>
<music21.stream.Part Bass>
    <PitchedTimespan (0.0 to 0.5) <music21.note.Note A>>
    <PitchedTimespan (0.5 to 1.0) <music21.note.Note G#>>
    <PitchedTimespan (1.0 to 2.0) <music21.note.Note F#>>
<music21.stream.Part Soprano>
    <PitchedTimespan (0.0 to 0.5) <music21.note.Note C#>>
    <PitchedTimespan (0.5 to 1.0) <music21.note.Note B>>
    <PitchedTimespan (1.0 to 2.0) <music21.note.Note A>>
<music21.stream.Part Tenor>
    <PitchedTimespan (0.0 to 0.5) <music21.note.Note A>>
    <PitchedTimespan (0.5 to 1.0) <music21.note.Note B>>
    <PitchedTimespan (1.0 to 2.0) <music21.note.Note C#>>

Methods inherited from OffsetTree:

Methods inherited from ElementTree:

Methods inherited from AVLTree: