
    rh                    R   S SK Jr  S SKJr  S SKrS SKrS SKrS SKJr  S SK	J
r
  S SK	Jr  S SK	Jr  S SKJr   " S	 S
\R                  5      r " S S\5      r " S S\R$                  5      r " S S5      r " S S\R*                  5      r\S:X  a  S SK	r	\	R0                  " \5        gg)    )annotations)CounterN)cast)base)exceptions21)metadata)hasherc                      \ rS rSrSrg)AlignerException    N__name__
__module____qualname____firstlineno____static_attributes__r       X/home/james-whalen/.local/lib/python3.13/site-packages/music21/alpha/analysis/aligner.pyr   r          r   r   c                      \ rS rSrSrg)AlignmentTracebackException   r   Nr   r   r   r   r   r      r   r   r   c                  8    \ rS rSrSrSrSrSrSr\	S 5       r
Srg	)
	ChangeOps"   aK  
>>> ins = alpha.analysis.aligner.ChangeOps.Insertion
>>> ins.color
'green'

>>> deletion = alpha.analysis.aligner.ChangeOps.Deletion
>>> deletion.color
'red'

>>> subs = alpha.analysis.aligner.ChangeOps.Substitution
>>> subs.color
'purple'

>>> noChange = alpha.analysis.aligner.ChangeOps.NoChange
>>> noChange.color is None
True
r            c                ,    SSSS S.nXR                      $ )Ngreenredpurple)r   r   r   r   )value)self	colorDicts     r   colorChangeOps.color9   s    Eh4@	$$r   r   N)r   r   r   r   __doc__	InsertionDeletionSubstitutionNoChangepropertyr'   r   r   r   r   r   r   "   s/    " IHLH% %r   r   c                  z    \ rS rSrSrSS jrS rS rS rS r	S	 r
S
 rS rS rS rS rS rS rS rSS jrSrg)StreamAligner?   a5  
Stream Aligner is a dumb object that takes in two streams and forces them to align
without any thought to any external variables

These terms are associated with the Target stream are:
- n, the number of rows in the distance matrix, the left-most column of the matrix
- i, the index into rows in the distance matrix
- the first element of tuple

These terms are associated with the Source stream are:
- m, the number of columns in the distance matrix, the top-most row of the matrix
- j, the index into columns in the distance matrix
- the second element of tuple
Nc                    Xl         X l        S U l        Uc  U R                  5       nX0l        X@l        / U l        SU l        SU l        SU l	        S U l
        S U l        S U l        g )Nr   )targetStreamsourceStreamdistanceMatrixgetDefaultHasherr	   	preHashedchangessimilarityScorenmhashedTargetStreamhashedSourceStreamchangesCount)r%   r3   r4   hasher_funcr7   s        r   __init__StreamAligner.__init__O   sk    (("//1K!"  "&"& r   c                L    [         R                  " 5       nSUl        SUl        U$ )aO  
returns a default hasher.Hasher object
that does not hashOffset or include the reference.

called by __init__ if no hasher is passed in.

>>> sa = alpha.analysis.aligner.StreamAligner()
>>> h = sa.getDefaultHasher()
>>> h
<music21.alpha.analysis.hasher.Hasher object at 0x1068cf6a0>
>>> h.hashOffset
False
>>> h.includeReference
True
FT)r	   Hasher
hashOffsetincludeReference)r%   hs     r   r6   StreamAligner.getDefaultHasherf   s#    " MMO!r   c                    U R                  5         U R                  5         U R                  5         U R                  5         g )N)makeHashedStreamssetupDistanceMatrixpopulateDistanceMatrixcalculateChangesListr%   s    r   alignStreamAligner.align|   s2       "##%!!#r   c                   U R                   (       dU  U R                  R                  U R                  5      U l        U R                  R                  U R
                  5      U l        gU R                  U l        U R
                  U l        g)a  
Hashes streams if not pre-hashed

>>> tStream = stream.Stream()
>>> sStream = stream.Stream()

>>> note1 = note.Note('C4')
>>> note2 = note.Note('D4')
>>> note3 = note.Note('C4')
>>> note4 = note.Note('E4')

>>> tStream.append([note1, note2])
>>> sStream.append([note3, note4])
>>> sa1 = alpha.analysis.aligner.StreamAligner(tStream, sStream)

>>> h = alpha.analysis.hasher.Hasher()
>>> h.includeReference = True

>>> toBeHashedTarStream = stream.Stream()
>>> toBeHashedSouStream = stream.Stream()

>>> note5 = note.Note('A4')
>>> note6 = note.Note('B4')
>>> note7 = note.Note('A4')
>>> note8 = note.Note('G4')

>>> toBeHashedTarStream.append([note5, note6])
>>> toBeHashedSouStream.append([note7, note8])
>>> hashedTarStr = h.hashStream(toBeHashedTarStream)
>>> hashedSouStr = h.hashStream(toBeHashedSouStream)
>>> sa2 = alpha.analysis.aligner.StreamAligner(
...             hashedTarStr, hashedSouStr, preHashed=True)

>>> sa2.makeHashedStreams()
>>> sa1.makeHashedStreams()

>>> sa1.hashedTargetStream
[NoteHashWithReference(Pitch=60, Duration=1.0),
 NoteHashWithReference(Pitch=62, Duration=1.0)]
>>> sa1.hashedSourceStream
[NoteHashWithReference(Pitch=60, Duration=1.0),
 NoteHashWithReference(Pitch=64, Duration=1.0)]

>>> sa2.hashedTargetStream
[NoteHashWithReference(Pitch=69, Duration=1.0, Offset=0.0),
 NoteHashWithReference(Pitch=71, Duration=1.0, Offset=1.0)]
>>> sa2.hashedSourceStream
[NoteHashWithReference(Pitch=69, Duration=1.0, Offset=0.0),
 NoteHashWithReference(Pitch=67, Duration=1.0, Offset=1.0)]

N)r7   r	   
hashStreamr3   r<   r4   r=   rM   s    r   rI   StreamAligner.makeHashedStreams   sd    h ~~&*kk&<&<T=N=N&OD#&*kk&<&<T=N=N&OD# '+&7&7D#&*&7&7D#r   c                   U R                   (       d  U R                  5         [        U R                   5      U l        [        U R                  5      U l        U R                  S:X  a  [        S5      eU R
                  S:X  a  [        S5      eS[        R                  ;   a  [        S5      eSSK	nUR                  U R                  S-   U R
                  S-   4[        S9U l        g)	a  
Creates a distance matrix of the right size after hashing

>>> note1 = note.Note('C4')
>>> note2 = note.Note('D4')
>>> note3 = note.Note('C4')
>>> note4 = note.Note('E4')

Test for streams of length 3 and 4

>>> target0 = converter.parse('tinyNotation: C4 D C E')
>>> source0 = converter.parse('tinyNotation: C4 D C')


>>> sa0 = alpha.analysis.aligner.StreamAligner(target0, source0)
>>> sa0.setupDistanceMatrix()
>>> sa0.distanceMatrix.size
20
>>> sa0.distanceMatrix.shape
(5, 4)

Test for empty target stream

>>> target1 = stream.Stream()
>>> source1 = stream.Stream()
>>> source1.append(note1)
>>> sa1 = alpha.analysis.aligner.StreamAligner(target1, source1)
>>> sa1.makeHashedStreams()
>>> sa1.setupDistanceMatrix()
Traceback (most recent call last):
music21.alpha.analysis.aligner.AlignerException:
Cannot perform alignment with empty target stream.

Test for empty source stream

>>> target2 = stream.Stream()
>>> source2 = stream.Stream()
>>> target2.append(note3)
>>> sa2 = alpha.analysis.aligner.StreamAligner(target2, source2)
>>> sa2.makeHashedStreams()
>>> sa2.setupDistanceMatrix()
Traceback (most recent call last):
music21.alpha.analysis.aligner.AlignerException:
    Cannot perform alignment with empty source stream.

r   z2Cannot perform alignment with empty target stream.z2Cannot perform alignment with empty source stream.numpyz!Cannot run Aligner without numpy.Nr   )dtype)r<   rI   lenr:   r=   r;   r   r   _missingImportrT   zerosintr5   )r%   nps     r   rJ   !StreamAligner.setupDistanceMatrix   s    ^ &&""$T,,-T,,-66Q;"#WXX66Q;"#WXX d)))"#FGG hh
DFFQJ'?shKr   c                J   U R                  U R                  S   5      nU R                  U R                  S   5      n[        SU R                  S-   5       H+  nU R
                  US-
     S   U-   U R
                  U   S'   M-     [        SU R                  S-   5       H+  nU R
                  S   US-
     U-   U R
                  S   U'   M-     [        SU R                  S-   5       H  n[        SU R                  S-   5       H  nU R                  U R                  US-
     U R                  US-
     5      nU R
                  US-
     U   U-   U R
                  U   US-
     U-   U R
                  US-
     US-
     U-   /n[        U5      U R
                  U   U'   M     M     g)aD  
Sets up the distance matrix for back-tracing

>>> note1 = note.Note('C#4')
>>> note2 = note.Note('C4')

Test 1: similar streams

>>> targetA = stream.Stream()
>>> sourceA = stream.Stream()
>>> targetA.append([note1, note2])
>>> sourceA.append([note1, note2])
>>> saA = alpha.analysis.aligner.StreamAligner(targetA, sourceA)
>>> saA.makeHashedStreams()
>>> saA.setupDistanceMatrix()
>>> saA.populateDistanceMatrix()
>>> saA.distanceMatrix
array([[0, 2, 4],
       [2, 0, 2],
       [4, 2, 0]])

Second Test

>>> targetB = stream.Stream()
>>> sourceB = stream.Stream()
>>> targetB.append([note1, note2])
>>> sourceB.append(note1)
>>> saB = alpha.analysis.aligner.StreamAligner(targetB, sourceB)
>>> saB.makeHashedStreams()
>>> saB.setupDistanceMatrix()
>>> saB.populateDistanceMatrix()
>>> saB.distanceMatrix
array([[0, 2],
       [2, 0],
       [4, 2]])

Third Test

>>> note3 = note.Note('D5')
>>> note3.quarterLength = 3
>>> note4 = note.Note('E3')
>>> targetC = stream.Stream()
>>> sourceC = stream.Stream()
>>> targetC.append([note1, note2, note4])
>>> sourceC.append([note3, note1, note4])
>>> saC = alpha.analysis.aligner.StreamAligner(targetC, sourceC)
>>> saC.makeHashedStreams()
>>> saC.setupDistanceMatrix()
>>> saC.populateDistanceMatrix()
>>> saC.distanceMatrix
array([[0, 2, 4, 6],
   [2, 2, 2, 4],
   [4, 4, 3, 3],
   [6, 6, 5, 3]])

r   r   N)

insertCostr=   
deleteCostranger:   r5   r;   substitutionCostr<   min)r%   r]   r^   ij	substCostpreviousValuess          r   rK   $StreamAligner.populateDistanceMatrix   s   v __T%<%<Q%?@
__T%<%<Q%?@
 q$&&1*%A(,(;(;AE(B1(E
(RD"1% & q$&&1*%A(,(;(;A(>q1u(E
(RD"1% & q$&&1*%A1dffqj) 11$2I2I!a%2P262I2I!a%2PR	 #'"5"5a!e"<Q"?*"L"&"5"5a"8Q"?*"L"&"5"5a!e"<QU"Ci"O"Q -0,?##A&q) * &r   c                   US:  a  [        U R                  US-
     U   5      OSnUS:  a  [        U R                  U   US-
     5      OSnUS:  a'  US:  a!  [        U R                  US-
     US-
     5      OSnX4U/nU$ )a}  
i and j are current row and column indices in self.distanceMatrix
returns all possible moves (0 up to 3)
vertical, horizontal, diagonal costs of adjacent entries in self.distMatrix

>>> target = stream.Stream()
>>> source = stream.Stream()

>>> note1 = note.Note('C4')
>>> note2 = note.Note('D4')
>>> note3 = note.Note('C4')
>>> note4 = note.Note('E4')

>>> target.append([note1, note2, note3, note4])
>>> source.append([note1, note2, note3])
>>> sa = alpha.analysis.aligner.StreamAligner(target, source)
>>> sa.makeHashedStreams()
>>> sa.setupDistanceMatrix()
>>> for i in range(4+1):
...     for j in range(3+1):
...         sa.distanceMatrix[i][j] = i * j

>>> sa.distanceMatrix
array([[ 0,  0,  0,  0],
       [ 0,  1,  2,  3],
       [ 0,  2,  4,  6],
       [ 0,  3,  6,  9],
       [ 0,  4,  8, 12]])

>>> sa.getPossibleMovesFromLocation(0, 0)
[None, None, None]

>>> sa.getPossibleMovesFromLocation(1, 1)
[0, 0, 0]

>>> sa.getPossibleMovesFromLocation(4, 3)
[9, 8, 6]

>>> sa.getPossibleMovesFromLocation(2, 2)
[2, 2, 1]

>>> sa.getPossibleMovesFromLocation(0, 2)
[None, 0, None]

>>> sa.getPossibleMovesFromLocation(3, 0)
[0, None, None]

r   N)rY   r5   )r%   rb   rc   verticalCosthorizontalCostdiagonalCostpossibleMovess          r   getPossibleMovesFromLocation*StreamAligner.getPossibleMovesFromLocationR  s    d >?!Vs4..q1u5a89?@AvT003AE:;4BCq&QRSVs4..q1u5a!e<=Z^%|Dr   c                T   U R                  X5      nUS   c!  US   c  [        S5      e[        R                  $ US   c  [        R                  $ U R
                  U   U   n[        [        U5      [        R                  " S5      S9u  pVXF:X  a  [        R                  $ [        U5      $ )a%  
Insert, Delete, Substitution, No Change = range(4)

return the direction that traceback moves
0: vertical movement, insertion
1: horizontal movement, deletion
2: diagonal movement, substitution
3: diagonal movement, no change

raises a ValueError if i == 0 and j == 0.
>>> target = stream.Stream()
>>> source = stream.Stream()

>>> note1 = note.Note('C4')
>>> note2 = note.Note('D4')
>>> note3 = note.Note('C4')
>>> note4 = note.Note('E4')

>>> target.append([note1, note2, note3, note4])
>>> source.append([note1, note2, note3])

>>> sa = alpha.analysis.aligner.StreamAligner(target, source)
>>> sa.makeHashedStreams()
>>> sa.setupDistanceMatrix()
>>> sa.populateDistanceMatrix()
>>> sa.distanceMatrix
array([[0, 2, 4, 6],
       [2, 0, 2, 4],
       [4, 2, 0, 2],
       [6, 4, 2, 0],
       [8, 6, 4, 2]])


>>> sa.getOpFromLocation(4, 3)
<ChangeOps.Insertion: 0>

>>> sa.getOpFromLocation(2, 2)
<ChangeOps.NoChange: 3>

>>> sa.getOpFromLocation(0, 2)
<ChangeOps.Deletion: 1>

>>> sa.distanceMatrix[0][0] = 1
>>> sa.distanceMatrix
array([[1, 2, 4, 6],
       [2, 0, 2, 4],
       [4, 2, 0, 2],
       [6, 4, 2, 0],
       [8, 6, 4, 2]])

>>> sa.getOpFromLocation(1, 1)
<ChangeOps.Substitution: 2>

>>> sa.getOpFromLocation(0, 0)
Traceback (most recent call last):
ValueError: No movement possible from the origin
r   r   z$No movement possible from the origin)key)rl   
ValueErrorr   r+   r*   r5   ra   	enumerateoperator
itemgetterr-   )r%   rb   rc   rk   currentCostminIndex
minNewCosts          r   getOpFromLocationStreamAligner.getOpFromLocation  s    t 99!?#Q' !GHH%%%1%&&&))!,Q/"9]#;ATATUVAWX$%%%X&&r   c                0    [        UR                  5      nU$ )a  
Cost of inserting an extra hashed item.
For now, it's just the size of the keys of the NoteHashWithReference

>>> target = stream.Stream()
>>> source = stream.Stream()

>>> note1 = note.Note('C4')
>>> note2 = note.Note('D4')
>>> note3 = note.Note('C4')
>>> note4 = note.Note('E4')

>>> target.append([note1, note2, note3, note4])
>>> source.append([note1, note2, note3])

This is a StreamAligner with default hasher settings

>>> sa0 = alpha.analysis.aligner.StreamAligner(target, source)
>>> sa0.align()
>>> tup0 = sa0.hashedTargetStream[0]
>>> sa0.insertCost(tup0)
2

This is a StreamAligner with a modified hasher that doesn't hash pitch at all

>>> sa1 = alpha.analysis.aligner.StreamAligner(target, source)
>>> sa1.hasher.hashPitch = False
>>> sa1.align()
>>> tup1 = sa1.hashedTargetStream[0]
>>> sa1.insertCost(tup1)
1

This is a StreamAligner with a modified hasher that hashes 3 additional properties

>>> sa2 = alpha.analysis.aligner.StreamAligner(target, source)
>>> sa2.hasher.hashOctave = True
>>> sa2.hasher.hashIntervalFromLastNote = True
>>> sa2.hasher.hashIsAccidental = True
>>> sa2.align()
>>> tup2 = sa2.hashedTargetStream[0]
>>> sa2.insertCost(tup2)
5
rV   hashItemsKeysr%   tupkeyDictSizes      r   r]   StreamAligner.insertCost      X #++,r   c                0    [        UR                  5      nU$ )a  
Cost of deleting an extra hashed item.
For now, it's just the size of the keys of the NoteHashWithReference

>>> target = stream.Stream()
>>> source = stream.Stream()

>>> note1 = note.Note('C4')
>>> note2 = note.Note('D4')
>>> note3 = note.Note('C4')
>>> note4 = note.Note('E4')

>>> target.append([note1, note2, note3, note4])
>>> source.append([note1, note2, note3])

This is a StreamAligner with default hasher settings

>>> sa0 = alpha.analysis.aligner.StreamAligner(target, source)
>>> sa0.align()
>>> tup0 = sa0.hashedSourceStream[0]
>>> sa0.deleteCost(tup0)
2

This is a StreamAligner with a modified hasher that doesn't hash pitch at all

>>> sa1 = alpha.analysis.aligner.StreamAligner(target, source)
>>> sa1.hasher.hashPitch = False
>>> sa1.align()
>>> tup1 = sa1.hashedSourceStream[0]
>>> sa1.deleteCost(tup1)
1

This is a StreamAligner with a modified hasher that hashes 3 additional properties

>>> sa2 = alpha.analysis.aligner.StreamAligner(target, source)
>>> sa2.hasher.hashOctave = True
>>> sa2.hasher.hashIntervalFromLastNote = True
>>> sa2.hasher.hashIsAccidental = True
>>> sa2.align()
>>> tup2 = sa2.hashedSourceStream[0]
>>> sa2.deleteCost(tup2)
5
rz   r|   s      r   r^   StreamAligner.deleteCost  r   r   c                    U R                  X5      (       a  g[        UR                  5      nU R                  X5      nX4-  nU$ )aP  
Finds the cost of substituting the targetTup with the sourceTup.
For now, it's just an interpolation of how many things they have in common

Example: equality testing, both streams made from same note
targetA will not have the same reference as sourceA
but their hashes will be equal, which makes for their hashed objects to be
able to be equal.

>>> note1 = note.Note('C4')
>>> targetA = stream.Stream()
>>> sourceA = stream.Stream()
>>> targetA.append(note1)
>>> sourceA.append(note1)
>>> targetA == sourceA
False

>>> saA = alpha.analysis.aligner.StreamAligner(targetA, sourceA)
>>> saA.align()
>>> hashedItem1A = saA.hashedTargetStream[0]
>>> hashedItem2A = saA.hashedSourceStream[0]
>>> print(hashedItem1A)
NoteHashWithReference(Pitch=60, Duration=1.0)

>>> print(hashedItem2A)
NoteHashWithReference(Pitch=60, Duration=1.0)

>>> saA.tupleEqualityWithoutReference(hashedItem1A, hashedItem2A)
True
>>> saA.substitutionCost(hashedItem1A, hashedItem2A)
0

>>> note2 = note.Note('D4')
>>> targetB = stream.Stream()
>>> sourceB = stream.Stream()
>>> targetB.append(note1)
>>> sourceB.append(note2)
>>> saB = alpha.analysis.aligner.StreamAligner(targetB, sourceB)
>>> saB.align()
>>> hashedItem1B = saB.hashedTargetStream[0]
>>> hashedItem2B = saB.hashedSourceStream[0]

hashed items only differ in 1 spot

>>> print(hashedItem1B)
NoteHashWithReference(Pitch=60, Duration=1.0)

>>> print(hashedItem2B)
NoteHashWithReference(Pitch=62, Duration=1.0)

>>> saB.substitutionCost(hashedItem1B, hashedItem2B)
1

>>> note3 = note.Note('E4')
>>> note4 = note.Note('E#4')
>>> note4.duration = duration.Duration('half')
>>> targetC = stream.Stream()
>>> sourceC = stream.Stream()
>>> targetC.append(note3)
>>> sourceC.append(note4)
>>> saC = alpha.analysis.aligner.StreamAligner(targetC, sourceC)
>>> saC.align()
>>> hashedItem1C = saC.hashedTargetStream[0]
>>> hashedItem2C = saC.hashedSourceStream[0]

hashed items should differ in 2 spots

>>> print(hashedItem1C)
NoteHashWithReference(Pitch=64, Duration=1.0)

>>> print(hashedItem2C)
NoteHashWithReference(Pitch=65, Duration=2.0)

>>> saC.substitutionCost(hashedItem1C, hashedItem2C)
2
r   )tupleEqualityWithoutReferencerV   r{   calculateNumSimilarities)r%   	targetTup	sourceTuptotalPossibleDifferencesnumSimilaritiesInTuples        r   r`   StreamAligner.substitutionCost3  sJ    Z --iCC#&y'>'>#? !%!>!>y!T : ''r   c                n    SnUR                    H"  n[        X5      [        X$5      :X  d  M  US-  nM$     U$ )a  
Returns the number of attributes that two tuples have that are the same

>>> target = stream.Stream()
>>> source = stream.Stream()

>>> note1 = note.Note('D1')
>>> target.append([note1])
>>> source.append([note1])
>>> sa = alpha.analysis.aligner.StreamAligner(target, source)

>>> from collections import namedtuple
>>> NoteHash = namedtuple('NoteHash', ['Pitch', 'Duration'])
>>> nh1 = NoteHash(60, 4)
>>> nhwr1 = alpha.analysis.hasher.NoteHashWithReference(nh1)
>>> nhwr1.reference = note.Note('C4')
>>> nhwr1
NoteHashWithReference(Pitch=60, Duration=4)

>>> nh2 = NoteHash(60, 4)
>>> nhwr2 = alpha.analysis.hasher.NoteHashWithReference(nh2)
>>> nhwr2.reference = note.Note('C4')
>>> nhwr2
NoteHashWithReference(Pitch=60, Duration=4)

>>> sa.calculateNumSimilarities(nhwr1, nhwr2)
2

>>> nh3 = NoteHash(61, 4)
>>> nhwr3 = alpha.analysis.hasher.NoteHashWithReference(nh3)
>>> nhwr3.reference = note.Note('C#4')
>>> nhwr3
NoteHashWithReference(Pitch=61, Duration=4)

>>> sa.calculateNumSimilarities(nhwr1, nhwr3)
1

>>> nh4 = NoteHash(59, 1)
>>> nhwr4 = alpha.analysis.hasher.NoteHashWithReference(nh4)
>>> nhwr4.reference = note.Note('B3')
>>> nhwr4
NoteHashWithReference(Pitch=59, Duration=1)

>>> sa.calculateNumSimilarities(nhwr2, nhwr4)
0
r   r   r{   getattr)r%   r   r   countvals        r   r   &StreamAligner.calculateNumSimilarities  s<    ` **Cy&')*AA
 + r   c                ^    UR                    H  n[        X5      [        X#5      :w  d  M    g   g)aI  
Returns whether two hashed items have the same attributes,
even though their references are different?

>>> target = stream.Stream()
>>> source = stream.Stream()

>>> note1 = note.Note('D1')
>>> target.append([note1])
>>> source.append([note1])
>>> sa = alpha.analysis.aligner.StreamAligner(target, source)

>>> from collections import namedtuple
>>> NoteHash = namedtuple('NoteHash', ['Pitch', 'Duration'])
>>> nh1 = NoteHash(60, 4)
>>> nhwr1 = alpha.analysis.hasher.NoteHashWithReference(nh1)
>>> nhwr1.reference = note.Note('C4')
>>> nhwr1
NoteHashWithReference(Pitch=60, Duration=4)

>>> nh2 = NoteHash(60, 4)
>>> nhwr2 = alpha.analysis.hasher.NoteHashWithReference(nh2)
>>> nhwr2.reference = note.Note('B#3')
>>> nhwr2
NoteHashWithReference(Pitch=60, Duration=4)

>>> sa.tupleEqualityWithoutReference(nhwr1, nhwr2)
True

This is a very difference has

>>> nh3 = NoteHash(61, 4)
>>> nhwr3 = alpha.analysis.hasher.NoteHashWithReference(nh3)
>>> nhwr3.reference = note.Note('C#4')
>>> nhwr3
NoteHashWithReference(Pitch=61, Duration=4)

>>> sa.tupleEqualityWithoutReference(nhwr1, nhwr3)
False

FTr   )r%   tup1tup2r   s       r   r   +StreamAligner.tupleEqualityWithoutReference  s/    T %%Ct!WT%77 & r   c                   U R                   nU R                  nUS:w  d  US:w  a  U R                  X5      nU R                  US-
     R                  nU R
                  US-
     R                  nXEU4nU R                  R                  SU5        U[        R                  :X  a  US-  nOCU[        R                  :X  a  US-  nO)U[        R                  :X  a  US-  nUS-  nO
US-  nUS-  nUS:w  a  M  US:w  a  M  US:w  a  US:w  a  [        S5      e[        S U R                   5       5      U l        U R                  [        R                     [!        U R                  5      -  U l        g)a
  
Traverses through self.distanceMatrix from the bottom right corner to top left looking at
bestOp at every move to determine which change was most likely at any point. Compiles
the list of changes in self.changes. Also calculates some metrics like self.similarityScore
and self.changesCount.

>>> note1 = note.Note('C#4')
>>> note2 = note.Note('C4')

test 1: one insertion, one no change. Target stream has one more note than
source stream, so source stream needs an insertion to match target stream.
should be 0.5 similarity between the two

>>> targetA = stream.Stream()
>>> sourceA = stream.Stream()
>>> targetA.append([note1, note2])
>>> sourceA.append(note1)
>>> saA = alpha.analysis.aligner.StreamAligner(targetA, sourceA)
>>> saA.makeHashedStreams()
>>> saA.setupDistanceMatrix()
>>> saA.populateDistanceMatrix()
>>> saA.calculateChangesList()
>>> saA.changesCount[alpha.analysis.aligner.ChangeOps.Insertion]
1
>>> saA.changesCount[alpha.analysis.aligner.ChangeOps.NoChange]
1
>>> saA.similarityScore
0.5

test 2: one deletion, one no change. Target stream has one fewer note than
source stream, so source stream needs a deletion to match target stream.
should be 0.5 similarity between the two

>>> targetB = stream.Stream()
>>> sourceB = stream.Stream()
>>> targetB.append(note1)
>>> sourceB.append([note1, note2])
>>> saB = alpha.analysis.aligner.StreamAligner(targetB, sourceB)
>>> saB.makeHashedStreams()
>>> saB.setupDistanceMatrix()
>>> saB.populateDistanceMatrix()
>>> saB.calculateChangesList()
>>> saB.changesCount[alpha.analysis.aligner.ChangeOps.Deletion]
1
>>> saB.changesCount[alpha.analysis.aligner.ChangeOps.NoChange]
1
>>> saB.similarityScore
0.5

test 3: no changes

>>> targetC = stream.Stream()
>>> sourceC = stream.Stream()
>>> targetC.append([note1, note2])
>>> sourceC.append([note1, note2])
>>> saC = alpha.analysis.aligner.StreamAligner(targetC, sourceC)
>>> saC.makeHashedStreams()
>>> saC.setupDistanceMatrix()
>>> saC.populateDistanceMatrix()
>>> saC.calculateChangesList()
>>> saC.changesCount[alpha.analysis.aligner.ChangeOps.NoChange]
2
>>> saC.similarityScore
1.0

test 4: 1 no change, 1 substitution

>>> targetD = stream.Stream()
>>> sourceD = stream.Stream()
>>> note3 = note.Note('C4')
>>> note3.quarterLength = 2  # same pitch and offset as note2
>>> targetD.append([note1, note2])
>>> sourceD.append([note1, note3])
>>> saD = alpha.analysis.aligner.StreamAligner(targetD, sourceD)
>>> saD.makeHashedStreams()
>>> saD.setupDistanceMatrix()
>>> saD.populateDistanceMatrix()
>>> saD.calculateChangesList()
>>> saD.changesCount[alpha.analysis.aligner.ChangeOps.Substitution]
1
>>> saD.changesCount[alpha.analysis.aligner.ChangeOps.NoChange]
1
>>> saD.similarityScore
0.5

r   r   z0Traceback of best alignment did not end properlyc              3  *   #    U  H	  oS    v   M     g7f)r   Nr   ).0elems     r   	<genexpr>5StreamAligner.calculateChangesList.<locals>.<genexpr>b  s     #EGs   N)r:   r;   rw   r<   	referencer=   r8   insertr   r*   r+   r,   r   r   r>   r-   rV   r9   )r%   rb   rc   bestOptargetStreamReferencesourceStreamReferenceopTuples          r   rL   "StreamAligner.calculateChangesList  sV   n FFFF1fQ ++A1F$($;$;AE$B$L$L!$($;$;AE$B$L$L!,VLGLL7+
 ,,,Q9---Q9111QQ QQ1 1fQ2 6a1f-.`aa##E#EE#001C1CDs4<<GXXr   c                   [        U R                  5       Hw  u  nu  p4nU[        R                  :X  a  M  UR                  UR
                  l        UR                  U5        UR                  UR
                  l        UR                  U5        My     [        R                  " 5       U R                  l        [        R                  " 5       U R                  l        S[        U R                  R                  5      -   U R                  R                  l        S[        U R                  R                  5      -   U R                  R                  l        U R                  R                  R                  U R                  R                  l        U R                  R                  R                  U R                  R                  l        U(       a5  U R                  R                  5         U R                  R                  5         gg)zv
Visual and debugging feature to display which notes are changed.
Will open in MuseScore, unless show is set to False
zTarget zSource N)rq   r8   r   r-   r'   styleaddLyricr   Metadatar3   r4   stridtitlemovementNameshow)r%   r   idxmidiNoteRef
omrNoteRefchanges         r   showChangesStreamAligner.showChangese  sp   
 9B$,,8O4S3;F+++*0,,!!'$$S))/
  &##C( 9P &.%6%6%8"%-%6%6%8"+4s4;L;L;O;O7P+P""(+4s4;L;L;O;O7P+P""(262C2C2L2L2R2R""/262C2C2L2L2R2R""/""$""$ r   )r8   r>   r5   r=   r<   r	   r;   r:   r7   r9   r4   r3   )NNNF)F)r   r   r   r   r)   r@   r6   rN   rI   rJ   rK   rl   rw   r]   r^   r`   r   r   rL   r   r   r   r   r   r0   r0   ?   se    !.,$:8x@LDP@d7rH'T-^-^S(j4l-^vYp%r   r0   c                  P    \ rS rSrS rS rS rS rS rS r	S r
S	 rS
 rS rSrg)Testi  c                R   SSK Jn  SSK Jn  UR                  5       nUR                  5       nUR	                  S5      nUR	                  S5      nUR                  U5        UR                  U5        [        X45      nUR                  5         U R                  UR                  S5        g)z9
two streams of the same note should have 1.0 similarity
r   streamnoteC4      ?N
music21r   r   StreamNoteappendr0   rN   assertEqualr9   r%   r   r   targetsourcenote1note2sas           r   testSimpleStreamOneNoteTest.testSimpleStreamOneNote  s{     	# 		$		$ee6*

++S1r   c                `   SSK Jn  SSK Jn  UR                  5       nUR                  5       nUR	                  S5      nUR	                  S5      nSUl        UR                  U5        UR                  U5        [        X45      nUR                  5         U R                  UR                  S5        g)	z?
two streams of two different notes should have 0.0 similarity
r   r   r   r   zC#4   g        N)r   r   r   r   r   quarterLengthr   r0   rN   r   r9   r   s           r    testSimpleStreamOneNoteDifferent%Test.testSimpleStreamOneNoteDifferent  s     	# 		$		% ee6*

++S1r   c                   SSK Jn  SSK Jn  UR                  5       nUR                  5       nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR                  XVXx/5        UR                  XVXx/5        [        X45      n	U	R                  5         U R                  U	R                  S5        g	)
zE
two streams of the same notes should have 1.0 percentage similarity
r   r   r   r   D4E4F4r   Nr   
r%   r   r   r   r   r   r   note3note4r   s
             r   testSameSimpleStreamTest.testSameSimpleStream  s     	# 		$		$		$		$uU23uU236*

++S1r   c                   SSK Jn  SSK Jn  UR                  5       nUR                  5       nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR                  XVU/5        UR                  XWU/5        [        X45      n	U	R                  5         U R                  U	R                  S5        g)	z>
two streams of the 2/3 same notes should have 2/3 similarity
r   r   r   r   zD#4zD-4gUUUUUU?Nr   r   s
             r   testSameSimpleStream2Test.testSameSimpleStream2  s     	# 		$		% 		% 		$uU+,uU+,6*

++U3r   c                   SSK Jn  SSK Jn  UR                  5       nUR                  5       nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR	                  S5      n	UR                  XVXx/5        UR                  XVXy/5        [        X45      n
U
R                  5         U R                  U
R                  S	5        g
)zO
two streams with just 1 note different should have 0.75 percentage similarity
r   r   r   r   r   r   r   G4      ?Nr   )r%   r   r   r   r   r   r   r   r   note5r   s              r   testSameOneOffStreamTest.testSameOneOffStream  s     	# 		$		$		$		$		$uU23uU236*

++T2r   c                   SSK Jn  SSK Jn  UR                  5       nUR                  5       nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR                  XVXx/5        UR                  XVU/5        [        X45      n	U	R                  5         U	R                  5         U R                  U	R                  S5        g	)
z^
two streams, both the same, but one has an extra note should
have 0.75 percentage similarity
r   r   r   r   r   r   r   r   N)r   r   r   r   r   r   r0   rN   r   r   r9   r   s
             r   testOneOffDeletionStreamTest.testOneOffDeletionStream  s    
 	# 		$		$		$		$uU23uU+,6*


++T2r   c                4   SSK Jn  SSK Jn  UR                  5       nUR                  5       nUR	                  / SQ5      nUR                  U5        UR                  U5        [        X45      nUR                  5         U R                  UR                  S5        g)z&
two streams, one with explicit chord
r   r   )chord)E3r   r   r   N)
r   r   r   r   Chordr   r0   rN   r   r9   )r%   r   r   r   r   cMajorr   s          r   testChordSimilarityStreamTest.testChordSimilarityStream  sp     	#!/0ff6*

++S1r   c                <   SSK Jn  SSK Jn  UR                  5       nUR                  5       nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR	                  S5      n	UR	                  S5      n
UR	                  S5      nUR                  XVX{/5        UR                  XU
/5        [        X45      nUR                  5         UR                  5         UR                  UR                  S   S   R                  5      nU R                  U5        [        UR                  U5      nU R                  UR                  R                   S5        U R                  UR"                  S5        UR                  UR                  S   S	   R                  5      nU R                  U5        [        UR                  U5      nU R                  UR                  R                   S5        U R                  UR"                  S5        g
)zy
Given two streams:

MIDI is `C C C B`
OMR is `C C C`

Therefore, there needs to be an insertion to get from OMR to MIDI
r   r   r   r   B3r   r!   3r   Nr   r   r   r   r   r   r0   rN   r   getElementByIdr8   r   assertIsNotNoner   r   r   r'   lyricr%   r   r   r   r   noteC1noteC2noteC3noteC4noteC5noteC6noteBr   n0n1s                  r   testShowInsertionTest.testShowInsertion  s    	# 444444		$vv56vv./6*


""2::a=#3#6#67R $))R 13'""2::a=#3#6#67R $))R 13'r   c                <   SSK Jn  SSK Jn  UR                  5       nUR                  5       nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR	                  S5      n	UR	                  S5      n
UR	                  S5      nUR                  XVU/5        UR                  XX/5        [        X45      nUR                  5         UR                  5         UR                  UR                  S   S   R                  5      nU R                  U5        [        UR                  U5      nU R                  UR                  R                   S5        U R                  UR"                  S5        UR                  UR                  S   S	   R                  5      nU R                  U5        [        UR                  U5      nU R                  UR                  R                   S5        U R                  UR"                  S5        g
)zy
Given two streams:

MIDI is `C C C`

OMR is `C C C B`

Therefore, there needs to be a deletion to get from OMR to MIDI.
r   r   r   r   r   r   r"   r   r   Nr   r   s                  r   testShowDeletionTest.testShowDeletionH  s    	# 444444		$vv./vv566*


""2::a=#3#6#67R $))R /3'""2::a=#3#6#67R $))R /3'r   c                   SSK Jn  SSK Jn  UR                  5       nUR                  5       nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR	                  S5      nUR	                  S5      n	UR	                  S5      n
UR                  XVU/5        UR                  XU
/5        [        X45      nUR                  5         UR                  5         UR                  UR                  S   S   R                  5      nU R                  U5        [        UR                  U5      nU R                  UR                  R                   S5        U R                  UR"                  S5        UR                  UR                  S   S	   R                  5      nU R                  U5        [        UR                  U5      nU R                  UR                  R                   S5        U R                  UR"                  S5        g
)zr
two streams:
MIDI is `C C C`
OMR is `C C B`

Therefore, there needs to be a substitution to get from OMR to MIDI
r   r   r   r   r   r   r#   2r   Nr   )r%   r   r   r   r   r   r   r   r   r   r   r   r   r   s                 r   testShowSubstitutionTest.testShowSubstitutions  s    	# 44444		$vv./vu-.6*


 ""2::a=#3#6#67R $))R 23'""2::a=#3#6#67R $))R 23'r   r   N)r   r   r   r   r   r   r   r   r   r   r   r   r   r  r   r   r   r   r   r     s6    2*2,2.4.30322$((T)(V'(r   r   __main__)
__future__r   collectionsr   enumrr   unittesttypingr   r   r   r   r   music21.alpha.analysisr	   Music21Exceptionr   r   IntEnumr   r0   TestCaser   r   mainTestr   r   r   <module>r     s    #          )	|44 		"2 	% %:% %DY(8 Y(x zT r   