
    rhP                       S r SSKJr  SSKrSSKJr  SSKrSSKrSSK	J
r
  SSKJr  SSKJr  \R                  (       a  SSKJr  SS	KJr  \R"                  " S
5      r " S S\R&                  5      r " S S5      r " S S\5      r " S S\5      r " S S\R0                  5      r\S:X  a  SSKr\R6                  " \5        gg)za
Tools for grouping notes and chords into a searchable tree
organized by start and stop offsets.
    )annotationsN)inf)
OffsetQLIn)environment)exceptions21)basestreamz
tree.spansc                      \ rS rSrSrg)TimespanException$    N__name__
__module____qualname____firstlineno____static_attributes__r       L/home/james-whalen/.local/lib/python3.13/site-packages/music21/tree/spans.pyr   r   $       r   r   c                  p    \ rS rSrSr\* \4S jrS rS r\	S 5       r
\	S 5       rSS	 jrS
 rS rS rSrg)Timespan*   a  
A span of time, with a start offset and stop offset.

Useful for demonstrating various properties of the timespan-collection class
family.

>>> timespan = tree.spans.Timespan(-1.5, 3.25)
>>> print(timespan)
<Timespan -1.5 3.25>

A timespan has two attributes, its offset and its endTime.  They are immutable.

>>> timespan.offset
-1.5
>>> timespan.endTime
3.25

To create a changed timespan, call the .new() method on the timespan.

>>> ts2 = timespan.new(offset=0.0)
>>> ts2
<Timespan 0.0 3.25>

>>> ts3 = timespan.new(endTime=5.0)
>>> ts3
<Timespan -1.5 5.0>

Two timespans are equal if they have the same offset and endTime

>>> ts4 = tree.spans.Timespan(-1.5, 5.0)
>>> ts3 == ts4
True
>>> ts4 == ts2
False
c                    Ub  [        U5      nXl        Ub  [        U5      nX l        Ub  Ub  X:  a  [        SU< SU< 35      eg g g )Noffset z must be after endTime )float_offset_endTimer   selfoffsetendTimes      r   __init__Timespan.__init__O   sc    6]FGnG'"5''&;RSZR](^__   #6r   c                    [        U 5      [        U5      L a5  U R                  UR                  :X  a  U R                  UR                  :X  a  gg)NTF)typer"   r#   )r!   exprs     r   __eq__Timespan.__eq__Z   s8    :d#{{dkk)<<4<</r   c                l    [        U 5      R                  nSU SU R                   SU R                   S3$ )N< >)r'   r   r"   r#   r!   typeNames     r   __repr__Timespan.__repr__a   s4    :&&8*Adkk]!DLL>;;r   c                    U R                   $ )a  
The start offset of the Timespan, relative to its
containing score.

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans()
>>> verticality = scoreTree.getVerticalityAt(1.0)
>>> timespan = verticality.startTimespans[0]
>>> timespan.offset
1.0
)r   r!   s    r   r"   Timespan.offsete   s     ||r   c                    U R                   $ )a  
The stop offset of the Timespan, relative to its
containing score.

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans()
>>> verticality = scoreTree.getVerticalityAt(1.0)
>>> timespan = verticality.startTimespans[0]
>>> timespan.endTime
2.0
)r   r4   s    r   r#   Timespan.endTimeu   s     }}r   Nc                \    Uc  U R                   nUc  U R                  n[        U 5      " XS9$ )z7
return a new object with the given offset and endTime
r"   r#   )r"   r#   r'   r    s      r   newTimespan.new   s/     >[[F?llGDz99r   c                    [        U[        U 5      5      (       d  SU  SU S3nSU4$ U R                  UR                  :X  d'  UR                  U R                  :X  d  SU  SU S3nSU4$ g)a<  
returns a tuple of (True or False) if these timespans can be merged
with the second element being a message or None.

>>> ts1 = tree.spans.Timespan(0, 5)
>>> ts2 = tree.spans.Timespan(5, 7)
>>> ts1.canMerge(ts2)
(True, '')
>>> ts3 = tree.spans.Timespan(6, 10)
>>> ts1.canMerge(ts3)
(False, 'Cannot merge <Timespan 0.0 5.0> with <Timespan 6.0 10.0>: not contiguous')

Overlapping Timespans cannot be merged, just contiguous ones.

>>> ts4 = tree.spans.Timespan(3, 4)
>>> ts1.canMerge(ts4)
(False, 'Cannot merge <Timespan 0.0 5.0> with <Timespan 3.0 4.0>: not contiguous')
Cannot merge  with z: wrong typesFz: not contiguous)T )
isinstancer'   r#   r"   )r!   othermessages      r   canMergeTimespan.canMerge   sw    & %d,,%dV6%FG7##-MMT[[0%dV6%8HIG7##r   c                    U R                  U5      u  p#USL a  [        U5      eU R                  UR                  :  a  U R                  UR                  S9nU$ UR                  U R                  S9nU$ )a  
Merges two consecutive/contiguous timespans, keeping the
information from the former of the two.

>>> ts1 = tree.spans.Timespan(0, 5)
>>> ts2 = tree.spans.Timespan(5, 7)
>>> ts3 = ts1.mergeWith(ts2)
>>> ts3
<Timespan 0.0 7.0>

Note that (for now), overlapping timespans cannot be merged:

>>> ts4 = tree.spans.Timespan(6, 10)
>>> ts3.mergeWith(ts4)
Traceback (most recent call last):
music21.tree.spans.TimespanException: Cannot merge <Timespan 0.0 7.0> with
    <Timespan 6.0 10.0>: not contiguous
Fr#   )rC   r   r"   r:   r#   )r!   rA   canrB   mergedTimespans        r   	mergeWithTimespan.mergeWith   sn    & }}U+%<#G,,;;%!XXemmX<N  #YYt||Y<Nr   c                    XR                   :  d  U R                  U:  a  U 4$ U R                  US9nU R                  US9nX#4$ )aa  
Split Timespan at `offset`.

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans(classList=(note.Note,))
>>> verticality = scoreTree.getVerticalityAt(0)
>>> verticality
<music21.tree.verticality.Verticality 0 {A3 E4 C#5}>

>>> timespan = verticality.startTimespans[0]
>>> timespan
<PitchedTimespan (0.0 to 0.5) <music21.note.Note C#>>

>>> for shard in timespan.splitAt(0.25):
...     shard
...
<PitchedTimespan (0.0 to 0.25) <music21.note.Note C#>>
<PitchedTimespan (0.25 to 0.5) <music21.note.Note C#>>

>>> timespan.splitAt(1000)
(<PitchedTimespan (0.0 to 0.5) <music21.note.Note C#>>,)
rF   )r"   )r"   r#   r:   )r!   r"   leftrights       r   splitAtTimespan.splitAt   sH    0 KK4<<&#87Nxxx''{r   )r   r   )NN)r   r   r   r   __doc__r   r$   r)   r1   propertyr"   r#   r:   rC   rI   rN   r   r   r   r   r   r   *   s]    "H  #dC 	`<    	:8:r   r   c                     ^  \ rS rSr% SrSS0rS\S'         S           SU 4S jjjrS rS	 r	\
S
 5       r     SS jr\
S 5       rS r\
S 5       rSSS jjrSrU =r$ )ElementTimespan   a  
A span of time anchored to an element in a score.  The span of time may
be the same length as the element in the score.  It may be shorter (a
"slice" of an element) or it may be longer (in the case of a timespan
that is anchored to a single element but extends over rests or other
notes following a note)

PitchedTimespans give information about an element (such as a Note).  It knows
its absolute position with respect to the element passed into TimespanTree.
It contains information about what measure it's in, what part it's in, etc.

Example, getting a passing tone from a known location from a Bach chorale.

First we create an Offset tree:

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans()
>>> scoreTree
<TimespanTree {199} (0.0 to 36.0) <music21.stream.Score ...>>

Then get the verticality from offset 6.5, which is beat two-and-a-half of
measure 2 (the piece is in 4/4 with a quarter-note pickup)

>>> verticality = scoreTree.getVerticalityAt(6.5)
>>> verticality
<music21.tree.verticality.Verticality 6.5 {E3 D4 G#4 B4}>

There are four PitchedTimespans in the verticality -- each representing
a note.  The notes are arranged from lowest to highest.


We can find all the PitchedTimespans that start exactly at 6.5. There's
one.

>>> verticality.startTimespans
(<PitchedTimespan (6.5 to 7.0) <music21.note.Note D>>,)

>>> pitchedTimespan = verticality.startTimespans[0]
>>> pitchedTimespan
<PitchedTimespan (6.5 to 7.0) <music21.note.Note D>>

What can we do with a PitchedTimespan? We can get its Part object and from there the
Part object name

>>> pitchedTimespan.part
<music21.stream.Part Tenor>
>>> pitchedTimespan.part.partName
'Tenor'

Find out what measure it's in:

>>> pitchedTimespan.measureNumber
2
>>> pitchedTimespan.parentOffset
5.0

The position in the measure is given by subtracting that from the
.offset:

>>> pitchedTimespan.offset - pitchedTimespan.parentOffset
1.5

>>> pitchedTimespan.element
<music21.note.Note D>

These are not dynamic, so changing the Score object does not change the
measureNumber, etc.
	parentageau  
            The Stream hierarchy above the element in a ElementTimespan.

            >>> score = corpus.parse('bwv66.6')
            >>> scoreTree = score.asTimespans()
            >>> verticality = scoreTree.getVerticalityAt(1.0)
            >>> pitchedTimespan = verticality.startTimespans[0]
            >>> pitchedTimespan
            <PitchedTimespan (1.0 to 2.0) <music21.note.Note A>>
            >>> for streamSite in pitchedTimespan.parentage:
            ...     streamSite
            <music21.stream.Measure 1 offset=1.0>
            <music21.stream.Part Soprano>
            <music21.stream.Score ...>
            zdict[str, str]	_DOC_ATTRc                   > [         TU ]  XVS9  Xl        X@l        Ub  [	        U5      nX l        Ub  [	        U5      nX0l        Ub  Ub  X#:  a  [        SU< SU< 35      eg g g )Nr9   r   z must be after parentEndTime )superr$   elementrU   r   parentOffsetparentEndTimer   r!   rY   rZ   r[   rU   r"   r#   	__class__s          r   r$   ElementTimespan.__init__E  s     	807"# .L($!-0M*#(A+'l--J=J[\^ ^ , )B#r   c                    XL $ )Nr   )r!   rA   s     r   r)   ElementTimespan.__eq__^  s
    }r   c           	         [        U 5      R                  nSU SU R                   SU R                   SU R                  < S3	$ )Nr,   z (z to z) r.   )r'   r   r"   r#   rY   r/   s     r   r1   ElementTimespan.__repr__a  s?    :&&8*Bt{{m4~R?OqQQr   c                4    U R                   U R                  -
  $ )a   
The quarterLength of the Timespan, which, due to manipulation, may be different
from that of the element.

>>> n = note.Note('D-')
>>> n.offset = 1.0
>>> n.duration.quarterLength = 2.0

>>> pts = tree.spans.PitchedTimespan(n, offset=n.offset, endTime=3.0)
>>> pts
<PitchedTimespan (1.0 to 3.0) <music21.note.Note D->>
>>> pts.quarterLength
2.0
>>> n.duration.quarterLength
2.0

>>> pts2 = pts.new(offset=0.0)
>>> pts2
<PitchedTimespan (0.0 to 3.0) <music21.note.Note D->>
>>> pts2.quarterLength
3.0
>>> pts2.element.duration.quarterLength
2.0
)r#   r"   r4   s    r   quarterLengthElementTimespan.quarterLengthg  s    4 ||dkk))r   c           	         U=(       d    U R                   nUc  U R                  nUc  U R                  nUc  U R                  nUc  U R                  n[        U 5      " UUUU R                  UUS9$ )a  
Create a new object that is identical to the calling object
but with some parameters overridden.

>>> n = note.Note('C#')
>>> pts = tree.spans.PitchedTimespan(n, offset=11.0, endTime=12.0)
>>> pts
<PitchedTimespan (11.0 to 12.0) <music21.note.Note C#>>
>>> pts2 = pts.new(endTime=13.0)
>>> pts2
<PitchedTimespan (11.0 to 13.0) <music21.note.Note C#>>
>>> pts.element is pts2.element
True
rY   rZ   r[   rU   r"   r#   )rY   rZ   r[   r"   r#   r'   rU   )r!   rY   rZ   r[   r"   r#   s         r   r:   ElementTimespan.new  sw    * )T\\,,L  ..M>[[F?llGDz%'nn
 	
r   c                .    U R                   R                  $ )a  
The measure number of the measure containing the element.

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans()
>>> verticality = scoreTree.getVerticalityAt(1.0)
>>> pitchedTimespan = verticality.startTimespans[0]
>>> pitchedTimespan.measureNumber
1
)rY   measureNumberr4   s    r   rj   ElementTimespan.measureNumber  s     ||)))r   c                R    U R                    H  n[        X!5      (       d  M  Us  $    g)a  
returns that is the first parentage that has this classList.
default stream.Part

>>> score = corpus.parse('bwv66.6')
>>> score.id = 'bach'
>>> scoreTree = score.asTimespans()
>>> verticality = scoreTree.getVerticalityAt(1.0)
>>> pitchedTimespan = verticality.startTimespans[2]
>>> pitchedTimespan
<PitchedTimespan (1.0 to 2.0) <music21.note.Note C#>>
>>> pitchedTimespan.getParentageByClass(classList=(stream.Part,))
<music21.stream.Part Tenor>
>>> pitchedTimespan.getParentageByClass(classList=(stream.Measure,))
<music21.stream.Measure 1 offset=1.0>
>>> pitchedTimespan.getParentageByClass(classList=(stream.Score,))
<music21.stream.Score bach>

The closest parent is returned in case of a multiple list:

>>> searchTuple = (stream.Voice, stream.Measure, stream.Part)
>>> pitchedTimespan.getParentageByClass(classList=searchTuple)
<music21.stream.Measure 1 offset=1.0>

TODO: this should take a normal class list.
N)rU   r@   )r!   	classListparents      r   getParentageByClass#ElementTimespan.getParentageByClass  s'    6 nnF&,, % r   c                B    SSK Jn  U R                  UR                  4S9$ )ag  
find the object in the parentage that is a Part object:

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans()
>>> verticality = scoreTree.getVerticalityAt(1.0)
>>> pitchedTimespan = verticality.startTimespans[2]
>>> pitchedTimespan
<PitchedTimespan (1.0 to 2.0) <music21.note.Note C#>>
>>> pitchedTimespan.part
<music21.stream.Part Tenor>
r   r	   )rm   )music21r
   ro   Part)r!   r
   s     r   partElementTimespan.part  s!     	#''6;;.'AAr   c                    U R                   nUc  gU(       a9  Un[        R                  " U5      nX2R                  l        SUR                  l        U R                  UR                  l        U$ )z~
Return a copy of the element (or the same one if makeCopy is False)
with the quarterLength set to the length of the timespan
Nzspans.makeElement)rY   copydeepcopy
derivationoriginmethodrd   duration)r!   makeCopyelel_olds       r   makeElementElementTimespan.makeElement  sX    
 \\:Fv&B#)MM #6BMM $($6$6!	r   )rY   r[   rZ   rU   )NNNr   NN)rY   base.Music21Object | NonerZ   OffsetQLIn | Noner[   r   rU   ztuple[stream.Stream, ...]r"   r   r#   r   )NNNNN)T)r}   boolreturnr   )r   r   r   r   rP   rV   __annotations__r$   r)   r1   rQ   rd   r:   rj   ro   rt   r   r   __classcell__r]   s   @r   rS   rS      s    CN 	 !I~ , ,0(,)-/1"&#'^(^ &^ '	^
 -^  ^ !^ ^2R * *< &
T * *&@ B B  r   rS   c                  T   ^  \ rS rSr      SU 4S jjr\S 5       rU 4S jrSrU =r	$ )PitchedTimespani	  c           	     *   > [         TU ]  UUUUUUS9  g )Nrg   )rX   r$   r\   s          r   r$   PitchedTimespan.__init__
  s(     	&2'4#, &!( 	 	*r   c                .    U R                   R                  $ )aI  
Gets the pitches of the element wrapped by this PitchedTimespan.

>>> c = chord.Chord('C4 E4 G4')
>>> pts = tree.spans.PitchedTimespan(c, offset=0.0, endTime=1.0)
>>> pts.pitches
(<music21.pitch.Pitch C4>, <music21.pitch.Pitch E4>, <music21.pitch.Pitch G4>)
>>> pts.pitches == c.pitches
True
>>> pts.pitches is c.pitches
False

)rY   pitchesr4   s    r   r   PitchedTimespan.pitches  s     ||###r   c                   > [         TU ]  U5      u  p#USL a%  U R                  UR                  :w  a  SU  SU S3nSnX#4$ )a  
sub-method of base canMerge that checks to see if the pitches are the same.

For quick score reductions, we can merge two consecutive
like-pitched element timespans, keeping
score-relevant information from the first of the two, such as its
Music21 Element.

This is useful when using timespans to perform score reduction.

Let's demonstrate merging some contiguous E's in the alto part of a Bach
chorale:

>>> score = corpus.parse('bwv66.6')
>>> scoreTree = score.asTimespans(classList=(note.Note,))
>>> timespan_one = scoreTree[12]
>>> print(timespan_one)
<PitchedTimespan (2.0 to 3.0) <music21.note.Note E>>

>>> print(timespan_one.part)
<music21.stream.Part Alto>

>>> timespan_two = scoreTree.findNextPitchedTimespanInSameStreamByClass(timespan_one)
>>> print(timespan_two)
<PitchedTimespan (3.0 to 4.0) <music21.note.Note E>>

>>> timespan_one.canMerge(timespan_two)
(True, '')

>>> merged = timespan_one.mergeWith(timespan_two)
>>> print(merged)
<PitchedTimespan (2.0 to 4.0) <music21.note.Note E>>

>>> merged.part is timespan_one.part
True


Attempting to merge timespans which are not contiguous, or which do not
have identical pitches will result in error:

>>> scoreTree[0].canMerge(scoreTree[50])
(False, 'Cannot merge <PitchedTimespan (0.0 to 0.5) <music21.note.Note C#>>
     with <PitchedTimespan (9.5 to 10.0) <music21.note.Note B>>: not contiguous')


>>> scoreTree[0].mergeWith(scoreTree[50])
Traceback (most recent call last):
music21.tree.spans.TimespanException: Cannot merge
        <PitchedTimespan (0.0 to 0.5) <music21.note.Note C#>>
        with <PitchedTimespan (9.5 to 10.0) <music21.note.Note B>>: not contiguous


This is probably not what you want to do: get the next element timespan in
the same score:

>>> timespan_twoWrong = scoreTree.findNextPitchedTimespanInSameStreamByClass(
...     timespan_one, classList=(stream.Score,))
>>> print(timespan_twoWrong)
<PitchedTimespan (3.0 to 4.0) <music21.note.Note C#>>
>>> print(timespan_twoWrong.part)
<music21.stream.Part Soprano>

Tr=   r>   z: different pitchesF)rX   rC   r   )r!   rA   rG   rB   r]   s       r   rC   PitchedTimespan.canMerge*  sP    @ w'.$;||u}},)$veW<OP~r   r   )NNNNNN)
r   r   r   r   r$   rQ   r   rC   r   r   r   s   @r   r   r   	  s:    "#* $ $ E Er   r   c                      \ rS rSrSrg)Testit  r   Nr   r   r   r   r   r   t  r   r   r   __main__)rP   
__future__r   rw   mathr   typingtunittestmusic21.common.typesr   rr   r   r   TYPE_CHECKINGr   r
   EnvironmentenvironLocalTreeExceptionr   r   rS   r   TestCaser   r   mainTestr   r   r   <module>r      s    #     +   ??&&|4	22 	{ {@Zh Z~fo fV	8 	 zT r   