
    rh                    L   % S r SSKJr  / SQrSSKrSSK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  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KJr  SSKJr  SSKJr  \R2                  " S5      rSrSrS rS rS.S jrS r S/S jr!S.S jr"S0S jr#S1S jr$S2S jr%S.S jr&SSS .   S3S! jjr'S" r(S# r)S4S$ jr*   S5S% jr+  S/S& jr, " S' S(\RZ                  5      r. " S) S*\	R^                  5      r0/ r1S+\2S,'   \3S-:X  a  SSKr\Rh                  " \05        gg)6z
Base routines used throughout audioSearching and score-following.

Requires numpy and matplotlib.  Installing scipy makes the process faster
and more accurate using FFT convolve.
    )annotations)transcriber	recordingscoreFollower	histogramautocorrelationFunctionprepareThresholdsinterpolationnormalizeInputFrequencypitchFrequenciesToObjectsgetFrequenciesFromMicrophonegetFrequenciesFromAudioFile"getFrequenciesFromPartialAudioFiledetectPitchFrequenciessmoothFrequenciesjoinConsecutiveIdenticalPitchesquantizeDurationquarterLengthEstimationnotesAndDurationsToStreamdecisionProcessAudioSearchExceptionN)cast)base)environment)exceptions21)features)metadata)note)pitch)scale)stream)r   )r   audioSearchi   iD  c                ~   [        U 5      n[        U 5      nX#-
  U-  n/ n[        [        U5      5       H  nUR	                  S5        M     U  H/  nSnXcXt-  -   :  a  US-  nXcXt-  -   :  a  M  XWS-
  ==   S-  ss'   M1     U/nSn[        [        U5      5       H  nUR	                  X7U-  -   5        US-  nM      XX4$ )a  
Partition the list in `data` into a number of bins defined by `bins`
and return the number of elements in each bin and a set of `bins` + 1
elements where the first element (0) is the start of the first bin,
the last element (-1) is the end of the last bin, and every remaining element (i)
is the dividing point between one bin and another.

>>> data = [1, 1, 4, 5, 6, 0, 8, 8, 8, 8, 8]
>>> outputData, bins = audioSearch.histogram(data, 8)
>>> print(outputData)
[3, 0, 0, 1, 1, 1, 0, 5]
>>> bins
[0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
>>> print([int(b) for b in bins])
[0, 1, 2, 3, 4, 5, 6, 7, 8]

>>> outputData, bins = audioSearch.histogram(data, 4)
>>> print(outputData)
[3, 1, 2, 5]
>>> print([int(b) for b in bins])
[0, 2, 4, 6, 8]
r      )maxminrangeintappend)	databinsmaxValueminValuelengthEachBin	containericount
binsLimitss	            V/home/james-whalen/.local/lib/python3.13/site-packages/music21/audioSearch/__init__.pyr   r   E   s    0 4yH4yH(D0MI3t9 U222QJE U222!)!	  JE3t9(]%::;
        c                   S[         R                  =n;   a  [        SSU 3-   5      eSSKnSn [        R
                  " 5          [        R                  " S[        5        [        R                  " S[        5        SSK	J
n  SSS5        UR                  U 5      n U" X SSS	2   S
S9n[        U5      S-  nXVS nUR!                  U5      nUR#                  US:  5      S   n[        U5      S:X  a  Sn	U	$ US   n
UR%                  XZS 5      U
-   n['        X[5      nX-  n	U	$ ! , (       d  f       N= f! [         a%    [        R                  " S5        UR                  n Nf = f)a  
Converts the temporal domain into a frequency domain. To do that, it
uses the autocorrelation function, which finds periodicities in the signal
in the temporal domain and, consequently, finds the frequency in each instant
of time.

>>> import wave
>>> import numpy  # you need to have numpy, scipy, and matplotlib installed to use this

>>> wv = wave.open(str(common.getSourceFilePath() /
...                     'audioSearch' / 'test_audio.wav'), 'r')
>>> data = wv.readframes(1024)
>>> samples = numpy.frombuffer(data, dtype=numpy.int16)
>>> finalResult = audioSearch.autocorrelationFunction(samples, 44100)
>>> wv.close()
>>> print(finalResult)
143.6276...
numpyz+Cannot run autocorrelationFunction without z.numpy installed (scipy recommended).  Missing r   Nignore)fftconvolvez0Running convolve without scipy -- will be slowerfull)mode   
   )r   _missingImportr   r6   warningscatch_warningssimplefilterImportWarningDeprecationWarningscipy.signalr8   ImportErrorwarnconvolvearraylendiffwhereargmaxr
   )recordedSignalrecordSampleRateInbmir6   rG   correlationlengthCorrelation
differencepositiveDifferencesfinalResult	beginningpeakvertexs                r3   r   r   r   si   * $---3."9>seDEF 	F H	"$$&!!(M:!!(,>?< ' [[0N>$B$+?fMKK(A-01KK(J++j1n5a8
1$ 	 (*	||K
34y@{1(1/ '&  "HI>>"s/   D1 =D D1  
D.*D1 .D1 1,E E c                   U c  [         R                  " S5      n U R                  n/ nU HK  n[        R                  " UR
                  5      nUR                  [        R                  " U5      S   5        MM     US==   S-  ss'   / n[        [        U5      S-
  5       H!  nUR                  X&   X&S-      -   S-  5        M#     XQ4$ )a  
returns two elements.  The first is a list of threshold values
for one octave of a given scale, `useScale`,
(including the octave repetition) (Default is a ChromaticScale).
The second is the pitches of the scale.

A threshold value is the fractional part of the log-base-2 value of the
frequency.

For instance if A = 440 and B-flat = 460, then the threshold between
A and B-flat will be 450.  Notes below 450 should be considered As and those
above 450 should be considered B-flats.

Thus, the list returned has one less element than the number of notes in the
scale + octave repetition.  If useScale is a ChromaticScale, `prepareThresholds`
will return a 12-element list.  If it's a diatonic scale, it'll have 7 elements.


>>> pitchThresholds, pitches = audioSearch.prepareThresholds(scale.MajorScale('A3'))
>>> for i in range(len(pitchThresholds)):
...    print(f'{pitches[i]} < {pitchThresholds[i]:.2f} < {pitches[i + 1]}')
A3 < 0.86 < B3
B3 < 0.53 < C#4
C#4 < 0.16 < D4
D4 < 0.28 < E4
E4 < 0.45 < F#4
F#4 < 0.61 < G#4
G#4 < 1.24 < A4
C4r   r9   r$   r<   )
r    ChromaticScalepitchesmathlog2	frequencyr)   modfr'   rI   )useScale	scPitchesscPitchesRemainderppLog2scPitchesThresholdr0   s          r3   r	   r	      s    > ''-  I		!++&!!$))E"21"56  ra3)*Q./!!#5#8;MRSe;T#TXY"YZ 0 ((r4   c                    US[        U 5      S-
  4;   a  U$ XS-
     XS-      -
  XS-
     SX   -  -
  XS-      -   -  nUS-  U-   nU$ )a  
Interpolation for estimating the true position of an
inter-sample maximum when nearby samples are known.

Correlation is a vector and peak is an index for that vector.

Returns the x coordinate of the vertex of that parabola.

>>> import numpy
>>> f = [2, 3, 1, 6, 4, 2, 3, 1]
>>> peak = int(numpy.argmax(f))
>>> peak  # f[3] is 6, which is the max.
3
>>> audioSearch.interpolation(f, peak)
3.21428571...
r   r$          @g      ?)rI   )rP   rV   rW   s      r3   r
   r
      st    $ 3{#a'(((#k(&;;1Hk&7 77+Qh:OOQFc\D FMr4   c                `   Uc  Uc  Ub  Uc  [        S5      eUc  [        5       u  p[        R                  " U 5      n[        R                  " U5      u  pE[        U5      n[        [        U5      5       H_  nX   nXG:  d  M  [        R                  " X&   5      nUS-
  Ul
        [        R                  " [        X&   5      5      n	U	R                  U4s  $    [        R                  " US   5      nUS-
  Ul
        Xl        [        R                  " [        US   5      5      n	U	R                  U4$ )a  
Takes in an inputFrequency, a set of threshold values, and a set of allowable pitches
(given by prepareThresholds) and returns a tuple of the normalized frequency and the
pitch detected (as a :class:`~music21.pitch.Pitch` object)

It will convert the frequency to be within the range of the default frequencies
(usually C4 to C5), but the pitch object will have the correct octave.

>>> audioSearch.normalizeInputFrequency(441.72)
(440.0, <music21.pitch.Pitch A4>)

If you will be doing this often, it's best to cache your thresholds and
pitches by running `prepareThresholds` once first:

>>> thresholds, pitches = audioSearch.prepareThresholds(scale.ChromaticScale('C4'))
>>> for fq in [450, 510, 550, 600]:
...      print(audioSearch.normalizeInputFrequency(fq, thresholds, pitches))
(440.0, <music21.pitch.Pitch A4>)
(523.25113..., <music21.pitch.Pitch C5>)
(277.18263..., <music21.pitch.Pitch C#5>)
(293.66476..., <music21.pitch.Pitch D5>)
z[Cannot normalize input frequency if thresholds are given and pitches are not, or vice-versa   r9      )r   r	   r\   r]   r_   r(   r'   rI   copydeepcopyoctaver   Pitchstrr^   inputFrequency)
inputPitchFrequency
thresholdsr[   inputPitchLog2	remainderrm   r0   	thresholdreturnPitch	name_notes
             r3   r   r      s   2 
	 3"w"/0 	0  1 3YY23N))N3Y[F3z?#M	 --
3K!'!KC
O4I&&33 $ --,K!K!4C,-I++r4   c                   Uc  [         R                  " S5      n/ n[        U5      u  p4[        [	        U 5      5       H&  nX   n[        XcU5      u  pxUR                  U5        M(     / n	SnU[	        U5      S-
  :  a  X%   R                  n
UnSnU[	        U5      S-
  :  aN  X%   R                  U
:X  a<  XU   R                  -   nUS-   nU[	        U5      S-
  :  a  X%   R                  U
:X  a  M<  [        XU-
  -  5      n[        X[-
  5       H4  nXX-   S-
     l        U	R                  X+U-   S-
     R                  5        M6     U[	        U5      S-
  :  a  M  X)4$ )a  
Takes in a list of detected pitch frequencies and returns a tuple where the first element
is a list of :class:~`music21.pitch.Pitch` objects that best match these frequencies
and the second element is a list of the frequencies of those objects that can
be plotted for matplotlib

TODO: only return the former.  The latter can be generated in other ways.

>>> readPath = common.getSourceFilePath() / 'audioSearch' / 'test_audio.wav'
>>> freqFromAQList = audioSearch.getFrequenciesFromAudioFile(waveFilename=readPath)

>>> detectedPitchesFreq = audioSearch.detectPitchFrequencies(
...   freqFromAQList, useScale=scale.ChromaticScale('C4'))
>>> detectedPitchesFreq = audioSearch.smoothFrequencies(detectedPitchesFreq)
>>> (detectedPitchObjects, listPlot) = audioSearch.pitchFrequenciesToObjects(
...   detectedPitchesFreq, useScale=scale.ChromaticScale('C4'))
>>> [str(p) for p in detectedPitchObjects]
['A5', 'A5', 'A6', 'D6', 'D4', 'B4', 'A4', 'F4', 'E-4', 'C#3', 'B3', 'B3', 'B3', 'A3', 'G3',...]
rY   r   r$   )r    
MajorScaler	   r'   rI   r   r)   namerm   roundr^   )detectedPitchesFreqr`   detectedPitchObjectsrr   r[   r0   rq   unused_freq
pitch_namelistPlotrz   hold
tot_octavejs                 r3   r   r   -  s{   * ##D)-h7Z3*+,14"9:M[b"c##J/ -
 H	A
c&'!+
+#&++
#*+a//4H4K4P4PTX4X#1&=&D&DDJAA #*+a//4H4K4P4PTX4X :T23
qxA8BA.5OO0A>HHI ! c&'!+
+  ))r4   c                Z   S[         R                  ;   a  [        S5      eSSKn[        R                  S5        [        R                  " U U[        S9n[        R                  S5        / nU H;  nUR                  XRR                  S9nUR                  [        U[        5      5        M=     U$ )	z
records for length (=seconds) a set of frequencies from the microphone.

If storeWaveFilename is not None, then it will store the recording on disk
in a wave file.

Returns a list of frequencies detected.

TODO -- find a way to test or at least demo
r6   z?Cannot run getFrequenciesFromMicrophone without numpy installedr   Nz* start recording)seconds	storeFilerecordChunkLengthz* stop recordingdtype)r   r>   r   r6   environLocal
printDebugr   samplesFromRecordingaudioChunkLength
frombufferint16r)   r   recordSampleRate)lengthstoreWaveFilenamer6   storedWaveSampleListfreqFromAQListr*   sampless          r3   r   r   ]  s     $%%%"MO 	O /0$99&DUL\^ ./N$""4{{";5g?OPQ % r4   c           	     Z   S[         R                  ;   a  [        S5      eSSKn/ n[        R                  S5         [        R                  " [        U 5      S5      n[        [        UR                  5       [        -  5      5       H)  nUR                  [        5      nUR                  U5        M+     / nU HD  nUR!                  XQR"                  S	9nUR                  [%        ['        U[(        5      5      5        MF     UR+                  5         U$ ! [         a    [        SU  S35      ef = f)
a|  
gets a list of frequencies from a complete audio file.

Each sample is a window of audioSearch.audioChunkLength long.

>>> audioSearch.audioChunkLength
1024

>>> readPath = common.getSourceFilePath() / 'audioSearch' / 'test_audio.wav'
>>> freq = audioSearch.getFrequenciesFromAudioFile(waveFilename=readPath)
>>> print(freq)
[143.627..., 99.083..., 211.004..., 4700.313..., ...]
r6   z>Cannot run getFrequenciesFromAudioFile without numpy installedr   Nz* reading entire file from diskrCannot open  for reading, does not existr   )r   r>   r   r6   r   r   waveopenro   IOErrorr'   r(   
getnframesr   
readframesr)   r   r   floatr   r   close)waveFilenamer6   r   wvr0   r*   r   r   s           r3   r   r   |  s    $%%%"LN 	N=>^YYs<(#.
 3r}})99:;}}-.##D) < N$""4{{";e$;GEU$VWX % HHJ  ^"\,?[#\]]^s    D D*c                f   S[         R                  ;   a  [        S5      eSSKnU S:X  a  [        R                  5       S-  n [        U [        R                  5      (       a  [        U 5      n [        U [        5      (       a  U n [        R                  " US5      nO[        [        R                  U 5      n/ n[        R                  S
5        [!        [#        [$        R&                  " U[(        -  [*        -  5      5      5       HG  nU[*        -   nX%R-                  5       :  d  M!  UR/                  [*        5      nUR1                  U5        MI     / n	U H;  nUR3                  XR4                  S9n
U	R1                  [7        U
[(        5      5        M=     UnXU4$ ! [         a    [        SU S	35      ef = f)a*  
It calculates the fundamental frequency at every instant of time of an audio signal
extracted either from the microphone or from an already recorded song.
It uses a period of time defined by the variable "length" in seconds.

It returns a list with the frequencies, a variable with the file descriptor,
and the end sample position.

>>> #_DOCS_SHOW readFile = 'pachelbel.wav'
>>> sp = common.getSourceFilePath() #_DOCS_HIDE
>>> readFile = sp / 'audioSearch' / 'test_audio.wav' #_DOCS_HIDE
>>> fTup  = audioSearch.getFrequenciesFromPartialAudioFile(readFile, length=1.0)
>>> frequencyList, pachelbelFileHandle, currentSample = fTup
>>> for frequencyIndex in range(5):
...     print(frequencyList[frequencyIndex])
143.627...
99.083...
211.004...
4700.313...
767.827...
>>> print(currentSample)  # should be near 44100, but probably not exact
44032

Now read the next 1 second:

>>> fTup = audioSearch.getFrequenciesFromPartialAudioFile(pachelbelFileHandle, length=1.0,
...                                                       startSample=currentSample)
>>> frequencyList, pachelbelFileHandle, currentSample = fTup
>>> for frequencyIndex in range(5):
...     print(frequencyList[frequencyIndex])
187.798...
238.263...
409.700...
149.958...
101.989...
>>> print(currentSample)  # should be exactly double the previous
88064
r6   zECannot run getFrequenciesFromPartialAudioFile without numpy installedr   Ntempztemp.wavr   r   r   z+* reading file from disk a part of the songr   )r   r>   r   r6   r   getRootTempDir
isinstancepathlibPathro   r   r   r   r   	Wave_readr   r'   r(   r\   floorr   r   r   r   r)   r   r   r   )waveFilenameOrHandler   startSampler6   r   
waveHandler   r0   r*   r   r   	endSamples               r3   r   r     s   P $%%%"SU 	Uv%+::<zI&55"#78&,,+	b<5J
 $..*>?
IJ3tzz&+;";>N"NOPQ!$44..00(()9:D ''-	 R
 N$""4{{";5g?OPQ % I	22+  	b&l^C_'`aa	bs   F F0c                    Uc  [         R                  " S5      n[        U5      u  p#/ n[        [	        U 5      5       H0  nX   n[        XbU5      u  pxUR                  UR                  5        M2     U$ )a$  
Detects the pitches of the notes from a list of frequencies, using thresholds which
depend on the useScale option. If useScale is None,
the default value is the Major Scale beginning C4.

Returns the frequency of each pitch after normalizing them.

>>> freqFromAQList=[143.627689055, 99.0835452019, 211.004784689, 4700.31347962, 2197.9431119]
>>> cMaj = scale.MajorScale('C4')
>>> pitchesList = audioSearch.detectPitchFrequencies(freqFromAQList, useScale=cMaj)
>>> for i in range(5):
...     print(int(round(pitchesList[i])))
147
98
220
4699
2093
rY   )r    ry   r	   r'   rI   r   r)   r^   )	r   r`   rr   r[   r|   r0   rq   r~   r   s	            r3   r   r     sw    ( ##D)-h7Z3~&',/"9:M[b"c"":#7#78 ( r4      F)smoothLevelsinPlacec                  US:  a  [        S5      e[        U 5      nX:  a  [        SU SU S35      eU nU(       a  U Vs/ s H  n[        U5      PM     nnO/[        R                  " U5       Vs/ s H  n[        U5      PM     nnSnSn[	        U5       H  n	XvU	   -   nXUS-
  U	-
     -   nM     Xq-  nX-  n[	        U5       H  n	U	[        [        R                  " US-  5      5      :  a  [        U5      Xi'   M8  X[        [        R                  " US-  5      5      -
  S-
  :  a  [        U5      Xi'   Mr  Sn
[	        U5       H/  nXX-   [        [        R                  " US-  5      5      -
     -  n
M1     X-  Xi'   M     U Vs/ s H  n[        [        U5      5      PM     nnU(       d  U$ [	        [        U 5      5       H	  n	X   X	'   M     gs  snf s  snf s  snf )	a  
Smooths the shape of the signal to avoid false detections in the fundamental
frequency.  Takes in a list of ints or floats.

The second pitch below is certainly too low.  It will be smoothed out:

>>> inputPitches = [440, 220, 440, 440, 442, 443, 441, 470, 440, 441, 440,
...                 442, 440, 440, 440, 397, 440, 440, 440, 442, 443, 441,
...                 440, 440, 440, 440, 440, 442, 443, 441, 440, 440]
>>> result = audioSearch.smoothFrequencies(inputPitches)
>>> result
[409, 409, 409, 428, 435, 438, 442, 444, 441, 441, 441,
 441, 434, 433, 432, 431, 437, 438, 439, 440, 440, 440,
 440, 440, 440, 441, 441, 441, 440, 440, 440, 440]

Original list is unchanged:

>>> inputPitches[1]
220

Different levels of smoothing have different effects.  At smoothLevel=2,
the isolated 220hz sample is pulling down the surrounding samples:

>>> audioSearch.smoothFrequencies(inputPitches, smoothLevels=2)[:5]
[330, 275, 358, 399, 420]

Doing this enough times will smooth out a lot of inconsistencies.

>>> audioSearch.smoothFrequencies(inputPitches, smoothLevels=28)[:5]
[432, 432, 432, 432, 432]


If inPlace is True then the list is modified in place and nothing is returned:

>>> audioSearch.smoothFrequencies(inputPitches, inPlace=True)
>>> inputPitches[:5]
[409, 409, 409, 428, 435]

Note that `smoothLevels=1` is the baseline that does nothing:

>>> audioSearch.smoothFrequencies(inputPitches, smoothLevels=1) == inputPitches
True

And less than 1 raises a ValueError:

>>> audioSearch.smoothFrequencies(inputPitches, smoothLevels=0)
Traceback (most recent call last):
ValueError: smoothLevels must be >= 1

There cannot be more smoothLevels than input frequencies:

>>> audioSearch.smoothFrequencies(inputPitches, smoothLevels=40)
Traceback (most recent call last):
ValueError: There cannot be more smoothLevels (40) than inputPitches (32)

Note that the system runs on O(smoothLevels * len(frequenciesList)),
so additional smoothLevels can be costly on a large set.

This function always returns a list of ints -- rounding to the nearest
hertz (you did want it smoothed right?)

* Changed in v6: inPlace defaults to False (like other music21
  functions) and if done in Place, returns nothing.  smoothLevels and inPlace
  became keyword only.
r$   zsmoothLevels must be >= 1z#There cannot be more smoothLevels (z) than inputPitches ()g        rg   N)

ValueErrorrI   r   rk   r'   r(   r\   r   ceilr{   )frequencyListr   r   numFreqsdpffr|   rU   endsr0   changer   outs                r3   r   r     s   N a455=!H1,?TU]T^^_`
 	
 C145AuQx5153@AuQx@ ID< A 66	(Q,*:;; ! (ID8_s4::lS0122%(^"C		,*< =>>BB%(Y"F<(aec$**\TWEW:X6Y.YZZ )%+%:"  .AA-@c%(m-@CA
s=)*A"vM +A 6@0 Bs   G1GGc                   SnXS   l         SnSnSnSnSnSn/ n/ n	U[        U 5      :  Ga  X   R                   n
U[        U 5      :  a  XU   R                   :X  aw  US-   nUS:  aD  SnUS:  a:  U	R                  U5        US-   nUR                  [        R                  " 5       5        SnUS-   nU[        U 5      :  a  XU   R                   :X  a  Mw  U(       aI  U	R                  U5        US-   n[        R
                  " 5       nXS-
     Ul        UR                  U5        OXC-   nSnSnUS-   nU[        U 5      :  a  GM  X4$ )a[  
takes a list of equally spaced :class:`~music21.pitch.Pitch` objects
and returns a tuple of two lists, the first a list of
:class:`~music21.note.Note`
or :class:`~music21.note.Rest` objects (each of quarterLength 1.0)
and a list of how many were joined together to make that object.

N.B. the returned list is NOT a :class:`~music21.stream.Stream`.

>>> readPath = common.getSourceFilePath() / 'audioSearch' / 'test_audio.wav'
>>> freqFromAQList = audioSearch.getFrequenciesFromAudioFile(waveFilename=readPath)
>>> chrome = scale.ChromaticScale('C4')
>>> detectedPitchesFreq = audioSearch.detectPitchFrequencies(freqFromAQList, useScale=chrome)
>>> detectedPitchesFreq = audioSearch.smoothFrequencies(detectedPitchesFreq)
>>> (detectedPitches, listPlot) = audioSearch.pitchFrequenciesToObjects(
...        detectedPitchesFreq, useScale=chrome)
>>> len(detectedPitches)
861
>>> notesList, durationList = audioSearch.joinConsecutiveIdenticalPitches(detectedPitches)
>>> len(notesList)
24
>>> print(notesList)
[<music21.note.Rest quarter>, <music21.note.Note C>, <music21.note.Note C>,
 <music21.note.Note D>, <music21.note.Note E>, <music21.note.Note F>,
 <music21.note.Note G>, <music21.note.Note A>, <music21.note.Note B>,
 <music21.note.Note C>, ...]
>>> print(durationList)
[71, 6, 14, 23, 34, 40, 27, 36, 35, 15, 17, 15, 6, 33, 22, 13, 16, 39, 35, 38, 27, 27, 26, 8]
r=   r   Fr$      T   )r^   rI   r)   r   RestNoter   )r}   REST_FREQUENCYr   goodbad
valid_notetotal_notestotal_rests	notesListdurationListfrns               r3   r   r     so   @ N(6% 	
AD
CJKKIL
c&'
'!$.. #*++16M6W6W0W!8D qy!
 "9 '',"-/K$$TYY[1AA #*++16M6W6W0W %%/K 		A*q51AGQ*C
E? c&'
'@ ""r4   c                    U S-  n / SQn/ n[        [        U5      S-
  5       H!  nUR                  X   XS-      -   S-  5        M#     US   n[        [        U5      5       H  nX#   nX:  d  M  XS-      nM     US-  $ )aN  
round an approximately transcribed quarterLength to a better one in
music21.

Should be replaced by a full-featured routine in midi or stream.

See :meth:`~music21.stream.Stream.quantize` for more information
on the standard music21 methodology.

>>> audioSearch.quantizeDuration(1.01)
1.0
>>> audioSearch.quantizeDuration(1.70)
1.5
d   )g      9@g      I@g      Y@g     b@g      i@g      y@r$   r<   r   )r'   rI   r)   )r   typicalLengthsrr   r0   finalLengthru   s         r3   r   r     s     c\FCNJ3~&*+>,~!e/DDIJ , !#K3z?#M	(Q/K $ r4   c                r   [         R                   " U 5      nUR                  S5        [        US5      u  p4[        U5      S-
  nX5   [	        U5      :w  a  US-
  nX5   [	        U5      :w  a  M  XE   XES-      -   S-  nUS:X  a  SnS[
        R                  " U5      -
  nU[
        R                  " SU5      -  nU$ )a  
takes a list of lengths of notes (measured in
audio samples) and tries to estimate what the length of a
quarter note should be in this list.

If mostRepeatedQuarterLength is another number, it still returns the
estimated length of a quarter note, but chooses it so that the most
common note in durationList will be the other note.  See example 2:

Returns a float -- and not an int.

>>> durationList = [20, 19, 10, 30, 6, 21]
>>> audioSearch.quarterLengthEstimation(durationList)
20.625

Example 2: suppose these are the inputted durations for a
score where most of the notes are half notes.  Show how long
a quarter note should be:

>>> audioSearch.quarterLengthEstimation(durationList, mostRepeatedQuarterLength=2.0)
10.3125
r   g       @r$   rg         ?r<   )rk   r)   r   rI   r%   r\   r]   pow)r   mostRepeatedQuarterLengthdlpdfr+   r0   qlebinPositions           r3   r   r     s    0 
<	 BIIaL"c"IC 	C1A
&CH
E &CH
7Ta%[ C
'C A%$'!dii 9::K
K(
(C Jr4   c                p   [         R                  " 5       nUbH  [        R                  R	                  U5      nUR                  5       R                  S   n[        X5      nOUc  [        U5      n[        [        U5      5       HL  n[        X   U-  5      n	XU   l        U(       a  X   R                  S:X  a  M7  UR                  X   5        SnMN     [         R                  " 5       n
[        R                   " 5       U
l        SU
R                  l        U
R%                  SU5        Uc  U
[        U5      4$ X4$ )a  
take a list of :class:`~music21.note.Note` objects or rests
and an equally long list of how long
each one lasts in terms of samples and returns a
Stream using the information from quarterLengthEstimation
and quantizeDurations.

returns a :class:`~music21.stream.Score` object, containing
a metadata object and a single :class:`~music21.stream.Part` object, which in turn
contains the notes, etc.  Does not run :meth:`~music21.stream.base.Stream.makeNotation`
on the Score.


>>> durationList = [20, 19, 10, 30, 6, 21]
>>> n = note.Note
>>> noteList = [n('C#4'), n('D5'), n('B4'), n('F#5'), n('C5'), note.Rest()]
>>> s,lengthPart = audioSearch.notesAndDurationsToStream(noteList, durationList)
>>> s.show('text')
{0.0} <music21.metadata.Metadata object at ...>
{0.0} <music21.stream.Part ...>
    {0.0} <music21.note.Note C#>
    {1.0} <music21.note.Note D>
    {2.0} <music21.note.Note B>
    {2.5} <music21.note.Note F#>
    {4.0} <music21.note.Note C>
    {4.25} <music21.note.Rest quarter>
r   restFzAutomatic Music21 Transcription)r!   Partr   nativeMostCommonNoteQuarterLengthextractvectorr   r'   rI   r   quarterLengthrz   r)   Scorer   Metadatatitleinsert)r   r   scNotesremoveRestsAtBeginningr   p2fe
mostCommonr0   actualDurationscs              r3   r   r   0  s   H 
B
 __88AZZ\((+
%l?	%l33|$%),/C*?@%3!"&IL,=,=,GIIil#%*" & 
B##%BK9BKKIIa3r7{wr4   c                   SnSnU[        U 5      :  aT  U[        X   R                  5         U:  a6  US-   nUnU[        U 5      :  a   U[        X   R                  5         U:  a  M6  [        U 5      S:X  a  Sn[        R                  " US   U-
  5      n	[        [        U 5      5       H  nU[        X   R                  5         n
X   R                  SU S   R                  -  :  d  MA  X:  d  MH  [        R                  " X-
  5      U	:  d  Mf  [        R                  " X-
  5      n	UnM     U[        X   R                  5         n
U[        U 5      :  a   X::  a  [        R                  SU
 SU 35        X   R                  S:  d  [        U 5      S:X  a  [        R                  S5        US-   nOU	S:  a%  US:X  a  US-  n[        R                  S	U	< 35        O^U	S
:  a%  US:X  a  US-  n[        R                  S	U	< 35        O3Ub.  Ub+  X:  d  X:  a!  US:  a  US-  n[        R                  S5        OSn[        R                  SU	SU
SU/5        X4$ )a  
Decides which of the given parts of the score has the best match with
the recorded part of the song.
If there is not a part of the score with a high probability to be the correct part,
it starts a "countdown" in order stop the score following if the bad matching persists.
In this case, it does not match the recorded part of the song with any part of the score.

Inputs: partsList, contains all the possible parts of the score, sorted from the
higher probability to be the best matching at the beginning to the lowest probability.
notePrediction is the position of the score in which the next note should start.
beginningData is a list with all the beginnings of the used fragments of the score to find
the best matching.
lastNotePosition is the position of the score in which the last matched fragment of the
score finishes.
Countdown is a counter of consecutive errors in the matching process.

Outputs: Returns the beginning of the best matching fragment of
score and the countdown.

>>> scNotes = corpus.parse('luca/gloria').parts[0].flatten().notes.stream()
>>> scoreStream = scNotes
>>> sfp = common.getSourceFilePath() #_DOCS_HIDE
>>> readPath = sfp / 'audioSearch' / 'test_audio.wav' #_DOCS_HIDE
>>> freqFromAQList = audioSearch.getFrequenciesFromAudioFile(waveFilename=readPath) #_DOCS_HIDE

>>> tf = 'test_audio.wav'
>>> #_DOCS_SHOW freqFromAQList = audioSearch.getFrequenciesFromAudioFile(waveFilename=tf)
>>> chrome = scale.ChromaticScale('C4')
>>> detectedPitchesFreq = audioSearch.detectPitchFrequencies(freqFromAQList, useScale=chrome)
>>> detectedPitchesFreq = audioSearch.smoothFrequencies(detectedPitchesFreq)
>>> (detectedPitches, listPlot) = audioSearch.pitchFrequenciesToObjects(
...                                             detectedPitchesFreq, useScale=chrome)
>>> (notesList, durationList) = audioSearch.joinConsecutiveIdenticalPitches(detectedPitches)
>>> transcribedScore, qle = audioSearch.notesAndDurationsToStream(notesList, durationList,
...                                             scNotes=scNotes, qle=None)
>>> hop = 6
>>> tn_recording = 24
>>> totScores = []
>>> beginningData = []
>>> lengthData = []
>>> for i in range(4):
...     excerpt = scoreStream[i * hop + 1:i * hop + tn_recording + 1]
...     scNotes = stream.Part(excerpt)
...     name = str(i)
...     beginningData.append(i * hop + 1)
...     lengthData.append(tn_recording)
...     scNotes.id = name
...     totScores.append(scNotes)
>>> listOfParts = search.approximateNoteSearch(transcribedScore.flatten().notes.stream(),
...                                            totScores)
>>> notePrediction = 0
>>> lastNotePosition = 0
>>> countdown = 0
>>> positionInList, countdown = audioSearch.decisionProcess(
...          listOfParts, notePrediction, beginningData, lastNotePosition, countdown)
>>> print(positionInList)
0

The countdown result is 1 because the song used is completely different from the score!!

>>> print(countdown)
1
r   r$   g?z	 error ? z, g333333?z,Are you sure you are playing the right song?   zExcessive distance? dist=   r<   zplaying in a not shown partz2****????**** DECISION PROCESS: dist from expected:zbeginning data:lastNotePos)	rI   r(   idr\   fabsr'   matchProbabilityr   r   )	partsListnotePredictionbeginningDatalastNotePosition	countdownfirstNotePagelastNotePager0   positiondistpositionBeginningDatas              r3   r   r   r  sO   R 	
AH
c)n
s9<??/C!D~!UE c)n
s9<??/C!D~!U 9~99]1%67D3y>" -c),//.B C\**cIaL4Q4Q.QQ*=yy.?@4Gyy!6!GH # *#i.A.D.D*EF #i. %:%N),A+B"EUDV WX++c1S^q5H NOM		yA~Q	"<tg >?	yA~Q	"<tg >?$)A%5(7q=Q	 =>	QSW.0E*,<> ? r4   c                      \ rS rSrSrg)r   i   N__name__
__module____qualname____firstlineno____static_attributes__r   r4   r3   r   r         r4   r   c                      \ rS rSrSrg)Testi  r   Nr   r   r4   r3   r  r    r  r4   r  z
list[type]
_DOC_ORDER__main__)N)NN)      $@N)zxmas.wav)r   r
  r   )r   zlist[int | float]returnzlist[int] | None)r   )NTN)5__doc__
__future__r   __all__rk   r\   r   r   r?   unittesttypingr   music21r   r   r   r   r   r   r   r    r!   music21.audioSearchr   r   Environmentr   r   r   r   r   r	   r
   r   r   r   r   r   r   r   r   r   r   r   r   Music21Exceptionr   TestCaser  r  __annotations__r   mainTestr   r4   r3   <module>r     sL   #$                  ) +&&}5  *!Z4n.)b65,p-*`>&RN3bH 	s"s
 stN#b:.h ?P vr	<88 		8 	 
J  zT r4   