
    rh|5                    b   % S r SSKJ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
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  \R"                  " S
5      rSSSSS.S jrS rSS jrS rSSS.S jrS rSS jrS r   S S jr    S!S jr/ rS\S'   \S:X  a  SSKr\R@                  " 5         gg)"az  
tools for segmenting -- that is, dividing up a score into small, possibly overlapping
sections -- for searching across pieces for similarity.

Speed notes:

   this module is definitely a case where running PyPy rather than cPython will
   give you a 3-5x speedup.

   If you really want to do lots of comparisons, the `scoreSimilarity` method will
   use python-Levenshtein if it is installed, unless forceDifflib is set to True.
   python-Levenshtein can be installed via **pip install python-Levenshtein**.
   The ratios are very slightly different, but the speedup is between 10 and 100x!
   (But then PyPy probably won't work.)
    )annotations)OrderedDictN)partial)common)	converter)corpus)environmentzsearch.segment      )segmentLengthsoverlap	algorithmjitterc               B   SSK Jn  Uc  UR                  nU R                  5       R                  R                  5       nU" USS9u  px[        U5      n	[        [        R                  " XU-
  -  5      5      n
[        U
5       Vs/ s H	  oX-
  -  PM     nn/ n/ nU Hz  nU[        R                  " SU-  U5      -  n[        SU5      n[        XS-
  5      n[        X-   U	5      nXU nX   UUS-
     4nUR                  U5        UR                  U5        M|     X4$ s  snf )ap  
Translates a monophonic part with measures to a set of segments of length
`segmentLengths` (measured in number of notes) with an overlap of `overlap` notes
using a conversion algorithm of `algorithm` (default: search.translateStreamToStringNoRhythm).
Returns two lists, a list of segments, and a list of tuples of measure start and end
numbers that match the segments.

If algorithm is None then a default algorithm of music21.search.translateStreamToStringNoRhythm
is used

>>> luca = corpus.parse('luca/gloria')
>>> lucaCantus = luca.parts[0]
>>> segments, measureLists = search.segment.translateMonophonicPartToSegments(lucaCantus)
>>> segments[0:2]
['HJHEAAEHHCE@JHGECA@A>@A><A@AAE', '@A>@A><A@AAEEECGHJHGH@CAE@FECA']


Segment zero begins at measure 1 and ends in m. 12.  Segment 1 spans m.7 - m.18:

>>> measureLists[0:2]
[(1, 12), (7, 18)]

>>> segments, measureLists = search.segment.translateMonophonicPartToSegments(
...     lucaCantus,
...     algorithm=search.translateDiatonicStreamToString)
>>> segments[0:2]
['CRJOMTHCQNALRQPAGFEFDLFDCFEMOO', 'EFDLFDCFEMOOONPJDCBJSNTHLBOGFE']

>>> measureLists[0:2]
[(1, 12), (7, 18)]

r   )searchT)returnMeasures   )music21r   translateStreamToStringNoRhythmrecursenotesstreamlenintmathceilrangerandomrandintmaxminappend)inputStreamr   r   r   r   r   nStream	outputStrmeasurestotalLengthnumberOfSegmentsisegmentStartssegmentListmeasureListsegmentStart
segmentEndcurrentSegmentmeasureTuples                      P/home/james-whalen/.local/lib/python3.13/site-packages/music21/search/segment.py!translateMonophonicPartToSegmentsr3   .   s1   P ::	!!#))002G#GDAIi.K499[W4L%MNO=BCS=TU=T.23=TMU KK%rF{F;;1l+<q96D
"
; .a0HI>*<( & %%# Vs   Dc                x    U R                   n/ nU H%  n[        U40 UD6u  pVUR                  UUS.5        M'     U$ )aQ  
Creates segment and measure lists for each part of a score
Returns list of dictionaries of segment and measure lists

>>> bach = corpus.parse('bwv66.6')
>>> scoreList = search.segment.indexScoreParts(bach)
>>> scoreList[1]['segmentList'][0]
'@B@@@@ED@DBDA=BB@?==B@@EBBDBBA'
>>> scoreList[1]['measureList'][0:3]
[(0, 7), (4, 9), (8, 9)]
)r,   r-   )partsr3   r#   )	scoreFilekeywordsscoreFilePartsindexedListpartr,   r-   s          r2   indexScorePartsr;   t   sU     __NK#D$$ &&
 	      Fc                   [        U [        R                  5      (       d  [        R                  " U 5      n U R                  n [	        U 40 UD6nX4U 4$ ! [
         a&  nU(       d  [        SU  SU 35        Sn SnAN-UeSnAff = f)z-
Index one path in the context of multicore.
zFailed on parse/index for, z:  N)
isinstancepathlibPathnameindexOnePath	Exceptionprint)filePathfailFastr7   shortFpindexOutputes         r2   _indexSingleMulticorerK      s     h--<<)mmG"88x8 (++  /zA3?@KGs   A 
BA><A>>Bc           	     4    [        SUS    SU  SU S35        g )NzIndexed r    (/))rE   )numRuntotalRunlatestOutputs      r2   _giveUpdatesMulticorerS      s$    	H\!_%Rxq
!
<=r<   T)giveUpdatesrunMulticorec                  USL a  [         nOSn[        [        40 UD6n[        [	        U 5      5       H@  n[        X   [        R                  5      (       a  M&  [        R                  " X   5      X'   MB     U(       a  [        R                  " U UUS9nO[        R                  " U UUS9n0 nU H  u  pnX4X'   M     / nU  H  nUR                  X   5        M     [        U5      nU$ )au  
Returns a dictionary of the lists from indexScoreParts for each score in
scoreFilePaths

>>> #_DOCS_SHOW searchResults = corpus.search('bwv190')
>>> searchResults = corpus.corpora.CoreCorpus().search('bwv190') #_DOCS_HIDE
>>> fpsNamesOnly = sorted([searchResult.sourcePath for searchResult in searchResults])
>>> len(fpsNamesOnly)
2

>>> scoreDict = search.segment.indexScoreFilePaths(fpsNamesOnly)
>>> len(scoreDict['bwv190.7.mxl'])
4

>>> scoreDict['bwv190.7.mxl'][0]['measureList']
[(0, 9), (6, 15), (11, 20), (17, 25), (22, 31), (27, 32)]

>>> scoreDict['bwv190.7.mxl'][0]['segmentList'][0]
'NNJLNOLLLJJIJLLLLNJJJIJLLJNNJL'
TN)updateFunction)rS   r   rK   r   r   r?   r@   rA   r   runParallelrunNonParallelr#   r   )scoreFilePathsrT   rU   r7   rW   	indexFuncr*   rpListUnOrderedrpDictoutShortNameoutDataoriginalPathlibrpListp	scoreDicts                  r2   indexScoreFilePathsrd      s    4 d.-::I3~&'.+W\\:: '^-> ?N (  ,,)+
 !//)+ F2A.#/"9 3B Ffi   F#Ir<   c                   [        U [        R                  5      (       d  [        R                  " U 5      n U R                  5       (       d  [        R
                  " U 5      nO[        R
                  " U 5      n[        U40 UD6nU$ )z0
Index a single path.  Returns a scoreDictEntry
)r?   r@   rA   is_absoluter   parser   r;   )rF   r7   scoreObjscoreDictEntrys       r2   rC   rC      sc     h--<<)!!<<)??8,$X::Nr<   c                   Uc  [         R                  S5      nO1[        U[        [        45      (       a  [
        R                  " U5      nUR                  S5       n[        R                  " X5        SSS5        U$ ! , (       d  f       U$ = f)z
Save the score dict from indexScoreFilePaths as a .json file for quickly
reloading

Returns the filepath (assumes you'll probably be using a temporary file)
as a pathlib.Path()
Nz.jsonwb)
environLocalgetTempFiler?   strbytesr@   rA   openjsondump)rc   rF   fs      r2   saveScoreDictrt      so     ++G4	HsEl	+	+<<)	t			) 
 O 
	 Os   A==
Bc                    [        U [        R                  5      (       d  [        R                  " U 5      n U R                  S5       n[        R
                  " U5      nSSS5        U$ ! , (       d  f       W$ = f)z)
Load the scoreDictionary from filePath.
rbN)r?   r@   rA   rp   rq   load)rF   rs   rc   s      r2   loadScoreDictrx     sX     h--<<)	t	IIaL	 
 
	s   A((
A7c                    USL a  [         R                  " USU 5      nU$  SSKJn  UR                  USU 5      nU$ ! [         a    [         R                  " USU 5      n U$ f = f)z
Returns either a difflib.SequenceMatcher or pyLevenshtein
StringMatcher.StringMatcher object depending on what is installed.

If forceDifflib is True then use difflib even if pyLevenshtein is installed:
Tr>   r   )StringMatcher)difflibSequenceMatcherLevenshteinrz   ImportError)seq2junkforceDifflibsmObjectpyLevenshteins        r2   getDifflibOrPyLevr     st     t**4T: O	?B$224TBH O  	?..tR>HO	?s   < "A"!A"c                  ^ ^^^^	^
^^^^^^ / mSm[        T 5      m[        T R                  5       5      m
Sm	SmUUUU	U U
UUUUUU4S jn[        T5       H  nT
U   mT T   nTS-  mUSL a  [	        ST ST ST S	35        [        [        U5      5       HB  m	[        UT	   S
   5       H*  u  mn[        U5      T:  a  M  UT	   S   T   mU" U5        M,     MD     M     T$ )a  
Find the level of similarity between each pair of segments in a scoreDict.

This takes twice as long as it should because it does not cache the
pairwise similarity.

>>> filePaths = []
>>> for p in ('bwv197.5.mxl', 'bwv190.7.mxl', 'bwv197.10.mxl'):
...     #_DOCS_SHOW source = corpus.search(p)[0].sourcePath
...     source = corpus.corpora.CoreCorpus().search(p)[0].sourcePath #_DOCS_HIDE
...     filePaths.append(source)
>>> scoreDict = search.segment.indexScoreFilePaths(filePaths)
>>> scoreSim = search.segment.scoreSimilarity(scoreDict, forceDifflib=True) #_DOCS_HIDE
>>> #_DOCS_SHOW scoreSim = search.segment.scoreSimilarity(scoreDict)
>>> len(scoreSim)
496

Returns a list of tuples of first score name, first score voice number, first score
measure number, second score name, second score voice number, second score
measure number, and similarity score (0 to 1).

>>> for result in scoreSim[133:137]:
...     result
('bwv197.5.mxl', 1, 1, (4, 10), 'bwv190.7.mxl', 3, 4, (22, 30), 0.13...)
('bwv197.5.mxl', 1, 1, (4, 10), 'bwv197.10.mxl', 0, 0, (0, 8), 0.2)
('bwv197.5.mxl', 1, 1, (4, 10), 'bwv197.10.mxl', 1, 0, (0, 7), 0.266...)
('bwv197.5.mxl', 1, 1, (4, 10), 'bwv197.10.mxl', 1, 1, (4, 9), 0.307...)
r   Nc                  > [        U TS9n[        TT5       H  nTU   nTU   n[        [        U5      5       H  n[        XE   S   5       H  u  pg[        U5      T:  a  M  UR	                  U5        UR                  5       nXE   S   U   n	TTTTUUUU	U4	n
TR                  U
5        T(       d  Mf  UUUU	TTTTU4	nTR                  U5        M     M     M     g )N)r   r,   r-   )r   r   r   	enumerateset_seq1ratior#   )thisSegmentdlthatScoreNumberthatScoreKey	thatScorepNum2thatSegmentNumberthatSegmentr   thatMeasureNumbersimilarityTuplesimilarityTupleReversedr   includeReverseminimumLengthpNumrc   scoreDictKeys
scoreIndexsegmentNumbersimilarityScoresthisMeasureNumberthisScoreKeytotalScoress               r2   doOneSegment%scoreSimilarity.<locals>.doOneSegmentT  s   {F$Z=O(9L!,/Is9~.6?!(7792%;'-7 KK,HHJE(1(8(GHY(Z%$%)$))
'O %++O<) $))$%)
/+ %++,CDA79 /  >r<   r   Tz
Comparing rM   rN   rO   r,   r-   )r   listkeysr   rE   r   )rc   r   rT   r   r   r   thisScoreNumber	thisScorethisSegmentOuterr   r   r   r   r   r   r   r   s   `` ``    @@@@@@@@r2   scoreSimilarityr   )  s   H Ji.K)*MDM'E 'E 'ER !-$_5l+	a
$J|nBzl!K=JK#i.)D3<Yt_]=[3\//'(=8$-dOM$B=$Q!-.	 4] * . r<   z
list[type]
_DOC_ORDER__main__)F)N)NNF)   FFF)!__doc__
__future__r   collectionsr   r{   	functoolsr   rq   r   r@   r   r   r   r   r   r	   Environmentrl   r3   r;   rK   rS   rd   rC   rt   rx   r   r   r   __annotations____name__mainTest r<   r2   <module>r      s     # #          &&'78 B&L0,(> %*%);| &	 
	2 cP 
J  z r<   