
    3j˕                   6   U d Z ddlmZ g dZddlm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m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 ddlmZ ddlmZ ddlmZmZmZmZmZm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z& ddlm'Z' ddl(m)Z) e
jT                  rddlm+Z+ ddlm,Z, ddlm-Z-  ej\                  d      Z/ G d de
j`                        Z1 G d dejd                        Z3 G d de3      Z4e3e4e1gZ5de6d<   e7d k(  rddlZ ejp                          yy)!a  
Classes and functions for creating and processing metadata associated with
scores, works, and fragments, such as titles, movements, authors, publishers,
and regions.

The :class:`~music21.metadata.Metadata` object is the main public interface to
metadata components. A Metadata object can be added to a Stream and used to set
common score attributes, such as title and composer. A Metadata object found at
offset zero can be accessed through a Stream's
:attr:`~music21.stream.Stream.metadata` property.

The following example creates a :class:`~music21.stream.Stream` object, adds a
:class:`~music21.note.Note` object, and configures and adds the
:attr:`~music21.metadata.Metadata.title` and
:attr:`~music21.metadata.Metadata.composer` properties of a Metadata object.

>>> s = stream.Score()
>>> p = stream.Part()
>>> m = stream.Measure()
>>> m.append(note.Note())
>>> p.append(m)
>>> s.append(p)
>>> s.insert(0, metadata.Metadata())
>>> s.metadata.title = 'title'
>>> s.metadata.composer = 'composer'
>>> #_DOCS_SHOW s.show()

.. image:: images/moduleMetadata-01.*
    :width: 600

A guide to the v8+ Dublin Core implementation:

The class Metadata has been completely rewritten in music21 v8 to support
significant new functionality.

The previous Metadata implementation had a list of supported workIds, and also
a list of standard contributor roles.  More than one of each contributor role
could exist, but only one of each workId.  And while there was some support for
custom contributor roles, there was no support for other custom metadata, only
the specified list of workIds.

In the v8 implementation, contributor roles are treated the same as other
non-contributor metadata.  Music21 includes a list of supported property terms,
which are pulled from Dublin Core (namespace = 'dcterms'), MARC Relator codes
(namespace = 'marcrel'), and Humdrum (namespace = 'humdrum').  Each property
term is assigned a unique name (e.g. 'composer', 'alternativeTitle', etc.).

Each metadata property can be specified by 'uniqueName' or by 'namespace:name'.
For example: `md['composer']` and `md['marcrel:CMP']` are equivalent, as are
`md['alternativeTitle']` and `md['dcterms:alternative']`. There can be more than
one of any such item (not just contributors).  And you can also have metadata
items with custom names.

For simple metadata items, like a single title, there is an easy way to get/set
them: use an attribute-style get operation (e.g. `t = md.title`).  This will always
return a single string.  If there is more than one item of that name, a summary
string will be returned.  To see the full list of metadata items in their native
value type, use a dictionary-style get operation (e.g. `titles = md['title']`).
If an item or list of items is set (whether attribute-style or dictionary-style),
any existing items of that name are deleted. To add an item or list of items
without deleting existing items, use the `md.add()` API.  See the examples below:

Set a title (overwrites any existing titles):

>>> md = metadata.Metadata()
>>> md.title = 'A Title'
>>> md.title
'A Title'
>>> md['title']
(<music21.metadata.primitives.Text A Title>,)

Set two titles (overwrites any existing titles):

>>> md['title'] = ['The Title', 'A Second Title']
>>> md['title']
(<music21.metadata.primitives.Text The Title>,
<music21.metadata.primitives.Text A Second Title>)
>>> md.title
'The Title, A Second Title'

Add a third title (leaves any existing titles in place):

>>> md.add('title', 'Third Title, A')
>>> md['title']
(<music21.metadata.primitives.Text The Title>,
<music21.metadata.primitives.Text A Second Title>,
<music21.metadata.primitives.Text Third Title, A>)
>>> md.title
'The Title, A Second Title, A Third Title'

You can also set/add/get free-form custom metadata items:

>>> md.setCustom('modification description', 'added missing sharp in measure 27')
>>> md.getCustom('modification description')
(<music21.metadata.primitives.Text added missing sharp in measure 27>,)

Adding another custom element for the same description creates a second
entry.

>>> md.addCustom('modification description', 'deleted redundant natural in measure 28')
>>> md.getCustom('modification description')
(<music21.metadata.primitives.Text added missing sharp in measure 27>,
 <music21.metadata.primitives.Text deleted redundant natural in measure 28>)

Metadata does not explicitly support client-specified namespaces, but by using
getCustom/addCustom/setCustom, clients can set anything they want. For instance, to
embed the old SoundTracker .MOD format's sample name, a .MOD file parser could use
`md.addCustom('soundtracker:SampleName', 'Bassoon')`, and a .MOD file writer that
understood 'soundtracker:' metadata could then write it back accurately to one of
those files. Custom metadata (whether namespaced this way, or free form) can also
be written to various other file formats without interpretation, as long as there
is a place for it (e.g. in the '<miscellaneous>' tag in MusicXML).

In music21 v8, primitives.Text has been updated to add `isTranslated` to keep track of
whether the text has been translated,
as well as an encoding scheme, that specifies which standard should be used to parse
the string.  See metadata/primitives.py for more information.
    )annotations)AmbitusShortMetadataRichMetadataContributor	CopyrightCreatorDateDateBetweenDatePrimitiveDateRelativeDateSelection
DateSingleImprintText	ValueTypePropertyDescriptionbundlescaching
primitives
properties)IterableN)overload)base)common)DocOrderOffsetQL)defaults)environment)exceptions21)interval)r   )r   )r   )r   r   r	   r
   r   r   r   r   r   r   r   r   )r   )r   keymetertempometadatac                  :    e Zd ZU dZded<   ded<   ded<   ded<   y)	r   z
    A namedtuple representing the ambitus (range) using ints and strings

    >>> metadata.AmbitusShort(7, 'P5', 'B3', 'F#4')
    AmbitusShort(semitones=7, diatonic='P5', pitchLowest='B3', pitchHighest='F#4')
    int	semitonesstrdiatonicpitchLowestpitchHighestN)__name__
__module____qualname____doc____annotations__     F/DATA/.local/lib/python3.12/site-packages/music21/metadata/__init__.pyr   r      s     NMr6   r   c                  :    e Zd ZdZdZdQ fdZ	 	 	 	 	 	 dRdZdSdZdTdZdTdZ	e
dUd       Ze
dVd	       Ze
dWd
       ZdXdZedYd       ZedZd       Zed        Zej&                  d[d       Zddddd	 	 	 	 	 	 	 	 	 d\dZd Zd] fdZe	 	 	 	 d^d       Ze	 	 	 	 d_d       Zed`d       ZdadZdbdZdcdZdddZ	 	 de	 	 	 	 	 dfdZed        Zej&                  d[d       Zed         Zej&                  d[d!       Zed"        Zej&                  dgd#       Zed$        Z e j&                  d[d%       Z edhd&       Z!e!j&                  d[d'       Z!edhd(       Z"e"j&                  d[d)       Z"edhd*       Z#e#j&                  d[d+       Z#edhd,       Z$e$j&                  d[d-       Z$ed.        Z%e%j&                  d[d/       Z%ed0        Z&e&j&                  d[d1       Z&ed2        Z'e'j&                  dgd3       Z'ed4        Z(e(j&                  d[d5       Z(ed6        Z)e)j&                  dgd7       Z)ed8        Z*e*j&                  d[d9       Z*edhd:       Z+e+j&                  d[d;       Z+edhd<       Z,e,j&                  d[d=       Z,edhd>       Z-e-j&                  d[d?       Z-ed@        Z.e.j&                  d[dA       Z.edhdB       Z/dVdCZ0didDZ1djdEZ2dkdFZ3dldGZ4e
dmdH       Z5e
dldI       Z6e
dmdJ       Z7e
dmdK       Z8e
dndL       Z9dodMZ:dpdNZ;dpdOZ<e
dqdP       Z= xZ>S )rr   a  
    Metadata objects contain information about a work, including title, composer,
    dates, and other relevant information. Metadata objects can also cover a fragment
    of a Score such as individual parts or a range of measures.

    Metadata is a :class:`~music21.base.Music21Object` subclass, meaning that it
    can be positioned on a Stream by offset and have a
    :class:`~music21.duration.Duration`.

    In many cases, each Stream will have a single Metadata object at the zero-offset
    position.

    To get a simple string, use attribute-style access by unique name.
    Some workIds from music21 v7 have been renamed (e.g. 'date' has been renamed
    to 'dateCreated').  The old music21 v7 name in these cases is still supported
    when you use attribute-style access.

    >>> md = metadata.Metadata(title='Concerto in F')
    >>> md.title
    'Concerto in F'

    Attribute access also works with three-letter workId abbreviations (these are
    grandfathered in from music21 v7; abbreviations have not been added for
    new-in-v8 metadata items):

    >>> md = metadata.Metadata(otl='Concerto in F')
    >>> md.otl
    'Concerto in F'
    >>> md.title
    'Concerto in F'

    It is also possible to set a list/tuple of values or get a tuple full of
    (richer-typed) values using dictionary-style access.

    >>> md = metadata.Metadata()
    >>> md['composer'] = ['Billy Strayhorn', 'Duke Ellington']
    >>> md['composer']
    (<music21.metadata.primitives.Contributor composer:Billy Strayhorn>,
     <music21.metadata.primitives.Contributor composer:Duke Ellington>)
    >>> md.composer
    'Billy Strayhorn and Duke Ellington'
    >>> md.contributors
    (<music21.metadata.primitives.Contributor composer:Billy Strayhorn>,
     <music21.metadata.primitives.Contributor composer:Duke Ellington>)

    Here is the list of grandfathered v7 synonyms, which may disappear in a
    future version:

    >>> sorted(metadata.properties.ALL_MUSIC21_WORK_IDS)
    ['commission', 'date', 'dedication', 'volume']

    And here are their new v8 standard unique names:

    >>> sorted(metadata.properties.MUSIC21_WORK_ID_TO_UNIQUE_NAME.values())
    ['commissionedBy', 'dateCreated', 'dedicatedTo', 'volumeNumber']
    ic                   i }i }|j                         D ]"  \  }}|t        j                  v r|||<   |||<   $ t        |   di | i | _        |j                         D ]  \  }}t        | ||        t        j                  g| d<   y )Nsoftwarer5   )	itemsr   ALL_LEGAL_ATTRIBUTESsuper__init__	_contentssetattrr   r:   )selfkeywordsm21BaseKeywords
myKeywordsattrvalue	__class__s         r7   r>   zMetadata.__init__  s    ,.')

 $>>+KD%z666#(
4 (-%	 , 	+?+57%++-KD%D$& . %--.Zr6   c                ,    | j                  ||d       y)u  
        Adds a single item or multiple items with this name, leaving any existing
        items with this name in place.

        The name can be the item's uniqueName or 'namespace:name'.  If it is
        not the uniqueName or namespaceName of one of the standard metadata
        properties, KeyError will be raised.

        >>> md = metadata.Metadata()
        >>> md.add('average duration', '180 minutes')
        Traceback (most recent call last):
        KeyError: "Name='average duration' is not a standard metadata name...

        Example of adding a composer and two titles:

        >>> md.add('composer', 'Houcine Slaoui')
        >>> md['composer']
        (<music21.metadata.primitives.Contributor composer:Houcine Slaoui>,)

        >>> md.add('title', metadata.Text('الماريكان', language='ar'))
        >>> md.add('title', metadata.Text('The Americans',  language='en'))
        >>> titles = md['title']
        >>> titles
        (<music21.metadata.primitives.Text الماريكان>,
         <music21.metadata.primitives.Text The Americans>)
        >>> titles[0].language
        'ar'
        >>> titles[1].language
        'en'

        If you do in fact want to overwrite any existing items with this name,
        you can use dictionary-style or attribute-style setting instead.
        See :meth:`~music21.metadata.Metadata.__setitem__` and
        :meth:`~music21.metadata.Metadata.__setattr__` for details.
        FisCustomN_addrA   namerF   s      r7   addzMetadata.add3  s    N 			$	.r6   c                (    | j                  |d      S )a/  
        Gets any custom-named metadata items. The name can be free-form,
        or it can be a custom 'namespace:name'.

        getCustom always returns tuple[Text, ...], which may be empty.

        >>> md = metadata.Metadata()
        >>> md.setCustom('measure with 2nd ending', 'measure 128')
        >>> md.getCustom('measure with 2nd ending')
        (<music21.metadata.primitives.Text measure 128>,)

        A second item can also be added.

        >>> md.addCustom('measure with 2nd ending', 'measure 192')
        >>> measures = md.getCustom('measure with 2nd ending')

        >>> isinstance(measures, tuple)
        True
        >>> len(measures)
        2
        >>> measures
        (<music21.metadata.primitives.Text measure 128>,
         <music21.metadata.primitives.Text measure 192>)
        TrI   )_getrA   rN   s     r7   	getCustomzMetadata.getCustom\  s    2 yyy--r6   c                ,    | j                  ||d       y)a  
        Adds any custom-named metadata items. The name can be free-form,
        or it can be a custom 'namespace:name'.

        addCustom takes a single object of any type, or a list/tuple of
        objects of any type.  The object(s) will be converted to Text.

        >>> md = metadata.Metadata()
        >>> md.addCustom('measure with 2nd ending', 'measure 128')
        >>> md.getCustom('measure with 2nd ending')
        (<music21.metadata.primitives.Text measure 128>,)

        An item list can also be added.

        >>> md.addCustom('measure with 2nd ending', ['measure 192', 'measure 256'])
        >>> measures = md.getCustom('measure with 2nd ending')

        >>> isinstance(measures, tuple)
        True
        >>> len(measures)
        3
        >>> measures
        (<music21.metadata.primitives.Text measure 128>,
         <music21.metadata.primitives.Text measure 192>,
         <music21.metadata.primitives.Text measure 256>)
        TrI   NrK   rM   s      r7   	addCustomzMetadata.addCustomw  s    6 			$	-r6   c                ,    | j                  ||d       y)a  
        Sets any custom-named metadata items (deleting any existing such items).
        The name can be free-form, or it can be a custom 'namespace:name'.

        setCustom takes a single object of any type, or a list/tuple of
        objects of any type.  The object(s) will be converted to Text.

        >>> md = metadata.Metadata()
        >>> md.setCustom('measure with 2nd ending', 'measure 128')
        >>> md.getCustom('measure with 2nd ending')
        (<music21.metadata.primitives.Text measure 128>,)

        An item list can also be set.

        >>> md.setCustom('measure with 2nd ending', ['measure 192', 'measure 256'])
        >>> measures = md.getCustom('measure with 2nd ending')

        >>> isinstance(measures, tuple)
        True
        >>> len(measures)
        2
        >>> measures
        (<music21.metadata.primitives.Text measure 192>,
         <music21.metadata.primitives.Text measure 256>)
        TrI   N)_setrM   s      r7   	setCustomzMetadata.setCustom  s    4 			$	-r6   c                B    t         j                  j                  | d      S )a  
        Translates a unique name to the associated standard property's
        namespace name (i.e. the property's name in the form 'namespace:name').

        An example from the MARC Relators namespace: the namespace name of
        'librettist' is 'marcrel:LBT'.

        >>> metadata.Metadata.uniqueNameToNamespaceName('librettist')
        'marcrel:LBT'

        Returns None if no such associated standard property can be found.

        >>> metadata.Metadata.uniqueNameToNamespaceName('average duration') is None
        True

        An example from the Dublin Core namespace: the namespace name of
        'alternativeTitle' is 'dcterms:alternative'.

        >>> metadata.Metadata.uniqueNameToNamespaceName('alternativeTitle')
        'dcterms:alternative'
        N)r   UNIQUE_NAME_TO_NAMESPACE_NAMEget)
uniqueNames    r7   uniqueNameToNamespaceNamez"Metadata.uniqueNameToNamespaceName  s    0 77;;JMMr6   c                B    t         j                  j                  | d      S )a  
        Translates a standard property namespace name ('namespace:name') to that
        standard property's uniqueName.

        An example from the MARC Relators namespace: the unique name of
        'marcrel:LBT' is 'librettist'.

        >>> metadata.Metadata.namespaceNameToUniqueName('marcrel:LBT')
        'librettist'

        Returns None if no such standard property exists.

        >>> metadata.Metadata.namespaceNameToUniqueName('soundtracker:SampleName') is None
        True

        An example from the Dublin Core namespace: the unique name of
        'dcterms:alternative' is 'alternativeTitle'.

        >>> metadata.Metadata.namespaceNameToUniqueName('dcterms:alternative')
        'alternativeTitle'
        N)r   NAMESPACE_NAME_TO_UNIQUE_NAMEr[   )namespaceNames    r7   namespaceNameToUniqueNamez"Metadata.namespaceNameToUniqueName  s    . 77;;M4PPr6   c                f    | syt         j                  j                  | d      }|y|j                  S )a  
        Determines if a unique name is associated with a standard contributor
        property.  Returns False if no such associated standard contributor
        property can be found.

        We allow uniqueName == None, since None is a valid custom contributor role.

        Example: 'librettist' and 'otherContributor' are unique names of standard
        contributors.

        >>> metadata.Metadata.isContributorUniqueName('librettist')
        True
        >>> metadata.Metadata.isContributorUniqueName('otherContributor')
        True

        Example: 'alternativeTitle' is the unique name of a standard property,
        but it is not a contributor.

        >>> metadata.Metadata.isContributorUniqueName('alternativeTitle')
        False

        Example: 'average duration' is not the unique name of a standard property.

        >>> metadata.Metadata.isContributorUniqueName('average duration')
        False
        FNr   #UNIQUE_NAME_TO_PROPERTY_DESCRIPTIONr[   isContributorr\   props     r7   isContributorUniqueNamez Metadata.isContributorUniqueName  s;    8 ::>>z4P 	 <!!!r6   c                L    | j                  |      ry| j                  |      ryy)ac  
        Determines if name is either a 'namespace:name' or a 'uniqueName'
        associated with a standard property.

        Returns False if no such associated standard property can be found.

        >>> md = metadata.Metadata()
        >>> md.isStandardName('librettist')
        True

        'marcrel:LBT' is the namespace name of 'librettist'

        >>> md.isStandardName('marcrel:LBT')
        True

        Some examples of non-standard (custom) names.

        >>> md.isStandardName('average duration')
        False
        >>> md.isStandardName('soundtracker:SampleName')
        False
        TF)_isStandardNamespaceName_isStandardUniqueNamerR   s     r7   isStandardNamezMetadata.isStandardName  s)    . ((.%%d+r6   c                $    | j                  d      S )aE  
        Returns a tuple of software names/versions.

        Returns an empty tuple if no software names/versions exist,
        but this is rare, since music21 adds its own version when
        initializing a Metadata object.

        >>> md = metadata.Metadata()
        >>> md.software
        ('music21 v...',)
        >>> md.add('software', 'Finale for Macintosh')
        >>> md.software
        ('music21 v...',
         'Finale for Macintosh')
        >>> md['software']
        (<music21.metadata.primitives.Text music21 v...>,
         <music21.metadata.primitives.Text Finale for Macintosh>)

        Note that `.software` is an exception to the general rule that
        singular looking properties return a string.  In fact, it is always
        plural and returns a tuple of strings.  There is no singular version
        r:   _getPluralAttributerA   s    r7   r:   zMetadata.software.  s    0 ''
33r6   c                v    g }| j                  ddd      D ]  \  }}|j                  |        t        |      S )a  
        Returns a tuple of all the Contributors found in the metadata.
        Returns an empty tuple if no Contributors exist.

        >>> md = metadata.Metadata()
        >>> md.composer = 'Richard Strauss'
        >>> md.librettist = 'Oscar Wilde'

        When we add something that is not a person, such as a title
        (whether through `.attribute` setting, `[item]` setting,
        or the :meth:`~music21.metadata.Metadata.add` method), it will not show up
        in the list of contributors.

        >>> md.add('title', 'Salome')
        >>> contribs = md.contributors
        >>> contribs
        (<music21.metadata.primitives.Contributor composer:Richard Strauss>,
         <music21.metadata.primitives.Contributor librettist:Oscar Wilde>)

        Note that `.contributors` cannot be set.  Add them separately via
        specific setters or the `.addContributor()` method.
        TFskipNonContributorsreturnPrimitivesreturnSorted)allappendtuple)rA   output_contribs       r7   contributorszMetadata.contributorsH  sH    0 %'(($(!%" # $JAw MM'"	$
 V}r6   c                $    | j                  d      S )u}  
        Returns the copyright as a str.
        Returns None if no copyright exists in the metadata.
        Returns all the copyright values in one string (with ', ' between them)
        if multiple copyrights exist in the metadata. Use md['copyright'] to
        get all the copyrights as Copyright objects.

        >>> md = metadata.Metadata()
        >>> md.copyright is None
        True
        >>> md.copyright = 'Copyright © 1896, Éditions Durand (expired)'
        >>> md.copyright
        'Copyright © 1896, Éditions Durand (expired)'

        Using dictionary-style access, you can use either the uniqueName ('copyright')
        or the namespaceName ('dcterms:rights').  Here you can see how multiple
        copyrights are handled.

        >>> md.copyright = 'Copyright © 1984 All Rights Reserved'
        >>> md.copyright
        'Copyright © 1984 All Rights Reserved'

        To add another copyright to the list, call md.add().

        >>> md.add('copyright', 'Lyrics copyright © 1987 All Rights Reserved')

        md.copyright will now return both copyrights in one string

        >>> md.copyright
        'Copyright © 1984 All Rights Reserved, Lyrics copyright © 1987 All Rights Reserved'

        md['copyright'] will return a tuple containing both Copyright objects.

        >>> md['copyright']
        (<music21.metadata.primitives.Copyright Copyright © 1984 All Rights Reserved>,
         <music21.metadata.primitives.Copyright Lyrics copyright © 1987 All Rights Reserved>)

        You can set str, Text, or Copyright values, and they will be converted to
        Copyright automatically if necessary.  Note that 'dcterms:rights'
        is Dublin Core terminology for 'copyright', and can be used interchangeably
        with 'copyright' as a metadata dictionary-style key.

        >>> md.copyright = metadata.Text('Copyright © 1984')
        >>> md['copyright']
        (<music21.metadata.primitives.Copyright Copyright © 1984>,)
        >>> md.copyright = metadata.Copyright('Copyright © 1985', role='something')
        >>> md['dcterms:rights']
        (<music21.metadata.primitives.Copyright Copyright © 1985>,)
        	copyright_getSingularAttributerp   s    r7   r~   zMetadata.copyrighth  s    f ))+66r6   c                    || _         yz7
        For type checking only. Does not run.
        N)r~   rA   rF   s     r7   r~   zMetadata.copyright      
 r6   FTskipContributorsrs   rt   ru   c               4   g }| j                   j                         D ]Y  \  }}| j                  |      }|r|r|r|s!|D ]4  }	|r|j                  ||	f       |j                  |t	        |	      f       6 [ |rt        t        |            S t        |      S )u  
        Returns the values stored in this metadata as a Tuple of (uniqueName, value) pairs.
        There are four bool options. The three that are new in v8 (skipNonContributors,
        returnPrimitives, returnSorted) are defaulted to behave like v7.

        If skipContributors is True, only non-contributor metadata will be returned.  If
        skipNonContributors is True, only contributor metadata will be returned.  If both
        of these are True, the returned Tuple will be empty. If returnPrimitives is False
        (default), values are all converted to str.  If returnPrimitives is True, the values
        will retain their original ValueType (e.g. Text, Contributor, Copyright, etc.).  If
        returnSorted is False, the returned Tuple will not be sorted by uniqueName (the
        default behavior is to sort).

        Note that we cannot properly type-hint the return value, since derived classes (such
        as RichMetadata) are allowed to return their own typed values that might not be str
        or ValueType.

        >>> c = corpus.parse('corelli/opus3no1/1grave')
        >>> c.metadata.all()
        (('arranger', 'Michael Scott Cuthbert'),
         ('composer', 'Arcangelo Corelli'),
         ('copyright', '© 2014, Creative Commons License (CC-BY)'),
         ('corpusFilePath', 'corelli/opus3no1/1grave.xml'),
         ('fileFormat', 'musicxml'),
         ('filePath', '...corpus/corelli/opus3no1/1grave.xml'),
         ('movementName', 'Sonata da Chiesa, No. I (opus 3, no. 1)'),
         ('software', 'Dolet...'),
         ('software', 'Finale...'),
         ('software', 'music21 v...'))

        >>> c.metadata.date = metadata.DateRelative('1689', 'onOrBefore')
        >>> c.metadata.localeOfComposition = 'Rome'
        >>> c.metadata.all(skipContributors=True)
        (('copyright', '© 2014, Creative Commons License (CC-BY)'),
         ('corpusFilePath', 'corelli/opus3no1/1grave.xml'),
         ('dateCreated', '1689/--/-- or earlier'),
         ('fileFormat', 'musicxml'),
         ('filePath', '...corpus/corelli/opus3no1/1grave.xml'),
         ('localeOfComposition', 'Rome'),
         ('movementName', 'Sonata da Chiesa, No. I (opus 3, no. 1)'),
         ('software', 'Dolet...'),
         ('software', 'Finale...'),
         ('software', 'music21 v...'))

        >>> c.metadata.all(returnPrimitives=True, returnSorted=False)
        (('software', <music21.metadata.primitives.Text music21 v...>),
         ('software', <music21.metadata.primitives.Text Finale ...>),
         ('software', <music21.metadata.primitives.Text Dolet Light...>),
         ('movementName', <...Text Sonata da Chiesa, No. I (opus 3, no. 1)>),
         ('composer', <music21.metadata.primitives.Contributor composer:Arcangelo Corelli>),
         ...
         ('dateCreated', <music21.metadata.primitives.DateRelative 1689/--/-- or earlier>),
         ('localeOfComposition', <music21.metadata.primitives.Text Rome>))

        >>> c.metadata.all(skipNonContributors=True, returnPrimitives=True, returnSorted=True)
        (('arranger', <music21.metadata.primitives.Contributor arranger:Michael Scott Cuthbert>),
         ('composer', <music21.metadata.primitives.Contributor composer:Arcangelo Corelli>))
        )r?   r;   _isContributorUniqueNamerw   r,   rx   sorted)
rA   r   rs   rt   ru   allOutr\   	valueListre   rF   s
             r7   rv   zMetadata.all  s    F +- &*^^%9%9%;!J	"&"?"?
"KMM"= ##MM:u"56MM:s5z":;	 # &< ((V}r6   c                $    | j                  |      S )ac  
        Utility attribute access for all uniqueNames, grandfathered workIds,
        and grandfathered workId abbreviations.  Many grandfathered workIds
        have explicit property definitions, so they won't end up here.

        These always return str or None.  If there is more than one item
        for a particular name, we will try to summarize or list them all
        in one returned string.

        If name is not a valid attribute (uniqueName, grandfathered workId,
        or grandfathered workId abbreviation), then AttributeError is raised.

        >>> md = metadata.Metadata()

        An example of setting an attribute by uniqueName ('description').

        >>> md.description = metadata.Text('A description set via uniqueName', language='en')

        An example of setting an attribute by grandfathered workId ('dedication')

        >>> md.dedication = 'A dedication set via grandfathered workId'

        An example of setting an attribute by grandfathered workId abbreviation ('otl')

        >>> md.otl = metadata.Text('A title set via grandfathered workId abbreviation')

        See how we can get all three attributes by uniqueName, even though two of them
        were set by other names.

        An example of getting (by uniqueName) an attribute that was set by uniqueName.

        >>> md.description
        'A description set via uniqueName'

        An example of getting (by uniqueName) an attribute that was set by workId.
        The uniqueName for the grandfathered workId 'dedication' is 'dedicatedTo'.

        >>> md.dedicatedTo
        'A dedication set via grandfathered workId'

        An example of getting (by uniqueName) an attribute that was set by abbreviation.
        The uniqueName for the grandfathered workId abbreviation 'otl' is 'title'.

        >>> md.title
        'A title set via grandfathered workId abbreviation'

        An example of getting an attribute for which there are multiple values.

        >>> md.add('description', 'A description added via md.add()')
        >>> md.description
        'A description set via uniqueName, A description added via md.add()'
        r   rR   s     r7   __getattr__zMetadata.__getattr__  s    x ))$//r6   c                   |t         j                  v r4|2t        |t              r"t        |t              st        d| d| d      |t         j                  v r/t        |t              rt        |t              rt        d| d      |t         j                  v r&| j                  t         j                  |   |d       y|t         j                  v r&| j                  t         j                  |   |d       y|t         j                  v r&| j                  t         j                  |   |d       y|dv r|dd	 }| j                  ||d       yt        | 1  ||       y)
a  
        Attribute setter for all uniqueNames, grandfathered workIds,
        and grandfathered workId abbreviations, as well as the plural
        properties ('composers', 'librettists', 'lyricists') and the
        three new fileInfo properties.

        These can take a single value of any type (or, if appropriate, an
        iterable of any type of value), and will convert to the appropriate
        internal valueType.
        Nzmd.z+ can only be set to a single value; set md[z] to multiple values instead.z: can only be set to an iterable (e.g. a list, tuple, etc).FrI   )	composerslibrettists	lyricists)r   ALL_SINGLE_ATTRIBUTE_NAMES
isinstancer   r,   
ValueErrorALL_PLURAL_ATTRIBUTE_NAMESrZ   rW   !MUSIC21_WORK_ID_TO_NAMESPACE_NAME&MUSIC21_ABBREVIATION_TO_NAMESPACE_NAMEr=   __setattr__)rA   rN   rF   r\   rG   s       r7   r   zMetadata.__setattr__;  s|   0 :888!"5(3&uc2 3tf -++/&0M"O P P :888eX.*UC2H $YZ 
 :;;;II88>  
  :???II<<TB  
  :DDDIIAA$G  
  <<crJIIj%%I8 	D%(r6   c                     y Nr5   rA   r#   s     r7   __getitem__zMetadata.__getitem__  s     	r6   c                     y r   r5   r   s     r7   r   zMetadata.__getitem__  s    
 	r6   c                     y r   r5   r   s     r7   r   zMetadata.__getitem__  s    r6   c                ^    t        |t              st        d      | j                  |d      S )a  
        "Dictionary key" access for all standard uniqueNames and
        standard keys of the form 'namespace:name'.

        These always return tuple[ValueType, ...], which may be empty.

        If key is not a standard uniqueName or standard 'namespace:name',
        then KeyError is raised.

        >>> md = metadata.Metadata()
        >>> md['average duration']
        Traceback (most recent call last):
        KeyError: "Name='average duration' is not a standard metadata name...

        Example: setting, then getting (dictionary style) a single value. Note that
        it must be set as a single element list/tuple, and is always returned as a
        single element tuple.

        >>> md['description'] = [
        ...     metadata.Text('For the coronation of Catherine the Great.', language='en')
        ... ]
        >>> descs = md['description']
        >>> descs
        (<music21.metadata.primitives.Text For the coronation of Catherine the Great.>,)

        A second description can also be added.

        >>> md.add('description', 'In five sections, unique for its time.')
        >>> descs = md['description']
        >>> isinstance(descs, tuple)
        True
        >>> len(descs)
        2
        >>> descs
        (<music21.metadata.primitives.Text For the coronation of Catherine the Great.>,
         <music21.metadata.primitives.Text In five sections, unique for its time.>)
        >>> descs[0].language
        'en'
        >>> descs[1].language is None
        True
        metadata key must be strFrI   )r   r,   KeyErrorrQ   r   s     r7   r   zMetadata.__getitem__  s.    T #s#566yyuy--r6   c                b    t        |t              st        d      | j                  ||d       y)ao  
        "Dictionary key" access for all standard uniqueNames and
        standard keys of the form 'namespace:name'.

        If key is not a standard uniqueName or standard 'namespace:name',
        then KeyError is raised.

        >>> md = metadata.Metadata()
        >>> md['average duration'] = ['180 minutes']
        Traceback (most recent call last):
        KeyError: "Name='average duration' is not a standard metadata name...

        KeyError is also raised for non-str keys.

        >>> md[3] = ['180 minutes']
        Traceback (most recent call last):
        KeyError: 'metadata key must be str'

        r   FrI   N)r   r,   r   rW   )rA   r#   rF   s      r7   __setitem__zMetadata.__setitem__  s,    ( #s#566		#uu	-r6   c                    t        |t              st        j                  d|       | j	                  |j
                        }| j                  ||d       y)a  
        Assign a :class:`~music21.metadata.Contributor` object to this
        Metadata.

        >>> md = metadata.Metadata(title='Gaelic Symphony')
        >>> c = metadata.Contributor()
        >>> c.name = 'Beach, Amy'
        >>> c.role = 'composer'
        >>> md.addContributor(c)
        >>> md.composer
        'Beach, Amy'

        Add maiden name as an alternative composer name:

        >>> c_alt = metadata.Contributor()
        >>> c_alt.name = 'Cheney, Amy Marcy'
        >>> c_alt.role = 'composer'
        >>> md.addContributor(c_alt)
        >>> md.composers
        ('Beach, Amy', 'Cheney, Amy Marcy')

        >>> md.search('Beach')
        (True, 'composer')
        >>> md.search('Cheney')
        (True, 'composer')

        Note that in this case, a "composerAlias" would probably be a more
        appropriate role than a second composer.

        All contributor roles are searchable, even if they are not standard roles:

        >>> dancer = metadata.Contributor()
        >>> dancer.names = ['Merce Cunningham', 'Martha Graham']
        >>> dancer.role = 'interpretive dancer'
        >>> md.addContributor(dancer)
        >>> md.search('Cunningham')
        (True, 'interpretive dancer')
        z&supplied object is not a Contributor: FrI   N)r   r   r    MetadataException_contributorRoleToUniqueNamerolerL   )rA   cr\   s      r7   addContributorzMetadata.addContributor  sR    P ![)008<> >;;AFFC
		*a%	0r6   c                    g }| j                  ddd      D ]&  \  }}|j                  |k(  s|j                  |       ( t        |      S )a  
        Return a :class:`~music21.metadata.Contributor` if defined for a
        provided role.

        We allow role == None, since None is a valid custom contributor role.

        >>> md = metadata.Metadata(title='Violin Concerto')

        >>> c = metadata.Contributor()
        >>> c.name = 'Price, Florence'
        >>> c.role = 'composer'
        >>> md.addContributor(c)
        >>> cTuple = md.getContributorsByRole('composer')
        >>> cTuple
        (<music21.metadata.primitives.Contributor composer:Price, Florence>,)

        >>> cTuple[0].name
        'Price, Florence'

        Some musicxml files have contributors with no role defined.  To get
        these contributors, search for getContributorsByRole(None).  N.B. upon
        output to MusicXML, music21 gives these contributors the generic role
        of "creator"

        >>> c2 = metadata.Contributor()
        >>> c2.name = 'Baron van Swieten'
        >>> md.add('otherContributor', c2)
        >>> noRoleTuple = md.getContributorsByRole(None)
        >>> len(noRoleTuple)
        1
        >>> noRoleTuple[0].role is None
        True
        >>> noRoleTuple[0].name
        'Baron van Swieten'
        TFrr   )rv   r   rw   rx   )rA   r   resultrz   r{   s        r7   getContributorsByRolezMetadata.getContributorsByRole  sU    H %'(($(!%" # $JAw ||t#g&$ V}r6   c                   d}g }||sy||r|j                         \  }||j                         }d}	 | j                  |      }|r|D ]  }|j                  ||f        d}|s| j                  d      D ]  \  }	}| j                  |	      s||	j                         v r|j                  ||	f       d} nt        j                  j                  |	d      }
|
d||
j                         v sw|j                  ||
f       d} nA n?| j                  d      D ]*  \  }	}| j                  |	      s|j                  ||	f       , | j                  ddd      D ]  \  }}|W|j                  |j                         dk7  r(|j                  +|j                         |j                  j                         vr_|j                  D ](  }|j                  t        |      |j                  f       *  d}t        t        j                        rd}}nKt        t              r;t!        fdd	D              r'd}t#        j$                  t"        j&                  
      }|r7|5|D ]/  \  }}t        |t              s|j)                  |      }|+d|fc S  yt+              r|D ]  \  }} |      sd|fc S  y|D ]  \  }}t        |t              r1t              j                         |j                         v rd|fc S t        |t,              r!t/        d      rj0                  |k(  rd|fc S |k(  s~d|fc S  y# t        $ r Y w xY w)a  
        Search one or all fields with a query, given either as a string or a
        regular expression match.

        >>> md = metadata.Metadata()
        >>> md.composer = 'Joplin, Scott'
        >>> md.title = 'Maple Leaf Rag'

        >>> md.search(
        ...     'joplin',
        ...     field='composer',
        ...     )
        (True, 'composer')

        Note how the incomplete field name in the following example is still
        matched:

        >>> md.search(
        ...     'joplin',
        ...     field='compos',
        ...     )
        (True, 'composer')

        These don't work (Richard didn't have the sense of rhythm to write this!)

        >>> md.search(
        ...     'Wagner',
        ...     field='composer',
        ...     )
        (False, None)

        >>> md.search('Wagner')
        (False, None)

        >>> md.search('leaf')
        (True, 'title')

        >>> md.search(
        ...     'leaf',
        ...     field='composer',
        ...     )
        (False, None)

        >>> md.search(
        ...     'leaf',
        ...     field='title',
        ...     )
        (True, 'title')

        >>> md.search('leaf|entertainer')
        (True, 'title')

        >>> md.search('opl(.*)cott')
        (True, 'composer')

        * New in v4: use a keyword argument to search that field directly:

        >>> md.search(composer='Joplin')
        (True, 'composer')
        N)FNFT)r   rr   contributorc              3  &   K   | ]  }|v  
 y wr   r5   ).0	characterquerys     r7   	<genexpr>z"Metadata.search.<locals>.<genexpr>  s     D)Yi5()s   z*.|+?{})flagssharps)popitemlowerro   rw   AttributeErrorrv   rk   r   UNIQUE_NAME_TO_MUSIC21_WORK_IDr[   r   namesr,   r   tPatternanyrecompile
IGNORECASEsearchcallabler*   hasattrr   )rA   r   fieldrB   reQueryvalueFieldPairsmatchvaluesrF   r\   workIdrz   r{   rN   useRegex
innerFieldmatchReSearchs    `               r7   r   zMetadata.search>  so   J #'=U]8 ]u}#++-LE5KKMEE11%8!''..u~> "( E )-4)H%J55jA  
 0 0 22'..z/BC $ (2'P'P'T'T"D(F ~  .'..v? $5 *I8 &*XXtX%D!
E--j9#**E:+>? &E (($(!%" # $JAw  <<'EKKM],J<<+W\\EWEWEY0Y&&D	7<<'@A &$  eQYY'HG$D)DDHjjbmm<G+%4!zeS)$+NN5$9M$0#Z// &5. # e_%4!z<++ &5   &5!zeS)JE{{}5#Z//uc*#E84!LLE1++e^++ &5 q " s   /M
 
	MMc                $    | j                  d      S )z
        Get or set the alternative title.

        >>> md = metadata.Metadata(popularTitle='Eroica')
        >>> md.alternativeTitle = 'Heroic Symphony'
        >>> md.alternativeTitle
        'Heroic Symphony'
        alternativeTitler   rp   s    r7   r   zMetadata.alternativeTitle  s     ))*<==r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.alternativeTitle  s    
 !&r6   c                $    | j                  d      S )a  
        Get or set the composer of this work. Only the first composer can be
        got or set via properties.

        The composer attribute does not live in Metadata, but creates a
        :class:`~music21.metadata.Contributor` object in the .contributors
        object.

        >>> md = metadata.Metadata(
        ...     title='...(Iphigenia)',
        ...     )
        >>> md.composer = 'Shorter, Wayne'

        You can set multiple composers by setting them dictionary-style
        or by using `md.add`.
        >>> md.add('composer', 'Spalding, Esperanza')

        The `Metadata.composer` attribute returns a summary string if there is
        more than one composer.

        >>> md.composer
        'Shorter, Wayne and Spalding, Esperanza'
        composerr   rp   s    r7   r   zMetadata.composer  s    2 ))*55r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.composer4      
 r6   c                $    | j                  d      S )a@  
        Get a tuple or set an iterable of strings of all composer roles.

        >>> md = metadata.Metadata(title='Yellow River Concerto')
        >>> md.composers = ['Xian Xinghai', 'Yin Chengzong']

        (Yin Chengzong might be better called "Arranger" but this is for
        illustrative purposes)

        >>> md.composers
        ('Xian Xinghai', 'Yin Chengzong')

        Might as well add a third composer to the concerto committee?

        >>> contrib3 = metadata.Contributor(role='composer', name='Chu Wanghua')
        >>> md.add('composer', contrib3)
        >>> md.composers
        ('Xian Xinghai', 'Yin Chengzong', 'Chu Wanghua')

        If there are no composers, returns an empty list:

        >>> md = metadata.Metadata(title='Sentient Algorithmic Composition')
        >>> md.composers
        ()
        r   rn   rp   s    r7   r   zMetadata.composers;  s    6 ''
33r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.composersX  r   r6   c                $    | j                  d      S )a  
        Get or set the creation date of this work as one of the following date
        objects:

        :class:`~music21.metadata.DateSingle`,
        :class:`~music21.metadata.DateRelative`,
        :class:`~music21.metadata.DateBetween`,
        :class:`~music21.metadata.DateSelection`,

        >>> md = metadata.Metadata(
        ...     title='Third Symphony',
        ...     popularTitle='Eroica',
        ...     composer='Beethoven, Ludwig van',
        ...     )
        >>> md.dateCreated = '1805'
        >>> md.dateCreated
        '1805/--/--'

        >>> md.dateCreated = metadata.DateBetween(['1803/01/01', '1805/04/07'])
        >>> md.dateCreated
        '1803/01/01 to 1805/04/07'
        dateCreatedr   rp   s    r7   r   zMetadata.dateCreated_  s    0 ))-88r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.dateCreatedy      
 !r6   c                $    | j                  d      S )z=
        Get or set the file format that was parsed.
        
fileFormatr   rp   s    r7   r   zMetadata.fileFormat      
 )),77r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.fileFormat      
  r6   c                $    | j                  d      S )z
        Get or set the file path that was parsed.  This returns a string, not a Path object
        that is deliberate for caching.
        filePathr   rp   s    r7   r   zMetadata.filePath  s     ))*55r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.filePath  r   r6   c                $    | j                  d      S )zH
        Get or set the path within the corpus that was parsed.
        corpusFilePathr   rp   s    r7   r   zMetadata.corpusFilePath  s    
 ))*:;;r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.corpusFilePath      
 $r6   c                $    | j                  d      S )z=
        Get or set the file number that was parsed.
        
fileNumberr   rp   s    r7   r   zMetadata.fileNumber  r   r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.fileNumber  r   r6   c                $    | j                  d      S )z
        Get or set the locale of composition, or origin, of the work.

        >>> md = metadata.Metadata(popularTitle='Eroica')
        >>> md.localeOfComposition = 'Paris, France'
        >>> md.localeOfComposition
        'Paris, France'
        localeOfCompositionr   rp   s    r7   r   zMetadata.localeOfComposition  s     ))*?@@r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.localeOfComposition  s    
 $) r6   c                $    | j                  d      S )a  
        Gets or sets a single librettist for this work:

        >>> md = metadata.Metadata(title='Death of Klinghoffer, The')
        >>> md.librettist = 'Goodman, Alice'
        >>> md.librettist
        'Goodman, Alice'

        To preserve continuity with Humdrum, library catalogues, etc.,
        librettists should be distinguished from lyricists etc., but sometimes
        the line is not 100% clear.
        
librettistr   rp   s    r7   r   zMetadata.librettist  s     )),77r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.librettist  r   r6   c                $    | j                  d      S )aQ  
        Gets a tuple or sets an iterable of librettists for this work:

        >>> md = metadata.Metadata(title='Madama Butterfly')
        >>> md.librettists = ['Illica, Luigi', 'Giacosa, Giuseppe']
        >>> md.librettists
        ('Illica, Luigi', 'Giacosa, Giuseppe')

        Should be distinguished from lyricists etc.
        r   rn   rp   s    r7   r   zMetadata.librettists  s     ''55r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.librettists  r   r6   c                $    | j                  d      S )a  
        Gets or sets a single lyricist for this work:

        >>> md = metadata.Metadata(title='Girlfriend')
        >>> md.lyricist = 'Keys, Alicia'

        To preserve continuity with Humdrum, library catalogues, etc.,
        lyricists should be distinguished from librettists etc., but sometimes
        the line is not 100% clear:

        >>> md = metadata.Metadata(title='West Side Story')
        >>> md.lyricist = 'Sondheim, Stephen'
        >>> md.lyricist
        'Sondheim, Stephen'
        lyricistr   rp   s    r7   r   zMetadata.lyricist  s    " ))*55r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.lyricist  r   r6   c                $    | j                  d      S )ao  
        Gets a tuple or sets an iterable of lyricists for this work:

        >>> md = metadata.Metadata(title='Rumors')
        >>> md.lyricists = ['Buckingham, Lindsey', 'McVie, Christine', 'Nicks, Stevie']
        >>> md.lyricists
        ('Buckingham, Lindsey', 'McVie, Christine', 'Nicks, Stevie')

        Should be distinguished from librettists etc.
        r   rn   rp   s    r7   r   zMetadata.lyricists  s     ''
33r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.lyricists   r   r6   c                $    | j                  d      S )a  
        Get or set the movement title.

        >>> md = metadata.Metadata()
        >>> md.movementName = 'Vivace'
        >>> md.movementName
        'Vivace'

        Note that a number of pieces from various MusicXML datasets have
        the piece title as the movement title. For instance, the Bach
        Chorales, since they are technically movements of larger cantatas.
        movementNamer   rp   s    r7   r   zMetadata.movementName'  s     )).99r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.movementName7  s    
 "r6   c                $    | j                  d      S )a7  
        Get or set the movement number as a string (or None)

        >>> md = metadata.Metadata(title='Ode to Joy')
        >>> md.movementNumber = 4

        Note that movement numbers are always returned as strings!  This may
        change in the future.

        >>> md.movementNumber
        '4'
        movementNumberr   rp   s    r7   r   zMetadata.movementNumber>  s     ))*:;;r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.movementNumberN  r   r6   c                $    | j                  d      S )a  
        Get or set the number of the work within a collection of pieces,
        as a string. (for instance, the number within a collection of ABC files)

        >>> md = metadata.Metadata()
        >>> md.number = '4'

        Note that numbers are always returned as strings!  This may
        change in the future.

        >>> md.number
        '4'

        However, it is acceptable to set it as an int:

        >>> md.number = 2
        >>> md.number
        '2'
        numberr   rp   s    r7   r   zMetadata.numberU  s    * ))(33r6   c                    || _         yr   )r   r   s     r7   r   zMetadata.numberl  s    
 r6   c                $    | j                  d      S )a:  
        Get or set the opus number.

        >>> md = metadata.Metadata()
        >>> md.opusNumber = 56

        Note that opusNumbers are always returned as strings!  This may
        change in the future, however, it is less likely to change
        than `.number` or `.movementNumber` since Opus numbers such as
        `18a` are common.

        >>> md.opusNumber
        '56'

        There is no enforcement that only numbers actually called "opus"
        are used, and it could be used for other catalogue numbers.

        >>> md.opusNumber = 'K.622'
        
opusNumberr   rp   s    r7   r  zMetadata.opusNumbers  s    * )),77r6   c                    || _         yr   )r  r   s     r7   r  zMetadata.opusNumber  r   r6   c                $    | j                  d      S )z
        Get the title of the work.

        >>> md = metadata.Metadata(title='Third Symphony')
        >>> md.title
        'Third Symphony'

        >>> md = metadata.Metadata(popularTitle='Eroica')
        >>> md.title is None
        True
        titler   rp   s    r7   r  zMetadata.title  s     ))'22r6   c                    || _         yr   )r  r   s     r7   r  zMetadata.title  s    
 
r6   c                f    d}|D ]*  }| j                  t        j                  |         }|s(|c S  y)a#  
        Get the title of the work, or the next-matched title string
        available from a related parameter fields.

        >>> md = metadata.Metadata(title='Third Symphony')
        >>> md.bestTitle
        'Third Symphony'

        >>> md = metadata.Metadata(popularTitle='Eroica')
        >>> md.bestTitle
        'Eroica'

        >>> md = metadata.Metadata(
        ...     title='Third Symphony',
        ...     popularTitle='Eroica',
        ...     )
        >>> md.bestTitle
        'Third Symphony'

        >>> md.popularTitle
        'Eroica'

        >>> md.otp
        'Eroica'

        bestTitle cannot be set:

        >>> md.bestTitle = 'Bonaparte'
        Traceback (most recent call last):
        AttributeError: property 'bestTitle' of 'Metadata' object has no setter
        )r  popularTitler   r   N)_getStringValueByNamespaceNamer   rZ   )rA   searchIdr\   titleSummarys       r7   	bestTitlezMetadata.bestTitle  sD    B
 #J%)%H%H88D&L ## # r6   c                *   |t         j                  vry| j                  |d      }|sy| j                  |      rkt	        |      dk(  rt        |d         S t	        |      dk(  r t        |d         dz   t        |d         z   S t        |d         dt	        |      dz
   dz   S | j                  |      rFd	}t        |      D ]4  \  }}t        |t              sJ |dkD  r|d
z  }||j                         z  }6 |S d
j                  d |D              S )aa  
        Gets a single str value (a summary if necessary) for a supported
        'namespace:name'.

        >>> md = metadata.Metadata()
        >>> md['title'] = ['The Title', 'A Second Title']
        >>> md._getStringValueByNamespaceName('dcterms:title')
        'The Title, A Second Title'

        Returns None  (rather than raising KeyError) if namespaceName
        is not a supported 'namespace:name'.

        >>> md.setCustom('average duration', '180 minutes')
        >>> md._getStringValueByNamespaceName('average duration') is None
        True

        Returns None if the namespaceName is supported, but no such
        metadata item exists.

        >>> md._getStringValueByNamespaceName('dcterms:alternative') is None
        True

        Performs article normalization on metadata properties that are declared
        as needing it (generally, title-like properties).

        >>> md['alternativeTitle'] = ['Alternative Title, The', 'Second Alternative Title, A']
        >>> md._getStringValueByNamespaceName('dcterms:alternative')
        'The Alternative Title, A Second Alternative Title'
        NFrI      r      z and z others z, c              3  2   K   | ]  }t        |        y wr   r,   r   rF   s     r7   r   z:Metadata._getStringValueByNamespaceName.<locals>.<genexpr>  s     8U   )r   ALL_NAMESPACE_NAMESrQ   _isContributorNamespaceNamelenr,   '_namespaceNameNeedsArticleNormalization	enumerater   r   getNormalizedArticlejoin)rA   r`   r   ry   irF   s         r7   r  z'Metadata._getStringValueByNamespaceName  s   < 
 > >>(,		-%	(P++M:6{a6!9~%6{a6!9~/#fQi.@@vay>eCK!O+<G$DDD77FF%f-5!%...q5dNF%4466	 .
 Myy8888r6   c                H   |t         j                  vr
t               S | j                  |d      }|s
t               S | j	                  |      rEg }|D ]3  }t        |t              sJ |j                  |j                                5 t        |      S t        d |D              S )aN  
        Gets a tuple of str values for a supported 'namespace:name'.

        >>> md = metadata.Metadata()
        >>> md['title'] = ['The Title', 'A Second Title']
        >>> md._getStringValuesByNamespaceName('dcterms:title')
        ('The Title', 'A Second Title')

        Returns an empty tuple (rather than raising KeyError) if namespaceName
        is not a supported 'namespace:name'.

        >>> md.setCustom('average duration', '180 minutes')
        >>> md._getStringValuesByNamespaceName('average duration')
        ()

        Returns an empty tuple if the namespaceName is supported, but no such
        metadata item exists.

        >>> md._getStringValuesByNamespaceName('dcterms:alternative')
        ()

        Performs article normalization on metadata properties that are declared
        as needing it (generally, title-like properties).

        >>> md['alternativeTitle'] = ['Alternative Title, The', 'Second Alternative Title, A']
        >>> md._getStringValuesByNamespaceName('dcterms:alternative')
        ('The Alternative Title', 'A Second Alternative Title')
        FrI   c              3  2   K   | ]  }t        |        y wr   r  r  s     r7   r   z;Metadata._getStringValuesByNamespaceName.<locals>.<genexpr><  s     4VESZVr  )	r   r  rx   rQ   r  r   r   rw   r  )rA   r`   r   ry   rF   s        r7   _getStringValuesByNamespaceNamez(Metadata._getStringValuesByNamespaceName  s    : 
 > >>7N(,		-%	(P7N77F "F!%...e88:;   = 4V444r6   c                V   |t         j                  v r"| j                  t         j                  |         S |t         j                  v r"| j                  t         j                  |         S |t         j                  v r"| j                  t         j                  |         S t        d|       )aA  
        This does what __getattr__ would do if we supported plural attributeNames
        (but it takes singular attributeNames, of course).  It returns a tuple
        of strings (perhaps empty) for supported uniqueNames, grandfathered
        workIds, and grandfathered workId abbrevations.  It is used in search,
        as well as in the various plural properties, such as md.composers, etc.

        >>> md = metadata.Metadata()
        >>> md['dedicatedTo'] = ('The First Dedicatee', 'A Second Dedicatee')

        Example: _getPluralAttribute by uniqueName

        >>> md._getPluralAttribute('dedicatedTo')
        ('The First Dedicatee', 'A Second Dedicatee')

        Example: _getPluralAttribute by grandfathered workId

        >>> md._getPluralAttribute('dedication')
        ('The First Dedicatee', 'A Second Dedicatee')

        Example: _getPluralAttribute by grandfathered workId abbreviation

        >>> md._getPluralAttribute('ode')
        ('The First Dedicatee', 'A Second Dedicatee')

        It raises AttributeError if attributeName is not a valid uniqueName,
        workId, or workId abbreviation.

        Example: _getPluralAttribute by 'namespace:name'

        >>> md._getPluralAttribute('humdrum:ODE')
        Traceback (most recent call last):
        AttributeError: invalid attributeName: humdrum:ODE
        zinvalid attributeName: )r   rZ   r  r   r   r   rA   attributeNames     r7   ro   zMetadata._getPluralAttribute>  s    F JDDD7788G 
 JHHH77<<]K 
 JMMM77AA-P  6}oFGGr6   c                   |t         j                  v r"| j                  t         j                  |         S |t         j                  v r"| j                  t         j                  |         S |t         j                  v r"| j                  t         j                  |         S t        | j                  j                  d|      )ab  
        This returns a single string (perhaps a summary) for supported uniqueNames,
        grandfathered workIds, and grandfathered workId abbrevations.

        >>> md = metadata.Metadata()
        >>> md['dedicatedTo'] = ('The First Dedicatee', 'A Second Dedicatee')

        Example: _getSingularAttribute by uniqueName

        >>> md._getSingularAttribute('dedicatedTo')
        'The First Dedicatee, A Second Dedicatee'

        Example: _getSingularAttribute by grandfathered workId

        >>> md._getSingularAttribute('dedication')
        'The First Dedicatee, A Second Dedicatee'

        Example: _getSingularAttribute by grandfathered workId abbreviation

        >>> md._getSingularAttribute('ode')
        'The First Dedicatee, A Second Dedicatee'

        It raises AttributeError if attributeName is not a valid uniqueName,
        workId, or workId abbreviation.

        Example: _getSingularAttribute by 'namespace:name'

        >>> md._getSingularAttribute('humdrum:ODE')
        Traceback (most recent call last):
        AttributeError: 'Metadata' object has no attribute: 'humdrum:ODE'
        z object has no attribute: )r   rZ   r  r   r   r   rG   r0   r   s     r7   r   zMetadata._getSingularAttributet  s    @ JDDD6688G 
 JHHH66<<]K 
 JMMM66AA-P  ~~&&))CMCTU
 	
r6   c                &    |t         j                  v S )a  
        Determines if a unique name is associated with a standard property.
        Returns False if no such associated standard property can be found.

        Example: a standard contributor uniqueName returns True

        >>> md = metadata.Metadata()
        >>> md._isStandardUniqueName('librettist')
        True

        Example: a standard 'namespace:name' returns False (it is a standard
        namespaceName, but not a standard uniqueName)

        >>> md._isStandardUniqueName('marcrel:LBT')
        False

        Example: a standard non-contributor uniqueName returns True

        >>> md._isStandardUniqueName('alternativeTitle')
        True

        Example: a custom (non-standard) name returns False (it is not
        a standard name of any sort)

        >>> md._isStandardUniqueName('average duration')
        False

        Example: a RichMetadata additional attribute name returns False

        >>> md._isStandardUniqueName('ambitus')
        False

        )r   rd   )rA   r\   s     r7   rk   zMetadata._isStandardUniqueName  s    D ZKKKKr6   c                J    t         j                  j                  | d      }|yy)aV  
        Determines if a 'namespace:name' is associated with a standard property.
        Returns False if no such associated standard property can be found.

        Example: a standard 'namespace:name' returns True

        >>> metadata.Metadata._isStandardNamespaceName('marcrel:LBT')
        True

        Example: a standard contributor uniqueName returns False (it is a
        standard uniqueName, but not a standard namespaceName)

        >>> metadata.Metadata._isStandardNamespaceName('librettist')
        False

        Example: a namespaceName with a non-standard namespace returns False

        >>> metadata.Metadata._isStandardNamespaceName('nonstandardnamespace:LBT')
        False

        Example: a namespaceName with a standard namespace, but a non-standard name
        returns False

        >>> metadata.Metadata._isStandardNamespaceName('marcrel:nonstandardname')
        False

        Example: a custom (non-standard) name returns False (it is not
        a standard name of any sort)

        >>> metadata.Metadata._isStandardNamespaceName('average duration')
        False
        NFT)r   &NAMESPACE_NAME_TO_PROPERTY_DESCRIPTIONr[   r`   rg   s     r7   rj   z!Metadata._isStandardNamespaceName  s.    F ==AA-QUV 	 <r6   c                ^    t         j                  j                  |       }|y|j                  S )a2  
        Determines if a uniqueName is associated with a standard contributor
        property. Returns False if no such associated standard property can be found,
        or if the associated standard property is not a contributor property.

        Example: 'librettist' returns True ('librettist' is a standard contributor
        property).

        >>> metadata.Metadata._isContributorUniqueName('librettist')
        True

        Example: 'alternativeTitle' returns False (it is a standard namespaceName,
        but it is not a contributor).

        >>> metadata.Metadata._isContributorUniqueName('alternativeTitle')
        False

        Example: 'marcrel:LBT' returns False (it is a standard contributor
        namespaceName, but not a standard contributor uniqueName)

        >>> metadata.Metadata._isContributorUniqueName('marcrel:LBT')
        False

        Example: a custom (non-standard) name returns False (it is not
        a standard name of any sort)

        >>> metadata.Metadata._isContributorUniqueName('average duration')
        False
        Frc   rf   s     r7   r   z!Metadata._isContributorUniqueName  s4    @ ::>>zJ 	 <!!!r6   c                ^    t         j                  j                  |       }|y|j                  S )a  
        Determines if a 'namespace:name' is associated with a standard contributor
        property. Returns False if no such associated standard property can be found,
        or if the associated standard property is not a contributor property.

        Example: 'marcrel:LBT' returns True ('marcrel:LBT' is a standard contributor
        property: the librettist).

        >>> metadata.Metadata._isContributorNamespaceName('marcrel:LBT')
        True

        Example: 'dcterms:alternative' returns False (it is a standard namespaceName,
        but it is not a contributor).

        >>> metadata.Metadata._isContributorNamespaceName('dcterms:alternative')
        False

        Example: 'librettist' returns False (it is a standard contributor
        uniqueName, but not a standard contributor namespaceName)

        >>> metadata.Metadata._isContributorNamespaceName('librettist')
        False

        Example: a namespaceName with a non-standard namespace returns False

        >>> metadata.Metadata._isContributorNamespaceName('nonstandardnamespace:LBT')
        False

        Example: a namespaceName with a standard namespace, but a non-standard name
        returns False

        >>> metadata.Metadata._isContributorNamespaceName('marcrel:nonstandardname')
        False

        Example: a custom (non-standard) name returns False (it is not
        a standard name of any sort)

        >>> metadata.Metadata._isContributorNamespaceName('average duration')
        False
        F)r   r%  r[   re   r&  s     r7   r  z$Metadata._isContributorNamespaceName  s4    V ==AA-P 	 <!!!r6   c                ^    t         j                  j                  |       }|y|j                  S )a`  
        Determines if a 'namespace:name' is associated with a standard property that
        needs article normalization (generally title-like properties). Returns False
        if no such associated standard property can be found, or if the associated
        standard property is not a contributor property.

        Example: 'dcterms:title' returns True

        >>> metadata.Metadata._namespaceNameNeedsArticleNormalization('dcterms:title')
        True

        Example: 'title' returns False ('title' is the uniqueName of a standard property
        that needs article normalization, but it is not a namespaceName).

        >>> metadata.Metadata._namespaceNameNeedsArticleNormalization('title')
        False

        Example: 'marcrel:LBT' (the librettist) returns False (it doesn't need article
        normalization)

        >>> metadata.Metadata._namespaceNameNeedsArticleNormalization('marcrel:LBT')
        False

        Example: 'average duration' returns False (it is not a standard name)

        >>> metadata.Metadata._namespaceNameNeedsArticleNormalization('average duration')
        False
        F)r   r%  r[   needsArticleNormalizationr&  s     r7   r  z0Metadata._namespaceNameNeedsArticleNormalizationP  s3    > ==AA-P 	 <---r6   c                j    | yt         j                  j                  |       }|y|j                  sy| S )a  
        Translates a contributor role to a standard uniqueName that
        should be used to store that contributor.  For standard contributor
        roles, this simply returns role, because standard roles are their
        own standard uniqueNames. But for non-standard roles, 'otherContributor'
        is returned. This is the standard uniqueName that should be used for
        contributors with custom roles.

        Example: 'composer' and 'lyricist' are standard contributor roles whose
        uniqueNames are 'composer' and 'lyricist', respectively.

        >>> metadata.Metadata._contributorRoleToUniqueName('composer')
        'composer'
        >>> metadata.Metadata._contributorRoleToUniqueName('lyricist')
        'lyricist'

        Example: 'interpretive dancer' is a non-standard contributor role, so
        'otherContributor' is returned.

        >>> metadata.Metadata._contributorRoleToUniqueName('interpretive dancer')
        'otherContributor'

        Example: None is a non-standard contributor role, so 'otherContributor' is returned.

        >>> metadata.Metadata._contributorRoleToUniqueName(None)
        'otherContributor'
        otherContributorrc   )r   rg   s     r7   r   z%Metadata._contributorRoleToUniqueNamev  sB    : <% ::>>tD 	 <%!!%r6   c                   |sU|}| j                  |      r t        j                  j                  |d      }| j	                  |      st        d|d      |}| j                  j                  |      }|s
t               S t        |      S )a  
        Returns all the items stored in metadata with this name.
        The returned value is always a Tuple. If there are no items, an empty
        Tuple is returned.

        If isCustom is True, then the name will be used unconditionally as a custom name.

        If isCustom is False, and the name is not a standard uniqueName or a standard
        'namespace:name', KeyError will be raised.
        r  Name=V is not a standard metadata name. Call addCustom/setCustom/getCustom for custom names.)rj   r   r_   r[   rk   r   r?   rx   )rA   rN   rJ   r\   r   s        r7   rQ   zMetadata._get  s     "J,,T2'EEII$PRS
--j9D8 $L LM M D*...*<*<T*B	7N Yr6   c                   |sU|}| j                  |      r t        j                  j                  |d      }| j	                  |      st        d|d      |}t        |t              s|g}t        |t              r|g}g }|D ]#  }|j                  | j                  ||             % | j                  j                  |d      }|s|| j                  |<   y||z   | j                  |<   y)ag  
        Adds a single item or multiple items with this name, leaving any existing
        items with this name in place.

        If isCustom is True, then the name will be used unconditionally as a custom name.

        If isCustom is False, and the name is not a standard uniqueName or a standard
        'namespace:name', KeyError will be raised.
        r  r.  r/  N)rj   r   r_   r[   rk   r   r   r   r,   rw   convertValuer?   )rA   rN   rF   rJ   r\   convertedValuesv
prevValuess           r7   rL   zMetadata._add  s     "J,,T2'EEII$PRS
--j9D8 $L LM M D%*GEeS!GE+-A""4#4#4T1#=>  ,0>>+=+=dD+I
 $3DNN4  $.#?DNN4 r6   c                   |sU|}| j                  |      r t        j                  j                  |d      }| j	                  |      st        d|d      |}| j                  j                  |d       || j                  |||       yy)u  
        Sets a single item or multiple items with this name, replacing any
        existing items with this name.  If isCustom is False, the name must
        be a standard uniqueName or a standard 'namespace:name'.  If isCustom
        is True, the name can be any custom name.

        >>> md = metadata.Metadata()

        Example: set the librettist

        >>> md._set('librettist', metadata.Text('Marie Červinková-Riegrová'), isCustom=False)
        >>> md['librettist']
        (<music21.metadata.primitives.Contributor librettist:Marie Červinková-Riegrová>,)

        Example: replace that librettist with two other librettists

        >>> md._set('librettist', ['Melissa Li', 'Kit Yan Win'], isCustom=False)
        >>> md['marcrel:LBT']
        (<music21.metadata.primitives.Contributor librettist:Melissa Li>,
         <music21.metadata.primitives.Contributor librettist:Kit Yan Win>)

        If isCustom is True, then the name will be used unconditionally as a custom name.

        >>> md._set('average duration', '180 minutes', isCustom=True)
        >>> md._get('average duration', isCustom=True)
        (<music21.metadata.primitives.Text 180 minutes>,)

        If isCustom is False, and the name is not a standard uniqueName or a standard
        'namespace:name', KeyError will be raised.

        >>> md._set('average duration', '180 minutes', isCustom=False)
        Traceback (most recent call last):
        KeyError: "Name='average duration' is not a standard metadata name...
        r  r.  r/  N)	rj   r   r_   r[   rk   r   r?   poprL   )rA   rN   rF   rJ   r\   s        r7   rW   zMetadata._set  s    F "J,,T2'EEII$PRS
--j9D8 $L LM M D4&IIdE8, r6   c                   t         j                  j                  | d      }|}|t        }t	        ||      r|S |t        u r/t	        |t
              rt        |      S t        t        |            S |t        u rat	        |t
              rt        |      S t	        |t              rt        |      S t        j                  dt        |      j                         |t        u rt	        |t              rt        |      }t	        |t              r|S t	        |t
        t        j                  t        f      r	 t        |      S t        j                  dt        |      j                         |t         u rct	        |t
              rt        |      }t	        |t              rt!        | |      S t        j                  dt        |      j                         |t"        u r	 t#        |      S t        j                  d      # t        $ r t        t        |            cY S w xY w# t        $ r1}t        j                  dt        |      j                         |d}~ww xY w)a%  
        Converts a value (string, date, etc.) to the appropriate ValueType
        (defined by uniqueName and metadata.proporties.UNIQUE_NAME_TO_VALUE_TYPE).

        Converts certain named values to Text:

        >>> metadata.Metadata.convertValue('title', 'Density 21.5')
        <music21.metadata.primitives.Text Density 21.5>

        Non-string values can often be converted automatically

        >>> metadata.Metadata.convertValue('title', 21.5)
        <music21.metadata.primitives.Text 21.5>

        If it is already the appropriate type, no change is made

        >>> md_text = metadata.Text('The Desert Music')
        >>> metadata.Metadata.convertValue('title', md_text)
        <music21.metadata.primitives.Text The Desert Music>
        >>> metadata.Metadata.convertValue('title', md_text) is md_text
        True

        Converts copyright named values to Copyright:

        >>> metadata.Metadata.convertValue('copyright', 'copyright-as-string')
        <music21.metadata.primitives.Copyright copyright-as-string>
        >>> metadata.Metadata.convertValue('copyright', metadata.Text('copyright-as-text'))
        <music21.metadata.primitives.Copyright copyright-as-text>
        >>> metadata.Metadata.convertValue('copyright', metadata.Copyright('2025 Ariadne Press'))
        <music21.metadata.primitives.Copyright 2025 Ariadne Press>

        Converts composer or other named values to Contributors:

        >>> metadata.Metadata.convertValue('composer', 'Kayleigh Rose Amstutz')
        <music21.metadata.primitives.Contributor composer:Kayleigh Rose Amstutz>
        >>> metadata.Metadata.convertValue('librettist', metadata.Text('Da Ponte'))
        <music21.metadata.primitives.Contributor librettist:Da Ponte>

        Unusual roles need to be passed with "otherContributor" and role already defined.
        (Essentially, make your own)

        >>> metadata.Metadata.convertValue('otherContributor',
        ...     metadata.Contributor(role='conceptual-artist', name='Robert Wilson'))
        <music21.metadata.primitives.Contributor conceptual-artist:Robert Wilson>

        Converts certain named values to DateSingle:

        >>> metadata.Metadata.convertValue('dateCreated', '1938')
        <music21.metadata.primitives.DateSingle 1938/--/-->
        >>> metadata.Metadata.convertValue('dateCreated', metadata.Text('1938/02'))
        <music21.metadata.primitives.DateSingle 1938/02/-->

        Datetime objects, like this one for the Rite of Spring riot, also get converted.

        >>> from datetime import datetime
        >>> dt = datetime(1913, 5, 29)
        >>> metadata.Metadata.convertValue('dateCreated', dt)
        <music21.metadata.primitives.DateSingle 1913/05/29>

        For DateBetween objects one must pass their own already created primitive
        (so no need to use this)

        >>> metadata.Metadata.convertValue('dateCreated',
        ...     metadata.DateBetween(['1938', '1939']))
        <music21.metadata.primitives.DateBetween 1938/--/-- to 1939/--/-->

        * Added in v10 -- newly exposed as a public function (was private)
        Nzinvalid type for Copyright: zinvalid type for DateSingle: )r   rN   zinvalid type for Contributor: zinvalid type for int: z!internal error: invalid valueType)r   UNIQUE_NAME_TO_VALUE_TYPEr[   r   r   r,   r   r    r   typer0   r   datetimer
   r   r   r   r*   )r\   rF   	valueTypeoriginalValuees        r7   r1  zMetadata.convertValue	  s:   L +5*N*N*R*R+
	  %IeY'L %%E{"E
##	!%% ''%& ''00.tE{/C/C.DEG G % %&E
%/ %#x'8'8$!?@4%e,,
 00/U0D0D/EFH H #%%U%&"
??000e1E1E0FGI I L5z!
 ,,-PQQ1 " 4M 2334(  L"44,T%[-A-A,BCEJKLLs*   4
G> 
H! >HH!	I*,IIreturnNone)rN   r,   rF   t.Any | Iterable[t.Any]r?  r@  )rN   r,   r?  tuple[ValueType, ...])rN   r,   rF   rA  )r\   r,   r?  
str | None)r`   r,   r?  rC  )r\   rC  r?  bool)rN   r,   r?  rD  r?  tuple[str, ...])r?  tuple[Contributor, ...])rF   r,   r?  r@  
r   rD  rs   rD  rt   rD  ru   rD  r?  ztuple[tuple[str, t.Any], ...])rN   r,   rF   t.Any)r#   z4t.Literal['movementName', 'movementNumber', 'title']r?  tuple[Text, ...])r#   zt.Literal['copyright',]r?  ztuple[Copyright, ...])r#   r,   r?  rJ  )r#   r,   r?  z(tuple[ValueType, ...] | tuple[Text, ...])r#   r,   rF   rA  )r   r   )r   rC  r?  rG  )NN)r   z0str | t.Pattern | t.Callable[[str], bool] | Noner   rC  r?  ztuple[bool, str | None])rF   zIterable[str]r?  r@  )r?  rC  )r`   r,   r?  rF  )r!  r,   r?  rF  )r!  r,   r?  rC  r\   r,   r?  rD  )r`   r,   r?  rD  )r   rC  r?  r,   )rN   r,   rJ   rD  r?  rB  )rN   r,   rF   rA  rJ   rD  )r\   r,   rF   rI  r?  r   )?r0   r1   r2   r3   classSortOrderr>   rO   rS   rU   rX   staticmethodr]   ra   rh   rl   propertyr:   r|   r~   setterrv   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  r  ro   r   rk   rj   r   r  r  r   rQ   rL   rW   r1  __classcell__rG   s   @r7   r   r      s   7v N/2'/'/('/ '/R.6.:.> N N2 Q Q0 #" #"JD 4 42  > 27 27h   &+(-%*!%V #V "&	V
 #V V 
'Vp<0|I)X 
 +   0   -.^.2,1\+^ =Am9m m
 
mP 	> 	> & & 6 64 __  4 48   9 92 ! ! 8 8     6 6 __  < < $ $ 8 8     	A 	A )  ) 8 8     6 6 ! ! 6 6$ __  4 4   : : " " < < $ $ 4 4, ]]  8 8,     3 3 \\  - -d59n+5Z4Hl3
j"LH ' 'R $" $"L /" /"b #. #.J + +Z <&@R/-b HR HRr6   r   c                       e Zd ZdZdZd fdZd fdZddZddZd Z	dddd	d
	 	 	 	 	 	 	 	 	 d fdZ
d fdZ xZS )r   a  
    RichMetadata adds to Metadata information about the contents of the Score
    it is attached to. TimeSignature, KeySignature and related analytical is
    stored.  RichMetadata are generally only created in the process of creating
    stored JSON metadata.

    >>> richMetadata = metadata.RichMetadata(title='Concerto in F')
    >>> richMetadata.title
    'Concerto in F'

    >>> richMetadata.keySignatureFirst = key.KeySignature(-1)
    >>> 'keySignatureFirst' in richMetadata.additionalRichMetadataAttributes
    True

    RichMetadata objects contain all the usual Metadata items, plus some observed
    musical information analyzed from the score.  Here is a list of what information
    is added:

    >>> richMetadata.additionalRichMetadataAttributes
    ('ambitus', 'keySignatureFirst', 'keySignatures', 'noteCount', 'numberOfParts',
     'pitchHighest', 'pitchLowest', 'scoreQuarterLength', 'sourcePath', 'tempoFirst',
     'tempos', 'timeSignatureFirst', 'timeSignatures')

    * Changed in v10: renamed `quarterLength` to `scoreQuarterLength`.
       Because RichMetadata is a Music21Object, `quarterLength` is a property that must
       return the length of the RichMetadata object itself and should not have been
       ovewritten

    )ambituskeySignatureFirstkeySignatures	noteCountnumberOfPartsr/   r.   scoreQuarterLength
sourcePath
tempoFirsttempostimeSignatureFirsttimeSignaturesc                    t        |   di | d | _        d | _        g | _        d | _        d | _        d | _        d | _        d| _	        d| _
        d | _        g | _        d | _        g | _        y )Ng        r  r5   )r=   r>   rS  rT  rU  rV  rW  r/   r.   rX  rY  rZ  r[  r\  r]  )rA   rB   rG   s     r7   r>   zRichMetadata.__init__	  sv    $8$*.8<57#'!&*%),/6:35<@9;r6   c                t    || j                   v rt        | |      }|
t               S |fS t        |   |      S r   ) additionalRichMetadataAttributesgetattrrx   r=   ro   )rA   r!  rF   rG   s      r7   ro   z RichMetadata._getPluralAttribute	  sD     DAAA D-0E}w8Ow*=99r6   Fc                    dg}t         j                  dg       |D ]1  }t        | |      }||r	 |t        ||      }|t        | ||       3 y# t        $ r Y @w xY w)ao  
        Given another Metadata or RichMetadata object, combine
        all attributes and return a new object.

        >>> md = metadata.Metadata(title='Concerto in F')
        >>> md.title
        'Concerto in F'

        >>> richMetadata = metadata.RichMetadata()
        >>> richMetadata.merge(md)
        >>> richMetadata.title
        'Concerto in F'

        r?   zRichMetadata: calling merge()N)environLocal
printDebugra  r@   r   )rA   other	favorSelf
localNamesrN   
localValue
otherValues          r7   mergezRichMetadata.merge	  s    $ 

 	!@ ABD t,J %)(%,UD%9
%1#D$
;  & s   A	AAc                   |j                   sy|j                   }|j                  r|j                  S |j                  }|syt        |t        j
                        st	        j
                  |      }	 |j                  t        j                               }|j                         S # t        $ r |j                         cY S w xY w)a%  
        Get a string of the path after the corpus for the piece.  Useful for
        searching on corpus items without proper composer data.

        >>> rmd = metadata.RichMetadata()
        >>> b = corpus.parse('bwv66.6')
        >>> rmd.getSourcePath(b)
        'bach/bwv66.6.mxl'
        r  )r(   r   r   r   pathlibPathrelative_tor   getCorpusFilePathas_posixr   )rA   	streamObjmdstreamFprelativePaths        r7   getSourcePathzRichMetadata.getSourcePath 
  s     !!$$$;;(GLL1||H-H	'#//0H0H0JKL((** 	'$$&&	's   12B$ $C ?C c                   ddl m} ddl m} ddl m} t        j                  dg       |j                         j                         }t        |j                        | _
        d| _        g | _        d| _        g | _        d| _        g | _        | j#                  |      | _        |D ]  }t'        ||j(                        r7|j*                  }|| j                   vs4| j                   j-                  |       Pt'        ||j.                        r?|j0                  | j                  vs| j                  j-                  |j0                         t'        ||j2                        st5        |      }|| j                  vs| j                  j-                  |        | j                   r| j                   d   | _        | j                  r| j                  d   | _        | j                  r| j                  d   | _        t        |j6                        | _        |j:                  | _        ddlm }	 d| _!        d| _"        d| _#        |	jI                  |      }
|
jJ                  B|
jL                  6|
jJ                  jN                  | _#        |
jL                  jN                  | _"        |
jQ                  |      }|tS        jT                  d      }tW        |jX                  |jZ                  j\                  | jF                  | jD                  	      | _!        y)
a  
        Given a Stream object, update attributes with stored objects.

        >>> rmd = metadata.RichMetadata()
        >>> rmd.keySignatureFirst is None
        True
        >>> rmd.sourcePath
        ''

        >>> b = corpus.parse('bwv66.6')
        >>> rmd.update(b)
        >>> rmd.keySignatureFirst
        3
        >>> rmd.sourcePath
        'bach/bwv66.6.mxl'
        >>> rmd.numberOfParts
        4
        r   r"   r$   r&   zRichMetadata: update(): startN)discreteP1)r+   r-   r.   r/   )/music21r#   r%   r'   rc  rd  flattenr   r  partsrW  rT  rU  rZ  r[  r\  r]  ru  rY  r   TimeSignatureratioStringrw   KeySignaturer   TempoIndicationr,   notesAndRestsrV  highestTimerX  music21.analysisrw  rS  r/   r.   AmbitusminPitchObjmaxPitchObjnameWithOctavegetSolutionr!   Intervalr   r+   r-   
simpleName)rA   rq  r#   r%   r'   flatelementr}  tempoIndicationStringrw  analysisObjectambitusIntervals               r7   updatezRichMetadata.update?
  s{   & 	 !!!@ AB  "))+ 1!%"& ,,Y7 G'5#6#67%11d&9&99''..{;GS%5%56>>););;&&--gnn=GU%:%:;(+G%(;KK&&'<=  &*&9&9!&<D#%)%7%7%:D";;"kk!nDOT//0"&"2"2 	. !)))4%%1n6P6P6\  .99HHD . : : I ID(44Y?"&//5O#o.G.G-<-E-E-P-P040@0@151B1B&r6   Tr   c                   t        t        | 	  ||||            }|rt        |      S | j                  D ]  }|j                  |t        | |      f       ! |rt        t        |            S t        |      S )u	  
        Returns all values stored in this RichMetadata as a Tuple of Tuples.
        Each individual Metadata Tuple is (uniqueName, value) and each additional
        RichMetadata tuple is (name, richAttributeValue).

        >>> rmd = metadata.RichMetadata()
        >>> c = corpus.parse('corelli/opus3no1/1grave')
        >>> rmd.merge(c.metadata)
        >>> rmd.update(c)
        >>> rmd.all()
        (('ambitus',
            AmbitusShort(semitones=48, diatonic='P1', pitchLowest='C2', pitchHighest='C6')),
         ('arranger', 'Michael Scott Cuthbert'),
         ('composer', 'Arcangelo Corelli'),
         ...
         ('sourcePath', 'corelli/opus3no1/1grave.xml'),
         ('tempoFirst', '<music21.tempo.MetronomeMark Quarter=60 (playback only)>'),
         ('tempos', ['<music21.tempo.MetronomeMark Quarter=60 (playback only)>']),
         ('timeSignatureFirst', '4/4'),
         ('timeSignatures', ['4/4']))

        >>> rmd.dateCreated = metadata.DateRelative('1689', 'onOrBefore')
        >>> rmd.localeOfComposition = 'Rome'
        >>> rmd.all(skipContributors=True)
        (('ambitus',
            AmbitusShort(semitones=48, diatonic='P1', pitchLowest='C2', pitchHighest='C6')),
         ('copyright', '© 2014, Creative Commons License (CC-BY)'),
         ('corpusFilePath', 'corelli/opus3no1/1grave.xml'),
         ('dateCreated', '1689/--/-- or earlier'),
         ('fileFormat', 'musicxml'),
         ...
         ('keySignatures', [-1]),
         ('localeOfComposition', 'Rome'),
         ('movementName', 'Sonata da Chiesa, No. I (opus 3, no. 1)'),
         ...
         ('timeSignatures', ['4/4']))

        >>> rmd.all(returnPrimitives=True, returnSorted=False)
        (('software', <music21.metadata.primitives.Text music21 ...>),
         ('software', <music21.metadata.primitives.Text Finale 2014 for Mac>),
         ('software', <music21.metadata.primitives.Text Dolet Light for Finale 2014>),
         ('movementName', <...Text Sonata da Chiesa, No. I (opus 3, no. 1)>),
         ('composer', <music21.metadata.primitives.Contributor composer:Arcangelo Corelli>),
         ...
         ('timeSignatures', ['4/4']))

        >>> rmd.all(skipNonContributors=True, returnPrimitives=True, returnSorted=True)
        (('arranger', <music21.metadata.primitives.Contributor arranger:Michael Scott Cuthbert>),
         ('composer', <music21.metadata.primitives.Contributor composer:Arcangelo Corelli>))
        r   )listr=   rv   rx   r`  rw   ra  r   )rA   r   rs   rt   ru   r   rN   rG   s          r7   rv   zRichMetadata.all
  s    t +/uw{- 3-%	 0; 0' +( =  99DMM4t!456 : ((V}r6   c                D    t         |   |      ry|| j                  v ryy)a  
        Determines if a unique name is associated with a standard property.
        Returns False if no such associated standard property can be found.

        Example: a RichMetadata additional attribute name returns True

        >>> rmd = metadata.RichMetadata()
        >>> rmd._isStandardUniqueName('ambitus')
        True

        Example: a standard contributor uniqueName returns True

        >>> rmd._isStandardUniqueName('librettist')
        True

        Example: a standard 'namespace:name' returns False (it is a standard
        namespaceName, but not a standard uniqueName)

        >>> rmd._isStandardUniqueName('marcrel:LBT')
        False

        Example: a standard non-contributor uniqueName returns True

        >>> rmd._isStandardUniqueName('alternativeTitle')
        True

        Example: a custom (non-standard) name returns False (it is not
        a standard name of any sort)

        >>> rmd._isStandardUniqueName('average duration')
        False
        TF)r=   rk   r`  )rA   r\   rG   s     r7   rk   z"RichMetadata._isStandardUniqueName
  s*    B 7(4>>>r6   r>  rE  )F)r?  r,   rH  rK  )r0   r1   r2   r3   r`  r>   ro   rj  ru  r  rv   rk   rP  rQ  s   @r7   r   r   	  s    B($$< :"H'>W&x &+(-%*!%K #K "&	K
 #K K 
'KZ% %r6   r   r   
_DOC_ORDER__main__)9r3   
__future__r   __all__collections.abcr   r:  rl  r   typingr   r   ry  r   r   music21.common.typesr   r   r   r   r    r!   music21.metadatar   r   r   music21.metadata.primitivesr   r   r	   r
   r   r   r   r   r   r   r   r   r   music21.metadata.propertiesr   TYPE_CHECKINGr#   r%   r'   Environmentrc  
NamedTupler   Music21Objectr   r   r  r4   r0   mainTestr5   r6   r7   <module>r     s   ul #4 %   	     3      $ $ '    ( ; ?? '{&&z2
1<< 
K#Rt!! K#R^Fa8 aL !,=
H = zG r6   