
    3ja                       U d Z ddlmZ g dZddlZddlZddlZddlZddlZddl	m
Z
 ddlmZ ddl	mZ ddl	mZ dd	lmZ  ej"                  d
      ZddiZded<   d Z G d d      Z G d d      Z G d d      Zd Zd Z G d dej6                        Z G d dej6                        ZeeeeegZded<   edk(  rddl	Z	 e	j@                  e       yy)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)']
    )annotations)SCALA_PATHS	ScalaData	ScalaFile
ScalaPitchgetPathsparsesclsearchN)common)DocOrder)environment)interval)r
   zscale.scalaallPathsz&dict[str, dict[str, list[str]] | None]r   c                    t         d   	t         d   S t        } t        | d      s| }nX| j                  d   }t	        t        j                  |            D cg c]"  }t
        j                  j                  ||      $ }}i }|D ]  }|j                  d      sg ||<   t
        j                  j                  |      \  }}|j                  dd      }||   j                  |       t
        j                  j                  |      \  }}|j                         }|j                  dd      }|j                  dd      }|j                  dd      }||   j                  |        |t         d<   |S c c}w )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          I/DATA/.local/lib/python3.12/site-packages/music21/scale/scala/__init__.pyr   r   M   sM    :*:&&J:z*
  
 ''*	:@IAV:WX:WQbggll9a0:W
XE;;vE"I GGMM"-MIrFB'B"IR GGMM"-MIrBFB'BC$BC$B"IR   $K
L' Ys   'E/c                  &    e Zd ZdZddZd ZddZy)r   aB  
    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                F    d | _         || j                  |       d | _        y N)src_setSrccents)selfsourceStrings     r)   __init__zScalaPitch.__init__   s$    #LL& 
    c                    |j                         }t        j                  |d      \  }}|j                         | _        y )Nz0123456789./)numbers)stripr   getNumFromStrr-   )r0   rawjunks      r)   r.   zScalaPitch._setSrc   s0    iik((nE	T99;r3   c                   || j                  |       d| j                  v r&t        | j                        | _        | j                  S d| j                  v r5| j                  j	                  d      \  }}t        |      t        |      }}nt        | j                        }d}dt        j                  ||z  d      z  | _        | j                  S )z=
        Parse the source string and set self.cents.
        ./g      ?g     @   )r.   r-   floatr/   r   mathlog)r0   r1   nds       r)   r	   zScalaPitch.parse   s     #LL&$((?txxDJ zz dhhxx~~c*1Qxq1$((O$((AEA"66DJzzr3   r,   )__name__
__module____qualname____doc__r2   r.   r	    r3   r)   r   r   z   s    *r3   r   c                  B    e Zd ZdZddZd Zd Zd Zd Zd Z	d	 Z
d
 Zy)r   uz  
    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                J    || _         || _        d | _        d | _        g | _        y r,   )r-   fileNamedescription
pitchCountpitchValues)r0   r1   rJ   s      r)   r2   zScalaData.__init__   s+        r3   c                   | j                   j                  d      }d}t        |      D ]  \  }}|j                         }|j	                  d      r.|dk(  r(| j
                  d|v r|dd j                         | _        U|dz  }|dk(  r|dk7  se|| _        m|dk(  r|dk7  sxt        |      | _        |dk7  st        |      }|j                          | j                  j                  |        y)	zP
        Parse a scala file delivered as a long string with line breaks
        
r   !Nr      r   r=   )r-   r   	enumerater6   
startswithrJ   rK   intrL   r   r	   rM   r    )r0   linescountilinesps         r)   r	   zScalaData.parse  s     t$ 'GAt::<Ds#6dmm3~(,QR(8
z2:'+D$!2:&)$iDO2:#D)BHHJ$$++B/+ (r3   c                T    | j                   D cg c]  }|j                   c}S c c}w )zG
        Return a list of cent values above the implied tonic.
        )rM   r/   )r0   rY   s     r)   getCentsAboveToniczScalaData.getCentsAboveTonic$  s(     $(#3#34#3R#3444s   %c                h    g }d}| j                         D ]  }||z
  }|j                  |       |} |S )z>
        Get cents values between adjacent intervals.
        r   )r[   r    )r0   postlocationcdifs        r)   getAdjacentCentszScalaData.getAdjacentCents+  sD     ((*Ah,C KKH + r3   c                    g | _         d}|D ]=  }t               }||z   |_        |j                  }| j                   j                  |       ? t	        | j                         | _        y)zw
        Given a list of adjacent cent values, create the necessary ScalaPitch
        objects and update them
        r   NrM   r   r/   r    lenrL   )r0   centListr^   r_   rY   s        r)   setAdjacentCentszScalaData.setAdjacentCents9  s^    
 AB!|BHxxH##B'	 
 d../r3   c                    g }| j                         D ])  }|j                  t        j                  |dz               + |S )z>
        Get the scale as a list of Interval objects.
        g{Gz?)ra   r    r   Interval)r0   r]   r_   s      r)   getIntervalSequencezScalaData.getIntervalSequenceG  s>     &&(AKK))!d(34 ) r3   c                    g | _         d}|D ]G  }t               }||j                  z   |_        |j                  }| j                   j                  |       I t	        | j                         | _        y)z@
        Set the scale from a list of Interval objects.
        r   Nrc   )r0   iListr^   rW   rY   s        r)   setIntervalSequencezScalaData.setIntervalSequenceQ  sc     AB!'')BHxxH##B'  d../r3   c                B   g }| j                   |j                  d| j                           |j                  d       | j                  |j                  | j                         n|j                  d       | j                  %|j                  t	        | j                               n|j                  d       |j                  d       | j
                  D ]&  }|j                  t	        |j                               ( |j                  d       dj                  |      S )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! rP   r   rO   )rJ   r    rK   rL   strrM   r/   r   )r0   msgrY   s      r)   getFileStringzScalaData.getFileString_  s     ==$JJDMM?+,

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

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

2yy~r3   )NN)rC   rD   rE   rF   r2   r	   r[   ra   rf   ri   rl   rp   rG   r3   r)   r   r      s1    DJ
0:500r3   r   c                  L    e Zd ZdZddZddZd Zd Zd Zd Z	d	 Z
d
 ZddZy)r   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                .    d | _         d | _        || _        y r,   )rJ   filedata)r0   rt   s     r)   r2   zScalaFile.__init__  s    		r3   c                    t        j                  ||d      | _        t        j                  j                  |      | _        y)z)
        Open a file for reading
        zlatin-1)encodingN)ioopenrs   r   r   basenamerJ   )r0   r'   modes      r)   rx   zScalaFile.open  s.     GGBy9	((,r3   c                    || _         y)zh
        Assign a file-like object, such as those provided by StringIO, as an open file object.
        N)rs   )r0   fileLikes     r)   openFileLikezScalaFile.openFileLike  s     	r3   c                
    d}|S )Nz<ScalaFile>rG   )r0   rs     r)   __repr__zScalaFile.__repr__  s    r3   c                8    | j                   j                          y r,   )rs   closer0   s    r)   r   zScalaFile.close  s    		r3   c                T    | j                  | j                  j                               S )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.
        )readstrrs   readr   s    r)   r   zScalaFile.read  s     ||DIINN,--r3   c                `    t        || j                        }|j                          || _        |S )zV
        Read a string and process all Tokens. Returns a ABCHandler instance.
        )r   rJ   r	   rt   )r0   strSrcsss      r)   r   zScalaFile.readstr  s)     vt}}-

		r3   c                Z    | j                         }| j                  j                  |       y r,   )writestrrs   write)r0   wss     r)   r   zScalaFile.write  s    ]]_		r3   c                l    t        | j                  t              r| j                  j                         S y)Nr   )
isinstancert   r   rp   r   s    r)   r   zScalaFile.writestr  s&    dii+99**,,r3   r,   )r   )returnrn   )rC   rD   rE   rF   r2   rx   r}   r   r   r   r   r   r   rG   r3   r)   r   r     s4    (-.r3   r   c                   d}t        | t        j                        rt        |       } t        j
                  j                  |       r| j                  d      r| }| j                  dd      } |Vt               D ]I  }t        j
                  j                  |      \  }}| j                         |j                         k(  sG|} n |8t               D ]+  }t               |   D ]  }| j                         |k(  s|} + - |7t               D ]*  }t               |   D ]  }| j                         |v s|} * , |=t               }|j                  |       |j                         }|j                          |S y)a  
    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Pathrn   r   r   existsr   r   r   r   r!   r   rx   r   r   )targetmatchr'   unused_directoryr(   altsfr   s           r)   r	   r	     sA   8 E&',,'V	ww~~f&//&"9 ^^C$F}*B#%77==#4 b||~+  }*Bz"~<<>S(E &  }*Bz"~<<>S(E &  [
WWY

	 r3   c                "   g }| j                  dd      } t               D ]\  }t        j                  j	                  |      \  }}| j                         |j                         k(  sG||vsL|j                  |       ^ t               D ]=  }t               |   D ]+  }| j                         |v s||vs|j                  |       - ? g }|D ]0  }|j                  t        j                  j                  |             2 |j                          |S )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    ry   sort)r   r   r'   r   r(   r   namess          r)   r   r     s     E ^^C$Fj!ww}}R0"<<>RXXZ'R   j:b>C||~$U?LL$ "  ERWW%%b)* 	JJLLr3   c                      e Zd Zy)TestExternalN)rC   rD   rE   rG   r3   r)   r   r   ;  s    r3   r   c                      e Zd Zd Zd Zd Zy)Testc                   d}t        |      }|j                          | j                  |j                  d       | j                  |j                  d       | j                  t        |j                        d       | j                  |j                  D cg c]  }|j                  d c}g d       | j                  |j                         D cg c]  }|d c}g d       | j                  |j                         D cg c]  }|d c}g d       | j                  |j                         D cg c]  }t        |       c}g d       y c c}w c c}w c c}w c c}w )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   )r   r	   assertEqualrL   rJ   rd   rM   r/   r[   ra   ri   rn   )r0   ro   r   r%   s       r)   testScalaScaleAzTest.testScalaScaleAB  sO   
 s^

*&67R^^,a0BNNCNqQWWSM*NC=	> 	b.C.C.EF.EQsG*.EF=	> 	b.A.A.CD.CQsG*.CD<	= 	"*@*@*BC*BQ#a&*BCC	D D G E Ds   EEE4E c                   d}t        |      }|j                          | j                  |j                  d       | j                  |j                  d       | j                  |j
                  d       | j                  |j                         D cg c]  }|d c}g d       | j                  |j                         D cg c]  }|d c}g d       | j                  |j                         D cg c]  }t        |       c}g d       t               }|j                  |j                                | j                  |j                         D cg c]  }|d c}g d       y c c}w c c}w c c}w c c}w )	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)>)r   r	   r   rL   rJ   rK   r[   ra   ri   rn   rf   )r0   ro   r   r%   ss2s        r)   testScalaScaleBzTest.testScalaScaleBg  sv   $ s^

+n5T	V 	b.C.C.EF.EQsG*.EF IZ 	[ 	b.A.A.CD.CQsG*.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QsG*.FG/	0[ G E D& Hs   E+E03E5E:c                >   d}t               }|j                  |      }| j                  |j                  d       | j                  |j	                         d d |d d        | j                  |j                         D cg c]  }t        |       c}g d       y c c}w )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
   rQ   )%<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   rL   rp   ri   rn   )r0   ro   r   r   r%   s        r)   testScalaFileAzTest.testScalaFileA  s     [ZZ_* 	))+BQ/Ra9"*@*@*BC*BQ#a&*BC<	=Cs   =BN)rC   rD   rE   r   r   r   rG   r3   r)   r   r   @  s    "DJT0l=r3   r   r   
_DOC_ORDER__main__)!rF   
__future__r   __all__rw   r?   r   r   unittestmusic21r   music21.common.typesr   r   r   music21.scale.scalar
   EnvironmentenvironLocalr   __annotations__r   r   r   r   r	   r   TestCaser   r   r   rC   mainTestrG   r3   r)   <module>r      s   > #	 
  	    )   $&{&&}5
 6@4F1 F)Z7 7tI IZG GXFR J	8$$ 	
[=8 [=@ "9j%H
H H zGT r3   