
    rh7W                        S SK Jr  S SKrS SKJ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
5      r " S S\R                  5      r\S:X  a  S SKr\R                   " \5        gg)    )annotationsN)time)environment)scale)search)streamzaudioSearch.scoreFollowerc                  T    \ rS rSrSS jr    SS jrS rS rS rS r	S	 r
S
 rSrg)ScoreFollower   Nc                   Xl         Ub.  UR                  5       R                  R                  5       U l        OS U l        [        [        R                  5       S-  5      U l        SU l	        SU l
        SU l        SU l	        SU l        SU l        SU l        SU l        S U l        S U l        S U l        SU l        SU l        SU l        SU l        S U l        S U l        S U l        S U l        S U l        S U l        g )NzscoreFollowerTemp.wavr   F   T)scoreStreamflattennotesAndRestsr   scoreNotesOnlystrenvironLocalgetRootTempDirwaveFilelastNotePositioncurrentSample	totalFilestartSearchAtSlotpredictedNotePosition	countdownEND_OF_SCOREqlefirstNotePagelastNotePage	firstSlotsilencePeriodCounternotesCounterbeginsuseScalesilencePeriodresultuseMicprocessing_timeseconds_recording)selfr   s     [/home/james-whalen/.local/lib/python3.13/site-packages/music21/audioSearch/scoreFollower.py__init__ScoreFollower.__init__   s    &""-"5"5"7"E"E"L"L"ND"&DL779<SST ! !!"%&"!! $%!!#!%    c                    Uc  [         R                  " S5      nX0l        X l        X@l        SU l        U R
                  SL a&  U R                  5       U l        U R
                  SL a  M&  [        R                  S5        g)z
The main program. It runs the 'repeatTranscription' until the
performance ends.

If `useScale` is none, then it uses a scale.ChromaticScale
NC4Fz* END)	r   ChromaticScaler)   r'   r$   r&   repeatTranscriptionr   
printDebug)r*   plotr'   secondsr$   s        r+   runScoreFollowerScoreFollower.runScoreFollower:   sk     ++D1H!( kkU"224DK kkU" 	(r.   c                   SSK Jn  [        R                  S5        U R                  SL a  UR                  U R                  SS9nOnUR                  nU" U R                  U R                  U R                  S9u  o l        U l	        U R                  S:X  a  U R                  R                  5       U l
        [        R                  S5        [        5       nUR                  X R                  5      nUR                  U5      nUR!                  XPR                  5      u  pgUR#                  U5      u  pU R%                  U5        [        R                  S	5        U R&                  U R(                  U R(                  [+        U5      -    n
[,        R.                  " U
5      nUR1                  UU	UU R2                  S
9u  ol        U R5                  U R&                  UU R6                  U R(                  5      u  ol        p[        5       U-
  U l        [        R                  S5        USL a  SnU$ U R;                  XU5      nU R                  SL a;  UR                  nU" U R                  U R8                  U R                  S9u  nnU l	        U R(                  [+        U R<                  5      :  a  SnO+U R                  SL a  U R                  U R                  :  a  Sn[        R                  SU S35        U$ )aw  
First, it records from the microphone (or from a file if is used for
test). Later, it processes the signal to detect the pitches.
It converts them into music21 objects and compares them with the score.
It finds the best matching position of the recorded signal with the
score, and decides, depending on matching accuracy, the last note
predicted and some other parameters, in which position the recorded
signal is.

It returns a value that is False if the song has not finished, or true
if there has been a problem like some consecutive bad matches or the
score has finished.

>>> from music21.audioSearch import scoreFollower
>>> scoreNotes = ' '.join(['c4', 'd', 'e', 'f', 'g', 'a', 'b', "c'", 'c', 'e',
...     'g', "c'", 'a', 'f', 'd', 'c#', 'd#', 'f#', 'c', 'e', 'g', "c'",
...     'a', 'f', 'd', 'c#', 'd#', 'f#'])
>>> scNotes = converter.parse('tinynotation: 4/4 ' + scoreNotes, makeNotation=False)
>>> ScF = scoreFollower.ScoreFollower(scoreStream=scNotes)
>>> ScF.useMic = False
>>> import os #_DOCS_HIDE
>>> ScF.waveFile = str(common.getSourceFilePath() #_DOCS_HIDE
...                 / 'audioSearch' / 'test_audio.wav') #_DOCS_HIDE
>>> #_DOCS_SHOW ScF.waveFile = 'test_audio.wav'
>>> ScF.seconds_recording = 10
>>> ScF.useScale = scale.ChromaticScale('C4')
>>> ScF.currentSample = 0
>>> exitType = ScF.repeatTranscription()
>>> print(exitType)
False
>>> print(ScF.lastNotePosition)
10

r   audioSearchzrepeat transcription startingTN)lengthstoreWaveFilename)r;   startSamplezgot Frequencies from Microphonezmade it to here.)scNotesr   zand even to here.
endOfScoreFfinishedPerformingwaveFileEOFzabout to return -- exitType:  )music21r:   r   r3   r'   getFrequenciesFromMicrophoner)   "getFrequenciesFromPartialAudioFiler   r   r   
getnframesr   detectPitchFrequenciesr$   smoothFrequenciespitchFrequenciesToObjectsjoinConsecutiveIdenticalPitchessilencePeriodDetectionr   r   lenr   PartnotesAndDurationsToStreamr   matchingNotesr   r(   updatePositionr   )r*   r:   freqFromAQListgetFreqFunc
time_startdetectedPitchesFreqdetectedPitchObjectsunused_plot	notesListdurationListexcerptr>   transcribedScoretotalLengthPeriodprobr   exitTypejunks                     r+   r2   !ScoreFollower.repeatTranscription[   s   F 	( 	 ?@;;$(EE--"& F N
 &HHK@K-- ..A=NM4+=
 ~~"!%!9!9!; ABV
)@@Q^Q^_);;<OP,7,Q,Q-0)"-"M"M #"	##I. 23""4#8#89N9NQTU^Q_9_`++g&%0%J%J	 &K &
"( HLGYGY""!!	H
D0$  $v
2 344#HO &&t
K;;%%HHK7B++ ..84ND$"4   3t':':#;;+H[[E!d&8&8DNN&J$H"?z KLr.   c                    SnU H  nUR                   S:w  d  M  SnM     USL a$  SU l        SU l        U =R                  S-  sl        gSU l        U =R                  S-  sl        SU l        g)a&  
Detection of consecutive periods of silence.
Useful if the musician has some consecutive measures of silence.

>>> from music21.audioSearch import scoreFollower
>>> scNotes = corpus.parse('luca/gloria').parts[0].flatten().notes.stream()
>>> ScF = scoreFollower.ScoreFollower(scoreStream=scNotes)
>>> notesList = []
>>> notesList.append(note.Rest())
>>> ScF.notesCounter = 3
>>> ScF.silencePeriodCounter = 0
>>> ScF.silencePeriodDetection(notesList)
>>> ScF.notesCounter
0
>>> ScF.silencePeriodCounter
1


>>> ScF = scoreFollower.ScoreFollower(scoreStream=scNotes)
>>> notesList = []
>>> notesList.append(note.Rest())
>>> notesList.append(note.Note())
>>> ScF.notesCounter = 1
>>> ScF.silencePeriodCounter = 3
>>> ScF.silencePeriodDetection(notesList)
>>> ScF.notesCounter
2
>>> ScF.silencePeriodCounter
0
TrestFr   r   N)namer%   r"   r!   )r*   rW   	onlyRestsis       r+   rK   $ScoreFollower.silencePeriodDetection   sp    > 	Avv!	  !%D !D%%*%!&D"()D%r.   c                `   SnU R                   (       GdV  U R                  S:X  a6  U R                  U l        [	        5       U-
  nU R                  X%5      U l        U$ U R                  S:X  a5  S[	        5       U-
  -  U R                  -   nU R                  X&5      U l        U$ U R                  S:X  a5  S[	        5       U-
  -  U R                  -   nU R                  X&5      U l        U$ U R                  S:X  a5  U R                  U l        U R                  U l        U R                  U l        U$ U R                  S:X  a  SU l        SU l        SU l        U$ [        R                  S5        Sn U$ US	:  a$  SU l        SU l        [        R                  S
5        OSU l         U R                  S:  a  SnU$ )a  
It updates the position in which the scoreFollower starts to search at,
and the predicted position in which the new fragment of the score
should start.  It updates these positions taking into account the value
of the "countdown", and if is the beginning of the song or not.

It returns the exitType, which determines whether the scoreFollower has
to stop (and why) or not.

See example of a bad prediction at the beginning of the song:

>>> from time import time
>>> from music21.audioSearch import scoreFollower
>>> scNotes = corpus.parse('luca/gloria').parts[0].flatten().notes.stream()
>>> ScF = scoreFollower.ScoreFollower(scoreStream=scNotes)
>>> ScF.begins = True
>>> ScF.startSearchAtSlot = 15
>>> ScF.countdown = 0
>>> prob = 0.5  # bad prediction
>>> totalLengthPeriod = 15
>>> time_start = time()
>>> exitType = ScF.updatePosition(prob, totalLengthPeriod, time_start)
>>> print(ScF.startSearchAtSlot)
0

Different examples for different countdowns:

Countdown = 0:

The last matching was good, so it calculates the position in which it
starts to search at, and the position in which the music should start.

>>> ScF = scoreFollower.ScoreFollower(scoreStream=scNotes)
>>> ScF.scoreNotesOnly = scNotes.flatten().notesAndRests
>>> ScF.begins = False
>>> ScF.countdown = 0
>>> ScF.startSearchAtSlot = 15
>>> ScF.lastNotePosition = 38
>>> ScF.predictedNotePosition = 19
>>> ScF.seconds_recording = 10
>>> prob = 0.8
>>> totalLengthPeriod = 25
>>> time_start = time()
>>> exitType = ScF.updatePosition(prob, totalLengthPeriod, time_start)
>>> print(ScF.startSearchAtSlot)
38

>>> ScF.predictedNotePosition >=38
True

Countdown = 1:

Now it doesn't change the slot in which it starts to search at.
It also predicts the position in which the music should start.

>>> ScF = scoreFollower.ScoreFollower(scoreStream=scNotes)
>>> ScF.begins = False
>>> ScF.countdown = 1
>>> ScF.startSearchAtSlot = 15
>>> ScF.lastNotePosition = 15
>>> ScF.predictedNotePosition = 19
>>> ScF.seconds_recording = 10
>>> prob = 0.8
>>> totalLengthPeriod = 25
>>> time_start = time()
>>> exitType = ScF.updatePosition(prob, totalLengthPeriod, time_start)
>>> print(ScF.startSearchAtSlot)
15

>>> ScF.predictedNotePosition > 15
True

Countdown = 2:

Now it starts searching at the beginning of the page of the screen.
The note prediction is also the beginning of the page.

>>> ScF = scoreFollower.ScoreFollower(scoreStream=scNotes)
>>> ScF.begins = False
>>> ScF.countdown = 2
>>> ScF.startSearchAtSlot = 15
>>> ScF.lastNotePosition = 15
>>> ScF.predictedNotePosition = 19
>>> ScF.seconds_recording = 10
>>> prob = 0.8
>>> totalLengthPeriod = 25
>>> time_start = time()
>>> exitType = ScF.updatePosition(prob, totalLengthPeriod, time_start)
>>> print(ScF.startSearchAtSlot)
15

>>> print(ScF.predictedNotePosition)
39

Countdown = 4:

Now it starts searching at the beginning of the page of the screen.
The note prediction is also the beginning of the page.

>>> ScF = scoreFollower.ScoreFollower(scoreStream=scNotes)
>>> ScF.begins = False
>>> ScF.countdown = 4
>>> ScF.startSearchAtSlot = 15
>>> ScF.lastNotePosition = 15
>>> ScF.predictedNotePosition = 19
>>> ScF.seconds_recording = 10
>>> prob = 0.8
>>> totalLengthPeriod = 25
>>> time_start = time()
>>> exitType = ScF.updatePosition(prob, totalLengthPeriod, time_start)
>>> print(ScF.startSearchAtSlot)
0

>>> print(ScF.predictedNotePosition)
0

Countdown = 5:

Now it stops the program.

>>> ScF = scoreFollower.ScoreFollower(scoreStream=scNotes)
>>> ScF.begins = False
>>> ScF.countdown = 5
>>> ScF.startSearchAtSlot = 15
>>> ScF.lastNotePosition = 15
>>> ScF.predictedNotePosition = 19
>>> ScF.seconds_recording = 10
>>> prob = 0.8
>>> totalLengthPeriod = 25
>>> time_start = time()
>>> exitType = ScF.updatePosition(prob, totalLengthPeriod, time_start)
>>> print(exitType)
countdownExceeded

Fr   r            zCOUNTDOWN = 5countdownExceededgffffff?z!Silence or noise at the beginning   5consecutiveCountdownsBeginning)r#   r   r   r   r   predictNextNotePositionr   r)   r    r   r3   )r*   r\   r[   rS   r]   r(   totalSecondss          r+   rP   ScoreFollower.updatePosition   s   P {{{~~")-)>)>&"&&:"5-1-I-I%.8*J G 1$ DFZ$784;Q;QQ-1-I-I%.5*@ = 1$ DFZ$784;Q;QQ-1-I-I%.5*6 3 1$(,%)-&-1^^** ) 1$ )*%)*&-.*  ''8.  cz()%)*&''(KL#~~"<r.   c                    g)zk
Returns the index of the first element on the screen right now.

Doesn't work. (maybe it's not necessary)
r    )r*   s    r+   getFirstSlotOnScreen"ScoreFollower.getFirstSlotOnScreen  s     r.   c                    X-  U R                   -  nSnSnXC:  a4  X@R                  U R                  U-      R                  -   nUS-   nXC:  a  M4  [	        XPR                  -   5      nU$ )a  
It predicts the position in which the first note of the following
recording note should start, taking into account the processing time of
the computer.  It has two inputs: totalLengthPeriod, that is the number
of pulses or beats in the recorded audio, and totalSeconds, that is the
length in seconds of the processing time.

It returns a number with the position of the predicted note in the
score.

>>> from time import time
>>> from music21.audioSearch import scoreFollower
>>> scNotes = corpus.parse('luca/gloria').parts[0].flatten().notes.stream()
>>> ScF = scoreFollower.ScoreFollower(scoreStream=scNotes)
>>> ScF.scoreNotesOnly = ScF.scoreStream.flatten().notesAndRests
>>> ScF.lastNotePosition = 14
>>> ScF.seconds_recording = 10.0
>>> totalLengthPeriod = 8
>>> totalSeconds = 2.0
>>> predictedStartPosition = ScF.predictNextNotePosition(
...     totalLengthPeriod, totalSeconds)
>>> print(predictedStartPosition)
18

r   r   )r)   r   r   quarterLengthint)r*   r[   rn   extraLengthmiddleRhythmslotspredictedStartingNotePositions          r+   rm   %ScoreFollower.predictNextNotePosition  s|    4 (69O9OO('*=*=%%-+//<}=LAIE ( ),E4I4I,I(J%,,r.   c           	     Z   SSK Jn  [        [        UR	                  5       R
                  5      5      n/ n/ n/ n	Sn
[        [        R                  " US-  5      5      n[        [        R                  " US-  5      5      nUS:X  a  SnOD[        [        R                  " [        U5      U-  5      [        R                  " X-  5      -
  5      n[        U5       Hu  nXU-  S-   X-  U-   S-    n[        R                  " U5      n[        U5      nUR                  X-  S-   5        U	R                  U5        UUl        UR                  U5        Mw     [        R                   " UR	                  5       R
                  R                  5       U5      nU[        U5      U-
  U-
  S-
  :  a+  [        U5      U-
  U-
  S-
  nSn
["        R%                  S5        UR'                  UUUUU R(                  U R*                  U R,                  5      u  nU l        Sn[        UU   R                  5      nU R.                  SL a:  U R0                  S	:  a*  ["        R%                  S
5        U =R(                  S-  sl        U R(                  S:w  a  SnOUU   R2                  n[        R4                  " UR	                  5       R
                  R                  5       U5      n[        R6                  " UR	                  5       R
                  R                  5       U5      n[        R8                  " UR	                  5       R
                  R                  5       U5      n[        [        UU   5      5       H  nUUU   U   R:                  -   nM     U R(                  S:X  a  U R0                  S:X  a  UU   U	U   -   nUUUU
4$ )Nr   r9   Fg?ri   r   TzLAST PART OF THE SCORErk   zAll rest period)rC   r:   rv   rL   r   r   mathceilfloorranger   rM   r   appendidr   approximateNoteSearchWeightedr   r3   decisionProcessr   r   r   r%   r!   matchProbabilityapproximateNoteSearchapproximateNoteSearchNoRhythmapproximateNoteSearchOnlyRhythmru   )r*   r   rZ   notePredictionr   r:   tn_recording	totScoresbeginningData
lengthDatar   	tn_windowhop
iterationsrd   rY   r>   rb   listOfPartspositiontotalLengthnumberprobabilityHitunused_listOfParts2unused_listOfParts3unused_listOfParts4s                             r+   rO   ScoreFollower.matchingNotes  sX    	( 3/779GGHI	
		,"456	$))IM*+!8Jdjj[)9C)?@#yy9: ;J z"A!c'A+ag.Dq.HIGkk'*Gq6D  1-l+GJW% # ::$$&44;;=yJ C,|;cAAEE -<sBQFNL##$<= $/#>#>NN$
 $. [*--.%$*C*Ca*G ##$56NNaN>>QN(2CCN$::$$&44;;=yJ$BB$$&44;;=yJ$DD$$&44;;=yJ s9V,-.A%	&(9!(<(J(JJK / >>Q4#<#<#A,V4z&7II,nlJJr.   )r   r#   r   r   r   r    r   r   r"   r   r(   r   r&   r   r   r)   r%   r!   r   r   r'   r$   r   )N)FFg      .@N)__name__
__module____qualname____firstlineno__r,   r6   r2   rK   rP   rr   rm   rO   __static_attributes__rq   r.   r+   r
   r
      sB    &@ )Bsj+*Ztl#-JVKr.   r
   c                      \ rS rSrS rSrg)TestExternali<  c                    SSK Jn  UR                  S5      R                  S   R	                  5       R
                  n[        US9nUR                  SSSS9  g )	Nr   )corpuszluca/gloria)r   FTg      $@)r4   r'   r5   )rC   r   parsepartsr   r   r
   r6   )r*   r   r>   ScFs       r+   xtestRunScoreFollower"TestExternal.xtestRunScoreFollower>  sL    ",,}-33A6>>@NN0%dCr.   rq   N)r   r   r   r   r   r   rq   r.   r+   r   r   <  s    Dr.   r   __main__)
__future__r   r}   r   unittestrC   r   r   r   r   Environmentr   r
   TestCaser   r   mainTestrq   r.   r+   <module>r      ss    #       &&'BC[K [KBD8$$ D z\" r.   