
    rh`                       % S 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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0rS
\S'   S r " S S5      r " S S5      r " S S5      rS rS r " S S\	R4                  5      r " S S\	R4                  5      r/ rS\S'   \S:X  a  SSK
r
\
R>                  " \5        gg)a  
This module defines classes for representing Scala scale data,
including Scala pitch representations, storage, and files.

The Scala format is defined at the following URL:
https://www.huygens-fokker.org/scala/scl_format.html

We thank Manuel Op de Coul for allowing us to include
the repository (as of May 11, 2011) with music21

Scala files are encoded as latin-1 (ISO-8859) text

Utility functions are also provided to search and find
scales in the Scala scale archive. File names can be found
with the :func:`~music21.scala.search` function.

To create a :class:`~music21.scale.ScalaScale` instance, simply
provide a root pitch and the name of the scale. Scale names are given as
the scala .scl filename.

>>> mbiraScales = scale.scala.search('mbira')
>>> mbiraScales
['mbira_banda.scl', 'mbira_banda2.scl', 'mbira_gondo.scl', 'mbira_kunaka.scl',
 'mbira_kunaka2.scl', 'mbira_mude.scl', 'mbira_mujuru.scl', 'mbira_zimb.scl']

For most people you'll want to do something like this:

>>> sc = scale.ScalaScale('a4', 'mbira_banda.scl')
>>> [str(p) for p in sc.pitches]
['A4', 'B4(-15c)', 'C#5(-11c)', 'E-5(-7c)', 'E~5(+6c)', 'F#5(+14c)', 'G~5(+1c)', 'B-5(+2c)']
    )annotationsN)common)environment)interval)sclzscale.scalaallPathsz&dict[str, dict[str, list[str]] | None]SCALA_PATHSc                     [         S   b	  [         S   $ [        n [        U S5      (       d  U nO[U R                  S   n[	        [
        R                  " U5      5       Vs/ s H"  n[
        R                  R                  X#5      PM$     nn0 nU H  nUR                  S5      (       d  M  / XE'   [
        R                  R                  U5      u  p&UR                  SS5      nXE   R                  U5        [
        R                  R                  U5      u  p&UR                  5       nUR                  SS5      nUR                  SS5      nUR                  SS5      nXE   R                  U5        M     U[         S'   U$ s  snf )z
Get all scala scale paths. This is called once or the module and
cached as SCALA_PATHS, which should be used instead of calls to this function.

>>> a = scale.scala.getPaths()
>>> len(a) >= 3800
True
r   __path__r   .scl _-)r	   r   hasattrr   sortedoslistdirpathjoinendswithsplitreplaceappendlower)
moduleName
dirListing	directoryxpathsfpfns          V/home/james-whalen/.local/lib/python3.13/site-packages/music21/scale/scala/__init__.pygetPathsr#   C   sG    :*:&&J:z**
  
 ''*	:@IAV:WX:WQbggll90:W
XE;;vEI GGMM"-MIFB'BIR GGMM"-MIBFB'BC$BC$BIR   $K
L' Ys   )E;c                  2    \ rS rSrSrSS jrS rSS jrSrg)	
ScalaPitchp   a  
Representation of a scala pitch notation

>>> sp = scale.scala.ScalaPitch(' 1066.667 cents')
>>> print(sp.parse())
1066.667

>>> sp = scale.scala.ScalaPitch(' 2/1')
>>> sp.parse()
1200.0
>>> sp.parse('100.0 C#')
100.0
>>> [sp.parse(x) for x in ['89/84', '55/49', '44/37', '63/50', '4/3', '99/70', '442/295',
...     '27/17', '37/22', '98/55', '15/8', '2/1']]
[100.0992..., 199.9798..., 299.9739..., 400.10848..., 498.04499...,
 600.0883..., 699.9976..., 800.9095..., 900.0260...,
 1000.0201..., 1088.2687..., 1200.0]
Nc                H    S U l         Ub  U R                  U5        S U l        g N)src_setSrccents)selfsourceStrings     r"   __init__ScalaPitch.__init__   s$    #LL& 
    c                |    UR                  5       n[        R                  " USS9u  pUR                  5       U l        g )Nz0123456789./)numbers)stripr   getNumFromStrr)   )r,   rawjunks      r"   r*   ScalaPitch._setSrc   s.    iik((nE	99;r0   c                   Ub  U R                  U5        SU R                  ;   a&  [        U R                  5      U l        U R                  $ SU R                  ;   a3  U R                  R	                  S5      u  p#[        U5      [        U5      p2O[        U R                  5      nSnS[
        R                  " X#-  S5      -  U l        U R                  $ )z-
Parse the source string and set self.cents.
./g      ?g     @   )r*   r)   floatr+   r   mathlog)r,   r-   nds       r"   parseScalaPitch.parse   s     #LL&$((?txxDJ zz dhhxx~~c*Qxq1$((O$((AEA"66DJzzr0   )r+   r)   r(   )	__name__
__module____qualname____firstlineno____doc__r.   r*   rA   __static_attributes__ r0   r"   r%   r%   p   s    *r0   r%   c                  L    \ 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g)	ScalaData   u  
Object representation of data stored in a Scala scale file. This object is used to
access Scala information stored in a file. To create a music21 scale with a Scala file,
use :class:`~music21.scale.ScalaScale`.

This is not called ScalaScale, as this name clashes with the
:class:`~music21.scale.ScalaScale` that uses this object.

>>> import os
>>> sf = scale.scala.ScalaFile()
>>> fp = common.getSourceFilePath() / 'scale' / 'scala' / 'scl' / 'tanaka.scl'
>>> sf.open(fp)
>>> sd = sf.read()

ScaleFile descriptions are converted to unicode.

>>> print(sd.description)
26-note choice system of Shohé Tanaka, Studien i.G.d. reinen Stimmung (1890)
>>> sd.pitchCount
26

Distances from the tonic:

>>> cat = sd.getCentsAboveTonic()
>>> len(cat)
26
>>> list(int(round(x)) for x in cat[0:4])
[71, 92, 112, 182]
>>> sd.pitchValues[0]
<music21.scale.scala.ScalaPitch object at 0x10b16fac8>
>>> sd.pitchValues[0].cents
70.6724...

This will not add up with centsAboveTonic above, due to rounding

>>> adj = sd.getAdjacentCents()
>>> list(int(round(x)) for x in adj[0:4])
[71, 22, 20, 71]

Interval Sequences

>>> intSeq = sd.getIntervalSequence()
>>> intSeq[0:4]
[<music21.interval.Interval m2 (-29c)>,
 <music21.interval.Interval P1 (+22c)>,
 <music21.interval.Interval P1 (+20c)>,
 <music21.interval.Interval m2 (-29c)>]

Tweak the file and be ready to write it back out:

>>> sd.pitchValues[0].cents = 73.25
>>> sd.fileName = 'tanaka2.scl'
>>> sd.description = 'Tweaked version of tanaka.scl'
>>> fs = sd.getFileString()
>>> print(fs)
! tanaka2.scl
!
Tweaked version of tanaka.scl
26
!
73.25
92.17...
111.73...
182.40...

Be sure to reencode `fs` as `latin-1` before writing to disk.

>>> sf.close()
Nc                F    Xl         X l        S U l        S U l        / U l        g r(   )r)   fileNamedescription
pitchCountpitchValues)r,   r-   rN   s      r"   r.   ScalaData.__init__   s'        r0   c                
   U R                   R                  S5      nSn[        U5       H  u  p4UR                  5       nUR	                  S5      (       a3  US:X  a+  U R
                  c  SU;   a  USS R                  5       U l        M^  US-  nUS:X  a  US:w  a  X@l        Mw  My  US:X  a  US:w  a  [        U5      U l        M  M  US:w  d  M  [        U5      nUR                  5         U R                  R                  U5        M     g)	z@
Parse a scala file delivered as a long string with line breaks

r   !Nr      r   r;   )r)   r   	enumerater3   
startswithrN   rO   intrP   r%   rA   rQ   r   )r,   linescountilinesps         r"   rA   ScalaData.parse   s     t$ 'GA::<Ds##6dmm3~(,QR(8
z2:'+$ !2:&)$iDO  2:#D)BHHJ$$++B/+ (r0   c                X    U R                    Vs/ s H  oR                  PM     sn$ s  snf )z7
Return a list of cent values above the implied tonic.
)rQ   r+   )r,   r^   s     r"   getCentsAboveTonicScalaData.getCentsAboveTonic  s&     $(#3#34#3R#3444s   'c                j    / nSnU R                  5        H  nX2-
  nUR                  U5        UnM     U$ )z.
Get cents values between adjacent intervals.
r   )ra   r   )r,   postlocationcdifs        r"   getAdjacentCentsScalaData.getAdjacentCents#  sB     ((*A,C KKH + r0   c                    / U l         SnU H=  n[        5       nX#-   Ul        UR                  nU R                   R                  U5        M?     [	        U R                   5      U l        g)z_
Given a list of adjacent cent values, create the necessary ScalaPitch
objects and update them
r   NrQ   r%   r+   r   lenrP   )r,   centListre   rf   r^   s        r"   setAdjacentCentsScalaData.setAdjacentCents1  s\    
 AB|BHxxH##B'	 
 d../r0   c                    / nU R                  5        H+  nUR                  [        R                  " US-  5      5        M-     U$ )z.
Get the scale as a list of Interval objects.
g{Gz?)rh   r   r   Interval)r,   rd   rf   s      r"   getIntervalSequenceScalaData.getIntervalSequence?  s>     &&(AKK))!d(34 ) r0   c                    / U l         SnU HG  n[        5       nX#R                  -   Ul        UR                  nU R                   R                  U5        MI     [	        U R                   5      U l        g)z0
Set the scale from a list of Interval objects.
r   Nrk   )r,   iListre   r\   r^   s        r"   setIntervalSequenceScalaData.setIntervalSequenceI  sa     AB'')BHxxH##B'  d../r0   c                L   / nU R                   b  UR                  SU R                    35        UR                  S5        U R                  b  UR                  U R                  5        OUR                  S5        U R                  b%  UR                  [	        U R                  5      5        OUR                  S5        UR                  S5        U R
                   H'  nUR                  [	        UR                  5      5        M)     UR                  S5        SR                  U5      $ )z
Return a unicode-string suitable for writing a Scala file

The unicode string should be encoded in Latin-1 for maximum
Scala compatibility.
z! rU   r   rT   )rN   r   rO   rP   strrQ   r+   r   )r,   msgr^   s      r"   getFileStringScalaData.getFileStringW  s     ==$JJDMM?+,

3'JJt''(JJrN??&JJs4??+,JJrN 	

3""BJJs288}% # 	

2yy~r0   )rO   rN   rP   rQ   r)   )NN)rC   rD   rE   rF   rG   r.   rA   ra   rh   rn   rr   rv   r{   rH   rI   r0   r"   rK   rK      s1    DJ
0:500r0   rK   c                  V    \ rS 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Srg)	ScalaFileiy  a  
Interface for reading and writing scala files.
On reading, returns a :class:`~music21.scala.ScalaData` object.

>>> import os
>>> sf = scale.scala.ScalaFile()
>>> fp = common.getSourceFilePath() / 'scale' / 'scala' / 'scl' / 'tanaka.scl'
>>> sf.open(fp)
>>> sd = sf.read()
>>> sd
<music21.scale.scala.ScalaData object at 0x10b170e10>
>>> sd is sf.data
True
>>> sf.fileName.endswith('tanaka.scl')
True
>>> sd.pitchCount
26
>>> sf.close()
Nc                ,    S U l         S U l        Xl        g r(   )rN   filedata)r,   r   s     r"   r.   ScalaFile.__init__  s    		r0   c                    [         R                  " XSS9U l        [        R                  R                  U5      U l        g)z
Open a file for reading
zlatin-1)encodingN)ioopenr   r   r   basenamerN   )r,   r    modes      r"   r   ScalaFile.open  s,     GGBy9	((,r0   c                    Xl         g)zX
Assign a file-like object, such as those provided by StringIO, as an open file object.
N)r   )r,   fileLikes     r"   openFileLikeScalaFile.openFileLike  s	     	r0   c                
    SnU$ )Nz<ScalaFile>rI   )r,   rs     r"   __repr__ScalaFile.__repr__  s    r0   c                8    U R                   R                  5         g r(   )r   closer,   s    r"   r   ScalaFile.close  s    		r0   c                T    U R                  U R                  R                  5       5      $ )z
Read a file. Note that this calls readstr, which processes all tokens.

If `number` is given, a work number will be extracted if possible.
)readstrr   readr   s    r"   r   ScalaFile.read  s     ||DIINN,--r0   c                \    [        XR                  5      nUR                  5         X l        U$ )zF
Read a string and process all Tokens. Returns a ABCHandler instance.
)rK   rN   rA   r   )r,   strSrcsss      r"   r   ScalaFile.readstr  s%     v}}-

		r0   c                Z    U R                  5       nU R                  R                  U5        g r(   )writestrr   write)r,   wss     r"   r   ScalaFile.write  s    ]]_		r0   c                v    [        U R                  [        5      (       a  U R                  R                  5       $ g r(   )
isinstancer   rK   r{   r   s    r"   r   ScalaFile.writestr  s+    dii++99**,, ,r0   )r   r   rN   r(   )r   )rC   rD   rE   rF   rG   r.   r   r   r   r   r   r   r   r   rH   rI   r0   r"   r~   r~   y  s4    (-.-r0   r~   c                2   Sn[        U [        R                  5      (       a  [        U 5      n [        R
                  R                  U 5      (       a  U R                  S5      (       a  U nU R                  SS5      n UcX  [        5        HJ  n[        R
                  R                  U5      u  p4U R                  5       UR                  5       :X  d  MH  Un  O   Uc>  [        5        H0  n[        5       U    H  nU R                  5       U:X  d  M  Un  M.     M2     Uc>  [        5        H0  n[        5       U    H  nU R                  5       U;   d  M  Un  M.     M2     Ub=  [        5       nUR                  U5        UR                  5       nUR                  5         U$ g)aZ  
Get a :class:`~music21.scala.ScalaData` object from
the bundled SCL archive or a file path.

>>> ss = scale.scala.parse('balafon6')
>>> ss.description
'Observed balafon tuning from Burma, Helmholtz/Ellis p. 518, nr.84'
>>> [str(i) for i in ss.getIntervalSequence()]
['<music21.interval.Interval m2 (+14c)>', '<music21.interval.Interval M2 (+36c)>',
'<music21.interval.Interval M2>', '<music21.interval.Interval m2 (+37c)>',
'<music21.interval.Interval M2 (-49c)>', '<music21.interval.Interval M2 (-6c)>',
'<music21.interval.Interval M2 (-36c)>']

>>> scale.scala.parse('incorrectFileName.scl') is None
True

>>> ss = scale.scala.parse('barbourChrom1')
>>> print(ss.description)
Barbour's #1 Chromatic
>>> ss.fileName
'barbour_chrom1.scl'


>>> ss = scale.scala.parse('blackj_gws.scl')
>>> ss.description
'Detempered Blackjack in 1/4 kleismic marvel tuning'
Nr    r   )r   pathlibPathry   r   r   existsr   r   r#   r   r   r~   r   r   r   )targetmatchr    unused_directoryr!   altsfr   s           r"   rA   rA     sH   : E&',,''V	ww~~f&//&"9"9 ^^C$F}*B#%77==#4 ||~+  }*Bz"~<<>S(E &  }*Bz"~<<>S(E &  [
WWY

	 r0   c                B   / nU R                  SS5      n [        5        H`  n[        R                  R	                  U5      u  p4U R                  5       UR                  5       :X  d  MH  X!;  d  MO  UR                  U5        Mb     [        5        HE  n[        5       U    H1  nU R                  5       U;   d  M  X!;  d  M   UR                  U5        M3     MG     / nU H1  nUR                  [        R                  R                  U5      5        M3     UR                  5         U$ )a  
Search the scala archive for matches based on a string

>>> mbiraScales = scale.scala.search('mbira')
>>> mbiraScales
['mbira_banda.scl', 'mbira_banda2.scl', 'mbira_gondo.scl', 'mbira_kunaka.scl',
 'mbira_kunaka2.scl', 'mbira_mude.scl', 'mbira_mujuru.scl', 'mbira_zimb.scl']
r   r   )	r   r#   r   r   r   r   r   r   sort)r   r   r    r   r!   r   namess          r"   searchr     s     E ^^C$Fj!ww}}R0<<>RXXZ'R   j:b>C||~$?LL$ "  ERWW%%b)* 	JJLLr0   c                      \ rS rSrSrg)TestExternali3  rI   N)rC   rD   rE   rF   rH   rI   r0   r"   r   r   3  s    r0   r   c                  &    \ rS rSrS rS rS rSrg)Testi8  c                   Sn[        U5      nUR                  5         U R                  UR                  S5        U R                  UR                  S5        U R                  [        UR                  5      S5        U R                  UR                   Vs/ s H  o3R                  S PM     sn/ SQ5        U R                  UR                  5        Vs/ s H  o3S PM     sn/ SQ5        U R                  UR                  5        Vs/ s H  o3S PM     sn/ SQ5        U R                  UR                  5        Vs/ s H  n[        U5      PM     sn/ SQ5        g s  snf s  snf s  snf s  snf )Nzs! slendro5_2.scl
!
A slendro type pentatonic which is based on intervals of 7, no. 2
 5
!
 7/6
 4/3
 3/2
 7/4
 2/1
   zslendro5_2.scl.9f)266.870905604498.044999135z701.955000865z968.8259064691200.000000000)r   231.174093531z203.910001731r   r   )%<music21.interval.Interval m3 (-33c)>%<music21.interval.Interval M2 (+31c)>z$<music21.interval.Interval M2 (+4c)>r   r   )rK   rA   assertEqualrP   rN   rl   rQ   r+   ra   rh   rr   ry   )r,   rz   r   r   s       r"   testScalaScaleATest.testScalaScaleA:  sI   
 s^

*&67R^^,a0BNNCNqWWSM*NC=	> 	b.C.C.EF.EsG*.EF=	> 	b.A.A.CD.CsG*.CD<	= 	"*@*@*BC*BQ#a&*BCC	D D G E Ds   EEE$:E)c                   Sn[        U5      nUR                  5         U R                  UR                  S5        U R                  UR                  S5        U R                  UR
                  S5        U R                  UR                  5        Vs/ s H  o3S PM     sn/ SQ5        U R                  UR                  5        Vs/ s H  o3S PM     sn/ SQ5        U R                  UR                  5        Vs/ s H  n[        U5      PM     sn/ SQ5        [        5       nUR                  UR                  5       5        U R                  UR                  5        Vs/ s H  o3S PM     sn/ SQ5        g s  snf s  snf s  snf s  snf )	Nz! fj-12tet.scl
!
Franck Jedrzejewski continued fractions approx. of 12-tet
 12
!
89/84
55/49
44/37
63/50
4/3
99/70
442/295
27/17
37/22
98/55
15/8
2/1
   zfj-12tet.sclz9Franck Jedrzejewski continued fractions approx. of 12-tetr   )100.099209825z199.979843291z299.973903610z400.108480470r   z600.088323762z699.997698171z800.909593096z900.026096390z1000.020156709z1088.268714730r   )r   z99.88063346699.994060319z100.134576860z97.936518664z102.043324627z99.909374409z100.911894925z99.116503294r   z88.248558022z111.731285270)$<music21.interval.Interval m2 (+0c)>$<music21.interval.Interval m2 (-0c)>r   r   z$<music21.interval.Interval m2 (-2c)>z$<music21.interval.Interval m2 (+2c)>r   z$<music21.interval.Interval m2 (+1c)>z$<music21.interval.Interval m2 (-1c)>r   z%<music21.interval.Interval m2 (-12c)>z%<music21.interval.Interval m2 (+12c)>)rK   rA   r   rP   rN   rO   ra   rh   rr   ry   rn   )r,   rz   r   r   ss2s        r"   testScalaScaleBTest.testScalaScaleB_  sp   $ s^

+n5T	V 	b.C.C.EF.EsG*.EF IZ 	[ 	b.A.A.CD.CsG*.CD GX 	Y 	"*@*@*BC*BQ#a&*BCC	D  kR0023c.D.D.FG.FsG*.FG/	0[ G E D& Hs   E4E97E>Fc                D   Sn[        5       nUR                  U5      nU R                  UR                  S5        U R                  UR	                  5       S S US S 5        U R                  UR                  5        Vs/ s H  n[        U5      PM     sn/ SQ5        g s  snf )Nz! arist_chromenh.scl
!
Aristoxenos' Chromatic/Enharmonic, 3 + 9 + 18 parts
 7
!
 50.00000
 200.00000
 500.00000
 700.00000
 750.00000
 900.00000
 2/1
   rV   )%<music21.interval.Interval P1 (+50c)>%<music21.interval.Interval m2 (+50c)><music21.interval.Interval m3>z<music21.interval.Interval M2>r   r   r   )r~   r   r   rP   r{   rr   ry   )r,   rz   r   r   r   s        r"   testScalaFileATest.testScalaFileA  s     [ZZ_* 	))+BQ/Ra9"*@*@*BC*BQ#a&*BC<	=Cs   =BrI   N)rC   rD   rE   rF   r   r   r   rH   rI   r0   r"   r   r   8  s    "DJT0l=r0   r   z
list[type]
_DOC_ORDER__main__) rG   
__future__r   r   r=   r   r   typingtunittestmusic21r   r   r   music21.scale.scalar   EnvironmentenvironLocalr	   __annotations__r#   r%   rK   r~   rA   r   TestCaser   r   r   rC   mainTestrI   r0   r"   <module>r      s   > # 	  	       $&&}5
 6@4F1 F)Z7 7xI IZF- F-VGT J	8$$ 	
[=8 [=@ 
J  zT r0   